yfinance로 수집한 주가 데이터를 MySQL에 저장하고 다시 불러오는 과정에서, Unknown column 'Date', Unknown column 'Open' 이라는 에러에 대해 제가 해결했던 방법을 기록합니다.
Pandas의 'MultiIndex(다중 인덱스)' 구조를 이해하고 해결하는 과정을 담은 기록입니다.
yf.download()로 데이터를 수집하고, to_sql()로 DB에 저장한 뒤, read_sql()로 다시 데이터를 읽어오는 간단한 코드였습니다. 하지만 SQL 쿼리 실행 단계에서 계속해서 특정 컬럼을 찾을 수 없다는 ProgrammingError가 발생했습니다.
# 문제가 발생했던 SQL 쿼리
sql_query = "SELECT Open, High, Low, Close FROM ohlcv ORDER BY Date"
# -> ProgrammingError: Unknown column 'Open' in 'field list'
Pandas의 MultiIndex(또는 Multi-level Index) 입니다. 여러 종목의 데이터를 한 번에 다룰 때, 어떤 데이터가 어떤 종목에 속하는지 구분하기 위해 이름표를 이중으로 붙이는 기능이죠. 예를 들어 ('Open', 'AAPL')은 'AAPL 종목의 시가'를, ('Open', 'GOOG')은 'GOOG 종목의 시가'를 의미합니다.
해결책: get_level_values()로 이름표 단일화하기
원인을 알았으니 해결은 간단합니다. 데이터베이스에 저장하기 전에, 단순한 단일 이름표로 바꿔주면 됩니다.
# yfinance로 데이터를 가져오면 MultiIndex 컬럼이 생성됨
df = yf.download(['AAPL', 'GOOG'], start='2025-01-01')
# 이 부분이 핵심!
# 이중 이름표 중 위에 있는 진짜 이름('Open', 'High'...)만 떼어내서
# 컬럼 이름으로 다시 설정합니다.
df.columns = df.columns.get_level_values(0)
print(df.head())
# 이제 컬럼이 'Open', 'High', 'Low', 'Close' 등으로 깔끔하게 정리됩니다.
추가학습내용
이번 에러를 계기로 함께 공부하면 좋은 Pandas의 개념들을 정리해 보았습니다.
- MultiIndex(다중 인덱스) 구조의 이해:
- 다중 인덱스는 행(row)에도, 열(column)에도 적용될 수 있습니다. 데이터를 그룹별로 계층적으로 분석할 때 매우 강력한 기능입니다. 공식 문서나 잘 정리된 튜토리얼을 통해 다중 인덱스를 직접 만들고, 특정 계층의 데이터를 선택하는 방법을 연습해보는 것이 좋습니다.
아래와 같은 긴 형태의 주가 데이터가 있다고 가정하면 아래와 같은 출력이 나옵니다.import pandas as pd data = {'Date': ['2025-10-08', '2025-10-08', '2025-10-09', '2025-10-09'], 'Ticker': ['AAPL', 'GOOG', 'AAPL', 'GOOG'], 'Close': [170, 140, 172, 141], 'Volume': [5000, 3000, 5500, 3200]} long_df = pd.DataFrame(data) print(long_df)
Date Ticker Close Volume 0 2025-10-08 AAPL 170 5000 1 2025-10-08 GOOG 140 3000 2 2025-10-09 AAPL 172 5500 3 2025-10-09 GOOG 141 3200
- 다중 인덱스는 행(row)에도, 열(column)에도 적용될 수 있습니다. 데이터를 그룹별로 계층적으로 분석할 때 매우 강력한 기능입니다. 공식 문서나 잘 정리된 튜토리얼을 통해 다중 인덱스를 직접 만들고, 특정 계층의 데이터를 선택하는 방법을 연습해보는 것이 좋습니다.
- stack() & unstack():
Close Volume Date Ticker 2025-10-08 AAPL 170 5000 GOOG 140 3000 2025-10-09 AAPL 172 5500 GOOG 141 3200- stack()은 컬럼을 인덱스로 '쌓는'(=아래로) 기능이고
# unstacked_df를 다시 stack stacked_df = unstacked_df.stack(level='Ticker') print(stacked_df) -
Close Volume Date Ticker 2025-10-08 AAPL 170 5000 GOOG 140 3000 2025-10-09 AAPL 172 5500 GOOG 141 3200
- stack은 unstack의 정반대 작업입니다. 컬럼의 일부를 다시 행 인덱스로 '쌓아서' 데이터를 더 길게 만듭니다.
- unstack()은 인덱스를 컬럼으로 '펼치는'(= 옆으로) 기능입니다.
# Ticker 레벨의 인덱스를 컬럼으로 unstack unstacked_df = df_multi_index.unstack(level='Ticker') print(unstacked_df) -
Close Volume Ticker AAPL GOOG AAPL GOOG Date 2025-10-08 170 140 5000 3000 2025-10-09 172 141 5500 3200
- 보시는 것처럼 unstack은 행 인덱스의 일부였던 Ticker를 컬럼 레벨로 이동시켜, 우리가 yfinance에서 처음 봤던 것과 같은 MultiIndex 컬럼 구조를 만듭니다.
- stack() & unstack()을 사용하면 넓은 형태(wide-format)의 데이터를 긴 형태(long-format)로 쉽게 바꿀 수 있습니다
- stack()은 컬럼을 인덱스로 '쌓는'(=아래로) 기능이고
- pivot_table(): (= 넓게 펼치기)
- 엑셀의 피벗 테이블과 동일한 기능입니다. 주어진 데이터로부터 원하는 행, 열, 값을 지정하여 데이터를 재구조화하고 요약 통계를 볼 수 있습니다.
- pivot_table의 결과물은 MultiIndex 구조를 갖는 경우가 많아, 위 개념들과 함께 익히면 시너지가 좋습니다.
- index : 새로운 표의 '행'이 될 칼럼(Data)
- columns : 새로운 표의 '열'이 될 칼럼(Ticker)
# Date를 행으로, Ticker를 열로, Close를 값으로 하는 피벗 테이블 생성 wide_df = long_df.pivot_table(index='Date', columns='Ticker', values='Close') print(wide_df) - pivot_table(index='Date', columns='Ticker', values='Close')
이 결과물은 'Date'를 행 인덱스로, 'Ticker'를 열 인덱스로 갖는 MultiIndex 컬럼 구조와 유사한 형태가 됩니다.Ticker AAPL GOOG Date 2025-10-08 170 140 2025-10-09 172 141
- 메서드 체이닝 (Method Chaining):
- 메서드 체이닝이란, 여러 개의 데이터 처리/조작 함수(메서드)를 점(.)으로 계속 연결하여 한 줄의 코드로 표현하는 기법입니다.
- 이점
- 가독성: 데이터가 어떤 순서로 변환되는지 위에서 아래로, 왼쪽에서 오른쪽으로 자연스럽게 읽힙니다.
- "A하고, 그리고(.) B하고, 그리고(.) C한다" 처럼 이야기 흐름이 만들어집니다.
- 효율성: df1, df2, df3 와 같이 불필요한 중간 과정 변수를 만들지 않아 코드가 간결해지고 메모리를 효율적으로 사용할 수 있습니다.
- 결측치가 있는 행을 제거한다.
- 'Volume'이 4000 이상인 데이터만 필터링한다.
- 'Ticker' 열을 기준으로 그룹화하여 'Close' 가격의 평균을 계산한다.
- 결과를 내림차순으로 정렬한다.
- 사용 전
# 예제 데이터 data = {'Date': ['2025-10-08', '2025-10-08', '2025-10-09', '2025-10-09', '2025-10-10'], 'Ticker': ['AAPL', 'GOOG', 'AAPL', 'GOOG', 'AAPL'], 'Close': [170, 140, 172, 141, None], # 결측치 포함 'Volume': [5000, 3000, 5500, 3200, 6000]} df = pd.DataFrame(data) # 1단계: 결측치 제거 df_no_na = df.dropna() # 2단계: Volume 필터링 df_filtered = df_no_na[df_no_na['Volume'] >= 4000] # 3단계: 그룹별 평균 계산 df_grouped = df_filtered.groupby('Ticker')['Close'].mean() # 4단계: 정렬 final_result = df_grouped.sort_values(ascending=False) print(final_result)
- 사용 후
- 위의 모든 단계를 하나의 흐름으로 연결합니다. 괄호 ()를 사용하면 여러 줄에 걸쳐 가독성 좋게 작성할 수 있습니다.
# 예제 데이터 (동일) df = pd.DataFrame(data) # 모든 단계를 하나의 체인으로 연결 final_result = (df.dropna() .query("Volume >= 4000") # query 메서드를 사용하면 필터링이 더 깔끔해집니다. .groupby('Ticker')['Close'] .mean() .sort_values(ascending=False) ) print(final_result)
- 위의 모든 단계를 하나의 흐름으로 연결합니다. 괄호 ()를 사용하면 여러 줄에 걸쳐 가독성 좋게 작성할 수 있습니다.
반응형
'데이터분석' 카테고리의 다른 글
| 실전 금융 머신러닝 (1) | 2026.02.02 |
|---|---|
| 교차 검증 전략 (Cross-Validation Strategies) (0) | 2025.04.30 |
| 유데미(Udemy) 러닝 크루 【한글자막】 머신러닝의 모든 것 with AI, Python & R + ChatGPT Prize [2024] 수강완료 후기 (5) | 2024.12.23 |
| 연관분석 (2) | 2024.11.10 |
| 군집 분석 (1) | 2024.11.10 |