Search
📈

[시리즈] 공공데이터를 활용한 데이터과학(파이썬)

생성일
2025/05/11 13:35
태그
두부쌤
파이썬
코랩
안녕하세요~ 두부쌤 입니다!
이번에는 매달 시리즈로 연재 되는 ‘공공 데이터를 활용하여 데이터 과학(파이썬)’을 진행해 보려고 합니다~
파이썬으로 데이터를 분석하기 위해, 따로 설치가 필요 없는 구글 코랩(Google Colab)을 활용해 보겠습니다!

구글 코랩이란?

Colaboratory(줄여서 'Colab')을 통해 브라우저 내에서 Python 스크립트를 작성하고 실행할 수 있습니다.
[장점]
구성이 필요하지 않음
무료로 GPU 사용
간편한 공유
다양한 공공 데이터들 중에 어떤 주제로 진행할까 굉장히 고민을 많이 했는데요…
최근 들어 더워지는 시기와 추워지는 시기가 빨라지는 것 같은데, 진짜 그럴까? 하는 의문이 들어 연도별로 가장 더운 시기가장 추운 시기를 찾고, 비교·분석하여 그 변화를 살펴본 후 이를 바탕으로 2025년의 고온/저온 시기를 예측해보는 프로젝트를 진행해 보았습니다.
데이터 분석에 필요한 다양한 라이브러리와 개념들을 간단하게 살펴보며 진행하니, 선생님들께서 프로젝트형 수업을 설계하실 때 참고하시면 좋을 것 같습니다~~ (저는 파이썬으로 설명하지만, 엔트리나 다른 도구로도 충분히 진행하실 수 있습니다! 엔트리나 다른 도구로 진행하는 것은 추후에 다시 소개하도록 하겠습니다~ )

■ 데이터 수집

그럼 시작해 보겠습니다~
먼저, 기온 데이터가 필요하겠죠?! 지역별 기온 데이터는 기상자료개방포털에서 제공하고 있습니다.
검색조건은 다음과 같습니다.
자료 형태: 일 자료
기간: 2015년 1월 1일 ~ 2024년 12월 31일(최근 10년)
장소: 서울특별시 - 서울
데이터: 기온 - 평균기온
조회 버튼을 누르고 CSV 파일을 다운로드 하겠습니다.
다운 받은 파일을 열면 위와 같이 [지점], [지점명], [일시], [평균기온] 속성과 데이터를 확인할 수 있습니다.
서울에서 측정한 하루 평균기온이 일자별로 있습니다.
(파일명은 ‘서울 일별 평균 기온 데이터(2015-2024)’로 바꾸겠습니다.)
이렇게 시간의 흐름에 따라 수집한 데이터를 시계열 데이터라고 합니다.
시계열 데이터(Time Series Data): 
시간의 흐름에 따라 수집한 데이터로, 특정 시간 간격을 두고 연속적으로 관측된 값
시계열 데이터를 분석하여 숨겨진 패턴이나 규칙을 찾아내고 미래를 예측하는 것을 시계열 분석이라고 하며, 주요 기법에는 이동평균법, 지수평활법 등이 있습니다. 저희는 이 중에서 이동평균법을 통해 데이터를 분석해보도록 하겠습니다!

■ 데이터 분석 시작

그러면 수집한 데이터를 바탕으로 코랩을 통해 본격적으로 데이터 분석해 보도록 하겠습니다~
진행 순서는 다음과 같습니다.
1.
코랩 준비하기
2.
데이터 파일 불러오기
3.
연도별 최고 기온, 최저 기온 날짜 분석
4.
데이터 시각화
5.
데이터 분석
+알파
6.
인공지능 예측

1. 코랩 준비하기

코랩에 접속하여 로그인한 후, 파일 - Drive의 새 노트북을 클릭합니다.
이제 준비가 다 되었습니다! 코드 셀에 코드를 작성하고 실행하면 됩니다~
※ 단축키 참고
Ctrl + Enter : 해당 셀 실행하고 커서는 해당 셀에 그대로
Shift + Enter : 해당 셀을 실행하고 커서를 다음 셀로 이동
Alt + Enter : 해당 셀을 실행하고 아래에 셀을 추가 후 이동
Ctrl + F8 : 현재 셀 포함 이전 셀을 실행
Ctrl + F10: 노트의 모든 셀을 실행

2. 데이터 파일 불러오기

이제 코랩 세션에 다운 받은 파일을 업로드 하겠습니다.
다음 코드를 실행하면 파일 업로드 창이 뜨고, 다운 받은 파일을 선택해서 업로드하면 됩니다. 혹은 직접 업로드해도 됩니다.
[코드 실행]
from google.colab import files seoul_temp = files.upload()
Python
복사
[직접 업로드]

3. 연도별 최고 기온, 최저 기온 날짜 분석

데이터 분석할 때 많이 활용하는 라이브러리인 pandas를 활용하여 데이터를 분석해 보도록 하겠습니다~
pandas 라이브러리를 불러온 후 read_csv()를 통해 파일을 읽어 temp_df에 할당합니다. 이후 temp_df의 데이터를 확인해 보겠습니다.
import pandas as pd # 파일 불러오기 temp_df = pd.read_csv('서울 일별 평균 기온 데이터(2015-2024).csv', encoding='cp949') # 데이터 프레임 출력 temp_df
Python
복사
데이터가 잘 할당되어 temp_df 데이터 프레임이 만들어졌습니다.
다음으로 시계열 분석을 위해 pd.to_datetime()를 통해 [일시] 속성의 데이터를 날짜 형식으로 변환한 후,
.dt.year, .dt.month, dt.dat를 통해 연도, 월, 일을 추출하겠습니다.
# 날짜 형식으로 변환 temp_df['일시'] = pd.to_datetime(temp_df['일시']) # 연도, 월, 일 추출 temp_df['연도'] = temp_df['일시'].dt.year temp_df['월'] = temp_df['일시'].dt.month temp_df['일'] = temp_df['일시'].dt.day # 데이터 프레임 출력 temp_df
Python
복사
[일시] 속성에서 [연도], [월], [일] 속성을 추출하여 temp_df에 새롭게 추가하였습니다.
이제 연도별로 최고 기온과 최저 기온과 최고 기온이 시작된 날짜를 찾아보도록 하겠습니다.

찾기 전에!

하루를 기준으로 가장 더운 날, 가장 추운 날을 설정하면 원하는 분석 결과를 방해하는 노이즈(Noise)가 발생할 수 있습니다. 노이즈란 분석하고자 하는 패턴을 방해하는 불규칙하고 예측 불가능한 요소를 뜻합니다. 겨울철에 유난히 기온이 높거나, 여름철에 유난히 기온이 낮은 날 등 일반적인 흐름에서 벗어난 값들이 바로 노이즈에 해당합니다.
따라서 노이즈를 줄이기 위해 일정한 기간 동안의 데이터 평균을 계산하고, 그 평균을 일정 간격으로 이동시키며 계산하는 이동평균법을 활용하겠습니다. 이동평균을 통해 불안정한 노이즈를 줄이고, 데이터를 안정적으로 만들어 패턴을 잘 파악할 수 있습니다.
노이즈를 줄이기 위해 7일 연속 평균기온을 기준으로 가장 더운 날과 가장 추운 날을 판단하겠습니다.
즉, 가장 더운 시기는 일주일 단위로 7일 연속 평균 기온이 가장 높은 시기로 설정하고, 가장 추운 시기도 마찬가지로 7일 연속 평균 기온이 가장 낮은 시기로 하겠습니다.
다음 코드를 통해 [7일 이동평균] 속성을 만들어봅시다.
temp_df['7일_이동평균'] = temp_df['평균기온(°C)'].rolling(window=7, center=True).mean()
Python
복사
※ 코드 설명
.rolling()은 일정한 크기의 이동하는 창을 만들어줍니다.
window=7 → 크기가 7인 창
center=True → 계산한 값을 중간 날짜에 맞춰서 배치(1~7일 평균을 4일 위치에 배치)
데이터를 시각화했을 때 평균값의 위치를 쉽게 파악하기 위해 center=True로 했습니다.
.mean()은 rolling()으로 만든 창에 대해 평균을 계산하도록 합니다.
계산 결과를 보도록 하겠습니다.
temp_df
Python
복사
7일간의 이동평균이 잘 계산되었네요.
하지만 수치로만 봐서는 한 눈에 파악하기 힘드니, 우선 2015년 데이터만 그래프로 시각화해 보겠습니다.
그래프로 시각화하기 위해서 matplotlib 라이브러리를 활용하겠습니다.
시각화하기에 앞서, 그래프상의 한글이 올바르게 표시하고, 마이너스 기호가 제대로 표시되도록 설정해 보겠습니다.
[글꼴 설정 전]
[마이너스 기호 설정 전]
다음 코드를 실행한 후, 런타임-세션 다시 시작 및 모두 실행을 실행합니다.
(다시 시작하지 않으면 오류가 발생합니다.)
# 한글 글꼴 설치 !sudo apt-get install -y fonts-nanum !sudo fc-cache -fv !rm ~/.cache/matplotlib -rf
Python
복사
세션 다시 시작 및 모두 실행을 진행한 후, 다음 코드를 실행합니다.
import matplotlib.pyplot as plt # 한글 글꼴 설정 plt.rc('font', family='NanumBarunGothic') # 마이너스 기호 제대로 표시되도록 설정 plt.rcParams['axes.unicode_minus'] = False
Python
복사
[글꼴 설정 후]
[마이너스 기호 설정 후]
이제 matplotlib의 pyplot 모듈을 활용해 데이터를 선 그래프로 시각화해 보겠습니다.
코드는 다음과 같습니다.
import matplotlib.pyplot as plt # 2015년 데이터 필터링 df_2015 = temp_df[temp_df['연도'] == 2015] # 그래프 크기 설정 plt.figure(figsize=(15, 5)) # 평균기온과 7일_이동평균 선 그래프 그리기 plt.plot(df_2015['일시'], df_2015['평균기온(°C)'], label='일일 평균기온', alpha=0.5) plt.plot(df_2015['일시'], df_2015['7일_이동평균'], label='7일 이동 평균', color='red') # 그래프 제목 및 축 레이블 설정 plt.title('2015년 평균기온 및 7일 이동평균') plt.xlabel('날짜') plt.ylabel('기온 (°C)') # 격자 설정 plt.grid(True) # 범례 표기 plt.legend() # 그래프 레이아웃 조정 plt.tight_layout() # 그래프 출력 plt.show()
Python
복사
※ 코드 설명
plt.figure(figsize=(15, 5)): 그래프의 크기 설정. 가로 15인치, 세로 5인치 크기
plt.plot(df_2015['일시'], df_2015['평균기온(°C)'], label='일일 평균기온', alpha=0.5)
plt.plot(): 데이터를 선 그래프로 나타냄
df_2015['일시'] : x축. 날짜를 나타냄
df_2015['평균기온(°C)'] : y축. 기온을 나타냄
label='일일 평균기온': 그래프에 범례 추가하기 위한 라벨 설정
alpha=0.5: 그래프 선의 투명도 설정. 0에 가까울수록 투명해짐
plt.title('2015년 평균기온 및 7일 이동평균'): 그래프의 제목 설정
plt.xlabel('날짜'), plt.ylabel('기온 (°C)'): x축, y축 라벨 설정
plt.legend(): 그래프에 범례(legend) 추가
plt.grid(True): 그래프에 기온 변화가 더 명확하게 보이도록 하는 그리드 선(격자) 추가
plt.tight_layout(): 그래프의 요소들이 겹치지 않도록 자동 여백 조정
plt.show(): 그래프를 화면에 출력
선 그래프를 통해 2015년의 추세를 확인할 수 있습니다~
이제 2015년의 이동평균 값이 가장 높은 날짜와, 가장 낮은 날짜를 구해보겠습니다.
# 2015년 데이터 중에서 7일 이동평균이 가장 높은 날의 정보를 추출 최고_2015 = df_2015.loc[df_2015['7일_이동평균'].idxmax()] # 2015년 데이터 중에서 7일 이동평균이 가장 낮은 날의 정보를 추출 최저_2015 = df_2015.loc[df_2015['7일_이동평균'].idxmin()] # 최고 이동평균 정보 출력 print("2015년 가장 높은 이동평균:") print(f"날짜: {최고_2015['일시']}, 7일 이동평균: {최고_2015['7일_이동평균']} °C") # 최저 이동평균 정보 출력 print("\n2015년 가장 낮은 이동평균:") print(f"날짜: {최저_2015['일시']}, 7일 이동평균: {최저_2015['7일_이동평균']} °C")
Python
복사
※ 코드 설명
df_2015['7일_이동평균'].idxmax() : 7일_이동평균 값 중에서 가장 큰 값의 인덱스 반환
df_2015.loc[ ] : 특정 인덱스를 기준으로 행 전체를 가져옴.
최고_2015
Python
복사
이 데이터들을 그래프 위에도 표기할 수 있습니다
# 그래프 plt.figure(figsize=(15, 5)) plt.plot(df_2015['일시'], df_2015['평균기온(°C)'], label='일일 평균기온', alpha=0.5) plt.plot(df_2015['일시'], df_2015['7일_이동평균'], label='7일 이동평균', color='red') # 최고점 표시 plt.scatter(최고_2015['일시'], 최고_2015['7일_이동평균'], color='green', s=100, zorder=3, label='최고 이동평균') plt.text(최고_2015['일시'], 최고_2015['7일_이동평균'] + 0.5, f"최고\n{최고_2015['일시'].date()}\n{최고_2015['7일_이동평균']:.1f}°C", ha='center', va='bottom', color='green', fontsize=9) # 최저점 표시 plt.scatter(최저_2015['일시'], 최저_2015['7일_이동평균'], color='blue', s=100, zorder=3, label='최저 이동평균') plt.text(최저_2015['일시'], 최저_2015['7일_이동평균'] - 1, f"최저\n{최저_2015['일시'].date()}\n{최저_2015['7일_이동평균']:.1f}°C", ha='center', va='top', color='blue', fontsize=9) # 기본 그래프 설정 # 그래프 제목 및 축 레이블 설정 plt.title('2015년 평균기온 및 7일 이동평균 (최고/최저 표시)') plt.xlabel('날짜') plt.ylabel('기온 (°C)') # 격자 설정 plt.grid(True) # 범례 표기 plt.legend() # 그래프 레이아웃 조정 plt.tight_layout() # 그래프 출력 plt.show()
Python
복사
※ 코드 설명
plt.scatter(x, y, ...): x축 좌표, y축 좌표를 기준으로 해당 위치에 점을 찍음. (산점도 그리는 함수로 데이터의 특정 지점을 강조하거나 데이터 간의 관계를 시각화할 때 사용)
최고_2015['일시']: x축
최고_2015['7일_이동평균']: y축
color='green': 점 색깔
s=100: 점의 크기
zorder=3: 그래프 위에 표시 되도록 순서 조정
label='최고 이동평균': 범례에 표시될 라벨 설정
plt.text(x, y, text): x, y 위치에 텍스트 작성.
최고_2015['일시']: x 위치
최고_2015['7일_이동평균'] + 0.5 : y 위치. 텍스트가 그래프에 가리지 않도록 약간 위로 설정.
f"최고\n{최고_2015['일시'].date()}\n{최고_2015['7일_이동평균']:.1f}°C": 텍스트 내용
최고_2015['일시'].date(): 날짜와 시간 정보 중에 날짜만 추출
ha='center': 수평 방향으로 가운데 정렬
va='bottom': 수직 방향에서 아래 정렬
color='green': 글자 색깔
fontsize=9: 글자 크기
이렇게 연도별로 그래프로 시각화한다면, 7일 이동평균이 가장 높은 날짜, 가장 낮은 날짜를 한눈에 확인할 수 있습니다.
이제 2015년부터 2024년까지 각 연도별로 최고 기온 시기와 최저 기온 시기를 차례대로 구해보겠습니다.
먼저 연도별 최고 기온부터 확인하겠습니다.
# 연도별 최고 기온 추출 peak_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmax()] # 출력하고 싶은 열 선택 peak_dates = peak_dates[['연도', '일시', '7일_이동평균']].reset_index(drop=True) # 결과 확인 peak_dates
Python
복사
※ 코드 설명
temp_df.groupby('연도') : 연도별로 데이터를 그룹화(묶기)
['7일_이동평균'].idxmax() : 각 연도별로 ‘7일_이동평균’ 값이 가장 높은 인덱스(행 번호)를 반환
temp_df.loc[ ] : 특정 인덱스를 기준으로 행 전체를 가져옴.
['연도', '일시', '7일_이동평균'] : 출력하고 싶은 열 선택
.reset_index(drop=True) : 원래 인덱스 번호를 제거하고, 인덱스를 0부터 새로 설정
출력 결과 옆의 그래프 표기를 누르면 그래프를 쉽게 그릴 수도 있습니다.
2018년이 가장 높았으며 2020년이 가장 낮다는 사실을 확인할 수 있습니다.
하지만 뚜렷한 증가나 감소의 추세는 나타나지 않았습니다.
다음으로 연도별 최저 기온을 확인하겠습니다.
# 연도별 최저 기온 추출 bottom_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmin()] # 출력하고 싶은 열 선택 bottom_dates = bottom_dates[['연도', '일시', '7일_이동평균']].reset_index(drop=True) # 결과 확인 bottom_dates
Python
복사
2018년이 가장 낮고 2019년이 가장 높으나, 특정한 패턴이나 일관된 경향은 보이지 않습니다.
하지만 연도별 최고기온과 최저기온은 어떠한 관계를 가지고 있는 것처럼 보입니다.
이를 한눈에 파악해보기 위해 연도별 최고기온과 최저기온을 하나의 그래프로 나타내보겠습니다.
import matplotlib.pyplot as plt # 연도별 최고/최저 기온 데이터 peak_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmax()] bottom_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmin()] # 그래프 크기 설정 plt.figure(figsize=(15, 5)) # 최고 기온과 최저 기온 선 그래프 그리기 plt.plot(peak_dates['연도'], peak_dates['7일_이동평균'], marker='o', color='red', label='최고 기온') plt.plot(bottom_dates['연도'], bottom_dates['7일_이동평균'], marker='o', color='blue', label='최저 기온') # 그래프 제목 및 축 레이블 설정 plt.title('연도별 최고 및 최저 7일 이동평균 기온') plt.xlabel('연도') plt.ylabel('기온 (°C)') # 격자 설정 plt.grid(True) # 범례 표기 plt.legend() # 그래프 레이아웃 조정 plt.tight_layout() # 그래프 출력 plt.show()
Python
복사
※ 코드 설명
marker='o’: 그래프에서 데이터 포인트를 원형 모양으로 표기
‘x’: 엑스 모양
‘^’: 삼각형
‘s’: 사각형
‘.’: 점
선 그래프로 나타내었으나, 아쉽게도 관계를 파악하기 쉽지 않습니다.
조금 더 명확하게 보기 위해 앞서 설명한 산점도로 시각화해 보도록 하겠습니다.
import matplotlib.pyplot as plt # 최고 기온과 최저 기온을 추출한 데이터 peak_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmax()] bottom_dates = temp_df.loc[temp_df.groupby('연도')['7일_이동평균'].idxmin()] # 그래프 크기 설정 plt.figure(figsize=(5, 5)) # 최고 기온과 최저 기온 산점도 그리기 plt.scatter(peak_dates['7일_이동평균'], bottom_dates['7일_이동평균'], color='purple') # 그래프 제목 및 축 레이블 설정 plt.title('최고기온 - 최저기온 산점도') plt.xlabel('7일 이동평균 최고기온 (℃)') plt.ylabel('7일 이동평균 최저기온 (℃)') # 격자 설정 plt.grid(True, linestyle='--', alpha=0.5) '-' ':' '-.' # 그래프 레이아웃 조정 plt.tight_layout() # 그래프 출력 plt.show()
Python
복사
※ 코드 설명
plt.scatter(peak_dates['7일_이동평균'], bottom_dates['7일_이동평균'], color='purple')
peak_dates['7일_이동평균']: x축
bottom_dates['7일_이동평균']: y축
plt.grid(True, linestyle='--', alpha=0.5)
True: 격자 표시 O
'--': 격자의 선 스타일을 ‘점선(dashed line)’으로
'-' : 실선
':': 점선 (dotted)
'-.': 점-실선 혼합 스타일
산점도로 시각화하니 최저기온과 최고기온 사이에 음의 상관관계가 있는 것처럼 보입니다.
조금 더 정밀한 분석 위해 회귀 분석을 시행해 보도록 하겠습니다.

회귀분석이란?

회귀분석은 두 변수 간의 관계를 수학적으로 표현하는 분석 방법입니다.
하나의 변수가 변화할 때, 다른 변수가 어떻게 반응하는지 파악하고 함수 형태로 모델링합니다.
즉, 얻은 데이터에 잘 들어맞는 f(x)를 추정하고 2개 변수( x와 y ) 간 관계를 구합니다.
y=f(x)+오차(ε)y = f(x) + 오차(ε)
x: 독립변수(설명변수). 원인이 되는 요소
y: 종속변수(반응변수). 결과 또는 예측하고자 하는 값
f(x): x에 대한 함수로 회귀분석을 통해 추정하고자 하는 함수
ε: 오차항. 설명되지 않는 예외나 변동성
회귀 분석을 통해 최저기온과 최고기온의 관계를 파악하기 위해 몇 가지 분석 지표를 계산하고 그래프 위에 나타내보도록 하겠습니다.

[분석 지표]

회귀 직선: 두 변수 간의 관계를 직선으로 모델링한 것으로, 변수 간의 경향성을 시각적으로 보여줍니다.
상관계수: 두 변수 간의 선형적 관계의 강도와 방향을 수치로 나타낸 지표입니다. (1에 가까울수록 양의 상관관계, -1에 가까울수록 음의 상관관계)
p-value(유의확률): 해당 상관관계가 우연히 나타났을 가능성을 보여주는 지표로, 일반적으로 0.05 미만이면 통계적으로 유의하다고 판단합니다.
통계적으로 유의하다: 어떤 결과가 단순한 우연이 아니라, 실제로 의미 있는 차이나 관계가 있다는 것 통계적으로 판단했다는 뜻입니다.
분석 지표의 상관계수를 측정하는 여러가지 방법 중에 피어슨 상관계수를 활용해 보도록 하겠습니다.

피어슨 상관계수

: 두 데이터 집합 간의 선형 상관 관계를 측정하는 상관계수
[조건]
두 변수 간의 관계가 선형 관계
두 변수가 모두 연속형 데이터
두 변수가 모두 정규분포를 따라야 함
이상치가 있으면 크게 왜곡될 수 있음
피어슨 상관계수를 활용하기 위해 두 변수가 모두 정규성을 가지고 있는지 Shapiro-Wilk 정규성 검정(p값이 0.05 이상이면 정규성 ○)을 통해 확인해 보겠습니다.
from scipy.stats import shapiro # 최고기온과 최저기온 추출 x = peak_dates['7일_이동평균'] # 연도별 최고 이동평균 기온 y = bottom_dates['7일_이동평균'] # 연도별 최저 이동평균 기온 # x (최고기온) 정규성 검정 stat_x, p_x = shapiro(x) print(f"x (최고기온) 정규성 검정 p-value: {p_x:.3f}") # y (최저기온) 정규성 검정 stat_y, p_y = shapiro(y) print(f"y (최저기온) 정규성 검정 p-value: {p_y:.3f}") # 결과 출력 if p_x > 0.05 and p_y > 0.05: print("두 변수 모두 정규성을 만족합니다.") else: print("정규성을 만족하지 않습니다.")
Python
복사
※ 코드 설명
stat_x, p_x = shapiro(x): x에 해당하는 데이터에 대해 정규성 검정 수행
stat_x: 검정 통계량
p_ x: p-value
[출력 결과]
x(최고기온), y(최저기온) 모두 정규성을 만족하므로 피어슨 상관계수를 사용할 수 있습니다.
피어슨 상관계수를 포함한 여러 분석 지표를 계산하여 그래프에 나타내보겠습니다.
import matplotlib.pyplot as plt import numpy as np import scipy.stats as stats # 최고기온과 최저기온 추출 x = peak_dates['7일_이동평균'] # 연도별 최고 이동평균 기온 y = bottom_dates['7일_이동평균'] # 연도별 최저 이동평균 기온 # 회귀선 계산 (기울기, 절편) slope, intercept = np.polyfit(x, y, 1) reg_line = slope * x + intercept # 상관계수 및 p-value 계산 corr, p_value = stats.pearsonr(x, y) # 그래프 시각화 plt.figure(figsize=(5, 5)) plt.scatter(x, y, color='purple', label='연도별 최고-최저점') plt.plot(x, reg_line, color='orange', linewidth=2, label='회귀선') # 제목 및 레이블 plt.title('최고기온 vs 최저기온 (회귀선 및 상관계수)') plt.xlabel('7일 이동평균 최고기온 (℃)') plt.ylabel('7일 이동평균 최저기온 (℃)') plt.grid(True, linestyle='--', alpha=0.5) plt.legend() plt.tight_layout() plt.show() # 결과 출력 print(f'회귀식: 최저기온 = {slope:.2f} * 최고기온 + {intercept:.2f}') print(f'상관계수 (Pearson): {corr:.3f}') print(f'P-value: {p_value:.3f}') # 해석 출력 if p_value < 0.05: print("통계적으로 유의한 상관관계가 있습니다.") else: print("통계적으로 유의한 상관관계는 없습니다.")
Python
복사
상관계수가 -0.788으로 통계적으로 유의한 상관관계(음의 상관관계)가 있음을 확인할 수 있습니다.
이를 통해 최고기온이 높은 해 일수록 최저기온은 낮은 경향이 있다는 것을 알 수 있습니다.
이는 서울에서 어떤 해의 여름이 매우 더웠다면, 그 해 겨울은 매우 추웠을 가능성이 높다는 것을 의미합니다. 특정 해에 기온의 양 극단이 동시에 나타나는 경향이 있다는 것입니다.
하지만 이는 서울 지역에 국한된 10년간의 자료를 바탕으로 도출한 결과이므로,
전 지구적인 기후 변화 추세로 일반화하기에는 한계가 있습니다!
(전 지구적인 변화 추세는 추후 기회가 된다면 다시 다뤄보도록 하겠습니다~)

■ 인공지능 예측

이제 앞서 분석한 2015년부터 2024년까지의 서울 일별 평균 기온 데이터를 활용하여 2025년 서울에서 최고기온과 최저기온이 나타나는 시기와 기온값을 예측해 보겠습니다.
이를 위해 SARIMA 모델을 활용하겠습니다.

SARIMA(Seasonal ARIMA)

: ARIMA 모델에 계절성 요소를 추가한 시계열 예측 모델입니다.
ARIMA(p,d,q)(P,D,Q,s)ARIMA(p,d,q)*(P,D,Q,s)
p: 자기회귀 차수 #현재 값을 예측할 때, 이전 값을 몇 개 반영할지
d: 차분 횟수 #데이터의 추세를 없애기 위해 몇 번 차분할지
q: MA(이동평균 차수) #과거 예측 오차를 몇 개 반영할지
P: 계절 AR 차수 #이전 계절 주기 단위의 값을 몇 개 사용할지
D: 계절 차분 횟수 #계절성 추세를 없애기 위해 몇 번 차분할지
Q: 계절 MA 차수 #이전 계절 예측 오차를 몇 개 사용할지
s: 계절 주기 #계절이 반복되는 주기(365 → 1년 주기)
2015년 ~ 2024년의 일일 데이터를 모두 사용하여 학습하여야 예측률이 높아지지만
학습에 너~~~무 많은 시간이 걸리기 때문에
여름은 7월, 8월, 겨울은 12월, 1월 데이터를 선택하고
이동평균(7일) 데이터를 활용해 간단하게 예측해 보겠습니다.
또한 2022년 → 2023년 → 2024년 데이터를 테스트 데이터로 활용하여
순차적으로 성능 평가를 실행하겠습니다.
훈련 데이터: 2015~2021 / 2015~2022 / 2015~2023
테스트 데이터: 2022/2023/2024
예측 데이터: 2025 ( 2015~2024로 훈련)
시간이 너무 많이 걸리기에, 여름 데이터, 겨울 데이터 각각을 따로 진행하겠습니다.
(그래도 많이 걸립니다... )

1) 2025년 여름 예측

먼저, 2025년 가장 더운 날을 예측해 보겠습니다. 데이터를 불러오는 것부터 차례대로 다시 진행해 봅시다~
import pandas as pd from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.metrics import mean_absolute_error, mean_squared_error # 1. 데이터 불러오기 및 전처리 df = pd.read_csv("서울 일별 평균 기온 데이터(2015-2024).csv", encoding='cp949') df['일시'] = pd.to_datetime(df['일시']) df['평균기온(°C)'] = pd.to_numeric(df['평균기온(°C)'], errors='coerce') df.set_index('일시', inplace=True) # 2. 7,8월 필터링 summer = df[df.index.month.isin([7, 8])].copy() # 3. 이동평균 적용 summer['7일이동평균'] = summer['평균기온(°C)'].rolling(window=7, center=True).mean() # 4. 예측 함수 정의 def evaluate_and_predict(train_year_end, test_year): train = summer[summer.index.year <= train_year_end]['7일이동평균'].dropna() test = summer[summer.index.year == test_year]['7일이동평균'].dropna() # 날짜 주기 지정 train = train.asfreq('D') train = train.interpolate() model = SARIMAX(train, order=(1,1,1), seasonal_order=(1,1,1,62)) model_fit = model.fit(disp=False) pred = model_fit.forecast(steps=len(test)) pred.index = test.index mae = mean_absolute_error(test, pred) mse = mean_squared_error(test, pred) print(f"[{test_year}년 여름 예측 성능]") print(f"MAE: {mae:.2f} °C") print(f"MSE: {mse:.2f} °C\n") # 5. 순차 평가 실행 evaluate_and_predict(2021, 2022) evaluate_and_predict(2022, 2023) evaluate_and_predict(2023, 2024) # 6. 2025년 예측 full_train = summer[summer.index.year <= 2024]['7일이동평균'].dropna().asfreq('D').interpolate() model_2025 = SARIMAX(full_train, order=(1,1,1), seasonal_order=(1,1,1,62)) fit_2025 = model_2025.fit(disp=False) pred_2025 = fit_2025.forecast(steps=62) pred_2025.index = pd.date_range(start='2025-07-01', periods=62, freq='D') # 7. 예측 결과 출력 max_day = pred_2025.idxmax() print("[2025년 여름 예측 결과]") print(f"가장 더운 날: {max_day.date()} / 평균기온(7일 이동평균): {pred_2025.max():.2f} °C")
Python
복사
모든 데이터를 활용하지 않아 모델의 성능이 좋지 않기에, 예측 결과의 신뢰도가 낮습니다.
따라서 이번 예측은 정확한 예측 결과를 목표로 하기 보다는, 과정 중심의 실험적 시도에 의의를 두고자 합니다.

1) 2025년 겨울 예측

이어서 1월, 12월 데이터를 활용해 가장 추운 날을 예측해 보겠습니다.
import pandas as pd from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.metrics import mean_absolute_error, mean_squared_error # 1. 데이터 불러오기 및 전처리 df = pd.read_csv("서울 일별 평균 기온 데이터(2015-2024).csv", encoding='cp949') df['일시'] = pd.to_datetime(df['일시']) df['평균기온(°C)'] = pd.to_numeric(df['평균기온(°C)'], errors='coerce') df.set_index('일시', inplace=True) # 2. 1월,12월 필터링 winter = df[df.index.month.isin([1, 12])].copy() # 3. 이동평균 적용 winter['7일이동평균'] = winter['평균기온(°C)'].rolling(window=7, center=True).mean() # 4. 예측 함수 정의 def evaluate_and_predict_winter(train_year_end, test_year): train = winter[winter.index.year <= train_year_end]['7일이동평균'].dropna() test = winter[winter.index.year == test_year]['7일이동평균'].dropna() # 날짜 주기 지정 train = train.asfreq('D').interpolate() model = SARIMAX(train, order=(1,1,1), seasonal_order=(1,1,1,62)) model_fit = model.fit(disp=False) pred = model_fit.forecast(steps=len(test)) pred.index = test.index mae = mean_absolute_error(test, pred) mse = mean_squared_error(test, pred) print(f"[{test_year}년 겨울 예측 성능]") print(f"MAE: {mae:.2f} °C") print(f"MSE: {mse:.2f} °C\n") # 5. 순차 평가 실행 evaluate_and_predict_winter(2021, 2022) evaluate_and_predict_winter(2022, 2023) evaluate_and_predict_winter(2023, 2024) # 6. 2025년 예측 full_train = winter[winter.index.year <= 2024]['7일이동평균'].dropna().asfreq('D').interpolate() model_2025 = SARIMAX(full_train, order=(1,1,1), seasonal_order=(1,1,1,62)) fit_2025 = model_2025.fit(disp=False) pred_2025 = fit_2025.forecast(steps=62) pred_2025.index = pd.date_range(start='2025-12-01', periods=62, freq='D') # 12월+1월 # 7. 예측 결과 출력 min_day = pred_2025.idxmin() print("[2025년 겨울 예측 결과]") print(f"가장 추운 날: {min_day.date()} / 평균기온(7일 이동평균): {pred_2025.min():.2f} °C")
Python
복사
2025년의 가장 추운 날을 예측할 수 있었습니다!
모델의 성능을 높이기 위해 여러 가지 방법으로 많이 시도해 보았으나, 코랩 환경에서는 자원의 한계로 인해 다양한 모델을 안정적으로 실행하는 데에 어려움이 있었습니다.
SARIMA 모델을 활용한 예측 결과는 일반화를 하기에는 한계가 있지만, 2022년 예측에는 괜찮은 성능을 보였습니다. 추후 완전한 데이터를 활용한다면 더 나은 성능을 보일 수 있을 것 같습니다!
오늘은 이렇게 공공 데이터를 바탕으로 실제 기온의 흐름을 분석하고, 2025년의 가장 더운 날과 가장 추운 날을 예측해보는 과정을 진행해 보았습니다. 단순히 정답을 찾는 것이 아니라, 데이터를 통해 질문하고, 가설을 세우고, 분석하고, 수정하고, 해석하는 데이터 과학의 과정을 체험해 보는 데 의미를 두고 준비하였습니다.
지금까지 긴 과정을 함께해 주셔서 감사합니다!
다음 달에는 더 멋진 주제로 돌아오겠습니다~~