目录
基础篇13:时间序列数据处理 (datetime)
时间序列数据在金融、经济、气象、传感器监控等各个领域都有着广泛应用。Pandas 作为 Python 中最重要的数据分析库之一,其强大的时间序列处理功能使得数据预处理、分析和预测变得更加高效和便捷。本文将从理论和实践两个层面详细讲解如何利用 Pandas 对时间序列数据进行处理,包括数据类型转换、索引设置、时间切片、重采样、滚动窗口计算、时区处理、偏移以及 Period 类型等方面的知识。
1. 时间序列数据简介
1.1 什么是时间序列数据?
时间序列数据是指按照时间顺序排列的一系列数据点。常见的时间序列数据包括每日股票价格、每月销售额、气温记录等。数学上可以将时间序列数据看作一个函数:
x
(
t
)
,
t
∈
T
x(t), \quad t \in T
x(t),t∈T
其中
t
t
t 表示时间点,
T
T
T 是时间点的集合,而
x
(
t
)
x(t)
x(t) 则是对应时间点的观测值。
1.2 时间序列数据的应用场景
- 金融分析:股票、基金、汇率等价格随时间变化的数据
- 经济指标:GDP、CPI、就业率等定期公布的经济数据
- 气象监测:温度、降水量、风速等数据
- 传感器数据:机器设备运行时的温度、压力、震动等监测数据
通过对时间序列数据的分析,我们可以捕捉数据中的趋势、季节性和周期性变化,甚至利用模型进行预测。
2. Pandas 中时间序列数据的表示
Pandas 提供了多种内置的数据类型来表示与时间相关的数据,主要包括以下几种:
2.1 Timestamp 与 DatetimeIndex
-
Timestamp
Pandas 中的 Timestamp 类似于 Python 标准库中的 datetime 对象,用于表示单个时刻。例如:import pandas as pd ts = pd.Timestamp("2023-11-11 13:30:00") print(ts) # 输出:2023-11-11 13:30:00
-
DatetimeIndex
当一组时间戳用作 Series 或 DataFrame 的索引时,就构成了 DatetimeIndex。DatetimeIndex 支持各种基于时间的操作,如切片、重采样、时间属性提取等。dates = pd.date_range("2023-01-01", periods=5, freq="D") s = pd.Series(range(5), index=dates) print(s) # 输出: # 2023-01-01 0 # 2023-01-02 1 # 2023-01-03 2 # 2023-01-04 3 # 2023-01-05 4 # Freq: D, dtype: int64
2.2 Period 与 PeriodIndex
-
Period
Period 用于表示一个时间区间,例如“2018年”或“2018-10月”。与 Timestamp 不同,Period 表示的是一段时间,而 Timestamp 表示某个具体时刻:p = pd.Period("2018-10", freq="M") print(p) # 输出:2018-10
-
PeriodIndex
PeriodIndex 是由多个 Period 组成的索引,可用于构造时间区间序列。pi = pd.period_range("2018-01", periods=12, freq="M") print(pi) # 输出:PeriodIndex(['2018-01', '2018-02', ..., '2018-12'], dtype='period[M]')
2.3 Timedelta 与 TimedeltaIndex
-
Timedelta
表示两个时间戳之间的差值,即持续时间:delta = pd.Timedelta(days=5, hours=3) print(delta) # 输出:5 days 03:00:00
-
TimedeltaIndex
当多个 Timedelta 用作 Series 或 DataFrame 的索引时,构成了 TimedeltaIndex。
以上这些数据类型构成了 Pandas 处理时间序列数据的基础,能够满足绝大多数场景的需求。
3. 将数据转换为时间类型
在实际应用中,数据源中的时间通常以字符串形式存在,为了充分利用 Pandas 的时间序列功能,必须将字符串转换为 datetime 类型。主要使用 pd.to_datetime()
函数。
3.1 pd.to_datetime() 的基本用法
import pandas as pd
# 将字符串转换为时间戳
date_str = "2023-11-11 13:30:00"
timestamp = pd.to_datetime(date_str)
print(timestamp)
# 输出:2023-11-11 13:30:00
3.2 处理日期格式
当日期字符串格式不统一或采用欧洲风格(日期在前)时,可以使用参数进行调整:
# 欧洲风格日期,dayfirst=True
date_str = "11-10-2023"
timestamp = pd.to_datetime(date_str, dayfirst=True)
print(timestamp)
# 输出:2023-10-11 00:00:00
3.3 在数据读取时解析日期
在使用 pd.read_csv()
读取 CSV 文件时,可以使用 parse_dates
参数自动将某一列或多列转换为 datetime 类型:
df = pd.read_csv("data/sample.csv", parse_dates=["date_column"])
这样读取后,date_column
列即为 datetime 类型,方便后续操作。[citeturn1search0]
4. 设置时间序列索引
时间序列分析最常用的操作之一是将 DataFrame 的某一列(通常为日期列)设置为索引,从而构建一个 DatetimeIndex,这样可以方便地进行时间切片、重采样等操作。
4.1 使用 set_index() 设置索引
# 假设 DataFrame df 中有一列 'date'
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
这样设置索引后,可以直接利用索引进行时间范围切片:
# 获取 2023 年 11 月的数据
nov_data = df["2023-11"]
4.2 DateTimeIndex 的优势
当日期作为索引时,Pandas 提供了一系列便捷的属性与方法。例如:
- 直接访问年份:
df.index.year
- 访问月份:
df.index.month
- 获取星期几:
df.index.weekday
- 切片操作:
df["2023-11-01":"2023-11-15"]
这些功能使得时间序列数据处理更加直观和高效。
5. 提取时间序列的组件
一旦数据的索引为 DatetimeIndex,就可以通过 .dt
访问器来提取时间的各个组成部分。
5.1 提取年份、月份、日、小时等
# 假设 df 的索引为 DatetimeIndex
df["year"] = df.index.year
df["month"] = df.index.month
df["day"] = df.index.day
df["hour"] = df.index.hour
# 例如,提取星期几(0 表示星期一,6 表示星期日)
df["weekday"] = df.index.weekday
这样可以为数据添加新的特征,便于后续分析。例如,可以计算每个月的平均值、每周的最大值等。
5.2 数学公式示例
假设我们有一组时间序列数据
x
(
t
)
x(t)
x(t),我们可以通过下面的方式提取其年、月、日:
year
i
=
index
[
i
]
.
year
month
i
=
index
[
i
]
.
month
day
i
=
index
[
i
]
.
day
\text{year}_i = \text{index}[i].\text{year} \quad \text{month}_i = \text{index}[i].\text{month} \quad \text{day}_i = \text{index}[i].\text{day}
yeari=index[i].yearmonthi=index[i].monthdayi=index[i].day
6. 时间序列的切片与过滤
由于 DatetimeIndex 内部保存了时间信息,可以像普通索引一样进行切片、过滤和查询。
6.1 使用字符串进行切片
# 选取 2023 年 11 月的数据
data_nov = df["2023-11"]
字符串切片具有灵活性,可以仅指定年份、月份或日期范围:
# 选取 2023-11-01 到 2023-11-15 的数据
data_partial = df["2023-11-01":"2023-11-15"]
6.2 使用 truncate 方法
truncate 方法可以根据指定的开始或结束日期截取时间序列:
# 截取截止到 2023-11-10 的数据
truncated = df.truncate(after="2023-11-10")
这种方式适用于对时间序列进行预处理和数据清洗时的日期范围控制。
7. 时间序列数据的算术运算
利用时间戳和 Timedelta,可以直接对时间序列数据进行算术运算,这为时间差计算和周期性分析提供了便利。
7.1 计算时间差
start_time = pd.Timestamp("2023-01-01")
end_time = pd.Timestamp("2023-11-11")
time_diff = end_time - start_time
print(time_diff)
# 输出类似于:304 days 00:00:00
数学上,可以表示为:
Δ
t
=
t
e
n
d
−
t
s
t
a
r
t
\Delta t = t_{end} - t_{start}
Δt=tend−tstart
7.2 加减时间
利用 Timedelta 对象,可以对时间进行加减操作:
# 增加 5 天
new_time = start_time + pd.Timedelta(days=5)
print(new_time)
例如,我们可以计算:
t
′
=
t
+
Δ
t
t' = t + \Delta t
t′=t+Δt
其中
Δ
t
\Delta t
Δt 由 pd.Timedelta 创建。
8. 时间范围生成
Pandas 提供了生成时间序列的多种方法,其中最常用的是 pd.date_range()
。
8.1 pd.date_range() 的基本用法
# 生成从 2023-01-01 开始,连续 10 天的日期序列
date_index = pd.date_range(start="2023-01-01", periods=10, freq="D")
print(date_index)
输出为:
DatetimeIndex(['2023-01-01', '2023-01-02', ..., '2023-01-10'], dtype='datetime64[ns]', freq='D')
8.2 其他时间序列生成函数
-
pd.bdate_range()
生成工作日日期序列:business_days = pd.bdate_range(start="2023-01-01", periods=10)
-
pd.period_range()
生成 PeriodIndex 序列:periods = pd.period_range(start="2023-01", periods=6, freq="M")
-
pd.timedelta_range()
生成 TimedeltaIndex 序列:time_deltas = pd.timedelta_range(start="0 days", periods=5, freq="D")
这些方法能够满足不同场景下对时间序列数据生成的需求。[citeturn1search7]
9. 重采样操作(resample)
在实际应用中,原始时间序列数据的频率可能过高或过低,需要转换到其他频率进行分析。Pandas 中的 resample() 方法提供了这种功能,与 groupby 类似,但专门用于时间序列数据。
9.1 降采样(Downsampling)
降采样是将高频数据转换为低频数据,例如,将每日数据转换为每月数据,并计算每月的均值或总和。
# 假设 df 已将日期设置为索引,且包含数值列 'value'
monthly_mean = df["value"].resample("M").mean()
print(monthly_mean)
数学上,每个月的均值计算公式为:
x
ˉ
m
o
n
t
h
=
1
n
∑
i
=
1
n
x
i
\bar{x}_{month} = \frac{1}{n} \sum_{i=1}^{n} x_i
xˉmonth=n1i=1∑nxi
9.2 升采样(Upsampling)
升采样是将低频数据转换为高频数据。通常需要对新增的时间点进行填充,如使用前向填充(ffill)。
# 将每日数据升采样为每小时数据,并使用前向填充
hourly_data = df["value"].resample("H").ffill()
9.3 常用聚合函数
在重采样过程中,可以指定聚合函数,例如:
- sum:求和
- mean:均值
- max/min:最大最小值
- ohlc:用于股票数据的开盘、最高、最低、收盘价
例如:
# 以2天为一个频率,计算各周期内的统计数据
resampled = df["value"].resample("2D").agg(["mean", "sum", "max", "min"])
print(resampled)
10. 滚动窗口计算(rolling)
滚动窗口计算常用于平滑时间序列数据、计算移动平均、移动标准差等统计量。
使用 rolling() 方法可以在指定窗口内计算聚合指标。
10.1 计算移动平均值
# 计算7日移动平均
rolling_mean = df["value"].rolling(window=7).mean()
print(rolling_mean)
数学上,移动平均值计算公式为:
M
A
t
=
1
n
∑
i
=
t
−
n
+
1
t
x
i
MA_t = \frac{1}{n} \sum_{i=t-n+1}^{t} x_i
MAt=n1i=t−n+1∑txi
其中
n
n
n 为窗口大小。
10.2 其他滚动计算
除了均值,还可以计算滚动标准差、滚动最大值、滚动最小值等:
rolling_std = df["value"].rolling(window=7).std()
rolling_max = df["value"].rolling(window=7).max()
rolling_min = df["value"].rolling(window=7).min()
11. 时间戳的偏移和移动
有时需要将整个时间序列向前或向后移动,以便进行对比分析。Pandas 提供了 shift() 方法和 DateOffset 对象来实现这一功能。
11.1 使用 shift() 方法
# 将时间序列向后移动 3 个周期(周期由索引的频率决定)
shifted = df["value"].shift(3)
若索引频率为“D”,则表示向后移动 3 天;如果需要指定不同的频率,可以传入 freq 参数:
# 将时间序列向前移动 3 天
shifted_freq = df["value"].shift(-3, freq="D")
11.2 使用 DateOffset 对象
DateOffset 对象允许更加灵活地对时间戳进行偏移:
from pandas import DateOffset
# 将时间戳增加 1 个月
df.index = df.index + DateOffset(months=1)
12. 时区处理
在全球化应用中,时间数据往往需要处理时区问题。Pandas 中可以通过 tz_localize 和 tz_convert 方法为时间数据添加或转换时区。
12.1 为 Naive 时间数据添加时区
假设时间数据没有时区信息:
naive_dates = pd.date_range("2023-11-01", periods=5, freq="D")
print(naive_dates)
# 输出:DatetimeIndex(['2023-11-01', '2023-11-02', ...], dtype='datetime64[ns]', freq='D')
# 添加 UTC 时区
utc_dates = naive_dates.tz_localize("UTC")
print(utc_dates)
12.2 时区转换
将具有时区信息的时间数据转换为其他时区:
# 将 UTC 时间转换为美国东部时间
eastern_dates = utc_dates.tz_convert("US/Eastern")
print(eastern_dates)
这样可以保证不同来源数据的时区一致性,方便比较与计算。[citeturn1search4]
13. Periods 与 PeriodIndex 的使用
Period 表示一段连续的时间区间,例如一月、一个季度、一年。与 Timestamp 相比,Period 更关注“期间”而非“时刻”。
13.1 创建 Period 对象
# 创建一个月的 Period 对象
p = pd.Period("2023-11", freq="M")
print(p)
# 输出:2023-11
13.2 生成 PeriodIndex
利用 period_range() 生成一个 PeriodIndex:
period_idx = pd.period_range(start="2023-01", periods=12, freq="M")
print(period_idx)
PeriodIndex 可用于构造基于周期的数据表,与 Timestamp 的 DatetimeIndex 类似。
14. 综合实战案例:气象数据的时间序列处理
下面通过一个实际案例演示如何利用 Pandas 进行时间序列数据的全流程处理。假设我们有一份空气质量数据,记录了不同测量站点在不同时间的 N O 2 NO_2 NO2 浓度。数据存储在 CSV 文件中,其中日期时间列为字符串格式。
14.1 数据读取与时间转换
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 读取空气质量数据,同时解析 datetime 列
df_air = pd.read_csv("data/air_quality.csv", parse_dates=["datetime"])
# 查看前 5 行数据
print(df_air.head())
假设数据格式如下:
city country datetime location parameter value unit
0 Paris FR 2019-06-21 00:00:00+00:00 FR04014 no2 20.0 µg/m³
1 Paris FR 2019-06-20 23:00:00+00:00 FR04014 no2 21.8 µg/m³
...
14.2 设置日期时间索引
# 将 datetime 列设置为索引
df_air.set_index("datetime", inplace=True)
print(df_air.index)
此时,df_air.index 即为 DatetimeIndex,可用于后续时间序列操作。
14.3 提取时间组件
# 添加月份和星期几等信息
df_air["month"] = df_air.index.month
df_air["weekday"] = df_air.index.weekday
print(df_air.head())
14.4 重采样与统计计算
例如,计算每个月的 N O 2 NO_2 NO2 最大浓度:
monthly_max = df_air["value"].resample("M").max()
print("每月 $NO_2$ 最大浓度:")
print(monthly_max)
或计算每天的平均浓度:
daily_mean = df_air["value"].resample("D").mean()
plt.figure(figsize=(10, 5))
daily_mean.plot(marker='o')
plt.title("每日平均 $NO_2$ 浓度")
plt.xlabel("日期")
plt.ylabel("$NO_2$ (µg/m³)")
plt.show()
14.5 滚动窗口计算
计算7天移动平均值,平滑数据波动:
rolling_mean = df_air["value"].rolling(window=7).mean()
plt.figure(figsize=(10, 5))
rolling_mean.plot(marker='o', color='red')
plt.title("7天移动平均 $NO_2$ 浓度")
plt.xlabel("日期")
plt.ylabel("$NO_2$ (µg/m³)")
plt.show()
14.6 时区转换
假设原始数据为 UTC 时间,现在需要转换为北京时间(UTC+8):
# 首先确保索引有时区信息
df_air.index = df_air.index.tz_localize("UTC")
# 转换为北京时间
df_air.index = df_air.index.tz_convert("Asia/Shanghai")
print(df_air.index[:5])
14.7 时间偏移操作
例如,将时间索引向后偏移 1 天:
df_air_shifted = df_air.shift(1, freq="D")
print(df_air_shifted.head())
通过偏移,可以对比不同时间段的数据变化。
14.8 使用 Period 处理周期数据
如果需要对数据进行周期性统计,例如统计每个季度的平均浓度,可以先将时间转换为 Period,然后再聚合:
# 将索引转换为季度 PeriodIndex
df_air["quarter"] = df_air.index.to_period("Q")
quarterly_mean = df_air.groupby("quarter")["value"].mean()
print("每个季度的平均 $NO_2$ 浓度:")
print(quarterly_mean)
15. 时间序列数据处理的最佳实践
在实际应用中,为了保证时间序列数据处理的准确性和高效性,我们总结了以下几点最佳实践:
-
数据转换与索引设置
在数据导入阶段,务必使用parse_dates
参数将日期字符串转换为 datetime 对象,并将日期列设置为索引。这样可以充分利用 Pandas 内置的时间序列功能。[citeturn1search2] -
注意数据缺失与异常
在重采样或滚动窗口计算前,检查数据是否存在缺失值,并采用适当的方法填充(如 ffill、bfill 或插值)。 -
选择合适的重采样频率
根据数据的特性和分析目标选择合适的频率。例如,金融数据通常以分钟、小时或日为单位,而经济数据则以季度或年为单位。 -
利用时间戳的属性
使用 DatetimeIndex 或 PeriodIndex 时,可直接访问年份、月份、星期等属性,便于分组和聚合统计。 -
时区处理
在涉及全球数据时,确保所有数据采用统一时区。利用 tz_localize 和 tz_convert 进行时区添加和转换。 -
结合滚动窗口与重采样
滚动窗口计算适用于平滑数据和捕捉局部趋势,而重采样适用于将数据聚合到更低或更高频率。两者结合可更全面地分析时间序列特性。 -
利用 Period 对周期性数据进行处理
Period 和 PeriodIndex 在处理具有明显周期性的数据(如月、季、年)时非常有用,能够提供更直观的聚合统计。 -
代码可读性与链式调用
利用 pipe() 等方法将多个操作链式调用,保持代码简洁易读,并便于调试和维护。
16. 总结
本文全面介绍了 Pandas 中时间序列数据的处理方法,主要内容包括:
- 时间序列数据的基本概念与应用场景:了解何为时间序列数据以及它在各领域中的重要性。
- 时间数据的多种数据类型:包括 Timestamp、DatetimeIndex、Period、Timedelta 等,这些类型为我们提供了灵活的时间数据表示方式。
- 数据转换与索引设置:使用
pd.to_datetime()
将字符串转换为 datetime,对 DataFrame 进行 set_index 操作构造 DatetimeIndex。 - 时间组件的提取:利用
.dt
访问器提取年份、月份、日、时、分、秒等,为后续分组聚合提供依据。 - 切片、过滤与算术运算:通过字符串切片、truncate 和 shift 操作实现对时间序列数据的灵活筛选和偏移计算。
- 时间范围生成与重采样:利用
pd.date_range()
、pd.bdate_range()
、pd.period_range()
等函数生成时间索引,并使用 resample() 对数据进行降采样或升采样,结合聚合函数计算统计量。 - 滚动窗口计算:使用 rolling() 方法实现移动平均、移动标准差等局部统计量的计算。
- 时区处理:通过 tz_localize() 与 tz_convert() 方法,为时间数据添加或转换时区信息,保证数据在全球范围内的一致性。
- Period 类型的应用:利用 Period 和 PeriodIndex 对周期性数据进行更直观的统计和转换。
- 综合实战案例:以空气质量数据为例,展示了从数据读取、转换、索引设置、时间组件提取、重采样、滚动窗口、时区转换到数据可视化的完整流程,并通过代码示例演示各步骤操作。
时间序列数据处理是数据预处理和分析中的重要环节,掌握其核心操作方法不仅能帮助我们更高效地处理数据,还能为后续的预测、建模和决策提供有力支撑。希望本文详细的讲解和丰富的代码示例能够帮助你深入理解 Pandas 的时间序列处理技术,并在实际项目中灵活应用这些知识。