MLOps엔지니어

ABC 부트캠프 AI파일럿 1기 MLOps 3일차_서울 기온 CSV 데이터 분석과 matplotlib 시각화

ideas9064 2026. 8. 1. 15:09

01. 무한 반복 영어 단어 퀴즈 while문으로

어제 한 for문의 영어퀴즈에 이어서 오늘은 while 문을 통해 영어 퀴즈를 만드는 것이다.

이번 목표는 "stop"을 입력할 때까지 무한히 문제가 나오고, 매번 어떤 단어가 나올지 예측할 수 없도록 랜덤하게 출제되도록 만드는 것이다.

import random

# 영어 단어 퀴즈가 무한 반복으로 진행되고, 사용자가 stop 키워드를 입력해야지만 퀴즈가 종료되게 설계

words = {'꽃':'flower',"나비":'butterfly','학교':'school','자동차':'car'}

#딕셔너리의 키들만 따로 저장 (퀴즈 문제로 사용)
words_keys = list(words.keys())

print('***영어 단어 맞추기 퀴즈 시작***')

while True:
  key = random.choice(words_keys) #random.choice
  input_word = input('%s에 해당되는 영어 단어를 입력하세요' % key)

  if input_word == 'stop':
    print('퀴즈를 종료합니다.')
    break
  elif input_word == words[key]:
      print('정답입니다.')
  else:
    print('오답입니다.')


print('***영어 단어 맞취기 퀴즈 끝***')

1) 딕셔너리에서 키 리스트를 확보.

  • 딕셔너리 리스트와 달리 순서를 보장하지 않기 때문에 인덱스로 키를 꺼내 쓸 수 없다.
  • 대신 딕셔너리가 제공하는 key() 메서드로 뷰 객체를 받은 뒤 list()로 감싸서 리스트로 변환 하여야 한다.

2) 무한 반복문과 random.choice

  • 먼저 random은 파이썬 표준 라이브러리에 포함되지 않아. import random이 필요하다.
  • random.choice()은 인자로 받은 시퀀스에서 하나의 요소를 무작위로 반환한다.
  • while True 는 조건 없이 진입 하는 무한 루프로서 종료 조건이 필요하다.
    • 'stop'을 입력 받으면 break로 탈줄한다.

02. csv 파일에 대한 이해와 NOtePad++ 편집기

1) CSV가 데이터 분석의 기본 포맷인 이유

  • CSV(Comma Separated Values)는 쉼표(,)로 필드를 구분한 순수 텍스트 파일이다.
  • 셀 파일과 대비되는 세 가지 장점
    • (1) 엑셀 파일은 별도의 소프트웨어(유료)가 설치되어야 열리지만 CSV는 메모장으로도 열린다.
    • (2) 엑셀은 대략 1만 행을 넘어가면 열기 자체가 어려워지지만 CSV는 대용량에서도 안정적이다.
    • (3) 학습 데이터셋과 공공 데이터는 대부분 CSV 형식으로 배포된다.

우리나라 공공 데이터 포털과 기상자료개방포털도 기본으로 CSV를 제공하며, 편의를 위해 XLSX도 병행 배포하는 경우가 많다. 다만 데이터 원본 이해라는 관점에서는 CSV를 다루는 능력이 더 근본적이다.

2) 인코딩과 한글 깨짐 이슈

  • CSV 파일의 가장 큰 함정은 인코딩이다. 파이썬의 기본 인코딩은 UTF-8이지만, 국내 공공기관이 배포하는 CSV는 대부분 EUC-KR로 저장되어 있다. UTF-8은 한글 한 글자를 3바이트로 표현하는 유니코드 기반 인코딩이고, EUC-KR은 한국어에 특화된 완성형 인코딩이다. 인코딩이 어긋나면 파일을 열 때 한글이 깨지거나 아예 오류가 발생한다.

3) Notepad++로 원본 확인하기

  • 원본 데이터는 무엇보다 눈으로 먼저 확인해야 한다는 것이 교수님께서 여러 번 강조하였다.
  • 원본 데이터를 볼 때 확인 해야 할 포인트
    • 파일의 총 행 수를 확인
    • 화면 하단에 표시된 인코딩을 확인
    • 컬럼 구분자가 실제로 쉼표인지, 앞뒤에 불필요한 공백이나 탭이 섞여 있지 않은지 확인
  • 위에 사항을 잘 확인하고 삭제한 뒤 저장할 필요가 있다. csv.reader로 스캔할 때 헤더 위에 잔여 텍스트가 있으면 파싱이 어긋날 수 있다.

4) 서울 데이터셋의 구조


03. csv 읽기

import csv

# 1) 파일을 연다.
f = open('/content/seoul (1).csv', 'r', encoding='euc-kr')

# 2) 파일을 읽는다.
data = csv.reader(f, delimiter=',')
print(data)

# 3) 파일 내용을 출력
for row in data:
    print(row)

# 4) 파일 닫기
f.close()

1) 파일은 f = open('/content/seoul (1).csv', 'r', encoding='euc-kr')이와 같은 방식으로 열어서 확인할 수 있다.
2) csv.reader(f, delimiter=',')
csv.reader에 전달하면 행 단위로 순회 가능한 이터레이터 객체가 만들어진다.
csv.readerdelimiter=','는 구분자를 명시하는 옵션이다.
for row in data로 순회한 결과, 각 행은 리스트로 반환되었다

  • 예시
    • 주목할 점은 작은 따옴표로 감싸진 문자열이다. csv.reader는 파일의 값을 자동으로 형변환하지 않는 것을 알 수 있다.
    • 마지막엔 f.close() 파일을 닫아준다.
  • ['1907-10-01', '108', '13.5', '7.9', '20.7']

04. 헤더 저장

1) next( )로 헤더 한 줄 분리

  • next(iterator)는 이터레이터에서 다음 요소 하나를 꺼내고 커서를 그다음 위치로 이동시키는 내장 함수이다.
# 헤더 저장 - next()가 한 줄 읽고 커서를 다음으로 넘김
header = next(data)

f.close()
print(header)
  • 출력은 ['날짜', '지점', '평균기온(℃)', '최저기온(℃)', '최고기온(℃)']이다.
    next()는 어제 배운 리스트의 pop()과 유사한 감각으로 이해할 수 있다. pop이 원본 리스트에서 요소를 잘라내는 반면 next는 원본을 그대로 두면서 커서만 앞으로 이동시킨다는 점이 다르다.
  • 2) 헤더 분리 없이 데이터 처리를 시도하면 어떻게 될까?*
  • 만약 헤더를 분리하지 않고 곧바로 최고 기온을 float(row[4])로 형변환하면 첫 번째 반복에서 '최고기온(℃)' 문자열을 실수로 바꾸려다 ValueError가 발생한다. 원본을 훼손하지 않으면서 컬럼명 라인만 건너뛰는 정석 방법이next()`이다.

05. 기온 출력과 결측치 처리

1) 결측치 처리에 대한 전략

  • 전략1. 극단값 대체
  • for row in data: if row[4] == '': # 결측치 확인 print(row[0]) # 결측치가 발생한 날짜 확인 row[4] = -999 # 극단값 대체 print(row[0], float(row[-1]))
  • 절략2. continue로 건너뛰기
  • # 날짜와 최저기온 출력 for row in data: if row[3] == '': continue print(row[0], float(row[3]))
  • # 날짜와 평균기온 출력 for row in data: if row[2] == '': continue # 결측치면 다음 반복으로 print(row[0], float(row[2]))

06. matplotlib 기본 그래프

라이브러리리 임포트와 별칭

import matplotlib.pyplot as plt

시각화의 사실상 표준 라이브러리는 matplotlib이며, 그중 pyplot 서브패키지가 상태 기반의 간편 API를 제공한다. 관례상 plt라는 별칭을 사용한다.

y값만 준 plot() 호출

plt.plot(y)처럼 리스트 하나만 인자로 넘기면 그 리스트를 y값으로 인식하고 x값은 0부터 자동으로 1씩 증가시키며 선을 그린다.

import matplotlib.pyplot as plt

plt.plot([20, 10, 50, 10, 30])  # y값만 주면 x는 자동으로 0부터 1씩 증가
plt.show()

y값만 준 plot() 호출

x와 y를 명시적으로 지정하려면 두 개의 리스트를 순서대로 전달한다.

import matplotlib.pyplot as plt

plt.plot([100, 200, 300, 400, 500], [10, 20, 30, 40, 50])  # x, y 순서
plt.show()

타이틀 속성 추가

차트에는 반드시 타이틀을 부여한다는 원칙이 있다. 타이틀·x축 라벨·y축 라벨·범례가 없는 차트는 정보 전달에 실패한 차트이다.

plt.title('plotting')
plt.plot([100, 200, 300, 400, 500], [10, 20, 30, 40, 50])
plt.show()

타이틀 속성 추가


07. 서울 기온 데이터 시각화

import csv
import matplotlib.pyplot as plt

# 1) 파일을 연다.
f = open('/content/seoul (1).csv', 'r', encoding='euc-kr')

# 2) 파일을 읽는다.
data = csv.reader(f, delimiter=',')

# 3) 헤더 저장
header = next(data)

# 4) 최고 기온을 담을 리스트 변수 선언
result = []

# 5) 결측치를 제외한 최고 기온 데이터만 result 리스트에 담기
for row in data:
    if row[4] != '':
        result.append(float(row[-1]))

# 6) 파일 닫기
f.close()

# 7) 차트 그리기
plt.title('seoul')
plt.plot(result, 'r')
plt.show()


08. 한글 깨짐 해결 - koreanize-matplotlib

matplotlib은 기본 폰트로 한글을 지원하지 않는다. plt.title('서울 최고 기온')을 그대로 실행하면 한글이 네모 박스(□□□)로 표시된다. Google Colab 환경에는 한글 폰트가 설치되어 있지 않기 때문이다.

koreanize-matplotlib 설치와 임포트

가장 간편한 해결책은 koreanize-matplotlib 패키지를 설치하고 임포트하는 것이다. pip 명령어로 설치할 수 있다.

!pip install koreanize-matplotlib

09. 문자열 슬라이싱으로 월별 데이터 추출

1) 날짜 문자열의 구조

날짜 컬럼은 YYYY-MM-DD 포맷의 문자열이다. 예를 들어 '1907-10-01'은 10자리 문자열이며 각 위치는 다음과 같이 대응된다.

인덱스 0 1 2 3 4 5 6 7 8 9
문자 1 9 0 7 - 1 0 - 0 1

파이썬 문자열 슬라이싱 s[start:end]start 이상 end 미만 구간을 반환한다. 이 성질을 이용해 연도, 월, 일을 각각 추출할 수 있다.

date = '1907-10-01'
print(date[0:4])   # '1907' - 연도
print(date[5:7])   # '10'   - 월
print(date[8:])    # '01'   - 일

10. 팀 프로젝트

한국의 철베리아라고 불리는 철원의 여름철 기온 변화보면서 지구온난화의 영향이 얼마나 유의미하게 나타나는지 알아보았다.

import csv
import matplotlib.pyplot as plt
import koreanize_matplotlib

f = open('/content/Cheorwon.csv', 'r', encoding='euc-kr')
data = csv.reader(f, delimiter=',')
header = next(data)

# 연도별 7월-8월 평균 기온을 저장할 딕셔너리
yearly_jul_aug_temps = {}

for row in data:
    try:
        date_str = row[0]
        year = int(date_str[0:4])
        month = int(date_str[5:7])
        avg_temp = float(row[2])

        if month in [7, 8]:
            if year not in yearly_jul_aug_temps:
                yearly_jul_aug_temps[year] = []
            yearly_jul_aug_temps[year].append(avg_temp)
    except (ValueError, IndexError):
        continue

f.close()

# 각 연도별 7-8월 평균 기온 계산
years = sorted(yearly_jul_aug_temps.keys())
avg_summer_temps = [sum(yearly_jul_aug_temps[year]) / len(yearly_jul_aug_temps[year])
                    for year in years if len(yearly_jul_aug_temps[year]) > 0]

filtered_years = [year for year in years if len(yearly_jul_aug_temps[year]) > 0]

plt.figure(figsize=(14, 7))
plt.plot(filtered_years, avg_summer_temps, marker='o',
         linestyle='-', color='blue', markersize=4)
plt.title('철원 7-8월 평균 기온 변화 (1907-현재)', fontsize=16)
plt.xlabel('년도', fontsize=12)
plt.ylabel('평균 기온 (℃)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.xticks(filtered_years, rotation=45, ha='right', fontsize=10)
plt.yticks(fontsize=10)
plt.tight_layout()
plt.show()

print(f"데이터 범위: {min(filtered_years)}년부터 {max(filtered_years)}년까지")

 

코드는 claude의 도움을 살짝 받긴 했다.

 

지구온난화가 본격적으로 시작하기 시점인 20세기 후반 21세기 초 상승하는 시점과 철원의 7~8월의 평균 기온도 엇비슷하게 상승하고 있다는 것을 볼 수 있다.