시계열 데이터 전처리는 원시 시계열 데이터를 분석 또는 모델링에 적합한 구조화된 형식으로 변환하는 과정입니다. Pandas 라이브러리는 이러한 작업을 위한 도구를 제공합니다.
| Concept | Scalar Class | Array Class | Data Type | Creation Method |
| 날짜와 시간 (Date times) | Timestamp | DatetimeIndex | datetime64[ns], tz | to_datetime, date_range |
| 시간 간격 (Time deltas) | Timedelta | TimedeltaIndex | timedelta64[ns] | to_timedelta, timedelta_range |
| 기간 (Time spans) | Period | PeriodIndex | period[freq] | Period, period_range |
| 날짜 오프셋 (Date offsets) | DateOffset | None | None | DateOffset |
- Date times (Timestamp / DatetimeIndex): 특정 시점(날짜와 시간)을 나타내며 시계열 데이터의 기본입니다.
- Time deltas (Timedelta / TimedeltaIndex): 두 시점 사이의 절대적인 기간(Duration)을 나타냅니다.
- Time spans (Period / PeriodIndex): 특정 빈도(freq)로 정의된 기간을 나타냅니다.
- Date offsets (DateOffset): 달력상의 규칙을 따르는 상대적 기간(예: 영업일 기준 3일 후) 계산에 사용됩니다.
Pandas에서 시계열 데이터를 다룰 때 시간을 일반적인 데이터(컬럼)로 두는 대신 DataFrame의 인덱스(색인)로 설정하는 것이 가장 일반적이고 강력한 방법입니다. 이를 DatetimeIndex라고 부릅니다.
또한 Pandas에서는 시간 또는 날짜 데이터(datetime64[ns], timedelta64[ns])에 대한 결측값(Missing Value)을 나타내기 위해 특별히 정의한 값입니다. 일반적인 결측값인 NaN(Not a Number)이 숫자형 데이터의 결측치를 나타내는 것과 대칭되는 개념입니다.
to_datetime
pandas.to_datetime() 함수는 강력하게 날짜 및 시간 형식의 데이터를 datetime 객체로 변환해주는 핵심 도구입니다.
pandas.to_datetime(arg, errors='raise',
dayfirst=False,
yearfirst=False,
utc=False,
format=None,
exact=<no_default>,
unit=None,
infer_datetime_format=<no_default>,
origin='unix', cache=True)
- arg: int, float, str, datetime, list, tuple, 1d array, Series, DataFrame/dict like
- errors: ignore, raise, coerce
- dayfirst, yearfirst: bool
- utc: bool
- format: str, ISO801, mixed
- unit: str D,s,ms,us,ns
- origin: unix, Timestamp convertible
1. 컬럼 조합을 통한 날짜/시간 객체 생성 (Structured Input)
pd.to_datetime()은 DataFrame이나 딕셔너리(dict) 형태의 구조화된 입력을 받아 여러 컬럼을 조합하여 하나의 날짜/시간(Timestamp) 객체를 생성할 수 있습니다.
>>> df = pd.DataFrame({'year': [2015, 2016], 'month': [2, 3], 'day': [4, 5]})
>>> pd.to_datetime(df)
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
2. 정수(Integer) 값의 유닉스 시간 변환 (unit 파라미터)
정수(Integer)가 입력되면, pd.to_datetime()은 기본적으로 그 정수를 경과된 시간으로 해석하고 이를 Timestamp로 변환합니다. 이때 unit 파라미터를 사용하여 해당 정수의 시간 단위를 명시합니다.
>>> pd.to_datetime(1490195805, unit='s')
Timestamp('2017-03-22 15:16:45')
>>> pd.to_datetime(1490195805433502912, unit='ns')
pd.to_datetime(1490195805433502912, unit='ns')
3. 기준 시점 변경 (origin 파라미터)
unit을 사용하여 정수 값을 경과 시간으로 해석할 때, 시간 계산의 시작점(Epoch)을 설정할 수 있습니다. 기본은 유닉스 시간의 시작점인 '1970-01-01' 자정(UTC)부터 경과된 시간으로 계산합니다.
아래의 코드는 1960-01-01을 기준으로 일(Day, D) 단위로 계산 시작하는 것입니다.
>>> pd.to_datetime([1, 2, 3], unit='D', origin=pd.Timestamp('1960-01-01'))
DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'],
dtype='datetime64[ns]', freq=None)
4. 시간대(Timezone) 표준화 (utc 파라미터)
pd.to_datetime()은 다양한 시간대 정보를 포함하는 문자열을 UTC(협정 세계시) 기준으로 표준화할 수 있습니다.
>>> pd.to_datetime(['2018-10-26 12:00 -0530', '2018-10-26 12:00 -0500'], utc=True)
DatetimeIndex(['2018-10-26 17:30:00+00:00', '2018-10-26 17:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq=None)
date_range
date_range() 함수는 일정한 빈도(Frequency)를 가진 연속적인 날짜 및 시간(DatetimeIndex)을 생성하는 데 사용되는 핵심 함수입니다. 시계열 데이터 분석에서 인덱스를 만들거나, 누락된 시점을 채우는 데 매우 유용합니다.
date_range 함수는 시작, 끝, 또는 기간 중 최소 두 가지 정보를 제공해야 DatetimeIndex를 생성할 수 있습니다.
pandas.date_range(start=None,
end=None,
periods=None,
freq=None,
tz=None,
normalize=False,
name=None,
inclusive='both',
*,
unit=None,
**kwargs)
- start: str, datetime like
- end: str, datetime like
- periods: int
- freq: str, Timedelta, datetime.timedelta, DateOffset, default 'D' (빈도설정)
- normalize: True인 경우 start/end를 자정으로 맞춤
- inclusive: both, neither, left, right
- unit: str
DatetimeIndex
Pandas가 DatetimeIndex를 사용하면 날짜와 시간을 문자열이나 표준 datetime 객체로 전달받아도 자동으로 해당 시점이나 기간을 인식하고 데이터를 추출해줍니다.
예시 내용을 통해서 알아봅시다. 먼저 시계열 데이터의 기반을 마련합니다.
>>> start = datetime.datetime(2025, 1, 1)
>>> end = datetime.datetime(2025, 12, 31)
>>> rng = pd.date_range(start, end, freq="BME")
DatetimeIndex는 일반적인 Python 리스트나 NumPy 배열처럼 정수 기반의 슬라이싱을 적용할 수 있습니다. 여기서는 2 간격으로 데이터를 추출했습니다.
>>> ts = pd.Series(range(len(rng)), index=rng)
>>> ts[::2].index
DatetimeIndex(['2025-01-31', '2025-03-31', '2025-05-30', '2025-07-31',
'2025-09-30', '2025-11-28'],
dtype='datetime64[ns]', freq=‘2BME')
Pandas는 '5/30/2025'와 같은 날짜 문자열을 전달받으면, 이를 자동으로 Timestamp 객체로 파싱하고 정확히 일치하는 인덱스를 찾아 해당 값을 반환합니다. 이처럼 사용자가 날짜/시간 포맷을 직접 맞추지 않아도 되는 유연성을 제공합니다.
>>> ts['5/30/2025']
np.float64(0.37875684785407626)
마지막으로 datetime.datetime(2025, 11, 15)와 같이 Python의 표준 datetime 객체를 슬라이싱 경계로 사용할 수 있습니다.
>>> ts[datetime.datetime(2025, 11, 15):]
2025-11-28 1.180612
2025-12-31 0.554140
Freq: BME, dtype: float64
시계열 데이터의 인덱스(DatetimeIndex)를 사용하여 데이터를 조회할 때, 사용자가 입력한 시간 문자열의 정밀도(해상도)에 따라 Pandas가 하나의 정확한 시점을 찾을지, 아니면 특정 기간을 포괄하는 범위(슬라이스)를 반환할지 결정할 수 있습니다.
즉, 사용자가 인덱스에 있는 데이터보다 덜 구체적인 시간 정보(즉, 더 낮은 해상도)를 입력하면, Pandas는 해당 문자열이 포괄하는 전체 기간을 자동으로 슬라이싱하여 반환합니다.
>>> ts['2025']
2025-01-31 0
2025-02-28 1
2025-03-31 2
2025-04-30 3
2025-05-30 4
2025-06-30 5
2025-07-31 6
2025-08-29 7
2025-09-30 8
2025-10-31 9
2025-11-28 10
2025-12-31 11
Freq: BME, dtype: int64
Resample
resample()은 고빈도 데이터를 저빈도 데이터로 집계하거나(Downsampling), 저빈도 데이터를 고빈도 데이터로 확장(Upsampling)할 때 사용됩니다.
resample()은 기본적으로 지정된 새로운 시간 주기(예: 5분)를 기준으로 데이터를 그룹화(GroupBy)하고, 이 그룹화된 데이터에 대해 통계 함수를 적용할 수 있는 Resampler 객체를 반환합니다.
아래의 코드는 Pandas의 date_range 함수와 resample 메서드를 사용하여 초(second) 단위의 시계열 데이터를 5분 단위로 집계(다운샘플링)하는 과정을 보여줍니다.
2025년 1월 1일부터 총 900개의 시점을 생성하여 초(second) 단위 간격으로 생성합니다. (총 900초 = 15분 동안의 데이터)
rng = pd.date_range("1/1/2025", periods=900, freq="s")
0부터 499 사이의 정수 난수 900개를 생성하여 위에서 생성한 초 단위의 시간 인덱스(rng)를 지정하여, 초 단위 시계열 데이터 ts를 만듭니다.
ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
초 단위 데이터(ts)를 5분("5Min") 단위로 그룹화하는 Resampler 객체를 생성합니다. 그룹화된 각 5분 단위 데이터 블록에 대해 평균을 계산하여 최종 결과를 반환합니다.
>>> ts.resample("5Min").mean()
2025-01-01 00:00:00 250.456667
2025-01-01 00:05:00 253.023333
2025-01-01 00:10:00 225.970000
Freq: 5min, dtype: float64
resample의 인자는 다음과 같습니다.
DataFrame.resample(rule,
axis=<no_default>,
closed=None,
label=None,
convention='start',
kind=<no_default>,
on=None,
level=None,
origin='start_day',
offset=None,
group_keys=False)
- rule: DateOffset, Timedelta, str
- closed: right for ME, YE, QE, BME, BA, BQE, W, left for the others
- label: right, left
- on: str
- level: str, int
- origin: Timestamp, epoch, start, startday, end, endday
- offset: Timedelta, str
- groupkeys: boo
Shift
DataFrame.shift(periods=1,
freq=None,
axis=0,
fill_value=<no_default>,
suffix=None
)
- periods: int, Sequence (이동시킬 step 정도)
- freq: DataOffset, tseries.offsets, timedelta, str. 값이 주어지는 여부에 따라 동작이 달라짐.
- axis: 0 or Index, 1 or columns
- fill value: object (결측값 채우기)
- suff: str. periods가 sequence로 주어졌을 때 유효.
pd.date_range("2020-01-01", "2020-01-04")에 의해 2020년 1월 1일부터 1월 4일까지의 일별 시계열 인덱스가 설정됩니다.
df = pd.DataFrame({"Col1": [10, 20, 15, 30], "Col2": [13, 23, 18, 33], "Col3": [17, 27, 22, 37]}, index=pd.date_range("2020-01-01", "2020-01-04"))
df

이 코드는 DataFrame df의 모든 데이터를 periods=2 만큼 아래로 이동시킵니다.
df.shift(periods=2)

데이터를 시간 축(index)이 아닌 컬럼 축으로 이동시킵니다.
df.shift(periods=1, axis="columns")

freq 파라미터를 사용하여 달력상의 빈도를 기반으로 데이터를 이동시킵니다. freq 파라미터를 사용하면 periods 인자는 무시됩니다.
df.shift(periods=3, freq="D")

Rolling
롤링은 시계열 데이터에서 고정된 크기의 윈도우(Window, 창)를 순차적으로 이동시키면서, 그 윈도우 내의 데이터에 대해 통계 함수를 적용하는 분석 방법입니다. 이렇게 계산된 값을 이동 통계(Moving Statistics)라고 부르며, 가장 흔한 예가 이동 평균(Moving Average)입니다.
DataFrame.rolling(window,
min_periods=None,
center=False,
win_type=None,
on=None,
axis=<no_default>,
closed=None,
step=None,
method='single'
)
- window: int, timedelta, str, offset, BaseIndexer (롤링의 크기)
- minperiods: int. windows로 입력되는 값의 type에 따라서 default 값이 달라짐.
- center: bool. Right edge를 label로.
- wintype: str. None이 주어지면 모든 값이 동일하게 다루어짐.
- on: str. DataFrame에서 Column name.
- closed: right, left, both, neither (윈도우의 경계)
- step: int
- method: single, table
아래와 같은 데이터프레임을 만듭니다.
df_time = pd.DataFrame({'B': [0, 1, 2, np.nan, 4]},
index=[pd.Timestamp('20130101 09:00:00'),
pd.Timestamp('20130101 09:00:02'),
pd.Timestamp('20130101 09:00:03'),
pd.Timestamp('20130101 09:00:05'),
pd.Timestamp('20130101 09:00:06')])
window=2는 데이터 포인트 2개를 포함하는 윈도우를 의미합니다. (시간 간격 무시)
df_time.rolling('2s').sum()

window='2s'는 2초의 시간 간격을 윈도우로 정의합니다. (데이터 개수 무시)
df_time.rolling(2).sum()

위의 두 예시 코드가 비슷해보이나 rolling의 인자로 int 2와 str '2s'의 차이점을 확인할 수 있습니다.
시간 간격(Offset) 문자열로 지정했을 때 min_periods는 1로 설정됩니다. 지정된 시간 간격 내에 유효한 데이터 포인트가 최소 1개만 있으면 롤링 계산(평균, 합계 등)을 수행합니다. 그러나 정수(Integer)로 윈도우를 지정할 경우 min_periods는 윈도우의 크기(정수 값)와 동일하게 설정됩니다. 롤링 계산을 수행하려면 지정한 윈도우 크기 2를 채우는 충분한 데이터 개수가 필요하다는 의미입니다. 그렇지않으면 NaN을 반환합니다.
Expanding
DataFrame.expanding(min_periods=1,
axis=<no_default>,
method='single'
)
- minperiods: int
- axis: 0 or index, 1 or columns
- method: single, table
expanding()은 데이터 세트의 시작점부터 현재 시점까지의 모든 관측치를 포함하는 윈도우를 사용합니다. 데이터가 추가될 때마다 윈도우 크기가 1씩 커지며, 윈도우 내의 모든 값에 대해 지정된 통계량을 계산합니다.
아래의 코드는 누적 평균(Cumulative Mean)을 계산하며, min_periods=2 조건이 적용됩니다.
df = pd.DataFrame({"B" : [0, 1, 2, np.nan, 4]})
df.expanding(2).mean()

누적 합계(Cumulative Sum)를 계산하지만, min_periods=3 조건이 적용됩니다.
df.expanding(3).sum()

'Basic Data Analysis' 카테고리의 다른 글
| 데이터 문해력 (1) | 2026.02.11 |
|---|---|
| Python 따릉이 데이터 분석 (0) | 2025.09.30 |
| Python Pandas를 활용한 데이터 분석 실습 (0) | 2025.09.26 |
| Python Pandas (0) | 2025.09.25 |