일기예보에서 “평년보다 3도 높습니다”라고 할 때, 그 ‘평년’이 뭔지 정확히 아는 분은 많지 않습니다. 하지만 이 평년값이야말로 기후변화를 측정하는 가장 강력한 기준선(Baseline)입니다.
2025년 현재, 전 세계는 이상기후로 신음하고 있습니다. 한국도 예외가 아니죠. “예전엔 이렇지 않았는데…”라는 말이 이제는 데이터로 증명됩니다. 기상청은 30년간의 기후 관측 데이터를 평균한 ‘평년값’을 공개하고 있으며, 이를 통해 우리는 기후가 얼마나 변했는지 객관적으로 확인할 수 있습니다.
오늘 포스팅에서는 기상청 API 시리즈의 심화 편으로, ‘지상관측데이터 평년값(일조, 구름, 기온, 습도) 조회서비스’를 완벽하게 마스터해 보겠습니다. 이전에 다룬 태양광 발전량 예측과 실시간 날씨 예측 모델에서는 실시간 데이터를 다뤘다면, 오늘은 ‘기준이 되는 데이터’를 확보하는 방법을 배웁니다.
이 API 하나면 1961년부터 2020년까지 60년치 평년값을 언제든지 조회할 수 있으며, 파이썬 코드 몇 줄로 전국의 기후 기준선을 손안에 쥘 수 있습니다!
데이터 끝에 =가 붙어있고, 헤더가 #으로 시작하는 기상청 특유의 독특한 구조입니다. 파싱에 주의해야 합니다!
2단계: 응답 데이터 파싱 (정제된 DataFrame 만들기)
API가 던져주는 날것의 CSV 텍스트를 Pandas DataFrame으로 변환해 봅시다. 기상청 특유의 # 주석과 마지막 = 기호를 제거하는 로직이 핵심입니다.
def parse_normal_data(response_text):
"""
기상청 평년값 API 응답을 DataFrame으로 변환
- # 주석 라인 제거
- 마지막 = 기호 제거
- 숫자형 변환
"""
lines = response_text.split('\n')
data_rows = []
columns = ['ST', 'STN', 'MM', 'DD', 'TA', 'TA_MAX', 'TA_MIN', 'HM', 'SS', 'CA_TOT']
for line in lines:
line = line.strip()
# 빈 줄이나 주석 건너뛰기
if not line or line.startswith('#'):
continue
# 끝에 =, 콤마 제거
line = line.rstrip('=,').strip()
# 콤마로 분리
parts = [p.strip() for p in line.split(',')]
# 빈 문자열 제거
parts = [p for p in parts if p]
# 정확히 10개 필드가 아니면 스킵
if len(parts) != 10:
continue
data_rows.append(parts)
# DataFrame 생성
df = pd.DataFrame(data_rows, columns=columns)
# 숫자형 변환
numeric_cols = ['ST', 'STN', 'MM', 'DD', 'TA', 'TA_MAX', 'TA_MIN', 'HM', 'SS', 'CA_TOT']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
return df
import time
# 주요 도시 지점번호
CITIES = {
'서울': 108,
'부산': 159,
'인천': 112,
'광주': 156,
'대전': 133,
'제주': 184
}
api = KMANormalValueAPI(api_key="YOUR_API_KEY")
results = {}
for city, stn in CITIES.items():
print(f"[{city}] 평년값 조회 중...")
df = api.fetch_norm_data(stn=stn, start_date='0101', end_date='1231')
if not df.empty:
results[city] = df
print(f" ✓ {len(df)}개 데이터 수집 완료")
else:
print(f" ✗ 데이터 수집 실패")
time.sleep(0.5) # API 부하 방지
# 연평균 기온 비교
annual_avg = {city: df['TA'].mean() for city, df in results.items()}
sorted_cities = sorted(annual_avg.items(), key=lambda x: x[1], reverse=True)
print("\n=== 연평균 기온 순위 (평년값 기준) ===")
for rank, (city, temp) in enumerate(sorted_cities, 1):
print(f"{rank}위: {city} {temp:.1f}°C")
결과
=== 연평균 기온 순위 (평년값 기준) ===
1위: 제주 16.2°C
2위: 부산 15.0°C
3위: 광주 14.2°C
4위: 대전 13.1°C
5위: 서울 12.9°C
6위: 인천 12.5°C
제주가 가장 따뜻하고, 고위도 지역이 가장 추운 것을 데이터로 확인할 수 있습니다!
5단계: 1년치 데이터 수집 및 CSV 저장
장기 분석을 위해 전체 연도 평년값을 수집하고 파일로 저장합니다.
import os
def collect_annual_normal_data(api_key: str, cities: dict, output_dir: str = "normal_data"):
"""
여러 도시의 연간 평년값 데이터를 수집하고 CSV로 저장
"""
os.makedirs(output_dir, exist_ok=True)
api = KMANormalValueAPI(api_key=api_key)
print(f"=== 평년값 데이터 수집 시작 ===")
for city, stn in cities.items():
print(f"\n[{city}] 데이터 요청 중...")
# 1년 전체 조회 (1월 1일 ~ 12월 31일)
df = api.fetch_norm_data(stn=stn, start_date='0101', end_date='1231')
if not df.empty:
# 날짜 컬럼 추가 (시각화용 - 가상의 연도 2024 사용)
df['DATE'] = pd.to_datetime(
'2024-' + df['MM'].astype(str) + '-' + df['DD'].astype(str),
errors='coerce'
)
# CSV 저장
filename = os.path.join(output_dir, f"{city}_평년값_2021.csv")
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f" ✓ 저장 완료: {filename} ({len(df)}개 행)")
else:
print(f" ✗ 데이터 없음")
time.sleep(0.5)
평년값 자체는 고정값이지만, 실제 관측값과 평년값의 차이(편차)를 예측하는 AI 모델을 만들 수 있습니다. 이는 기상청 일통계 데이터와 결합하면 더욱 강력해집니다.
평년값 기반 이상기후 탐지 모델
import torch
import torch.nn as nn
import numpy as np
from sklearn.preprocessing import MinMaxScaler
class AnomalyDetectionLSTM(nn.Module):
"""
평년값 대비 이상 패턴 감지 LSTM
"""
def __init__(self, input_size=6, hidden_size=64, num_layers=2, output_size=1):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers,
batch_first=True, dropout=0.2)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
lstm_out, _ = self.lstm(x)
last_output = lstm_out[:, -1, :]
prediction = self.fc(last_output)
return prediction
# 데이터 준비 (평년값 + 실제 관측값)
df_normal = pd.read_csv("normal_data/서울_평년값_2021.csv")
# df_actual = pd.read_csv("actual_data/서울_2023.csv") # 실제 관측 데이터 필요
# 편차 계산 (가상 예시)
# df_merged = pd.merge(df_actual, df_normal, on=['MM', 'DD'], suffixes=('_actual', '_normal'))
# df_merged['TA_anomaly'] = df_merged['TA_actual'] - df_merged['TA_normal']
# 이상치 라벨링 (편차 > ±3°C = 이상기후)
# df_merged['is_anomaly'] = (df_merged['TA_anomaly'].abs() > 3).astype(int)
# print(f"이상기후 발생 일수: {df_merged['is_anomaly'].sum()}일 / {len(df_merged)}일")
모델 학습 및 이상기후 예측
from torch.utils.data import DataLoader, TensorDataset
# 특징 추출 (실제 데이터 필요)
# features = ['TA_normal', 'TA_MAX_normal', 'TA_MIN_normal', 'HM_normal', 'SS_normal', 'CA_TOT_normal']
# X = df_merged[features].values
# y = df_merged['is_anomaly'].values
# 정규화
# scaler = MinMaxScaler()
# X_scaled = scaler.fit_transform(X)
# 시퀀스 생성 (7일 윈도우)
def create_sequences(X, y, seq_length=7):
X_seq, y_seq = [], []
for i in range(len(X) - seq_length):
X_seq.append(X[i:i+seq_length])
y_seq.append(y[i+seq_length])
return np.array(X_seq), np.array(y_seq)
# 훈련/테스트 분할 및 학습 (실제 데이터 준비 후 실행)
print("⚠️ 이 코드는 실제 관측값 데이터와 결합하여 사용합니다.")
print("평년값만으로는 시계열 예측이 불가능하므로,")
print("[기상청 일통계 API](https://doyouknow.kr/kma-solar-energy-api-pytorch-lstm/)와 함께 사용하세요!")
✅ 평년값 개념: 30년 평균으로 기후 기준선 설정 (WMO 표준) ✅ API 활용: 지점별, 기간별 평년값 조회 (텍스트 형식 주의!) ✅ 데이터 정제: 이상치 제거 및 결측치 보간, Assert 검증 ✅ 시각화: 평년값 변화로 기후변화 추세 확인 (+0.3~0.5°C 상승) ✅ 상관관계 분석: LinearRegression으로 기상 요소 간 관계 파악 (R²=0.82) ✅ AI 모델: LSTM으로 이상기후 탐지 (실측값과 결합 시) ✅ 실전 활용: 농업 컨설팅, 에너지 수요 예측
기후변화는 이제 선택이 아닌 생존의 문제입니다. 평년값 데이터로 무장한 여러분이라면, 변화하는 기후에 한 발 앞서 대응할 수 있을 것입니다.
이전 포스팅에서 만든 LSTM 예측 모델에 이 평년값 데이터를 추가 Feature로 넣어보세요. 모델이 계절적 추세를 훨씬 더 잘 이해하게 되어 예측 성능이 비약적으로 상승할 것입니다!