时间序列数据在很多领域都是重要的结构化数据形式,例如金融、经济、生态学、神经科学和物理学。在多个时间点观测或测量的数据形成了时间序列。许多时间序列是固定频率的,也就是说数据是根据相同的规则定期出现的,例如每15秒、每5分钟或每月1次。时间序列也可以是不规则的,没有固定的时间单位或单位间的偏移量
本文从零开始掌握时间序列预测的基础技能,涵盖:datetime类型、datetime与字符串互相转换、时间序列基础、时间序列数据获取、含有重复索引的时间序列、生成日期范围、移位日期、重采样与频率转换、移动窗口函数,工欲善其事必先利其器,掌握了本文的基础技能,后续在时间序列预测中将会事半功倍!
1、datetime 类型
在 Python 中表示时间的有 datetime 和 time 模块,time 适用与简单的时间戳操作、程序暂停一段时间执行功能,如下所示,它不具备复杂的日期和时间操作。
import time
# 获取当前时间戳
timestamp = time.time() # 1726974473.8066747
# 暂停2秒后,程序再执行
time.sleep(2)
而 datetime 可以处理复杂的日期和时间操作,比较适用于时间序列数据,所以本文我们主要围绕 datetime 进行深入分享。
datetime 不仅存储日期,并且存储细化到微妙的时间。通过 datetime 中的相应属性,我们可以获取 年/月/日/时/分/秒/微妙
from datetime import datetime
now = datetime.now()
now
# ----输出----
datetime.datetime(2024, 9, 22, 11, 0, 29, 367299)
now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond
# ----输出----
# (2024, 9, 22, 11, 0, 29, 367299)
两个 datetime 对象的时间差可以用 timedelta 表示,其包含相差天数(days)、相差秒数(seconds),可以通过对应的属性分别获取
from datetime import datetime
delta = datetime(2024,9,22, 12) - datetime(2024,8,22, 11)
delta
# ----输出----
datetime.timedelta(days=31, seconds=3600)
delta.days, delta.seconds
# ----输出----
# (31, 3600)
我们可以为一个 datetime 对象加上(或减去)一个 timedelta 或其整数倍来产生一个新的 datetime 对象
from datetime import timedelta
init_time = datetime(2024,9,10)
init_time + timedelta(10)
# ----输出----
# datetime.datetime(2024, 9, 20, 0, 0)
init_time = datetime(2024,9,10)
init_time + timedelta(days=10, seconds=3600)
# ----输出----
# datetime.datetime(2024, 9, 20, 1, 0)
2、datetime 与字符串互相转换
我们可以使用 strftime 方法将日期对象转换为字符串对象,使用 strptime 方法将字符串对象转换为日期对象
date_time = datetime(2024, 9, 10, 0, 0)
date_time
# ----输出----
# datetime.datetime(2024, 9, 10, 0, 0)
str_time = date_time.strftime('%Y-%m-%d')
str_time
# ----输出----
# '2024-09-10'
datetime.strptime(str_time, '%Y-%m-%d')
# ----输出----
# datetime.datetime(2024, 9, 10, 0, 0)
常用的转化格式符号如下所示
格式符号 |
描述 |
示例 |
|
年份(四位数) |
2023 |
|
年份(两位数) |
23 |
|
月份(01-12) |
09 |
|
日(01-31) |
22 |
|
小时(00-23) |
14 |
|
小时(01-12) |
02 |
|
分钟(00-59) |
30 |
|
秒(00-59) |
45 |
|
AM 或 PM |
AM |
|
星期几(完整名称) |
Saturday |
|
星期几(缩写) |
Sat |
|
月份(完整名称) |
September |
|
月份(缩写) |
Sep |
|
年中的天数(001-366) |
265 |
|
年中的周数(00-53,周日为一周的开始) |
38 |
|
年中的周数(00-53,周一为一周的开始) |
38 |
3、时间序列基础
时间序列数据由一组按时间顺序排列的数据点组成。它通常包括以下几个关键部分:时间索引、数值、数据类型、频率、时区
import pandas as pd
# 创建日期范围
date_range = pd.date_range(start='2024-10-01', periods=7, freq='D')
# 创建时间序列数据
time_series = pd.Series(range(1,8),index=date_range)
time_series
# ----输出----
# 2024-10-01 1
# 2024-10-02 2
# 2024-10-03 3
# 2024-10-04 4
# 2024-10-05 5
# 2024-10-06 6
# 2024-10-07 7
# Freq: D, dtype: int64
以上创建了基础的时间序列数据,我们可以进一步获取时间序列数据的信息
# 1、时间索引
time_series.index
# ----输出----
# DatetimeIndex(['2024-10-01', '2024-10-02', '2024-10-03', '2024-10-04',
# '2024-10-05', '2024-10-06', '2024-10-07'],
# dtype='datetime64[ns]', freq='D')
# 2、时间索引频率
time_series.index.freq
# ----输出----
# <Day>
# 3、时间索引值类型
time_series.index[0]
# ----输出----
# Timestamp('2024-10-01 00:00:00')
# 4、数据类型
time_series.dtype
# ----输出----
# dtype('int64')
4、时间序列数据获取
如果要从时间序列中获取对应数据,可以传入字符串类型或datetime类型的日期索引数据,结果是一样的
import pandas as pd
# 创建日期范围
date_range = pd.date_range(start='2024-09-28', periods=7, freq='D')
# 创建时间序列数据
time_series = pd.Series(range(1,8),index=date_range)
# 数据获取
time_series['2024-10-01'] # 输出:4
time_series[datetime(2024, 10, 1)] # 输出:4
当然如果要获取切片数据同样是可以
# 数据切片获取
time_series['2024-10-01': ]
time_series[datetime(2024, 10, 1):]
# ----输出----
# 2024-10-01 4
# 2024-10-02 5
# 2024-10-03 6
# 2024-10-04 7
# Freq: D, dtype: int64
有时候如果需要获取整月或者整年的数据,我们也可以通过传递一个年份或一个年份和月份来轻松地选择数据的切片
time_series['2024-10']
# ----输出----
# 2024-10-01 4
# 2024-10-02 5
# 2024-10-03 6
# 2024-10-04 7
# Freq: D, dtype: int64
5、含有重复索引的时间序列
有时候在时间序列数据中,有多个值落在同一个时间戳索引上
import pandas as pd
# 创建日期范围
date_range = pd.DatetimeIndex(['2024-10-1','2024-10-2','2024-10-3','2024-10-1','2024-10-2',])
# 创建时间序列数据
time_series = pd.Series(range(1,6),index=date_range)
time_series
# ----输出----
# 2024-10-01 1
# 2024-10-02 2
# 2024-10-03 3
# 2024-10-01 4
# 2024-10-02 5
# dtype: int64
我们可以通过索引的 is_unique 属性,我们可以看出索引并不是唯一的
time_series.index.is_unique
# ----输出----
# False
因为索引并不唯一,所以通过索引获取得到的并不是单一的数值,如下所示
time_series['2024-10-3']
# ----输出----
3
time_series['2024-10-1']
# ----输出----
# 2024-10-01 1
# 2024-10-01 4
# dtype: int64
6、生成日期范围
可以使用 pandas 中的 date_range 是用于根据特定频率生成指定长度的 DatetimeIndex,默认情况下,生成的是每日的时间戳
import pandas as pd
time_index = pd.date_range('2024-10-01', '2024-10-07')
time_index
# ----输出----
# DatetimeIndex(['2024-10-01', '2024-10-02', '2024-10-03', '2024-10-04',
# '2024-10-05', '2024-10-06', '2024-10-07'],
# dtype='datetime64[ns]', freq='D')
当然我们也可以传递一个起始日期或者结束日期,再加上对应范围的时间跨度数字
import pandas as pd
time_index = pd.date_range(start='2024-10-01', periods=7)
time_index = pd.date_range(end='2024-10-01', periods=7)
time_index
# ----输出----
# DatetimeIndex(['2024-10-01', '2024-10-02', '2024-10-03', '2024-10-04',
# '2024-10-05', '2024-10-06', '2024-10-07'],
# dtype='datetime64[ns]', freq='D')
正如前面提到的默认的时间频率为“天”,当然我们可以通过传递 freq 不同的值来设置不同的频率,以下是一些常用的频率字符串标识符:
-
'D'
: 每日 -
'B'
: 每个工作日 -
'H'
: 每小时 -
'T'
或'min'
: 每分钟 -
'S'
: 每秒 -
'W'
: 每周 -
'M'
: 每月月末 -
'Q'
: 每季度末 -
'A'
或'Y'
: 每年年末
pd.date_range('2024-01-01', '2024-12-31', freq='M')
# ----输出----
# DatetimeIndex(['2024-01-31', '2024-02-29', '2024-03-31', '2024-04-30',
# '2024-05-31', '2024-06-30', '2024-07-31', '2024-08-31',
# '2024-09-30', '2024-10-31', '2024-11-30', '2024-12-31'],
# dtype='datetime64[ns]', freq='M')
默认情况下,date_range 保留开始或结束时间戳的时间,我们可以通过设置 normalize 选项生成标准化为零点的时间戳
import pandas as pd
time_index = pd.date_range(start='2024-10-01 11:11:11', periods=5)
time_index
# ----输出----
# DatetimeIndex(['2024-10-01 11:11:11', '2024-10-02 11:11:11',
# '2024-10-03 11:11:11', '2024-10-04 11:11:11',
# '2024-10-05 11:11:11'],
# dtype='datetime64[ns]', freq='D')
time_index = pd.date_range(start='2024-10-01 11:11:11',
periods=5, normalize=True)
time_index
# ----输出----
# DatetimeIndex(['2024-10-01', '2024-10-02', '2024-10-03',
# '2024-10-04','2024-10-05'],
# dtype='datetime64[ns]', freq='D')
7、移位日期
pandas 中提供 shift 方法用于时间序列数据的前后移位,其不改变索引,会在时间序列的起始位置或结束位置引入缺失值
import pandas as pd
date_range = pd.date_range(start='2024-10-1', periods=3, freq='D')
time_series = pd.Series(range(1,4),index=date_range)
time_series
# ----输出----
# 2024-10-01 1
# 2024-10-02 2
# 2024-10-03 3
# Freq: D, dtype: int64
time_series.shift(2)
# ----输出----
# 2024-10-01 NaN
# 2024-10-02 NaN
# 2024-10-03 1.0
# Freq: D, dtype: float64
time_series.shift(-2)
# ----输出----
# 2024-10-01 3.0
# 2024-10-02 NaN
# 2024-10-03 NaN
# Freq: D, dtype: float64
很明显以上的移位效果并不是我们期望的,由于简单移位并不改变索引,一些数据会被丢弃。因此,如果频率是已知的,则可以将频率传递给 shift 来推移时间戳而不是简单的数据
time_series.shift(2, freq='D')
# ----输出----
# 2024-10-03 1
# 2024-10-04 2
# 2024-10-05 3
# Freq: D, dtype: int64
time_series.shift(-2, freq='D')
# ----输出----
# 2024-09-29 1
# 2024-09-30 2
# 2024-10-01 3
# Freq: D, dtype: int64
当然我们还可以使用偏置进行日期移动,以下举一些常见的例子
from pandas.tseries.offsets import Day, MonthEnd
day_time = datetime(2024,10,1)
day_time + 3 * Day()
# ----输出----
# Timestamp('2024-10-04 00:00:00')
day_time + MonthEnd()
# ----输出----
# Timestamp('2024-10-31 00:00:00')
8、重新采样与频率转换
在时间序列中,将更高频率的数据聚合到低频率被称为向下采样,而从低频率转换到高频率称为向上采样
例如,将分钟级数据转换为小时级数据。下面是一个简单的例子,演示如何使用 Pandas 的 resample 方法进行向下采样。
import pandas as pd
import numpy as np
# 创建一个示例时间序列数据集
rng = pd.date_range('2024-01-01', periods=120, freq='T') # 每分钟一个数据点,共120分钟
data = np.arange(len(rng)) # 随机生成一些数据
ts = pd.Series(data, index=rng)
# 向下采样到每小时
ts_hourly = ts.resample('H').count()
print(ts_hourly)
# ----输出----
# 2024-01-01 00:00:00 60
# 2024-01-01 01:00:00 60
# Freq: H, dtype: int64
在 Pandas 中,向上采样用于将时间序列数据从较低频率转换为较高频率。例如,将每日数据转换为每小时数据。向上采样通常需要填充缺失值。下面是一个简单的例子,演示如何使用 Pandas 的 resample 方法进行向上采样。
import pandas as pd
import numpy as np
# 创建一个示例时间序列数据集
rng = pd.date_range('2024-01-01', periods=5, freq='D') # 每天一个数据点,共5天
data = np.random.randn(len(rng)) # 随机生成一些数据
ts = pd.Series(data, index=rng)
# 向上采样到每小时,并填充缺失值
ts_hourly = ts.resample('H').interpolate() # 使用前向填充
print("\n向上采样后的数据(每小时):")
print(ts_hourly.head(5))
# ----输出----
# 2024-01-01 00:00:00 0.526870
# 2024-01-01 01:00:00 0.534922
# 2024-01-01 02:00:00 0.542973
# 2024-01-01 03:00:00 0.551025
# 2024-01-01 04:00:00 0.559076
# Freq: H, dtype: float64
其中填充有以下方法:
-
ffill():前向填充,用前一个有效值填充缺失值。
-
bfill():后向填充,用下一个有效值填充缺失值。
-
interpolate():插值填充,根据数据趋势填充缺失值。
9、移动窗口函数
移动窗口函数是一种在数据序列上应用的操作,它通过在数据的一个子集(窗口)上执行计算,来生成一个新的序列。窗口在数据上滑动,每次计算一个新的结果。这种方法常用于时间序列分析和信号处理。移动窗口函数可以用于计算平均值、和、最大值、最小值、标准差等。
在 Pandas 中,rolling 方法用于在时间序列或数据框上进行滚动计算。它提供了一种在移动窗口上执行聚合操作的方式。下面是一些使用 rolling 方法的例子,其中参数说明
-
window:窗口大小,表示滚动计算的范围。
-
min_periods:窗口中最少需要的非缺失观测值数量,默认为窗口大小。
-
center:布尔值,若为 True,则窗口居中对齐。
import pandas as pd
import numpy as np
# 创建示例数据
np.random.seed(0)
dates = pd.date_range('2024-01-01', periods=10)
data = np.arange(10)
ts = pd.Series(data, index=dates)
print("原始数据:")
print(ts)
# 计算3天滚动平均值
rolling_mean = ts.rolling(window=3, center=True, min_periods=1).mean()
print("\n3天滚动平均值:")
print(rolling_mean)
# ----输出----
# 原始数据:
# 2024-01-01 0
# 2024-01-02 1
# 2024-01-03 2
# 2024-01-04 3
# 2024-01-05 4
# 2024-01-06 5
# 2024-01-07 6
# 2024-01-08 7
# 2024-01-09 8
# 2024-01-10 9
# Freq: D, dtype: int32
# 3天滚动平均值:
# 2024-01-01 0.5
# 2024-01-02 1.0
# 2024-01-03 2.0
# 2024-01-04 3.0
# 2024-01-05 4.0
# 2024-01-06 5.0
# 2024-01-07 6.0
# 2024-01-08 7.0
# 2024-01-09 8.0
# 2024-01-10 8.5
# Freq: D, dtype: float64
如果你喜欢本文,欢迎点赞,并且关注我们的微信公众号:Python技术极客,我们会持续更新分享 Python 开发编程、数据分析、数据挖掘、AI 人工智能、网络爬虫等技术文章!让大家在Python 技术领域持续精进提升,成为更好的自己!
添加作者微信(coder_0101),拉你进入行业技术交流群,进行技术交流~