Pandas로 시계열 데이터 다루기: DatetimeIndex, shift, diff, resample, 시각화

Alchemist·4일 전

KT AIVLE

목록 보기
7/20

서비스 데이터에는 거의 항상 시간이 붙는다. 날짜별 가입자 수, 시간대별 API 요청 수, 일별 주문 금액, 서버 자원 사용률처럼 시간의 순서에 따라 값이 변하는 데이터를 다루려면 일반적인 표 조작만으로는 부족하다.

Pandas는 날짜 문자열을 실제 날짜 타입으로 바꾸고, 일정한 시간 간격을 만들고, 이전 시점과 비교하고, 시간 단위를 다시 구성하는 기능을 제공한다.

날짜 문자열
    ↓
DatetimeIndex
    ↓
시간 순서가 있는 Series / DataFrame
    ↓
shift / diff
    ↓
resample
    ↓
plot

시계열 데이터란?

시계열 데이터(Time Series Data) 는 시간 순서에 따라 기록된 관측값의 집합이다. 일반적인 테이블과 다른 점은 행의 순서 자체에 의미가 있다는 것이다.

예를 들어 API 요청 수가 다음과 같다고 하자.

daterequests
2026-09-011200
2026-09-021350
2026-09-031280

이 데이터에서 날짜 순서를 무시하면 증가·감소 추세를 제대로 해석하기 어렵다. 시계열을 다룰 때는 보통 시간 순서가 올바른지, 관측 간격이 일정한지를 먼저 확인해야 한다.


문자열 날짜를 datetime으로 바꾸기

CSV나 API에서 날짜를 읽으면 문자열인 경우가 많다.

dates = [
    "2026/01/01",
    "2026/02/01",
    "2026/03/01",
    "2026/04/01"
]

문자열 상태에서는 날짜 간 차이나 월별 집계 같은 시간 연산을 하기 어렵다. 이럴 때 pd.to_datetime() 을 사용한다.

import pandas as pd

date_index = pd.to_datetime(dates)

print(date_index)

실습에서는 변환 결과가 다음처럼 DatetimeIndex 로 확인됐다.

DatetimeIndex([
    '2020-01-01',
    '2020-02-01',
    '2020-03-01',
    '2020-04-01'
], dtype='datetime64[ns]', freq=None)

freq=None은 값이 datetime으로 변환되었지만 일정한 주기 정보가 별도로 지정되지는 않았다는 뜻이다.

일반적인 날짜 표현은 다음처럼 구분자가 조금 달라도 변환할 수 있다.

pd.to_datetime([
    "2026.01.01",
    "2026.02.01",
    "2026.03.01"
])

형식이 명확하다면 format을 지정할 수도 있다.

pd.to_datetime(
    ["2026-01-01", "2026-02-01"],
    format="%Y-%m-%d"
)

날짜를 인덱스로 사용하기

날짜를 인덱스로 사용하면 Pandas의 시계열 연산을 활용하기 편하다.

import numpy as np
import pandas as pd

values = np.array([8, 6, 7, 3])

date_index = pd.to_datetime([
    "2026-01-01",
    "2026-02-01",
    "2026-03-01",
    "2026-04-01"
])

series = pd.Series(
    values,
    index=date_index
)

print(series)

구조는 다음처럼 된다.

2026-01-01    8
2026-02-01    6
2026-03-01    7
2026-04-01    3

이제 shift(), diff(), resample() 같은 시계열 기능을 자연스럽게 사용할 수 있다.


일정한 날짜 범위 만들기: pd.date_range()

특정 기간의 날짜 인덱스를 만들고 싶다면 pd.date_range() 를 사용한다.

pd.date_range(
    start="2026-01-01",
    end="2026-01-10"
)

종료일 대신 생성할 개수를 지정할 수도 있다.

pd.date_range(
    start="2026-01-01",
    periods=7
)

freq로 시간 간격 지정하기

freq는 생성되는 날짜의 간격을 정한다.

pd.date_range(
    start="2026-01-01",
    periods=6,
    freq="MS"
)

대표적으로 다음과 같은 주기를 자주 접한다.

freq의미
D일 단위
W주 단위
W-MON월요일 기준 주 단위
MS월 시작일
M월 말일 계열
B주말을 제외한 영업일 계열

Pandas 버전에 따라 일부 주기 별칭의 권장 표기가 달라질 수 있으므로 경고가 발생한다면 사용하는 버전의 offset alias 문서를 확인하면 된다.


이전 시점의 값과 맞춰보기: shift()

시계열에서는 현재 값과 이전 값을 비교할 일이 많다.

오늘 매출 vs 어제 매출
이번 달 가입자 vs 지난달 가입자
현재 트래픽 vs 1시간 전 트래픽

이럴 때 shift() 를 사용할 수 있다.

np.random.seed(1)

values = np.random.randn(12)

index = pd.date_range(
    "2026-01-01",
    periods=12,
    freq="MS"
)

ts = pd.Series(
    values,
    index=index
)

한 기간 이전 값을 현재 행에 맞추려면 다음처럼 작성한다.

ts.shift(1)

개념적으로는 다음과 같다.

원본          shift(1)

1월 A         NaN
2월 B         A
3월 C         B
4월 D         C

실행 결과에서도 shift(1)은 첫 행이 NaN, shift(4)는 첫 4개 행이 NaN이 되는 것을 확인할 수 있었다.

ts.shift(4)

periods 이동과 freq 이동의 차이

ts.shift(1)

은 값을 한 행 이동시키고 기존 인덱스는 유지한다.

반면:

ts.shift(
    1,
    freq="W"
)

처럼 freq를 지정하면 시간 인덱스 자체를 주어진 규칙에 따라 이동시킨다.

사용 방식의미
shift(1)1기간 전 값을 현재 시점과 맞춤
shift(3)3기간 전 값을 현재 시점과 맞춤
shift(1, freq=...)날짜 인덱스를 시간 규칙만큼 이동

전일 대비나 전월 대비 계산에서는 보통 shift(periods) 방식이 많이 쓰인다.


변화량 계산하기: diff()

현재 값과 이전 값의 차이 자체가 필요하다면 diff() 가 더 간단하다.

ts.diff(1)

계산식은 다음과 같다.

현재 값 - 1기간 전 값

즉 개념적으로는:

ts.diff(1)

과

ts - ts.shift(1)

이 같은 형태의 변화량을 만든다고 이해할 수 있다.

실습에서는 첫 두 값이 약 1.624345, -0.611756일 때:

ts[1] - ts[0]

결과가 약:

-2.2361017773

으로 나왔고, ts.diff(1) 결과와 같은 값을 확인할 수 있었다.

3기간 전과 비교하려면 다음처럼 작성한다.

ts.diff(3)

처음 3개의 값은 비교 대상이 없으므로 NaN이 생긴다.

diff()는 일별 매출 증감, 트래픽 변화량, 사용자 수 변화처럼 기간별 변화 자체가 중요한 데이터에 유용하다.


시간 단위를 바꾸기: resample()

시계열 데이터를 분석할 때는 원본 시간 단위를 그대로 쓰지 않고 목적에 맞게 바꿀 때가 많다.

예를 들어 일별 데이터를 월별로 요약할 수 있다.

일별 데이터
    ↓
월별 구간으로 재구성
    ↓
평균 / 합계 / 최대값 계산
    ↓
월별 데이터

Pandas에서는 resample() 을 사용한다.


Down-sampling: 더 큰 시간 단위로 묶기

365일치 데이터를 만든다고 하자.

np.random.seed(1)

values = np.random.randn(365)

index = pd.date_range(
    "2026-01-01",
    periods=365,
    freq="D"
)

daily = pd.Series(
    values,
    index=index
)

일별 데이터를 월별 평균으로 바꾸면:

monthly = (
    daily
    .resample("M")
    .mean()
)

여러 관측값이 하나의 더 큰 시간 구간으로 합쳐지므로 Down-sampling이다.

집계 함수는 데이터 의미에 따라 달라진다.

daily.resample("M").mean()
daily.resample("M").sum()
daily.resample("M").max()

예를 들면:

  • 일별 응답시간 → 월별 평균 응답시간: mean()
  • 일별 주문 금액 → 월별 총매출: sum()
  • 일별 최고 접속자 → 월별 최고치: max()

처럼 선택할 수 있다.


Up-sampling: 더 작은 시간 단위로 확장하기

반대로 주별 데이터를 일별 데이터로 바꾸면 원래 없던 날짜가 새로 생성된다.

weekly = pd.Series(
    [10, 20, 15],
    index=pd.date_range(
        "2026-01-04",
        periods=3,
        freq="W"
    )
)

daily = weekly.resample("D")

이 경우 새로 생긴 날짜에는 원래 값이 없기 때문에 결측값이 생긴다.

Forward Fill

ffill() 은 앞의 값을 다음 관측 시점 전까지 전달한다.

weekly.resample("D").ffill()
1/04  10
1/05  10
1/06  10
...
1/11  20

실행 결과에서도 주간 값이 다음 주의 실제 관측값이 등장하기 전까지 유지되는 것을 확인할 수 있었다.

Backward Fill

bfill() 은 뒤에 있는 값을 앞쪽 결측 구간에 채운다.

weekly.resample("D").bfill()
1/04  10
1/05  20
1/06  20
...
1/11  20

ffill()과 bfill()은 편리하지만, 새로 채워진 값은 실제 측정값이 아니라 규칙으로 보완한 값이라는 점을 기억해야 한다.


Down-sampling과 Up-sampling 비교

구분Down-samplingUp-sampling
시간 간격더 크게 만듦더 작게 만듦
예시일별 → 월별주별 → 일별
데이터 개수보통 감소보통 증가
주요 처리집계결측값 처리
자주 쓰는 방식mean, sum, maxffill, bfill

핵심은 데이터 수를 단순히 늘리고 줄이는 것이 아니라 시간 해상도를 분석 목적에 맞게 조정하는 것이다.


시계열 데이터 시각화하기

시계열 데이터는 표보다 선 그래프로 봤을 때 흐름을 파악하기 쉽다.

Pandas의 DataFrame은 plot() 메서드를 제공한다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

np.random.seed(1)

values = np.random.randn(
    365,
    3
)

index = pd.date_range(
    start="2026-01-01",
    periods=365,
    freq="D"
)

ts_df = pd.DataFrame(
    values,
    index=index,
    columns=["API-A", "API-B", "API-C"]
)

ts_df.plot()
plt.show()

DatetimeIndex를 사용하면 x축이 시간으로 처리된다.

시계열 그래프에서는 주로 다음을 확인한다.

  • 전체적인 추세
  • 반복되는 패턴
  • 특정 시점의 이상치
  • 주기적으로 나타나는 계절성

직접 실행하면서 만난 matplotlib 오류

시각화를 위해 다음 코드를 실행했을 때:

import matplotlib.pyplot as plt

실습 환경에서는 다음 오류가 발생했다.

ModuleNotFoundError:
No module named 'matplotlib'

이 오류는 Python 문법 문제가 아니라 현재 실행 중인 Python 환경에 matplotlib가 설치되어 있지 않다는 의미다.

Jupyter Notebook에서는 다음처럼 설치할 수 있다.

%pip install matplotlib

그 뒤 다시 import를 실행했을 때 정상 수행되었고, DataFrame의 plot()도 실행할 수 있었다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

터미널에서 설치한다면

일반 터미널에서는 다음처럼 설치할 수 있다.

python -m pip install matplotlib

Conda 환경을 사용한다면 먼저 해당 환경을 활성화한다.

conda activate <환경이름>
python -m pip install matplotlib

또는:

conda install matplotlib

설치했는데도 같은 오류가 반복된다면 터미널의 Python 환경과 Jupyter 커널 환경이 같은지 확인해야 한다.


개발자 관점에서 서비스 데이터에 적용하기

시간대별 API 요청 수가 있다고 가정해보자.

traffic = pd.DataFrame({
    "timestamp": [
        "2026-09-01 09:00",
        "2026-09-01 10:00",
        "2026-09-01 11:00",
        "2026-09-02 09:00"
    ],
    "requests": [
        1200,
        1480,
        1520,
        1310
    ]
})

먼저 문자열 시간을 변환한다.

traffic["timestamp"] = pd.to_datetime(
    traffic["timestamp"]
)

그리고 시간을 인덱스로 설정한다.

traffic = traffic.set_index(
    "timestamp"
)

이제 하루 단위 총 요청 수를 만들 수 있다.

daily_requests = (
    traffic["requests"]
    .resample("D")
    .sum()
)

전일 대비 변화량도 쉽게 계산할 수 있다.

daily_change = daily_requests.diff(1)

시각화도 바로 이어진다.

daily_requests.plot()

전체 흐름은 다음처럼 정리할 수 있다.

로그의 문자열 timestamp
        ↓ pd.to_datetime()
DatetimeIndex
        ↓ resample()
일별 / 주별 / 월별 지표
        ↓ diff() / shift()
이전 기간과 비교
        ↓ plot()
시간 변화 확인

자주 헷갈리는 부분

1. datetime 타입과 문자열은 다르다

화면에 날짜처럼 보여도 문자열이면 시간 연산이 제한된다. 먼저 dtype을 확인하는 습관이 좋다.

2. shift와 diff는 목적이 다르다

shift()는 비교할 값을 옮겨 맞추는 기능이고, diff()는 두 시점의 차이를 바로 계산한다.

3. resample에는 시간 인덱스가 중요하다

resample()은 시간 기준으로 구간을 다시 만들기 때문에 DatetimeIndex 같은 시간 기반 인덱스를 사용하는 구조가 핵심이다.

4. 업샘플링으로 채운 값은 관측값이 아니다

ffill()이나 bfill()로 생성된 값은 실제 관측된 값과 의미가 다를 수 있다.

5. 집계 방식은 데이터 의미에 따라 선택해야 한다

매출은 합계가 자연스러울 수 있지만 응답시간은 평균이나 중앙값이 더 적절할 수 있다.


핵심 정리

  • 시계열 데이터는 시간 순서가 의미를 갖는 데이터다.
  • pd.to_datetime()으로 문자열 날짜를 datetime 타입으로 변환할 수 있다.
  • DatetimeIndex를 사용하면 시간 기반 연산이 편해진다.
  • pd.date_range()로 일정한 시간 간격의 날짜 인덱스를 생성할 수 있다.
  • freq로 일, 주, 월 등 관측 간격을 정할 수 있다.
  • shift()는 이전 시점의 값을 현재 시점과 맞출 때 유용하다.
  • diff(n)은 현재 값 - n기간 전 값을 계산한다.
  • resample()로 시간 단위를 다시 구성할 수 있다.
  • Down-sampling은 보통 집계가 필요하고, Up-sampling은 결측값 처리가 필요하다.
  • ffill()과 bfill()은 업샘플링 후 빈 구간을 채우는 대표적인 방법이다.
  • DatetimeIndex 기반 데이터는 plot()으로 시간 흐름을 쉽게 시각화할 수 있다.
  • ModuleNotFoundError: No module named 'matplotlib'가 발생하면 현재 실행 환경의 패키지 설치 여부를 확인한다.

정리하며

Pandas의 시계열 기능은 날짜를 저장하는 것에서 끝나지 않는다. 시간 단위를 맞추고, 이전 시점과 비교하고, 변화량을 계산하고, 시간 해상도를 바꾸고, 시각적으로 흐름을 확인하는 과정까지 자연스럽게 연결된다.

특히 to_datetime → date_range → shift/diff → resample → plot 흐름을 이해하면 로그 분석, 트래픽 모니터링, 매출 추이, 사용자 행동 데이터 같은 실제 서비스 데이터에도 그대로 응용할 수 있다.

시계열 데이터를 다룰 때는 함수 사용법뿐 아니라 시간 순서, 관측 간격, 새로 생성된 값의 의미까지 함께 생각하는 것이 중요하다.

profile
html_programming_language

0개의 댓글