
혜성이 지나간 자리에 남은 유성체(Meteoroid)들이 지구 공전 궤도와 만나면 유성우(Meteor Shower)가 발생합니다. 하지만 유성우가 떨어진다고 해서 언제나 볼 수 있는 것은 아닙니다.
관측 가능 조건:
이번 포스팅에서는 파이썬을 활용해 "내가 있는 도시에서 언제 유성우를 가장 잘 볼 수 있을지" 예측하는 로직을 구현해 보겠습니다.
사용할 데이터셋은 Microsoft Learning GitHub에서 가져왔습니다.
meteorshowers.csv: 5대 유성우 발생 기간 및 정보constellations.csv: 별자리별 관측 가능한 위도 범위moonphases.csv: 날짜별 달의 위상 (밝기)cities.csv: 주요 도시의 위도 정보데이터셋에 가상의 유성우 'Chang'e'(중국 달 탐사선 이름에서 유래한 가상 데이터)와 'Draco' 별자리 정보가 누락되어 있어 임의로 추가해 줍니다.
import pandas as pd
import numpy as np
# 유성우 데이터 추가
change_meteor_shower = {'name':'Chang\\'e','radiant':'Draco', 'bestmonth':'october', 'startmonth':'october', 'startday':1, 'endmonth':'october', 'endday':31, 'hemisphere':'northern', 'preferredhemisphere':'northern'}
meteor_showers = pd.concat([meteor_showers, pd.DataFrame([change_meteor_shower])], ignore_index=True)
# 별자리 데이터 추가
draco_constellation = {'constellation':'Draco','bestmonth':'july', 'latitudestart':90, 'latitudeend':-15, 'besttime':2100, 'hemisphere':'northern'}
constellations = pd.concat([constellations, pd.DataFrame([draco_constellation])], ignore_index=True)
컴퓨터가 이해하기 쉽도록 문자열 데이터를 숫자로 변환합니다.
# 월 매핑 딕셔너리
months = {'january':1, 'february':2, 'march':3, 'april':4, 'may':5, 'june':6, 'july':7, 'august':8, 'september':9, 'october':10, 'november':11, 'december':12}
# 매핑 적용
cols_to_map = ['bestmonth', 'startmonth', 'endmonth'] # 유성우 데이터
for col in cols_to_map:
meteor_showers[col] = meteor_showers[col].map(months)
moon_phases.month = moon_phases.month.map(months)
constellations.bestmonth = constellations.bestmonth.map(months)
# 반구 정보 매핑
hemispheres = {'northern':0, 'southern':1, 'northern, southern':3}
meteor_showers.hemisphere = meteor_showers.hemisphere.map(hemispheres)
constellations.hemisphere = constellations.hemisphere.map(hemispheres)
연산 편의를 위해 월, 일을 합쳐 datetime 객체로 변환합니다. (연도는 임의로 2020년으로 고정)
# YYYYMMDD 형태로 만들어 datetime 변환
meteor_showers['startdate'] = pd.to_datetime(2020*10000+meteor_showers.startmonth*100+meteor_showers.startday, format='%Y%m%d')
meteor_showers['enddate'] = pd.to_datetime(2020*10000+meteor_showers.endmonth*100+meteor_showers.endday, format='%Y%m%d')
moon_phases['date'] = pd.to_datetime(2020*10000+moon_phases.month*100+moon_phases.day, format='%Y%m%d')
전처리가 끝났거나 중복되는 컬럼을 삭제하여 데이터프레임을 경량화합니다.
meteor_showers = meteor_showers.drop(['startmonth', 'startday', 'endmonth', 'endday', 'hemisphere'], axis=1)
moon_phases = moon_phases.drop(['month','day','moonphase','specialevent'], axis=1)
constellations = constellations.drop(['besttime'], axis=1)
moon_phases 데이터를 확인해보면 달의 밝기(percentage)가 매일 기록되지 않고 띄엄띄엄 있습니다.
1일이 0%이고 5일이 100%라면, 그 사이 2~4일은 25%, 50%, 75% 정도로 추정할 수 있습니다. 이를 선형 보간법(Linear Interpolation)이라고 합니다.
방법 1: 반복문을 이용한 수동 구현
non_null_indices = moon_phases[moon_phases['percentage'].notnull()].index.tolist()
for i in range(len(non_null_indices) - 1):
start_idx = non_null_indices[i]
end_idx = non_null_indices[i + 1]
start_val = moon_phases.at[start_idx, 'percentage']
end_val = moon_phases.at[end_idx, 'percentage']
num_missing = end_idx - start_idx - 1
if num_missing > 0:
# np.linspace로 시작과 끝 사이를 균등하게 채움
fill_values = np.linspace(start_val, end_val, num_missing + 2)[1:-1]
moon_phases.loc[start_idx + 1:end_idx - 1, 'percentage'] = fill_values
방법 2: Pandas 내장 함수 활용
사실 판다스에는 이 기능을 한 줄로 처리해 주는 interpolate() 함수가 있습니다.
moon_phases['percentage'] = moon_phases['percentage'].interpolate(method='linear')
사용자가 도시 이름을 입력하면 다음과 같은 순서로 예측합니다.
def predict_best_meteor_shower_viewing(city):
meteor_shower_string = ""
# 1. 도시 확인
if city not in cities.values:
return "Unfortunately, " + city + " isn't available."
# 2. 위도 기반 별자리 필터링
latitude = cities.loc[cities['city'] == city, 'latitude'].iloc[0]
constellation_list = constellations.loc[(constellations['latitudestart'] >= latitude) & (constellations['latitudeend'] <= latitude), 'constellation'].tolist()
if not constellation_list:
return "Unfortunately, there are no meteor showers viewable from "+ city + "."
meteor_shower_string = "In " + city + " you can see the following meteor showers:\n"
for constellation in constellation_list:
# 3. 유성우 매칭
meteor_shower = meteor_showers.loc[meteor_showers['radiant'] == constellation, 'name'].iloc[0]
start_date = meteor_showers.loc[meteor_showers['radiant'] == constellation, 'startdate'].iloc[0]
end_date = meteor_showers.loc[meteor_showers['radiant'] == constellation, 'enddate'].iloc[0]
# 4. 기간 내 달 위상 확인
moon_list = moon_phases.loc[(moon_phases['date'] >= start_date) & (moon_phases['date'] <= end_date)]
if meteor_shower == 'Chang\'e':
best_date = moon_list.loc[moon_list['percentage'].idxmax()]['date'] # Chang'e는 보름달일 때 관측
else:
best_date = moon_list.loc[moon_list['percentage'].idxmin()]['date'] # 일반 유성우는 그믐달일 때 관측
meteor_shower_string += f"{meteor_shower} is best seen if you look towards the {constellation} constellation on {best_date.strftime('%B %d, %Y')}.\n"
return meteor_shower_string
기존 로직은 단순히 가능하다/아니면 가장 좋은 날 하루만 알려줍니다. Gemini의 도움을 받아 관측 점수(Visibility Score)를 매겨 랭킹을 보여주는 방식으로 고도화해 보았습니다.
개선 포인트:
pd.merge로 데이터를 미리 병합하여 루프 최소화def predict_enhanced_meteor_showers(city_name):
# 1. 데이터 병합 (미리 합치기)
df_merged = pd.merge(meteor_showers, constellations, left_on='radiant', right_on='constellation')
city = cities[cities['city'] == city_name]
if city.empty: return "City not found."
city_lat = city['latitude'].iloc[0]
# 2. 관측 가능 유성우 필터링
viewable = df_merged[(df_merged['latitudestart'] >= city_lat) & (df_merged['latitudeend'] <= city_lat)].copy()
results = []
for _, shower in viewable.iterrows():
# 3. 위도 점수 계산 (중앙에 가까울수록 Good)
lat_range = shower['latitudestart'] - shower['latitudeend']
mid_lat = (shower['latitudestart'] + shower['latitudeend']) / 2
lat_score = max(0, 1 - (abs(city_lat - mid_lat) / (lat_range / 2)))
# 4. 달 위상 점수 계산
relevant_moons = moon_phases[(moon_phases['date'] >= shower['startdate']) & (moon_phases['date'] <= shower['enddate'])]
for _, moon in relevant_moons.iterrows():
moon_score = 1 - moon['percentage']
total_score = (lat_score * 0.5 + moon_score * 0.5) * 100 # 100점 만점 환산
results.append({
'Meteor Shower': shower['name'],
'Date': moon['date'].strftime('%Y-%m-%d'),
'Constellation': shower['radiant'],
'Score': round(total_score, 2)
})
return pd.DataFrame(results).sort_values(by='Score', ascending=False)
Seoul)단순히 유성우 이름과 가장 좋은 날짜 하루씩만 나열합니다.
In Seoul you can see the following meteor showers:
- Lyrids is best seen ... on April 22, 2020.
- Eta Aquarids is best seen ... on April 22, 2020.
- ...
Seoul)날짜별로 관측 점수가 높은 순서대로 정렬되어, 언제가 '진짜' 최적기인지 알 수 있습니다.
| Meteor Shower | Date | Constellation | Score |
|---|---|---|---|
| Chang'e | 2020-10-16 | Draco | 136.0 |
| Chang'e | 2020-10-15 | Draco | 135.0 |
| ... | ... | ... | ... |
| Eta Aquarids | 2020-05-05 | Aquarius | 22.0 |
같은 유성우라도 날짜별로 점수가 다르게 계산되어 더 정밀한 관측 계획을 세울 수 있게 되었습니다.
도움이 되셨다면 ❤️와 댓글 부탁드립니다!