Pandas 日期時間處理

Pandas 提供了強大的日期時間處理功能,可以輕鬆解析、操作和分析時間序列資料。

建立日期時間

從字串轉換

import pandas as pd

# 單一日期
date = pd.to_datetime('2024-01-15')
print(date)  # 2024-01-15 00:00:00

# Series 轉換
s = pd.Series(['2024-01-01', '2024-01-15', '2024-02-01'])
dates = pd.to_datetime(s)
print(dates)

指定格式

# 自訂格式
s = pd.Series(['01/15/2024', '02/20/2024'])
dates = pd.to_datetime(s, format='%m/%d/%Y')

# 常見格式碼
# %Y - 四位年份 (2024)
# %m - 月份 (01-12)
# %d - 日期 (01-31)
# %H - 小時 (00-23)
# %M - 分鐘 (00-59)
# %S - 秒 (00-59)

處理錯誤

s = pd.Series(['2024-01-01', 'invalid', '2024-02-01'])

# 錯誤時回傳 NaT(Not a Time)
dates = pd.to_datetime(s, errors='coerce')

# 錯誤時保持原值
dates = pd.to_datetime(s, errors='ignore')

建立日期範圍

# 連續日期
dates = pd.date_range('2024-01-01', periods=5)
# DatetimeIndex(['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'])

# 指定結束日期
dates = pd.date_range('2024-01-01', '2024-01-10')

# 指定頻率
dates = pd.date_range('2024-01-01', periods=5, freq='W')   # 每週
dates = pd.date_range('2024-01-01', periods=5, freq='M')   # 每月
dates = pd.date_range('2024-01-01', periods=5, freq='H')   # 每小時

日期時間屬性

透過 .dt accessor 存取日期時間的各種屬性:

df = pd.DataFrame({
    'date': pd.to_datetime(['2024-01-15 10:30:45', '2024-06-20 14:15:30'])
})

df['year'] = df['date'].dt.year           # 年份
df['month'] = df['date'].dt.month         # 月份
df['day'] = df['date'].dt.day             # 日期
df['hour'] = df['date'].dt.hour           # 小時
df['minute'] = df['date'].dt.minute       # 分鐘
df['second'] = df['date'].dt.second       # 秒
df['weekday'] = df['date'].dt.weekday     # 星期幾(0=週一)
df['day_name'] = df['date'].dt.day_name() # 星期名稱
df['quarter'] = df['date'].dt.quarter     # 季度
df['dayofyear'] = df['date'].dt.dayofyear # 一年中的第幾天

判斷屬性

df['date'].dt.is_month_start   # 是否月初
df['date'].dt.is_month_end     # 是否月底
df['date'].dt.is_quarter_start # 是否季初
df['date'].dt.is_year_start    # 是否年初
df['date'].dt.is_leap_year     # 是否閏年

日期運算

時間差

df = pd.DataFrame({
    'start': pd.to_datetime(['2024-01-01', '2024-02-15']),
    'end': pd.to_datetime(['2024-01-10', '2024-03-01'])
})

# 計算天數差
df['days'] = (df['end'] - df['start']).dt.days

加減時間

from pandas import Timedelta

df = pd.DataFrame({
    'date': pd.to_datetime(['2024-01-15', '2024-06-20'])
})

# 加 7 天
df['plus_7_days'] = df['date'] + Timedelta(days=7)

# 減 1 個月
df['minus_1_month'] = df['date'] - pd.DateOffset(months=1)

# 加 2 小時
df['plus_2_hours'] = df['date'] + Timedelta(hours=2)

常用 DateOffset

pd.DateOffset(days=1)
pd.DateOffset(weeks=1)
pd.DateOffset(months=1)
pd.DateOffset(years=1)

日期格式化

轉換成字串

df = pd.DataFrame({
    'date': pd.to_datetime(['2024-01-15', '2024-06-20'])
})

df['formatted'] = df['date'].dt.strftime('%Y/%m/%d')
# '2024/01/15', '2024/06/20'

df['formatted'] = df['date'].dt.strftime('%Y年%m月%d日')
# '2024年01月15日', '2024年06月20日'

時區處理

df = pd.DataFrame({
    'date': pd.to_datetime(['2024-01-15 10:00:00', '2024-01-15 14:00:00'])
})

# 設定時區
df['date_utc'] = df['date'].dt.tz_localize('UTC')

# 轉換時區
df['date_taipei'] = df['date_utc'].dt.tz_convert('Asia/Taipei')

過濾日期

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=100),
    'value': range(100)
})

# 過濾特定日期範圍
mask = (df['date'] >= '2024-01-15') & (df['date'] <= '2024-02-15')
filtered = df[mask]

# 過濾特定月份
filtered = df[df['date'].dt.month == 1]

# 過濾特定星期
filtered = df[df['date'].dt.weekday == 0]  # 週一

重新取樣(Resample)

對時間序列資料進行重新取樣(類似 groupby):

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'value': range(100)
})
df = df.set_index('date')

# 按月彙總
monthly = df.resample('M').sum()

# 按週平均
weekly = df.resample('W').mean()

# 常用頻率
# 'D' - 每天
# 'W' - 每週
# 'M' - 每月
# 'Q' - 每季
# 'Y' - 每年
# 'H' - 每小時

實際應用

計算年齡

df = pd.DataFrame({
    'birthday': pd.to_datetime(['1990-05-15', '1985-10-20', '2000-01-01'])
})

today = pd.Timestamp.today()
df['age'] = (today - df['birthday']).dt.days // 365

計算工作天數

from numpy import busday_count

df = pd.DataFrame({
    'start': pd.to_datetime(['2024-01-01', '2024-02-01']),
    'end': pd.to_datetime(['2024-01-31', '2024-02-29'])
})

df['business_days'] = df.apply(
    lambda row: busday_count(row['start'].date(), row['end'].date()),
    axis=1
)

時間序列分析

df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=365),
    'sales': [100 + i % 30 for i in range(365)]
})
df = df.set_index('date')

# 計算移動平均
df['ma_7'] = df['sales'].rolling(window=7).mean()

# 計算同比(去年同期)
df['yoy'] = df['sales'].pct_change(periods=365)