时间序列数据是许多不同领域的结构化数据的重要形式,例如金融、经济、生态学、神经科学和物理学。在许多时间点重复记录的任何内容都会形成一个时间序列。许多时间序列是固定频率的,也就是说,数据点根据某些规则定期出现,例如每 15 秒、每 5 分钟或每月一次。时间序列也可以是不规则的,没有固定的时间单位或单位之间的偏移量。如何标记和引用时间序列数据取决于应用程序,常用的有以下几种标记时间序列的方式:
时间戳(Timestamps):特定的时刻。
固定期间(Fixed periods):例如 2020 年 1 月的整个月,或 2023 年全年。
时间间隔(Intervals of time):由开始和结束时间戳指示。周期可以被认为是间隔的特殊情况。
实验或已用时间(Experiment or elapsed time):每个时间戳都是相对于特定开始时间的时间度量,从 0 开始(例如,饼干放入烤箱后每秒烘烤的直径)。
最简单的时间序列是按 timestamp 索引。pandas 支持基于 timedeltas 的索引,这是表示 experiment 或 elapsed time 的有用方法,可以在 pandas 官方文档中了解更多信息。
pandas 提供了许多内置的时序工具和算法。可以有效地处理大型时间序列,并对不规则和固定频率的时间序列进行切片和切块、聚合和重新采样。其中一些工具对于金融和经济应用程序很有用,也可以使用它们来分析服务器日志数据等。
Python 标准库包括日期和时间数据的数据类型,以及与日历相关的功能。datetime、time 和 calendar 模块实现了主要的基础功能。datetime.datetime 类型(或简称 datetime)使用的非常频繁:
from datetime import datetimenow = datetime.now()
print(now)
print(now.year, now.month, now.day)
now输出当前年月日时分秒毫秒:2024-11-17 11:58:02.773699
now.year, now.month, now.day 输出:2024 11 17
datetime 将日期和时间存储到微秒。datetime.timedelta,或简称 timedelta,表示两个 datetime 对象之间的时间差异:
from datetime import datetimedelta = datetime(2024, 11, 17) - datetime(2023, 9, 1, 8, 15)
print(delta)
print(delta.days, delta.seconds)
delta输出:442 days, 15:45:00
delta.days, delta.seconds 输出:442 56700
上面输出的delta是datetime.timedelta对象,我们可以将 timedelta 或其倍数加(或减)到 datetime 对象中,以产生新的移位对象:
from datetime import datetime, timedeltastart = datetime(2024, 1, 6)
res1 = start + timedelta(12)
res2 = start - 2 * timedelta(12)
print(res1)
print(res2)
res1输出的结果是start日期+12天的日期:2024-01-18 00:00:00
res2输出的结果是start日期-24天的日期:2023-12-13 00:00:00
下图列表是datatime模块中包含的数据类型:
一、String 和 Datetime 类型转换
可以使用 str 或 strftime 方法将 datetime 对象和 pandas Timestamp 对象(稍后将学习)格式化为字符串,并传递字符串格式规范:
from datetime import datetime, timedeltastamp = datetime(2024, 1, 6)
print(str(stamp)) #输出 2024-01-06 00:00:00print(stamp.strftime("%Y-%m-%d")) # 输出 2024-01-06
以下是日期格式规范的列表截图(自己翻译学习使用):
可以使用许多相同的格式代码通过 datetime.strptime 将字符串转换为日期(但某些代码,如 %F,不能使用):
from datetime import datetime, timedeltavalue = "2024-01-06"
print(datetime.strptime(value, "%Y-%m-%d")) # 输出:2024-01-06 00:00:00datestrs = ["7/6/2024", "8/6/2024"]
res = [datetime.strptime(x, "%m/%d/%Y") for x in datestrs]
print(res) # 输出:[datetime.datetime(2024, 7, 6, 0, 0), datetime.datetime(2024, 8, 6, 0, 0)]
pandas 通常面向日期数组使用,无论是用作轴索引还是 DataFrame 中的列。pandas.to_datetime 方法解析许多不同类型的日期表示形式。可以快速解析 ISO 8601 等标准日期格式:
import pandas as pddatestrs = ["2024-07-06 12:00:00", "2024-08-06 00:00:00"]
res = pd.to_datetime(datestrs)
print(res) # 输出:DatetimeIndex(['2024-07-06 12:00:00', '2024-08-06 00:00:00'], dtype='datetime64[ns]', freq=None)# 处理应被视为缺失的值(None、空字符串等):
idx = pd.to_datetime(datestrs + [None])
print(idx) # 输出:DatetimeIndex(['2024-07-06 12:00:00', '2024-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)print(idx[2]) # 输出:NaTprint(pd.isna(idx)) # 输出:[False False True]
以上代码输出请看相关注释。
NaT (Not a Time) 是 pandas 的时间戳数据的 null 值。下面列表图片是特定于区域设置的日期格式:
二、Time Series Basics
pandas 中的一种基本时间序列对象是由时间戳索引的 Series,它通常在 pandas 之外表示为 Python 字符串或日期时间对象。与其他 Series 一样,具有时间序列索引的Series对象之间的算术运算会自动在日期上对齐。pandas 使用 NumPy 的 datetime64 数据类型以纳秒分辨率存储时间戳。DatetimeIndex 中的标量值是 pandas Timestamp 对象。看如下示例:
import numpy as np
import pandas as pd
from datetime import datetime, timedeltanp.random.seed(12345)dates = [datetime(2024, 1, 2), datetime(2024, 1, 5),datetime(2024, 1, 7), datetime(2024, 1, 8),datetime(2024, 1, 10), datetime(2024, 1, 12)]
ts = pd.Series(np.random.standard_normal(6), index=dates)
print(ts)# 在后台,这些 datetime 对象已被放入 DatetimeIndex 中:
print(ts.index)# 具有时间序列索引的Series对象之间的算术运算会自动在日期上对齐
# ts[::2] 在 ts 中步长为2选择元素。
res = ts + ts[::2]
print(res)# pandas 使用 NumPy 的 datetime64 数据类型以纳秒分辨率存储时间戳
print(ts.index.dtype)# DatetimeIndex 中的标量值是 pandas Timestamp 对象
stamp = ts.index[0]
print(stamp)
Series对象ts输出:
ts.index输出:
DatetimeIndex(['2024-01-02', '2024-01-05', '2024-01-07', '2024-01-08',
'2024-01-10', '2024-01-12'],
dtype='datetime64[ns]', freq=None)
ts + ts[::2] 运算输出:
ts.index.dtype 输出ts的索引数据类型:datetime64[ns]
ts.index[0] 输出第0个索引的值(标量值):2024-01-02 00:00:00
在大多情况下,pandas.Timestamp能够替换datetime对象使用,反之则不然,因为 pandas.Timestamp 可以存储纳秒精度数据,而 datetime 最多只能存储微秒。另外, pandas.Timestamp 可以存储频率信息(如果有)并能进行时区转换和其他类型的操作。
索引、选择、子集(Indexing, Selection, Subsetting)
根据标签为数据编制索引和选择数据时,时间序列的行为与其他序列是相似的:
import numpy as np
import pandas as pd
from datetime import datetime, timedeltanp.random.seed(12345)dates = [datetime(2024, 1, 2), datetime(2024, 1, 5),datetime(2024, 1, 7), datetime(2024, 1, 8),datetime(2024, 1, 10), datetime(2024, 1, 12)]
ts = pd.Series(np.random.standard_normal(6), index=dates)
print(ts)stamp = ts.index[0]
print(ts[stamp])# 为方便起见,可以传递可解释为 date 的字符串
print(ts["2024-01-10"])
ts[stamp]输出:-0.20470765948471295
ts["2024-01-10"]输出:1.9657805725027142
对于较长的时间序列,可以传递一年或仅传递一年和月份来轻松选择数据切片:
import numpy as np
import pandas as pdnp.random.seed(12345)longer_ts = pd.Series(np.random.standard_normal(1000),index=pd.date_range("2022-01-01", periods=1000))
print(longer_ts)
print(longer_ts["2023"])
longer_ts输出:
longer_ts["2023"]输出:
这里,longer_ts["2023"]中字符串 “2023” 被解释为年份并选择该时间段。如果指定月份,这也适用,例如:
import numpy as np
import pandas as pdnp.random.seed(12345)longer_ts = pd.Series(np.random.standard_normal(1000),index=pd.date_range("2022-01-01", periods=1000))
print(longer_ts)
print(longer_ts["2023"])
print(longer_ts["2023-06"])
longer_ts["2023-06"]输出:
使用 datetime 对象进行切片也有用,我们看如下代码示例:
import numpy as np
import pandas as pd
from datetime import datetimenp.random.seed(12345)dates = [datetime(2024, 1, 2), datetime(2024, 1, 5),datetime(2024, 1, 7), datetime(2024, 1, 8),datetime(2024, 1, 10), datetime(2024, 1, 12)]
ts = pd.Series(np.random.standard_normal(6), index=dates)
print(ts)print(ts[datetime(2024, 1, 7):])
print(ts[datetime(2024, 1, 7):datetime(2024, 1, 10)])# 由于大多数时间序列数据是按时间顺序排序的,因此可以使用时间序列中未包含的时间戳进行切片以执行范围查询
# ts时间序列索引未包含值"2024-01-06"和"2024-01-11",下面执行范围查询
print(ts["2024-01-06":"2024-01-11"])
ts输出:
ts[datetime(2024, 1, 7):] 输出:
ts[datetime(2024, 1, 7):datetime(2024, 1, 10)]输出:
ts["2024-01-06":"2024-01-11"] 输出:
与之前一样,可以传递字符串 date、datetime 或 timestamp。请记住,以这种方式进行切片会在源时间序列上生成视图,就像切片 NumPy 数组一样。这意味着不会复制任何数据,并且对切片的修改将反映在原始数据中。
有一个等效的实例方法 truncate,它在两个日期之间对 Series 进行切片:
import numpy as np
import pandas as pd
from datetime import datetimenp.random.seed(12345)dates = [datetime(2024, 1, 2), datetime(2024, 1, 5),datetime(2024, 1, 7), datetime(2024, 1, 8),datetime(2024, 1, 10), datetime(2024, 1, 12)]
ts = pd.Series(np.random.standard_normal(6), index=dates)print(ts.truncate(after="2024-01-09"))
ts.truncate(after="2024-01-09")输出:
以上这些也都适用于 DataFrame,对其行进行索引,还是代码示例来学习:
import numpy as np
import pandas as pdnp.random.seed(12345)dates = pd.date_range("2020-01-01", periods=100, freq="W-WED")
long_df = pd.DataFrame(np.random.standard_normal((100, 4)),index=dates,columns=["Colorado", "Texas","New York", "Ohio"])
print(long_df.loc["2021-05"])
输出结果:
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2021-05-05 | -0.115413 | -0.350745 | 0.044697 | -0.897756 |
2021-05-12 | 0.890874 | -1.151185 | -2.612303 | 1.141250 |
2021-05-19 | -0.867136 | 0.383583 | -0.437030 | 0.347489 |
2021-05-26 | -1.230179 | 0.571078 | 0.060061 | -0.225524 |
具有重复索引的时间序列(Time Series with Duplicate Indices)
在某些应用程序中,可能会有多个数据观测值落在特定时间戳上。下面是一个代码示例:
import numpy as np
import pandas as pdnp.random.seed(12345)dates = pd.DatetimeIndex(["2020-01-01", "2020-01-02", "2020-01-02","2020-01-02", "2020-01-03"])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts)# 可以通过检查索引的 is_unique 属性来判断索引不唯一
print(dup_ts.index.is_unique)# 索引到此时间序列将生成标量值或切片,具体取决于时间戳是否重复:
# 如果用的是没有重复的索引则输出一个标量值
# 如果是有重复的索引则输出的是一个切片
print(dup_ts["2020-01-03"]) # 无重复
print(dup_ts["2020-01-02"]) # 有重复# 如果要聚合具有非唯一时间戳的数据。
# 一种方法是使用 groupby 并传递 level=0 (唯一的级别)
grouped = dup_ts.groupby(level=0)
print(grouped.mean())
print(grouped.count())
dup_ts输出:
dup_ts.index.is_unique 输出:False
dup_ts["2020-01-03"] 输出:4
dup_ts["2020-01-02"] 输出:
grouped.mean() 输出:
grouped.count() 输出: