이미지 로딩 중...
AI Generated
2025. 11. 21. · 3 Views
Pandas 시계열 데이터 처리 완벽 가이드
주식 데이터, 센서 로그, 웹 트래픽 분석 등 시간에 따라 변하는 데이터를 Pandas로 효율적으로 처리하는 방법을 배웁니다. 날짜 인덱싱, 리샘플링, 이동 평균 등 실무에 바로 적용할 수 있는 핵심 기술을 다룹니다.
목차
1. DatetimeIndex 생성
시작하며
여러분이 주식 데이터나 웹사이트 방문자 로그를 분석할 때, 날짜와 시간이 그냥 문자열로 되어 있어서 불편했던 경험 있으신가요? "2024-01-15"라는 문자열로는 "지난주 데이터만 보여줘" 같은 요청을 처리하기가 정말 어렵습니다.
이런 문제는 실제 데이터 분석 현장에서 매일 발생합니다. 날짜를 문자열로 다루면 정렬도 제대로 안 되고, 특정 기간을 뽑아내는 것도 복잡한 코드를 작성해야 합니다.
게다가 "2024년 1월 매출"처럼 월별로 묶는 작업은 거의 불가능에 가깝습니다. 바로 이럴 때 필요한 것이 DatetimeIndex입니다.
이것은 Pandas가 날짜와 시간을 제대로 이해하고 처리할 수 있게 해주는 특별한 인덱스 형식입니다. 마치 달력과 시계를 컴퓨터가 완벽히 이해하게 만드는 것이죠.
개요
간단히 말해서, DatetimeIndex는 날짜와 시간을 DataFrame의 인덱스로 사용할 수 있게 해주는 Pandas의 특별한 기능입니다. 왜 이게 필요할까요?
실무에서 데이터는 대부분 시간 순서대로 쌓입니다. 센서 로그, 주식 가격, 웹사이트 트래픽, 매출 데이터 등 모두 "언제" 발생했는지가 중요합니다.
예를 들어, 서버 모니터링 데이터에서 "오늘 오후 3시부터 5시까지 CPU 사용률"을 확인하고 싶을 때 매우 유용합니다. 기존에는 날짜 컬럼을 문자열이나 숫자로 저장하고, 필요할 때마다 복잡한 조건문으로 필터링했다면, 이제는 DatetimeIndex를 사용해서 간단하게 df['2024-01-15'] 같은 방식으로 접근할 수 있습니다.
DatetimeIndex의 핵심 특징은 세 가지입니다. 첫째, 날짜와 시간을 실제 시간 객체로 다루기 때문에 시간 계산이 정확합니다.
둘째, 슬라이싱이 매우 직관적입니다. 셋째, 자동으로 시간 기반 그룹화와 집계가 가능합니다.
이러한 특징들이 데이터 분석 속도를 10배 이상 빠르게 만들어줍니다.
코드 예제
import pandas as pd
import numpy as np
# 날짜 범위 생성 - 2024년 1월 한 달간의 일별 데이터
dates = pd.date_range(start='2024-01-01', end='2024-01-31', freq='D')
# 랜덤한 매출 데이터 생성
sales = np.random.randint(1000, 5000, size=len(dates))
# DatetimeIndex를 사용한 DataFrame 생성
df = pd.DataFrame({'sales': sales}, index=dates)
# 인덱스 정보 확인
print(df.index) # DatetimeIndex(['2024-01-01', '2024-01-02', ...], dtype='datetime64[ns]', freq='D')
# 특정 날짜 접근
print(df.loc['2024-01-15']) # 2024년 1월 15일 매출
설명
이것이 하는 일: DatetimeIndex는 날짜와 시간 정보를 Pandas가 이해할 수 있는 특별한 형식으로 변환하여, 시간 기반 연산과 필터링을 자연스럽게 만들어줍니다. 첫 번째로, pd.date_range() 함수는 시작일과 종료일 사이의 날짜를 자동으로 생성합니다.
freq='D'는 하루(Day) 간격을 의미하며, 'H'는 시간, 'T'는 분 단위입니다. 왜 이렇게 하냐면, 실무에서는 완벽한 데이터가 없는 경우가 많아서 기준이 되는 날짜 범위를 먼저 만들어야 하기 때문입니다.
그 다음으로, 이 날짜들을 DataFrame의 index로 설정하면 마법 같은 일이 일어납니다. df.loc['2024-01-15']처럼 날짜 문자열만으로 데이터에 접근할 수 있게 됩니다.
내부적으로 Pandas는 이 문자열을 datetime 객체로 자동 변환하여 정확하게 매칭시킵니다. 마지막으로, index 속성을 출력해보면 dtype='datetime64[ns]'라고 표시됩니다.
이는 나노초(nanosecond) 단위까지 정확한 시간 데이터임을 의미합니다. 이런 정밀도 덕분에 금융 거래 데이터처럼 밀리초 단위가 중요한 데이터도 정확하게 처리할 수 있습니다.
여러분이 이 코드를 사용하면 날짜 기반 필터링, 정렬, 그룹화가 엄청나게 간단해집니다. 예를 들어, 엑셀에서 복잡한 필터를 걸던 작업이 한 줄 코드로 해결됩니다.
또한 시각화할 때 x축이 자동으로 날짜 형식으로 예쁘게 표시되는 이점도 있습니다.
실전 팁
💡 기존 데이터프레임의 날짜 컬럼을 인덱스로 바꾸려면 df.set_index('date_column')을 사용하되, 먼저 pd.to_datetime()으로 변환하세요. 문자열 날짜는 자동 변환되지만, 형식이 다양할 때는 format 매개변수로 명시하는 것이 10배 빠릅니다.
💡 freq 매개변수를 잘 활용하세요. 'D'(일), 'H'(시간), 'T'(분), 'S'(초), 'W'(주), 'M'(월말), 'MS'(월초), 'Q'(분기), 'Y'(년말) 등 다양합니다. 'M'과 'MS'의 차이를 모르면 월별 집계 시 예상치 못한 결과가 나올 수 있습니다.
💡 날짜 범위가 매우 클 때는 메모리를 고려하세요. 10년치 초 단위 데이터는 수억 개의 인덱스를 만들어 메모리 부족을 일으킬 수 있습니다. 이럴 때는 필요한 빈도로 리샘플링하거나 필요한 기간만 잘라서 사용하세요.
💡 DatetimeIndex는 시간대(timezone) 정보를 가질 수 있습니다. 글로벌 서비스 데이터를 다룬다면 tz 매개변수로 'UTC', 'Asia/Seoul' 등을 지정하여 시간대 혼란을 방지하세요.
💡 인덱스가 정렬되어 있지 않으면 슬라이싱이 제대로 작동하지 않습니다. 항상 df.sort_index()로 정렬 상태를 확인하고, 성능이 중요하다면 df.index.is_monotonic_increasing으로 체크하세요.
2. 날짜 기반 인덱싱
시작하며
여러분이 1년치 매출 데이터에서 "3월 데이터만 보고 싶다"거나 "지난 주 월요일부터 금요일까지만 필요하다"고 할 때, 엑셀처럼 일일이 필터를 걸어야 했던 경험 있으시죠? 수천, 수만 행의 데이터에서 원하는 기간을 찾는 것은 시간도 오래 걸리고 실수하기도 쉽습니다.
이런 문제는 시계열 데이터 분석에서 가장 기본적이면서도 자주 발생하는 작업입니다. 잘못된 기간을 선택하면 분석 결과 자체가 틀려지고, 복잡한 조건문을 작성하다 보면 코드가 지저분해지고 버그가 숨어들기 쉽습니다.
바로 이럴 때 필요한 것이 Pandas의 날짜 기반 인덱싱입니다. DatetimeIndex가 설정되어 있으면 마치 책의 목차를 보듯이 "2024년 3월"이라고만 입력해도 해당 기간의 모든 데이터를 단번에 가져올 수 있습니다.
개요
간단히 말해서, 날짜 기반 인덱싱은 DatetimeIndex를 활용해서 날짜나 기간을 지정하여 데이터를 추출하는 방법입니다. 왜 이 기능이 필요한지 실무 관점에서 설명하자면, 데이터 분석의 80%는 "특정 기간의 데이터"를 다루는 작업입니다.
예를 들어, 서비스 장애가 발생한 시간대의 로그를 분석하거나, 프로모션 기간 동안의 매출을 비교하거나, 분기별 실적을 집계하는 경우처럼 시간 범위를 지정하는 것이 핵심입니다. 기존에는 (df['date'] >= '2024-03-01') & (df['date'] <= '2024-03-31') 같은 복잡한 불린 인덱싱을 사용했다면, 이제는 df['2024-03']처럼 간단하게 접근할 수 있습니다.
코드가 짧아지는 것뿐만 아니라 가독성도 크게 향상됩니다. 핵심 특징은 세 가지입니다.
첫째, 부분 문자열 매칭이 가능합니다. '2024'만 입력해도 2024년 전체를 의미합니다.
둘째, 슬라이싱으로 범위를 지정할 수 있습니다. df['2024-01':'2024-03']처럼 시작과 끝을 지정하면 됩니다.
셋째, 시간까지 포함한 정밀한 필터링이 가능합니다. 이러한 특징들이 데이터 탐색을 마치 대화하듯 자연스럽게 만들어줍니다.
코드 예제
import pandas as pd
import numpy as np
# 2024년 전체 일별 데이터 생성
dates = pd.date_range('2024-01-01', '2024-12-31', freq='D')
df = pd.DataFrame({
'temperature': np.random.randint(15, 35, len(dates)),
'humidity': np.random.randint(40, 90, len(dates))
}, index=dates)
# 특정 날짜 하나 선택
print(df.loc['2024-03-15'])
# 특정 월 전체 선택
march_data = df['2024-03']
print(f"3월 데이터: {len(march_data)}일")
# 기간 범위 선택 (1월~3월)
q1_data = df['2024-01':'2024-03']
print(f"1분기 데이터: {len(q1_data)}일")
# 여러 조건 결합 - 3월의 온도 30도 이상인 날
hot_march = df.loc['2024-03'][df['temperature'] >= 30]
설명
이것이 하는 일: 날짜 기반 인덱싱은 DatetimeIndex의 패턴 매칭 기능을 활용하여, 복잡한 조건문 없이도 날짜 문자열만으로 원하는 데이터를 정확하게 추출합니다. 첫 번째로, df.loc['2024-03-15']는 정확히 해당 날짜의 행을 반환합니다.
.loc를 사용하는 이유는 라벨 기반 인덱싱이 더 안전하고 명확하기 때문입니다. 왜 이렇게 하냐면, 정수 인덱스와 날짜 인덱스가 섞여 있을 때 혼란을 방지할 수 있기 때문입니다.
두 번째로, df['2024-03']처럼 년-월만 입력하면 Pandas는 자동으로 해당 월의 모든 날짜를 찾아냅니다. 이것을 "부분 문자열 인덱싱"이라고 하는데, 내부적으로는 '2024-03-01'부터 '2024-03-31'까지의 범위를 자동 계산합니다.
이 기능 덕분에 월별 집계나 분석이 한 줄로 해결됩니다. 세 번째로, df['2024-01':'2024-03']은 슬라이싱 문법을 사용하여 시작일부터 종료일까지의 범위를 선택합니다.
주의할 점은 일반 리스트 슬라이싱과 달리 끝 날짜도 포함된다는 것입니다. 즉, '2024-03-31'까지 모두 포함됩니다.
이는 시간 데이터의 특성상 "3월 31일까지"라고 하면 31일도 당연히 포함되어야 하기 때문입니다. 여러분이 이 기능을 사용하면 데이터 필터링 코드가 극적으로 간결해집니다.
복잡한 날짜 비교 조건 대신 직관적인 문자열로 표현할 수 있어, 코드 리뷰나 유지보수가 훨씬 쉬워집니다. 또한 실수로 잘못된 기간을 선택할 위험도 크게 줄어듭니다.
성능 면에서도 Pandas가 내부적으로 최적화된 알고리즘을 사용하기 때문에 직접 필터링하는 것보다 빠릅니다.
실전 팁
💡 부분 문자열 인덱싱은 왼쪽부터 매칭됩니다. '2024'는 2024년 전체, '2024-03'은 3월 전체, '2024-03-15'는 해당 날짜입니다. 시간까지 포함된 데이터라면 '2024-03-15 14'로 14시대 데이터만 선택할 수 있습니다.
💡 슬라이싱 시 끝 날짜가 포함되는 것을 항상 기억하세요. df['2024-01':'2024-01']은 1월 전체를 의미하며, 빈 결과가 아닙니다. 이것은 숫자 인덱싱과 다른 중요한 차이점입니다.
💡 시간대가 있는 데이터를 다룰 때는 검색 문자열에도 시간대를 명시해야 정확합니다. 'UTC' 데이터에 'Asia/Seoul' 문자열로 검색하면 예상과 다른 결과가 나올 수 있습니다.
💡 대용량 데이터에서는 .loc 대신 .truncate() 메서드를 고려하세요. df.truncate(before='2024-01-01', after='2024-03-31')는 슬라이싱과 동일하지만 명시적이고 가독성이 좋습니다.
💡 인덱스가 정렬되지 않은 상태에서 슬라이싱하면 에러가 발생하거나 예상치 못한 결과가 나옵니다. 데이터를 처음 로드한 후 즉시 df.sort_index(inplace=True)를 실행하는 습관을 들이세요.
3. Resampling
시작하며
여러분이 분 단위로 기록된 센서 데이터가 하루에 1,440개씩 쌓이는데, 이걸 한눈에 파악하려니 너무 많아서 그래프가 알아보기 힘들었던 경험 있으신가요? 또는 일별 매출 데이터를 주별이나 월별로 합쳐서 보고 싶을 때 일일이 계산하느라 고생한 적은요?
이런 문제는 IoT 센서 모니터링, 금융 데이터 분석, 웹 트래픽 분석 등 실시간으로 데이터가 쌓이는 모든 분야에서 발생합니다. 너무 세밀한 데이터는 오히려 전체적인 트렌드를 파악하기 어렵게 만들고, 반대로 너무 뭉뚱그리면 중요한 변화를 놓칠 수 있습니다.
적절한 시간 단위로 데이터를 재구성하는 것이 핵심입니다. 바로 이럴 때 필요한 것이 Resampling입니다.
이것은 시계열 데이터의 시간 간격을 자유자유롭게 변경하는 기능으로, 마치 현미경의 배율을 조절하듯이 데이터를 확대하거나 축소할 수 있습니다.
개요
간단히 말해서, Resampling은 시계열 데이터를 다른 시간 빈도로 변환하는 작업입니다. 분 단위를 시간 단위로, 일 단위를 월 단위로 바꾸는 것이죠.
왜 이 기능이 필요할까요? 실무에서는 같은 데이터를 서로 다른 시간 단위로 봐야 하는 경우가 정말 많습니다.
예를 들어, 서버 CPU 사용률을 초 단위로 모니터링하지만, 보고서에는 시간별 평균을 표시해야 합니다. 주식 데이터는 틱 단위로 들어오지만 일봉, 주봉, 월봉 차트로 분석해야 합니다.
웹사이트 방문자는 실시간으로 기록되지만 일별, 주별 추이를 파악해야 합니다. 기존에는 날짜를 기준으로 groupby를 하고 집계 함수를 적용하는 복잡한 과정을 거쳤다면, 이제는 df.resample('D').sum() 같은 간단한 코드로 해결됩니다.
Resampling의 핵심 특징 세 가지입니다. 첫째, Downsampling(다운샘플링)은 고빈도 데이터를 저빈도로 변환합니다(예: 시간→일).
이때 sum, mean, max, min 등 집계 함수를 선택합니다. 둘째, Upsampling(업샘플링)은 저빈도를 고빈도로 변환합니다(예: 일→시간).
이때는 값을 채우는 방법(forward fill, backward fill 등)을 지정합니다. 셋째, 원본 데이터를 변경하지 않고 새로운 DataFrame을 반환하여 안전합니다.
이러한 특징들이 시계열 데이터 변환을 SQL의 GROUP BY처럼 간단하게 만들어줍니다.
코드 예제
import pandas as pd
import numpy as np
# 1시간 간격의 온도 데이터 (7일간)
dates = pd.date_range('2024-01-01', periods=24*7, freq='H')
df = pd.DataFrame({
'temperature': np.random.randint(15, 30, len(dates)),
'sales': np.random.randint(100, 1000, len(dates))
}, index=dates)
# 일별로 다운샘플링 - 온도는 평균, 매출은 합계
daily = df.resample('D').agg({
'temperature': 'mean',
'sales': 'sum'
})
print(daily.head())
# 6시간 단위로 최대값
six_hourly = df.resample('6H')['temperature'].max()
# 2일 단위로 여러 통계값
two_daily = df.resample('2D')['sales'].agg(['sum', 'mean', 'max', 'min'])
설명
이것이 하는 일: Resampling은 시간 축을 따라 데이터를 그룹화하고, 각 그룹에 대해 지정된 집계 함수를 적용하여 새로운 시간 빈도의 데이터를 생성합니다. 첫 번째로, df.resample('D')는 일별로 데이터를 그룹화하는 Resampler 객체를 만듭니다.
'D'는 Day를 의미하며, 'W'(주), 'M'(월), 'Q'(분기), 'H'(시간), '30T'(30분) 등 다양한 빈도를 지정할 수 있습니다. 왜 이렇게 두 단계로 나뉘냐면, 여러 집계 함수를 유연하게 적용할 수 있게 하기 위함입니다.
두 번째로, .agg() 메서드로 컬럼별로 다른 집계 함수를 적용합니다. 온도는 평균(mean)이 의미 있지만, 매출은 합계(sum)가 필요합니다.
딕셔너리 형태로 {컬럼명: 집계함수}를 전달하면 Pandas가 자동으로 각 컬럼에 맞는 계산을 수행합니다. 내부적으로는 시간 범위별로 데이터를 묶어서 해당 함수를 적용하는 것입니다.
세 번째로, .agg()에 리스트를 전달하면 여러 통계값을 한 번에 계산할 수 있습니다. ['sum', 'mean', 'max', 'min']처럼 지정하면 각 통계값이 별도 컬럼으로 생성됩니다.
이것은 마치 엑셀의 피벗 테이블처럼 작동하는데, 훨씬 빠르고 프로그래밍 가능하다는 장점이 있습니다. 마지막으로, resample('6H')처럼 숫자와 단위를 결합할 수 있습니다.
6시간, 30분, 2주 등 임의의 간격을 자유롭게 지정할 수 있어, 비즈니스 요구사항에 맞춘 분석이 가능합니다. 여러분이 이 기능을 사용하면 복잡한 날짜 계산 없이도 시간 단위 변환이 자동으로 처리됩니다.
특히 대용량 데이터를 다룰 때 Resampling으로 먼저 데이터 크기를 줄이면 이후 분석 속도가 크게 향상됩니다. 또한 시각화할 때 적절한 빈도로 리샘플링하면 그래프가 훨씬 깔끔하고 의미 있는 패턴을 보여줍니다.
실전 팁
💡 월별 리샘플링 시 'M'은 월말, 'MS'는 월초를 기준으로 합니다. 매출 집계는 보통 'M'을, 계획 대비 실적은 'MS'를 사용하는 것이 자연스럽습니다. 이 차이를 모르면 월별 그래프의 x축 라벨이 이상하게 표시됩니다.
💡 Upsampling(저빈도→고빈도) 시에는 빈 값이 생기므로 반드시 채우는 방법을 지정하세요. .resample('H').ffill()은 앞 값으로 채우고(forward fill), .bfill()은 뒤 값으로 채웁니다(backward fill). .interpolate()는 선형 보간을 사용합니다.
💡 label과 closed 매개변수로 경계 처리를 정확히 제어할 수 있습니다. resample('D', label='right', closed='right')는 각 일의 마지막 시점을 라벨로 사용하고, 마지막 값을 포함합니다. 금융 데이터에서는 이런 정확성이 필수입니다.
💡 집계 후 NaN이 생길 수 있으므로 .fillna(0)이나 .dropna()로 처리하세요. 특히 매출 합계 같은 경우 NaN을 0으로 채워야 차트가 끊기지 않습니다.
💡 대용량 데이터는 resample 전에 필요한 컬럼만 선택하세요. df[['temperature', 'sales']].resample('D').mean()이 df.resample('D')[['temperature', 'sales']].mean()보다 메모리 효율적입니다.
4. Rolling Window
시작하며
여러분이 주식 차트를 보면 "5일 이동평균선", "20일 이동평균선" 같은 선들이 그려져 있는 것 보셨죠? 또는 일별 매출 데이터가 요일마다 들쭉날쭉해서 전체적인 추세를 파악하기 어려웠던 경험은요?
하루하루의 변동보다는 전반적인 흐름을 보고 싶을 때가 많습니다. 이런 문제는 시계열 데이터 분석에서 "노이즈 제거"라는 이름으로 불립니다.
데이터에는 실제 트렌드와 함께 랜덤한 변동이 섞여 있는데, 이 변동 때문에 중요한 패턴을 놓치기 쉽습니다. 예를 들어, 서버 응답 시간이 갑자기 튀는 것이 실제 문제인지 일시적 현상인지 판단하기 어렵습니다.
바로 이럴 때 필요한 것이 Rolling Window(이동 윈도우)입니다. 이것은 일정 개수의 연속된 데이터를 묶어서 평균이나 합계를 계산하는 방법으로, 마치 움직이는 창문을 통해 데이터를 보는 것과 같습니다.
개요
간단히 말해서, Rolling Window는 지정된 크기의 "창"을 데이터 위로 한 칸씩 이동시키면서 매번 해당 창 안의 값들을 집계하는 기법입니다. 왜 이 기능이 필요한지 실무 관점에서 설명하자면, 원시 데이터는 너무 변동이 심해서 트렌드를 파악하기 어렵기 때문입니다.
예를 들어, 웹사이트 일별 방문자는 주말에 줄고 평일에 늘어나는 패턴이 반복되는데, 이것이 증가 추세인지 감소 추세인지 알려면 7일 이동평균을 봐야 합니다. 주식 트레이딩에서는 단기 이동평균선이 장기 이동평균선을 돌파하면 매수 신호로 해석합니다.
기존에는 for 루프로 직접 윈도우를 슬라이딩하면서 계산했다면, 이제는 df.rolling(window=7).mean() 한 줄로 해결됩니다. 성능도 월등히 빠르고 코드도 명확합니다.
Rolling Window의 핵심 특징은 네 가지입니다. 첫째, window 크기만큼의 과거 데이터를 사용합니다.
window=7이면 현재 포함 이전 7개 데이터입니다. 둘째, 각 위치에서 집계 함수(mean, sum, std, max 등)를 적용합니다.
셋째, 결과의 길이는 원본과 동일하며, 초기 부분은 NaN입니다(데이터가 충분하지 않으므로). 넷째, min_periods로 최소 필요 데이터 수를 조절할 수 있습니다.
이러한 특징들이 시계열 데이터의 스무딩(smoothing)과 트렌드 분석을 가능하게 합니다.
코드 예제
import pandas as pd
import numpy as np
# 30일간의 일별 주가 데이터
dates = pd.date_range('2024-01-01', periods=30, freq='D')
df = pd.DataFrame({
'price': [100 + np.random.randint(-10, 10) + i*0.5 for i in range(30)],
'volume': np.random.randint(1000, 5000, 30)
}, index=dates)
# 7일 이동평균 계산
df['price_ma7'] = df['price'].rolling(window=7).mean()
# 3일 이동 합계
df['volume_sum3'] = df['volume'].rolling(window=3).sum()
# 5일 이동 표준편차 (변동성 측정)
df['price_std5'] = df['price'].rolling(window=5).std()
# min_periods로 초기 NaN 줄이기
df['price_ma7_min3'] = df['price'].rolling(window=7, min_periods=3).mean()
print(df[['price', 'price_ma7', 'price_std5']].head(10))
설명
이것이 하는 일: Rolling Window는 시계열 데이터를 따라 고정된 크기의 창을 한 칸씩 이동시키면서, 각 위치에서 창 안의 값들에 대해 지정된 통계 함수를 계산합니다. 첫 번째로, df['price'].rolling(window=7)은 7개 데이터를 포함하는 윈도우를 생성합니다.
이 윈도우는 첫 번째 위치에서 시작하여 데이터의 끝까지 한 칸씩 이동합니다. 왜 7일이냐면, 주 단위 패턴을 평활화하기 위함입니다.
주말 효과나 주간 반복 패턴을 제거하려면 7의 배수를 사용하는 것이 효과적입니다. 두 번째로, .mean() 메서드를 호출하면 각 윈도우 위치에서 평균을 계산합니다.
첫 번째 값은 1개 데이터만 있으므로 NaN, 두 번째는 2개만 있으므로 NaN... 일곱 번째부터 비로소 7개 데이터의 평균이 계산됩니다.
내부적으로는 효율적인 슬라이딩 윈도우 알고리즘을 사용하여 매번 처음부터 계산하지 않고 이전 계산 결과를 재사용합니다. 세 번째로, .std()로 표준편차를 계산하면 가격 변동성을 측정할 수 있습니다.
표준편차가 크면 최근 가격 변동이 심하다는 의미이고, 작으면 안정적이라는 뜻입니다. 이것은 금융 리스크 관리에서 핵심 지표입니다.
마지막으로, min_periods=3 매개변수는 최소 3개 이상의 데이터가 있으면 계산을 시작하라는 의미입니다. 이렇게 하면 초기 NaN이 줄어들어 더 많은 데이터 포인트를 얻을 수 있습니다.
하지만 초기 값들은 완전한 윈도우가 아니므로 해석에 주의가 필요합니다. 여러분이 이 기능을 사용하면 데이터의 노이즈를 제거하고 진짜 트렌드를 발견할 수 있습니다.
예를 들어, 일별 매출이 들쭉날쭉하더라도 7일 이동평균을 그려보면 상승세인지 하락세인지 한눈에 파악됩니다. 또한 단기 이동평균과 장기 이동평균을 비교하여 추세 전환점을 찾는 등 고급 분석도 가능합니다.
성능 면에서도 Pandas의 C 수준 최적화 덕분에 대용량 데이터에서도 빠르게 작동합니다.
실전 팁
💡 이동평균의 window 크기 선택이 중요합니다. 너무 작으면 노이즈가 남고, 너무 크면 최근 변화를 놓칩니다. 일반적으로 7(주간), 30(월간), 90(분기) 등 의미 있는 기간 단위를 사용하세요.
💡 center=True 옵션을 사용하면 윈도우를 중앙 정렬할 수 있습니다. rolling(window=7, center=True)는 앞 3개, 현재, 뒤 3개를 사용하여 더 대칭적인 평활화를 제공합니다. 단, 미래 데이터를 사용하므로 예측에는 부적합합니다.
💡 지수 이동평균(EMA)은 최근 데이터에 더 큰 가중치를 줍니다. df.ewm(span=7).mean()로 계산하며, 단순 이동평균보다 빠르게 반응합니다. 주식 트레이딩에서는 EMA를 더 선호합니다.
💡 여러 윈도우 크기를 동시에 계산하여 비교하세요. 단기(5일), 중기(20일), 장기(60일) 이동평균을 함께 보면 트렌드의 강도와 방향을 파악하기 쉽습니다. 골든크로스(단기가 장기를 상향 돌파)는 강한 매수 신호입니다.
💡 rolling은 시간 기반 윈도우도 지원합니다. rolling(window='7D')는 정확히 7일치 데이터를 사용하며, 데이터가 불규칙하게 있을 때 유용합니다. 개수 기반(window=7)과 시간 기반(window='7D')의 차이를 이해하세요.
5. Time Shift
시작하며
여러분이 "이번 달 매출이 지난달보다 얼마나 늘었는지" 또는 "오늘 주가가 일주일 전보다 몇 퍼센트 올랐는지" 계산하려고 할 때, 데이터를 복사해서 날짜를 맞추고... 정말 복잡했던 경험 있으시죠?
시계열 분석에서 가장 중요한 것 중 하나가 바로 "과거와 현재의 비교"입니다. 이런 문제는 실무에서 정말 자주 발생합니다.
전년 동기 대비 성장률, 전주 대비 변화율, 전일 대비 증감 등 거의 모든 비즈니스 지표는 이전 시점과의 비교로 의미를 갖습니다. 하지만 데이터프레임에서 서로 다른 시점의 값을 매칭하는 것은 생각보다 까다롭습니다.
바로 이럴 때 필요한 것이 Time Shift입니다. 이것은 시계열 데이터를 시간 축을 따라 앞뒤로 이동시켜서, 과거나 미래의 값을 현재 행에서 바로 참조할 수 있게 해줍니다.
마치 타임머신처럼 데이터를 과거나 미래로 보내는 것이죠.
개요
간단히 말해서, Time Shift는 데이터를 시간 축을 따라 앞으로(미래) 또는 뒤로(과거) 이동시키는 기능입니다. shift()와 diff() 메서드가 핵심입니다.
왜 이 기능이 필요한지 실무 관점에서 설명하자면, 비교 분석이 데이터 분석의 핵심이기 때문입니다. 예를 들어, 마케팅 캠페인 효과를 측정하려면 "캠페인 전 일주일 평균"과 "캠페인 후 일주일 평균"을 비교해야 합니다.
주식 트레이딩에서는 "3일 전 가격 대비 현재 가격"으로 모멘텀을 계산합니다. A/B 테스트 결과도 "변경 전"과 "변경 후"의 비교입니다.
기존에는 데이터프레임을 복사하고 인덱스를 조정한 뒤 merge나 join을 사용하는 복잡한 과정을 거쳤다면, 이제는 df['sales'].shift(1)로 "1일 전 매출"을 현재 행에 바로 가져올 수 있습니다. Time Shift의 핵심 특징은 네 가지입니다.
첫째, shift(n)는 데이터를 n칸 아래로 이동시킵니다(양수는 과거 데이터를 가져옴). 둘째, shift(-n)는 n칸 위로 이동시킵니다(음수는 미래 데이터를 가져옴).
셋째, diff(n)는 현재 값에서 n칸 이전 값을 뺀 차이를 계산합니다. 넷째, pct_change(n)는 백분율 변화율을 계산합니다.
이러한 특징들이 시간에 따른 변화 분석을 엄청나게 간단하게 만들어줍니다.
코드 예제
import pandas as pd
import numpy as np
# 10일간의 매출 데이터
dates = pd.date_range('2024-01-01', periods=10, freq='D')
df = pd.DataFrame({
'sales': [100, 120, 115, 130, 125, 140, 135, 150, 145, 160]
}, index=dates)
# 1일 전 매출 (과거 데이터 가져오기)
df['sales_prev'] = df['sales'].shift(1)
# 전일 대비 변화량 (현재 - 1일 전)
df['sales_diff'] = df['sales'].diff(1) # 또는 df['sales'] - df['sales'].shift(1)
# 전일 대비 변화율 (%)
df['sales_pct_change'] = df['sales'].pct_change(1) * 100
# 3일 전과 비교
df['sales_3days_ago'] = df['sales'].shift(3)
df['sales_change_3d'] = df['sales'] - df['sales_3days_ago']
# 1일 후 매출 (미래 데이터 가져오기 - 주의!)
df['sales_next'] = df['sales'].shift(-1)
print(df)
설명
이것이 하는 일: Time Shift는 시계열 데이터의 각 행에 대해 지정된 칸 수만큼 떨어진 다른 행의 값을 가져오거나, 그 차이를 계산하여 시간에 따른 변화를 분석할 수 있게 합니다. 첫 번째로, shift(1)은 모든 값을 한 칸 아래로 밀어냅니다.
결과적으로 각 행에는 이전 시점의 값이 들어갑니다. 첫 번째 행은 이전 데이터가 없으므로 NaN이 됩니다.
왜 이렇게 하냐면, 현재 매출과 전일 매출을 같은 행에 나란히 놓아서 직접 비교할 수 있게 하기 위함입니다. 이것은 SQL의 LAG 함수와 동일한 개념입니다.
두 번째로, diff(1)은 내부적으로 df['sales'] - df['sales'].shift(1)을 계산한 것과 같습니다. 즉, 현재 값에서 이전 값을 뺀 차이를 구합니다.
이것은 "얼마나 증가했는지" 또는 "얼마나 감소했는지"를 나타내는 절대적 변화량입니다. 양수면 증가, 음수면 감소입니다.
세 번째로, pct_change(1)은 (현재 - 이전) / 이전 * 100으로 계산됩니다. 이것은 상대적 변화율로, 100원에서 110원으로 변한 것과 1000원에서 1100원으로 변한 것을 동등하게 "10% 증가"로 표현할 수 있게 해줍니다.
비율로 비교하는 것이 절대값보다 공정한 비교인 경우가 많습니다. 마지막으로, shift(-1)은 데이터를 위로 올립니다.
즉, 다음 시점의 값을 가져옵니다. 하지만 주의하세요!
미래 데이터를 사용하는 것은 예측 모델을 만들 때 치명적인 오류(data leakage)를 일으킵니다. 미래 정보를 이미 알고 있는 상태에서 모델을 학습하면 실전에서는 전혀 작동하지 않습니다.
미래 shift는 주로 검증 용도로만 사용해야 합니다. 여러분이 이 기능을 사용하면 복잡한 날짜 계산이나 self-join 없이도 시간에 따른 변화를 즉시 계산할 수 있습니다.
매출 증감 분석, 주가 모멘텀 계산, 센서 데이터 이상 탐지 등에 필수적입니다. 또한 머신러닝 피처 엔지니어링에서 "lag features"를 만들 때도 핵심 도구입니다.
실전 팁
💡 shift의 periods 매개변수는 시간 기반도 지원합니다. shift(freq='D')는 하루만큼 인덱스를 이동시키며, 데이터가 불규칙할 때 유용합니다. 개수 기반 shift(1)와 시간 기반 shift(freq='D')의 차이를 이해하세요.
💡 diff()와 shift()를 혼동하지 마세요. shift()는 값을 그대로 가져오고, diff()는 차이를 계산합니다. df['sales'].shift(1)는 전일 매출 자체이고, df['sales'].diff(1)는 전일 대비 증감입니다.
💡 결측치 처리에 주의하세요. shift() 결과의 첫 n개 행은 항상 NaN입니다. .fillna(0)으로 채울지, .dropna()로 제거할지는 분석 목적에 따라 결정하세요. 변화율 계산에서는 보통 NaN 행을 제거합니다.
💡 여러 기간의 lag를 동시에 생성하면 강력한 피처가 됩니다. for i in range(1, 8): df[f'sales_lag{i}'] = df['sales'].shift(i)로 일주일치 과거 데이터를 피처로 만들 수 있습니다. 시계열 예측 모델의 기본입니다.
💡 cumsum()과 결합하면 누적 변화를 추적할 수 있습니다. df['sales'].diff().cumsum()는 첫 날을 기준으로 한 누적 변화량이며, 추세를 시각화하기 좋습니다.
6. 시간대(Timezone) 처리
시작하며
여러분이 전 세계 사용자를 대상으로 하는 서비스를 운영하는데, 미국 사용자의 로그인 시간과 한국 사용자의 로그인 시간이 섞여서 "오늘 오후 3시 접속자"를 정확히 파악할 수 없었던 경험 있으신가요? 또는 서버 로그는 UTC인데 리포트는 한국 시간으로 해야 해서 매번 9시간을 더하느라 실수한 적은요?
이런 문제는 글로벌 서비스, 해외 거래소 데이터, 분산 시스템 로그 등을 다룰 때 정말 골치 아픈 이슈입니다. 같은 "2024-03-15 14:00"이라도 뉴욕 시간인지, 서울 시간인지, UTC인지에 따라 실제 시각이 12시간 이상 차이 날 수 있습니다.
시간대를 잘못 처리하면 데이터 분석 결과가 완전히 틀려집니다. 바로 이럴 때 필요한 것이 Timezone 처리입니다.
Pandas는 시간대 정보를 데이터에 포함시켜서, 서로 다른 시간대의 데이터를 정확하게 비교하고 변환할 수 있게 해줍니다. 마치 여러 나라의 시계를 동시에 보면서 정확한 시간을 맞추는 것과 같습니다.
개요
간단히 말해서, Timezone 처리는 시계열 데이터에 시간대 정보를 붙이고(localize), 다른 시간대로 변환(convert)하는 기능입니다. 왜 이 기능이 필요한지 실무 관점에서 설명하자면, 현대 시스템은 대부분 분산되어 있고 글로벌하기 때문입니다.
예를 들어, AWS 서버는 UTC로 로그를 남기지만, 한국 사용자에게 리포트할 때는 KST(한국 표준시)로 보여줘야 합니다. 뉴욕 증권거래소 데이터는 ET(동부 시간)인데, 한국에서 트레이딩할 때는 KST로 변환해야 합니다.
여러 지역의 지사 데이터를 합칠 때도 시간대 통일이 필수입니다. 기존에는 시간대를 무시하고 그냥 숫자로 더하거나 빼서 변환했다면(한국은 +9시간), 이제는 Pandas가 자동으로 일광절약시간(DST) 등 복잡한 규칙을 처리해줍니다.
Timezone 처리의 핵심 특징은 네 가지입니다. 첫째, tz_localize()는 시간대 정보가 없는(naive) datetime에 시간대를 지정합니다.
둘째, tz_convert()는 이미 시간대가 있는(aware) datetime을 다른 시간대로 변환합니다. 셋째, pytz 라이브러리를 사용하여 전 세계 모든 시간대를 지원합니다.
넷째, 일광절약시간 전환을 자동으로 처리합니다. 이러한 특징들이 글로벌 데이터를 정확하고 안전하게 다룰 수 있게 해줍니다.
코드 예제
import pandas as pd
import numpy as np
# 시간대 정보가 없는 datetime (naive)
dates = pd.date_range('2024-03-15 09:00', periods=5, freq='H')
df = pd.DataFrame({'value': [100, 110, 105, 120, 115]}, index=dates)
print("원본 (naive):", df.index[0])
# UTC 시간대로 localize (이 시간들이 UTC라고 선언)
df_utc = df.copy()
df_utc.index = df_utc.index.tz_localize('UTC')
print("UTC로 localize:", df_utc.index[0])
# UTC를 한국 시간으로 변환
df_kst = df_utc.copy()
df_kst.index = df_kst.index.tz_convert('Asia/Seoul')
print("KST로 convert:", df_kst.index[0]) # 9시간 추가됨
# 처음부터 한국 시간으로 localize
df_kst_direct = df.copy()
df_kst_direct.index = df_kst_direct.index.tz_localize('Asia/Seoul')
# 여러 시간대 데이터 비교 (자동으로 UTC 기준 정렬)
print(df_utc.index[0] == df_kst.index[0]) # True! 같은 순간을 가리킴
설명
이것이 하는 일: Timezone 처리는 datetime 객체에 시간대 메타데이터를 추가하고, 이를 기반으로 정확한 시간 변환과 비교를 수행합니다. 첫 번째로, tz_localize('UTC')는 "이 시간들이 UTC 시간대에 있다"고 선언하는 것입니다.
실제 시간 값은 변하지 않고, 메타데이터만 추가됩니다. 왜 이 단계가 필요하냐면, Pandas는 시간대 정보가 없으면 어느 시간대인지 알 수 없기 때문입니다.
'2024-03-15 09:00'이 서울 시간인지 뉴욕 시간인지 모르는 상태입니다. 두 번째로, tz_convert('Asia/Seoul')는 이미 시간대가 지정된 datetime을 다른 시간대로 변환합니다.
이때는 실제 시간 값이 변합니다. UTC 09:00은 한국 시간으로 18:00(+9시간)이 됩니다.
내부적으로 Pandas는 UTC 기준 타임스탬프를 유지하고, 표시만 해당 시간대로 바꿉니다. 그래서 서로 다른 시간대의 datetime을 비교해도 정확하게 작동합니다.
세 번째로, 시간대 이름은 'UTC', 'Asia/Seoul', 'America/New_York', 'Europe/London' 등 IANA Time Zone Database 형식을 사용합니다. 'KST'나 'EST' 같은 약어는 피하세요.
약어는 모호하고(EST가 호주인지 미국인지), 일광절약시간을 처리하지 못합니다. 마지막으로, 서로 다른 시간대의 datetime을 비교하면 Pandas가 자동으로 UTC 기준으로 변환하여 비교합니다.
그래서 df_utc.index[0] == df_kst.index[0]이 True가 됩니다. 둘 다 같은 순간을 가리키기 때문입니다(하나는 UTC로, 하나는 KST로 표현했을 뿐).
여러분이 이 기능을 사용하면 시간대 변환 실수를 완전히 방지할 수 있습니다. 특히 일광절약시간이 있는 국가(미국, 유럽 등)의 데이터를 다룰 때, 수동으로 +8시간, +9시간 계산하다가 DST 전환 시점에 1시간 오차가 생기는 버그를 막을 수 있습니다.
또한 여러 지역의 데이터를 merge할 때 시간 정렬이 정확해집니다.
실전 팁
💡 항상 UTC를 기본으로 사용하고, 표시할 때만 로컬 시간대로 변환하세요. 데이터베이스 저장도 UTC, 내부 계산도 UTC, 마지막 리포트에서만 tz_convert()로 변환하는 것이 베스트 프랙티스입니다.
💡 tz_localize()를 이미 시간대가 있는 datetime에 사용하면 에러가 납니다. 먼저 .tz_localize(None)으로 시간대를 제거한 후 다시 localize해야 합니다. 또는 처음부터 tz_convert()를 사용하세요.
💡 CSV 파일로 저장할 때 시간대 정보가 사라지므로 주의하세요. df.to_csv()는 시간대를 ISO 8601 형식(2024-03-15T09:00:00+09:00)으로 저장하지만, 다시 읽을 때 pd.read_csv(..., parse_dates=['date'])만으로는 시간대가 복원되지 않습니다. 명시적으로 tz_localize()를 다시 해야 합니다.
💡 시간대 약어 대신 전체 이름을 사용하세요. 'EST' 대신 'America/New_York', 'KST' 대신 'Asia/Seoul'을 사용해야 일광절약시간이 자동으로 처리됩니다. pytz.all_timezones로 전체 목록을 확인할 수 있습니다.
💡 대용량 데이터에서는 시간대 변환이 느릴 수 있습니다. 필요한 경우만 변환하고, 가능하면 UTC로 통일하여 사용하세요. 시각화나 리포트 직전에만 convert하는 것이 효율적입니다.
7. 결측 시간 처리
시작하며
여러분이 센서 데이터를 수집하는데, 통신 장애로 몇 시간치 데이터가 빠졌거나, 거래소가 주말에는 문을 닫아서 주말 데이터가 없는 경우를 만난 적 있으신가요? 또는 1시, 2시, 4시, 5시 데이터는 있는데 3시 데이터가 없어서 그래프에 구멍이 뻥 뚫린 것처럼 보였던 경험은요?
이런 문제는 실제 세계의 데이터에서 너무나 흔합니다. 센서 고장, 네트워크 단절, 휴일, 시스템 점검 등 다양한 이유로 데이터가 중간중간 빠집니다.
문제는 많은 분석 기법들이 "연속적인 시계열"을 가정한다는 것입니다. 이동평균, 자기상관 분석, 시계열 예측 등은 시간 간격이 일정해야 제대로 작동합니다.
바로 이럴 때 필요한 것이 결측 시간 처리입니다. Pandas는 빠진 시간을 자동으로 찾아내고, 적절한 값으로 채워서 완전한 시계열을 만들어줍니다.
마치 퍼즐의 빠진 조각을 적절히 메워서 전체 그림을 완성하는 것과 같습니다.
개요
간단히 말해서, 결측 시간 처리는 시계열 데이터에서 빠진 시간 포인트를 찾아내고(reindex), 적절한 값으로 채우는(fillna, interpolate) 작업입니다. 왜 이 기능이 필요한지 실무 관점에서 설명하자면, 불완전한 데이터로는 정확한 분석이 불가능하기 때문입니다.
예를 들어, 시간당 트래픽을 분석하는데 몇 시간치가 빠지면 일일 합계가 틀려집니다. 주식 차트에서 거래가 없는 시간의 데이터가 없으면 캔들 차트가 이상하게 그려집니다.
센서 데이터로 이상 탐지를 할 때 중간에 구멍이 있으면 알고리즘이 오작동합니다. 기존에는 엑셀에서 수동으로 빈 행을 찾아 채우거나, 복잡한 코드로 누락 시간을 계산했다면, 이제는 reindex()와 asfreq()로 자동으로 완전한 시간 인덱스를 만들 수 있습니다.
결측 시간 처리의 핵심 특징은 네 가지입니다. 첫째, reindex()로 원하는 시간 범위의 완전한 인덱스를 만듭니다.
빠진 시간은 NaN으로 채워집니다. 둘째, fillna()로 간단한 값(0, 평균 등)으로 채웁니다.
셋째, ffill()/bfill()로 앞/뒤 값으로 채웁니다. 넷째, interpolate()로 주변 값을 기반으로 선형 또는 다양한 방법으로 보간합니다.
이러한 특징들이 불완전한 실세계 데이터를 분석 가능한 완전한 시계열로 만들어줍니다.
코드 예제
import pandas as pd
import numpy as np
# 중간에 빠진 시간이 있는 데이터
irregular_dates = pd.to_datetime([
'2024-01-01 10:00', '2024-01-01 11:00',
'2024-01-01 13:00', # 12시 누락
'2024-01-01 14:00', '2024-01-01 16:00' # 15시 누락
])
df = pd.DataFrame({'value': [100, 110, 130, 140, 160]}, index=irregular_dates)
print("원본 (불완전):\n", df)
# 완전한 시간 범위 생성
full_range = pd.date_range(start='2024-01-01 10:00',
end='2024-01-01 16:00', freq='H')
# reindex로 빠진 시간 추가 (NaN으로 채워짐)
df_complete = df.reindex(full_range)
print("\nReindex 후 (NaN):\n", df_complete)
# 방법 1: 앞 값으로 채우기 (forward fill)
df_ffill = df_complete.ffill()
# 방법 2: 선형 보간
df_interp = df_complete.interpolate(method='linear')
# 방법 3: 0으로 채우기
df_zero = df_complete.fillna(0)
print("\n선형 보간:\n", df_interp)
설명
이것이 하는 일: 결측 시간 처리는 불규칙한 시계열 데이터를 규칙적인 시간 간격으로 재구성하고, 빈 부분을 통계적 또는 휴리스틱 방법으로 채워서 완전한 데이터셋을 만듭니다. 첫 번째로, pd.date_range()로 원하는 완전한 시간 범위를 생성합니다.
start, end, freq를 지정하면 빠짐없는 연속적인 시간 인덱스가 만들어집니다. 왜 이 단계가 필요하냐면, 원본 데이터에는 어떤 시간이 빠졌는지 정보가 없기 때문입니다.
완전한 기준을 먼저 만들어야 비교가 가능합니다. 두 번째로, reindex(full_range)는 DataFrame의 인덱스를 full_range로 재설정합니다.
원본에 있던 시간은 그대로 값을 유지하고, 없던 시간은 새로 추가되며 NaN이 채워집니다. 내부적으로는 인덱스 기반 left join과 유사하게 작동합니다.
세 번째로, fillna(), ffill(), bfill() 메서드로 NaN을 채웁니다. ffill()(forward fill)은 마지막으로 관측된 값을 그대로 사용하는 방식으로, "변화가 없었다"고 가정합니다.
예를 들어, 센서가 12시에 110을 기록한 후 13시 기록이 없으면, 12시 값을 그대로 사용합니다. bfill()(backward fill)은 반대로 다음 값을 사용합니다.
마지막으로, interpolate(method='linear')는 선형 보간을 수행합니다. 11시에 110, 13시에 130이면, 12시는 (110+130)/2 = 120으로 추정합니다.
method='polynomial', 'spline' 등 더 복잡한 보간도 가능하지만, 대부분의 경우 linear가 가장 안전하고 해석 가능합니다. 여러분이 이 기능을 사용하면 불완전한 데이터로 인한 분석 오류를 방지할 수 있습니다.
특히 시계열 예측 모델(ARIMA, Prophet 등)은 규칙적인 간격을 요구하므로, 전처리 단계에서 필수적입니다. 또한 여러 소스의 데이터를 시간 기준으로 합칠 때(merge), 완전한 인덱스가 있으면 훨씬 정확하게 정렬됩니다.
실전 팁
💡 보간 방법 선택이 중요합니다. ffill()은 주가 데이터처럼 "마지막 거래가가 유지된다"고 가정할 때 적합하고, interpolate()는 온도나 습도처럼 연속적으로 변하는 물리량에 적합합니다. 매출이나 횟수는 fillna(0)이 적합할 수 있습니다.
💡 limit 매개변수로 보간할 최대 개수를 제한하세요. ffill(limit=3)은 최대 3개까지만 앞 값으로 채우고, 그 이상 연속된 NaN은 그대로 둡니다. 너무 오래된 값을 무한정 사용하면 왜곡이 심해집니다.
💡 asfreq() 메서드는 reindex()의 간편 버전입니다. df.asfreq('H')는 시간 단위로 reindex하고, method='ffill'을 추가하면 동시에 채울 수 있습니다. 간단한 경우 더 직관적입니다.
💡 대량 결측 구간은 보간하지 말고 제거하거나 별도 분석하세요. 예를 들어, 3일 동안 센서가 완전히 멈췄다면 보간한 값은 신뢰할 수 없습니다. 결측 비율을 먼저 확인하고(df.isna().sum() / len(df)), 임계값 이상이면 해당 기간을 제외하세요.
💡 시간대가 있는 데이터를 reindex할 때는 full_range에도 같은 시간대를 지정해야 합니다. pd.date_range(..., tz='Asia/Seoul')처럼 명시하지 않으면 시간대 정보가 사라져 나중에 혼란을 일으킵니다.
8. 주기적 패턴 분석
시작하며
여러분이 웹사이트 트래픽을 분석하는데, "월요일 오전에 방문자가 많다"거나 "주말 저녁에는 구매가 급증한다" 같은 패턴을 발견하고 싶었던 경험 있으신가요? 또는 전력 소비 데이터에서 "평일 오후 2-5시에 피크가 온다" 같은 규칙성을 찾고 싶었지만, 어떻게 분석해야 할지 막막했던 적은요?
이런 문제는 비즈니스 인사이트를 얻는 데 정말 중요합니다. 시계열 데이터에는 일별, 주별, 월별, 시간대별로 반복되는 패턴이 숨어 있습니다.
이 패턴을 발견하면 리소스를 효율적으로 배치하고, 프로모션 타이밍을 최적화하며, 이상 탐지도 더 정확해집니다. 하지만 수천, 수만 개의 데이터에서 육안으로 패턴을 찾는 것은 거의 불가능합니다.
바로 이럴 때 필요한 것이 주기적 패턴 분석입니다. Pandas의 DatetimeIndex는 요일, 시간, 월 등 시간 속성을 쉽게 추출할 수 있어서, 이를 기반으로 그룹화하고 집계하면 숨겨진 패턴이 드러납니다.
개요
간단히 말해서, 주기적 패턴 분석은 DatetimeIndex의 시간 속성(요일, 시간, 월 등)을 추출하여 그룹별 통계를 계산하는 것입니다. 왜 이 기능이 필요한지 실무 관점에서 설명하자면, 시간에 따른 규칙성을 발견하는 것이 데이터 분석의 핵심 가치이기 때문입니다.
예를 들어, 음식 배달 앱은 "금요일 저녁 7-9시에 주문이 폭증한다"는 패턴을 알면 배달 기사를 미리 배치할 수 있습니다. 전자상거래는 "월말에 매출이 증가한다"는 패턴으로 재고를 관리합니다.
서버 관리자는 "평일 오전 9시에 트래픽 피크"를 알면 스케일 아웃을 자동화할 수 있습니다. 기존에는 날짜를 문자열로 파싱하고 요일을 직접 계산하는 복잡한 코드를 작성했다면, 이제는 df.index.dayofweek, df.index.hour 같은 속성으로 즉시 접근할 수 있습니다.
주기적 패턴 분석의 핵심 특징은 네 가지입니다. 첫째, DatetimeIndex는 year, month, day, hour, minute, dayofweek, weekday, quarter 등 풍부한 시간 속성을 제공합니다.
둘째, 이 속성들을 groupby의 키로 사용하여 시간 단위별 집계가 가능합니다. 셋째, 여러 속성을 조합하여 다차원 분석도 가능합니다(예: 요일+시간대).
넷째, 결과를 pivot_table이나 heatmap으로 시각화하면 패턴이 한눈에 보입니다. 이러한 특징들이 시계열 데이터에서 숨겨진 비즈니스 인사이트를 발굴하게 해줍니다.
코드 예제
import pandas as pd
import numpy as np
# 30일간 시간별 웹 트래픽 데이터
dates = pd.date_range('2024-01-01', periods=30*24, freq='H')
df = pd.DataFrame({
'visitors': np.random.randint(100, 1000, len(dates))
}, index=dates)
# 시간 속성 추출
df['hour'] = df.index.hour
df['dayofweek'] = df.index.dayofweek # 0=월요일, 6=일요일
df['day_name'] = df.index.day_name() # 'Monday', 'Tuesday', ...
# 요일별 평균 방문자
by_dayofweek = df.groupby('dayofweek')['visitors'].mean()
print("요일별 평균 방문자:\n", by_dayofweek)
# 시간대별 평균 방문자
by_hour = df.groupby('hour')['visitors'].mean()
print("\n시간대별 평균 방문자:\n", by_hour)
# 요일 + 시간대 2차원 분석
heatmap_data = df.groupby(['dayofweek', 'hour'])['visitors'].mean().unstack()
print("\n요일-시간 히트맵:\n", heatmap_data.head())
# 주말 vs 평일 비교
df['is_weekend'] = df['dayofweek'].isin([5, 6])
weekend_vs_weekday = df.groupby('is_weekend')['visitors'].mean()
print("\n주말 vs 평일:\n", weekend_vs_weekday)
설명
이것이 하는 일: 주기적 패턴 분석은 시계열 데이터의 각 시점을 시간 속성(요일, 시간 등)으로 라벨링하고, 같은 속성을 가진 시점들끼리 그룹화하여 평균적인 행동 패턴을 추출합니다. 첫 번째로, df.index.hour는 각 시점의 시간(0-23)을 추출합니다.
이것은 DatetimeIndex의 벡터화된 속성으로, 매우 빠르게 작동합니다. 왜 이것이 유용하냐면, 모든 "14시"의 데이터를 한 번에 그룹화하여 "오후 2시대의 평균 트래픽"을 계산할 수 있기 때문입니다.
내부적으로는 각 datetime 객체에서 hour 필드를 추출하는 것입니다. 두 번째로, groupby('hour')는 같은 시간대끼리 묶어줍니다.
1월 1일 14시, 1월 2일 14시, ... 모든 14시 데이터가 하나의 그룹이 됩니다.
.mean()을 호출하면 이 그룹의 평균 방문자 수가 계산되어, "평균적으로 14시대에는 방문자가 몇 명인지"를 알 수 있습니다. 이것은 시간대별 트렌드를 한눈에 파악할 수 있게 해줍니다.
세 번째로, groupby(['dayofweek', 'hour'])는 2차원 그룹화입니다. (월요일, 9시), (월요일, 10시), ..., (일요일, 23시)처럼 요일과 시간의 모든 조합에 대한 평균을 계산합니다.
.unstack()은 이것을 행렬 형태로 변환하여, 행이 요일, 열이 시간인 테이블을 만듭니다. 이것을 seaborn 같은 라이브러리로 히트맵으로 그리면 "월요일 오전 9시가 가장 바쁘다" 같은 패턴이 색으로 명확히 보입니다.
마지막으로, isin([5, 6])은 토요일(5)과 일요일(6)을 주말로 분류합니다. 이런 파생 변수를 만들어 비교하면 "주말 평균은 500명, 평일 평균은 800명"처럼 고수준 인사이트를 얻을 수 있습니다.
여러분이 이 기능을 사용하면 데이터에 숨겨진 시간 패턴을 과학적으로 발견할 수 있습니다. 감이나 추측이 아닌 실제 데이터 기반 의사결정이 가능해집니다.
또한 이상 탐지에도 유용합니다. 예를 들어, "월요일 오전 9시 평균은 1000인데 오늘은 100"이면 뭔가 문제가 있다는 신호입니다.
실전 팁
💡 dayofweek는 0(월요일)~6(일요일)이고, weekday는 동일하지만, day_name()은 문자열을 반환합니다. 그래프 라벨에는 day_name()이 보기 좋지만, 정렬이나 계산에는 dayofweek를 사용하세요.
💡 is_month_end, is_month_start, is_quarter_end 같은 불린 속성도 유용합니다. 월말 효과, 분기말 효과를 분석할 때 활용하세요. df[df.index.is_month_end]로 월말 데이터만 쉽게 필터링할 수 있습니다.
💡 여러 시간 속성을 조합하면 강력합니다. df['period'] = df.index.hour.map(lambda h: 'morning' if 6<=h<12 else 'afternoon' if 12<=h<18 else 'night')처럼 커스텀 기간을 정의하여 비즈니스 요구에 맞춘 분석이 가능합니다.
💡 시각화가 핵심입니다. groupby 결과를 .plot(kind='bar')나 seaborn.heatmap()으로 그려보세요. 숫자 표보다 그래프가 패턴을 훨씬 명확히 보여줍니다. 특히 히트맵은 2차원 패턴(요일x시간)을 이해하는 최고의 도구입니다.
💡 계절성(seasonality)을 분석하려면 월별 그룹화를 사용하세요. df.groupby(df.index.month)['sales'].mean()로 "몇 월에 매출이 높은지" 파악할 수 있습니다. 이것은 재고 계획, 마케팅 예산 배분에 직접적으로 활용됩니다.
댓글 (0)
함께 보면 좋은 카드 뉴스
데이터 증강과 정규화 완벽 가이드
머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.
ResNet과 Skip Connection 완벽 가이드
딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.
CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet
컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.
CNN 기초 Convolution과 Pooling 완벽 가이드
CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.
TensorFlow와 Keras 완벽 입문 가이드
머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.