碎碎念:由于我在时间序列分析方面的知识尚显薄弱,因此参加了和鲸举办的时间序列数据处理训练营,期望能够提升相关技能。同时,我借助了GPT来补充一些内容,希望这些分享能对各位读者有所帮助。欢迎大家一起学习交流!
关卡1:Pandas处理时间数据
欢迎来到时间序列数据处理训练营关卡1:Pandas处理时间数据。在本节中,我们将深入探讨如何使用Pandas库来处理和操作时间序列数据。
Pandas是一个强大的数据分析工具,特别适用于处理时间相关的数据。通过本关卡的学习,你将掌握如何创建时间戳、日期时间索引、时间段、时间差等,并了解如何进行时区处理和数据加载。这些技能对于时间序列分析、数据预处理和特征工程至关重要。
一、Pandas在时间处理方面的应用
Pandas功能
在时间处理时,Pandas能够生成固定频率的日期和时间跨度序列;将时间序列调整或转换为特定的频率;根据非标准的时间间隔计算"相对"日期(例如,年底前的5个工作日),或者向前或向后滚动日期。
Pandas时间相关的数据类型
import pandas as pd
import numpy as np
二、Timestamp (时间戳)
#创建表示特定日期的时间戳
pd.Timestamp('2016-07-10')
Timestamp('2016-07-10 00:00:00')
#增加细节 使时间戳更细致
pd.Timestamp('2016-07-10 10')
Timestamp('2016-07-10 10:00:00')
#再加细节
pd.Timestamp('2016-07-10 10:15')
Timestamp('2016-07-10 10:15:00')
思考: 一共可以增加多少细节?
pd.Timestamp('2016-07-10 10:15:30.123456789')
Timestamp('2016-07-10 10:15:30.123456789')
Timestamp 可以精确到纳秒级别。
- 年月日:2016-07-10
- 时分秒:10:15:30
- 毫秒:123
- 微秒:456
- 纳秒:789
# 时间戳有哪些属性?
# 提示: http://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
t = pd.Timestamp('2016-07-10 10:15')
时间/日期组件
可以从时间戳(Timestamp)或时间戳集合(如DateTimeIndex)中访问的几个时间/日期属性。
属性/方法 | 描述 |
---|---|
year | 日期时间的年份 |
month | 日期时间的月份 |
day | 日期时间的日 |
hour | 日期时间的小时 |
minute | 日期时间的分钟 |
second | 日期时间的秒 |
microsecond | 日期时间的微秒 |
date() | 返回datetime.date 对象(日期部分) |
time() | 返回datetime.time 对象(时间部分) |
dayofyear | 一年中的第几天(序数) |
week | 一年中的第几周(序数) |
weekday() | 一周中的第几天,周一为0,周日为6 |
strftime(‘%A’) | 星期几的名称(例如:星期五) |
day_name() | 获取星期几的名称,pandas 支持的替代方法(例如:星期五)。替代了weekday_name |
quarter | 季度:1-3月=1,4-6月=2,7-9月=3,10-12月=4。pandas 中的Timestamp 对象支持该属性 |
days_in_month | 返回当月的天数,pandas 中的Timestamp 对象支持该属性 |
is_month_start | 逻辑值,指示是否为月初(由频率定义),pandas 中的Timestamp 对象支持该属性 |
is_month_end | 逻辑值,指示是否为月末(由频率定义),pandas 中的Timestamp 对象支持该属性 |
is_quarter_start | 逻辑值,指示是否为季度开始(由频率定义),pandas 中的Timestamp 对象支持该属性 |
is_quarter_end | 逻辑值,指示是否为季度结束(由频率定义),pandas 中的Timestamp 对象支持该属性 |
is_year_start | 逻辑值,指示是否为年初(由频率定义),pandas 中的Timestamp 对象支持该属性 |
is_year_end | 逻辑值,指示是否为年末(由频率定义),pandas 中的Timestamp 对象支持该属性 |
已被替代或移除的方法
被移除/替代方法 | 新方法 | 描述 |
---|---|---|
weekday_name | day_name() | weekday_name 方法在较新版本的pandas 中被移除,使用day_name() 方法替代。 |
weekofyear | week | weekofyear 方法在pandas 旧版本中有效,但在新版中已被week 替代,用于返回年内的第几周。 |
print(t.weekday_name)
错误是因为pandas
中的Timestamp
对象在较新版本的pandas
中不再支持weekday_name
属性。weekday_name
曾经存在于pandas
的早期版本,但现在被移除或替代了其他方法。
print(t.day_name())
print('---')
print(t.second)
print('---')
print(t.dayofyear)
Sunday
---
0
---
192
上面只列举了一部分函数,我这里全部举个例子,方便学习理解。
ts = pd.Timestamp('2023-03-31 23:59:59')print(f"日期时间: {ts}\n")# 基本时间组件
print("基本时间组件:")
print(f"年份: {ts.year}")
print(f"月份: {ts.month}")
print(f"日: {ts.day}")
print(f"小时: {ts.hour}")
print(f"分钟: {ts.minute}")
print(f"秒: {ts.second}")
print(f"微秒: {ts.microsecond}")
print(f"纳秒: {ts.nanosecond}")# 日期和时间获取
print("\n日期和时间获取:")
print(f"日期: {ts.date()}")
print(f"时间: {ts.time()}")# 日期在年/周中的位置
print("\n日期在年/周中的位置:")
print(f"一年中的第几天: {ts.dayofyear}")
print(f"一年中的第几周: {ts.week}") # 或 ts.weekofyear
print(f"一周中的第几天(0=周一,6=周日): {ts.dayofweek}") # 或 ts.weekday
print(f"星期几: {ts.day_name()}")# 其他时间相关属性
print("\n其他时间相关属性:")
print(f"季度: {ts.quarter}")
print(f"当月的天数: {ts.days_in_month}")# 逻辑判断属性
print("\n逻辑判断属性:")# 月初/月末
ts_month_start = pd.Timestamp('2023-03-01')
ts_month_end = pd.Timestamp('2023-03-31')
print("月初/月末:")
print(f"2023-03-01 是否为月初: {ts_month_start.is_month_start}")
print(f"2023-03-31 是否为月末: {ts_month_end.is_month_end}")# 季度开始/结束
ts_quarter_start = pd.Timestamp('2023-04-01')
ts_quarter_end = pd.Timestamp('2023-06-30')
print("\n季度开始/结束:")
print(f"2023-04-01 是否为季度开始: {ts_quarter_start.is_quarter_start}")
print(f"2023-06-30 是否为季度结束: {ts_quarter_end.is_quarter_end}")# 年初/年末
ts_year_start = pd.Timestamp('2023-01-01')
ts_year_end = pd.Timestamp('2023-12-30')
print("\n年初/年末:")
print(f"2023-01-01 是否为年初: {ts_year_start.is_year_start}")
print(f"2023-12-30 是否为年末: {ts_year_end.is_year_end}")# 原始时间戳的逻辑判断
print("\n原始时间戳 (2023-03-31 23:59:59) 的逻辑判断:")
print(f"是否为月初: {ts.is_month_start}")
print(f"是否为月末: {ts.is_month_end}")
print(f"是否为季度开始: {ts.is_quarter_start}")
print(f"是否为季度结束: {ts.is_quarter_end}")
print(f"是否为年初: {ts.is_year_start}")
print(f"是否为年末: {ts.is_year_end}")
日期时间: 2023-03-31 23:59:59基本时间组件:
年份: 2023
月份: 3
日: 31
小时: 23
分钟: 59
秒: 59
微秒: 0
纳秒: 0日期和时间获取:
日期: 2023-03-31
时间: 23:59:59日期在年/周中的位置:
一年中的第几天: 90
一年中的第几周: 13
一周中的第几天(0=周一,6=周日): 4
星期几: Friday其他时间相关属性:
季度: 1
当月的天数: 31逻辑判断属性:
月初/月末:
2023-03-01 是否为月初: True
2023-03-31 是否为月末: True季度开始/结束:
2023-04-01 是否为季度开始: True
2023-06-30 是否为季度结束: True年初/年末:
2023-01-01 是否为年初: True
2023-12-30 是否为年末: False原始时间戳 (2023-03-31 23:59:59) 的逻辑判断:
是否为月初: False
是否为月末: True
是否为季度开始: False
是否为季度结束: True
是否为年初: False
是否为年末: False
三、DatetimeIndex(日期时间编码器)
日期时间索引的主要用途之一是作为Pandas对象的索引
日期时间索引类包含许多与时间序列相关的优化:
· 在底层预先计算并缓存了各种偏移量的大范围日期,以便快速生成后续的日期范围(只需获取一个切片)。
· 可以使用 shift 和 tshift 方法来对数据进行快速的移动或偏移操作。
· 对于具有相同频率的重叠的 DatetimeIndex 对象进行合并是非常快速的(这对于快速的数据对齐非常重要)。
· 可以通过年份、月份等属性快速访问日期字段。
· 包含正则化函数(snap,asof etc.)。
偏移别名
在 Pandas 中一些常见的时间序列频率被赋予了一些字符串别名,这些别名被称为偏移别名(在 v0.8.0 之前被称为时间规则)。
别名 | 描述 |
---|---|
B | 工作日频率 |
C | 自定义工作日频率(实验性) |
D | 日历日频率 |
W | 每周频率 |
M | 月末频率 |
BM | 工作日月末频率 |
CBM | 自定义工作日月末频率 |
MS | 月初频率 |
BMS | 工作日月初频率 |
CBMS | 自定义工作日月初频率 |
Q | 季度末频率 |
BQ | 工作日季度末频率 |
QS | 季度初频率 |
BQS | 工作日季度初频率 |
A | 年末频率 |
BA | 工作日年末频率 |
AS | 年初频率 |
BAS | 工作日年初频率 |
BH | 工作时间频率 |
H | 每小时频率 |
T, min | 每分钟频率 |
S | 每秒频率 |
L, ms | 毫秒 |
U, us | 微秒 |
N | 纳秒 |
#创建日期时间索引 rng,起始日期为'2016年7月1日',并且频率为每日一次('D'),共包含10个日期时间点
rng = pd.date_range('2016 Jul 1',periods = 10, freq = 'D')
rng
DatetimeIndex(['2016-07-01', '2016-07-02', '2016-07-03', '2016-07-04','2016-07-05', '2016-07-06', '2016-07-07', '2016-07-08','2016-07-09', '2016-07-10'],dtype='datetime64[ns]', freq='D')
思考:
-
以下哪种格式与其他的格式含义不同?
‘2016 Jul 1’ ‘7/1/2016’ ‘1/7/2016’ ‘July 1, 2016’ ‘2016-07-01’ ‘2016/07/01’ -
7/1/2016 是一月还是七月?
-
date_range中保存的单个对象的类别是什么?
-
在列出的格式中,‘1/7/2016’ 的含义可能与其他格式不同。这是因为:
- ‘2016 Jul 1’、‘July 1, 2016’、‘2016-07-01’、‘2016/07/01’ 都明确表示 2016 年 7 月 1 日。
- ‘7/1/2016’ 在美国日期格式中表示 2016 年 7 月 1 日。
- 但 ‘1/7/2016’ 可能有歧义:
- 在美国格式中,它表示 2016 年 1 月 7 日。
- 在许多其他国家(如英国、澳大利亚等),它表示 2016 年 7 月 1 日。
-
‘7/1/2016’ 的解释依赖于使用的日期格式约定:
- 在美国格式(MM/DD/YYYY)中,它表示 2016 年 7 月 1 日。
- 在其他许多国家使用的格式(DD/MM/YYYY)中,它会表示 2016 年 1 月 7 日。
- 在 Pandas 中,默认情况下它通常被解释为 7 月 1 日,因为 Pandas 倾向于使用美国日期格式。但这可以通过设置来改变。
-
在 Pandas 的 date_range 函数中,生成的每个单个对象的类别是 Timestamp,例子如下代码。
# 创建一个 date_range
date_range = pd.date_range(start='2023-01-01', end='2023-01-05')
print(date_range)# 检查第一个元素的类型
print(type(date_range[0]))
DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04','2023-01-05'],dtype='datetime64[ns]', freq='D')
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
日期偏移对象
在Pandas中,通过传递例如’M’、'W’和’BM’等频率字符串到freq关键字参数,我们创建了不同频率的DatetimeIndex对象。这些频率字符串在底层被转换为 pandas 的 DateOffset 实例,它代表了一种常规的频率增量。特定的偏移逻辑,如"月"、“工作日"或"一小时”,则在它的各种子类中表示。
类名 | 描述 |
---|---|
DateOffset | 通用偏移对象,默认值是 1 个日历日,可以用来创建自定义的日期偏移量。 |
BDay | 工作日(不包括周末)。例如增加或减少若干个工作日。 |
CDay | 自定义工作日(实验性功能),支持根据特定规则自定义哪些天是工作日。 |
Week | 一周的偏移量,可以选择锚定在一周的某一天。例如,每周的周三。 |
WeekOfMonth | 每月第 y 周的第 x 天,例如每月的第二个周五(非常灵活)。 |
LastWeekOfMonth | 每月的最后一周的第 x 天,例如每月最后一周的周二。 |
MonthEnd | 日历月的月末,例如 1 月的 31 日、2 月的 28/29 日。 |
MonthBegin | 日历月的月初,例如 1 月的 1 日、2 月的 1 日。 |
BMonthEnd | 工作月的月末,即月中最后一个工作日。例如,2025 年 1 月的月末为 1 月 31 日,但如果这一天是周六,那么 BMonthEnd 会指向 1 月 30 日(周五)。 |
BMonthBegin | 工作月的月初,即月中第一个工作日。例如,2025 年 1 月的月初是 1 月 1 日,但如果这一天是周六,那么 BMonthBegin 会指向 1 月 2 日(周一)。 |
CBMonthEnd | 自定义商业月的月末,用于特定的商业规则。例如,你可以定义某些日子为商业工作日,然后用它来定位商业月的月末。 |
CBMonthBegin | 自定义商业月的月初,用于特定的商业规则。例如,你可以定义某些日子为商业工作日,然后用它来定位商业月的月初。 |
QuarterEnd | 日历季度的季度末,例如第一季度的 3 月 31 日,第二季度的 6 月 30 日。 |
QuarterBegin | 日历季度的季度初,例如第一季度的 1 月 1 日,第二季度的 4 月 1 日。 |
BQuarterEnd | 工作季度的季度末,即季度中最后一个工作日。例如,2025 年第一季度的季度末是 3 月 31 日,但如果这一天是周六,则指向 3 月 28 日(周五)。 |
BQuarterBegin | 工作季度的季度初,即季度中第一个工作日。例如,2025 年第二季度的季度初是 4 月 1 日,但如果这一天是周六,则指向 4 月 3 日(周一)。 |
FY5253Quarter | 零售季度,用于基于 52 周或 53 周的财务会计周期(广泛用于零售业)。 |
YearEnd | 日历年的年末,例如 2025 年的 12 月 31 日。 |
YearBegin | 日历年的年初,例如 2025 年的 1 月 1 日。 |
BYearEnd | 工作年的年末,即最后一个工作日。例如,2025 年的年末是 12 月 31 日,但如果这一天是周六,则指向 12 月 29 日(周五)。 |
BYearBegin | 工作年的年初,即第一个工作日。例如,2025 年的年初是 1 月 1 日,但如果这一天是周日,则指向 1 月 2 日(周一)。 |
FY5253 | 零售年,用于基于 52 周或 53 周的财务会计周期,类似于 FY5253Quarter,但对应整个财年。 |
BusinessHour | 工作小时,例如增加或减少若干个标准工作小时(默认为上午 9 点到下午 5 点的时间段)。 |
CustomBusinessHour | 自定义的工作小时,可以设置工作时间的起止范围。例如上午 8 点到下午 6 点,适合非标准工作时间的场景。 |
Hour | 一小时的偏移,例如在原始时间上加减若干小时。 |
Minute | 一分钟的偏移,例如在原始时间上加减若干分钟。 |
Second | 一秒的偏移,例如在原始时间上加减若干秒。 |
Milli | 一毫秒的偏移,例如在原始时间上加减若干毫秒。 |
Micro | 一微秒的偏移,例如在原始时间上加减若干微秒。 |
Nano | 一纳秒的偏移,例如在原始时间上加减若干纳秒。 |
#DateOffset 示例,以下所示了夏令时的情况:
# 生成一个指定的时间,芬兰赫尔辛基时间执行夏令时
ts = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki')
ts
# Timestamp('2016-10-30 00:00:00+0300', tz='Europe/Helsinki')# 按日历时间
ts + pd.DateOffset(days=1)
# Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
夏令时(DST, Daylight Saving Time)是一种人为调整时间的制度,为了更好地利用日照资源,在夏季时将时钟向前拨快 1 小时,从而使人们可以在下午拥有更多的自然光线时间。这种调整通常在春季开始(时钟向前拨 1 小时),在秋季结束(时钟向后拨回 1 小时),我国从1992年起,不使用夏时令,而统一使用 北京时间(UTC+8) 作为标准时间,所以这里我打算使用更加适合我国的时间来学习偏移,我相信这样也会便于读者理解。
# 设置时间戳,使用北京时间
ts = pd.Timestamp('2023-03-31 23:59:59', tz='Asia/Shanghai') # 因为 pandas 本身没有Beijing,但是和上海是同一时区,因此用上海来表示。
print(f"原始时间戳: {ts}\n")# 偏移操作示例
print("DateOffset 偏移操作:")# 偏移 10 天
offset_days = pd.DateOffset(days=10)
print(f"偏移 10 天: {ts + offset_days}")# 偏移 5 个工作日
offset_bdays = pd.offsets.BDay(5)
print(f"偏移 5 个工作日: {ts + offset_bdays}")# 偏移 1 周
offset_week = pd.offsets.Week(1)
print(f"偏移 1 周: {ts + offset_week}")# 偏移到最近的月末
offset_month_end = pd.offsets.MonthEnd()
print(f"偏移到最近的月末: {ts + offset_month_end}")# 偏移到最近的季度末
offset_quarter_end = pd.offsets.QuarterEnd()
print(f"偏移到最近的季度末: {ts + offset_quarter_end}")# 偏移到最近的年末
offset_year_end = pd.offsets.YearEnd()
print(f"偏移到最近的年末: {ts + offset_year_end}")# 偏移 5 个工作小时
offset_bhour = pd.offsets.BusinessHour(5)
print(f"偏移 5 个工作小时: {ts + offset_bhour}")# 偏移 1 小时、30 分钟、10 秒
offset_hour = pd.offsets.Hour(1)
offset_minute = pd.offsets.Minute(30)
offset_second = pd.offsets.Second(10)
print(f"偏移 1 小时: {ts + offset_hour}")
print(f"偏移 30 分钟: {ts + offset_minute}")
print(f"偏移 10 秒: {ts + offset_second}")# 偏移 5 毫秒、10 微秒、1 纳秒
offset_milli = pd.offsets.Milli(5)
offset_micro = pd.offsets.Micro(10)
offset_nano = pd.offsets.Nano(1)
print(f"偏移 5 毫秒: {ts + offset_milli}")
print(f"偏移 10 微秒: {ts + offset_micro}")
print(f"偏移 1 纳秒: {ts + offset_nano}")
原始时间戳: 2023-03-31 23:59:59+08:00DateOffset 偏移操作:
偏移 10 天: 2023-04-10 23:59:59+08:00
偏移 5 个工作日: 2023-04-07 23:59:59+08:00
偏移 1 周: 2023-04-07 23:59:59+08:00
偏移到最近的月末: 2023-04-30 23:59:59+08:00
偏移到最近的季度末: 2023-06-30 23:59:59+08:00
偏移到最近的年末: 2023-12-31 23:59:59+08:00
偏移 5 个工作小时: 2023-04-03 14:00:00+08:00
偏移 1 小时: 2023-04-01 00:59:59+08:00
偏移 30 分钟: 2023-04-01 00:29:59+08:00
偏移 10 秒: 2023-04-01 00:00:09+08:00
偏移 5 毫秒: 2023-03-31 23:59:59.005000+08:00
偏移 10 微秒: 2023-03-31 23:59:59.000010+08:00
偏移 1 纳秒: 2023-03-31 23:59:59.000000001+08:00
锚定后缀
在 Pandas 中,除了常见的时间序列频率别名(如’D’表示每日频率、'W’表示每周频率等),还可以在某些频率后面添加一个特定的后缀,以指定时间序列中数据点的锚定点或起始点。
别名 | 描述 |
---|---|
W-SUN | 每周的频率,锚定在周日(和 ‘W’ 别名相同,默认锚定周日) |
W-MON | 每周的频率,锚定在周一 |
W-TUE | 每周的频率,锚定在周二 |
W-WED | 每周的频率,锚定在周三 |
W-THU | 每周的频率,锚定在周四 |
W-FRI | 每周的频率,锚定在周五 |
W-SAT | 每周的频率,锚定在周六 |
QE-DEC | 季度频率,年度结束于 12 月(和 ‘Q’ 别名相同,默认锚定 12 月) |
QE-JAN | 季度频率,年度结束于 1 月 |
QE-FEB | 季度频率,年度结束于 2 月 |
QE-MAR | 季度频率,年度结束于 3 月 |
BQE-DEC | 季度频率,仅包含工作日,年度结束于 12 月 |
BQE-JAN | 季度频率,仅包含工作日,年度结束于 1 月 |
BQE-FEB | 季度频率,仅包含工作日,年度结束于 2 月 |
BQE-MAR | 季度频率,仅包含工作日,年度结束于 3 月 |
BQS-DEC | 季度频率,仅包含工作日,起始日期为工作日,年度结束于 12 月 |
BQS-JAN | 季度频率,仅包含工作日,起始日期为工作日,年度结束于 1 月 |
BQS-FEB | 季度频率,仅包含工作日,起始日期为工作日,年度结束于 2 月 |
BQS-MAR | 季度频率,仅包含工作日,起始日期为工作日,年度结束于 3 月 |
在时间序列分析中,“锚定”是指将时间间隔(如每周、每月或每季度)对齐到特定的时间点(如一周的某一天、一个月的某一天或一个季度的某个结束月份)。这种操作的目的在于确保生成的时间序列具有一致性和规律性。
#当n不为0时,如果给定日期不在锚点上,则它会捕捉到下一个(上一个)锚点,并向前或向后移动 |n|-1 步。
pd.Timestamp('2014-01-02') + pd.offsets.MonthBegin(n=1)
# Timestamp('2014-02-01 00:00:00')
Timestamp('2014-02-01 00:00:00')
解释:什么叫“给定日期不在锚点上”?
在使用 pandas
中的偏移对象(如 MonthBegin
、MonthEnd
等)时,锚点是指偏移对象的目标时间点。例如:
- 对于
MonthBegin
,锚点是每月的月初。 - 对于
MonthEnd
,锚点是每月的月末。 - 对于
W-MON
,锚点是每周的周一。
“给定日期不在锚点上”,意思是当前的时间戳不在偏移对象的目标位置,例如:
- 当前日期是
2014-01-02
,而偏移对象是MonthBegin
,目标锚点是 2014 年 1 月的月初(即 2014-01-01),那么当前日期2014-01-02
不在目标锚点上。
#如果给定的日期在锚点上,则将其| n |移动。 指向前进或后退:
pd.Timestamp('2014-01-01') + pd.offsets.MonthBegin(n=1)
# Timestamp('2014-02-01 00:00:00')
Timestamp('2014-02-01 00:00:00')
#对于n = 0的情况,如果在锚点上,则日期不会移动,否则它将前滚到下一个锚点。
pd.Timestamp('2014-01-02') + pd.offsets.MonthBegin(n=0)
# Timestamp('2014-02-01 00:00:00')
Timestamp('2014-02-01 00:00:00')
# 按每周周一生成时间范围
date_range_mon = pd.date_range(start='2023-01-01', periods=5, freq='W-MON')
print("每周锚定在周一:\n", date_range_mon)# 按每周周五生成时间范围
date_range_fri = pd.date_range(start='2023-01-01', periods=5, freq='W-FRI')
print("\n每周锚定在周五:\n", date_range_fri)
每周锚定在周一:DatetimeIndex(['2023-01-02', '2023-01-09', '2023-01-16', '2023-01-23','2023-01-30'],dtype='datetime64[ns]', freq='W-MON')每周锚定在周五:DatetimeIndex(['2023-01-06', '2023-01-13', '2023-01-20', '2023-01-27','2023-02-03'],dtype='datetime64[ns]', freq='W-FRI')
# 按季度结束于 12 月生成时间范围
date_range_q_dec = pd.date_range(start='2024-01-01', periods=4, freq='QE-DEC')
print("季度结束于 12 月:\n", date_range_q_dec)# 按季度结束于 3 月生成时间范围
date_range_q_mar = pd.date_range(start='2024-01-01', periods=4, freq='QE-MAR')
print("\n季度结束于 3 月:\n", date_range_q_mar)
季度结束于 12 月:DatetimeIndex(['2024-03-31', '2024-06-30', '2024-09-30', '2024-12-31'], dtype='datetime64[ns]', freq='QE-DEC')季度结束于 3 月:DatetimeIndex(['2024-03-31', '2024-06-30', '2024-09-30', '2024-12-31'], dtype='datetime64[ns]', freq='QE-MAR')
# 按季度结束于 12 月(工作日)生成时间范围
date_range_bq_dec = pd.date_range(start='2024-01-01', periods=4, freq='BQE-DEC')
print("季度结束于 12 月(仅工作日):\n", date_range_bq_dec)# 按季度结束于 3 月(工作日)生成时间范围
date_range_bq_mar = pd.date_range(start='2024-01-01', periods=4, freq='BQE-MAR')
print("\n季度结束于 3 月(仅工作日):\n", date_range_bq_mar)
季度结束于 12 月(仅工作日):DatetimeIndex(['2024-03-29', '2024-06-28', '2024-09-30', '2024-12-31'], dtype='datetime64[ns]', freq='BQE-DEC')季度结束于 3 月(仅工作日):DatetimeIndex(['2024-03-29', '2024-06-28', '2024-09-30', '2024-12-31'], dtype='datetime64[ns]', freq='BQE-MAR')
-
每周频率的锚定: 使用
W-
后缀可以指定每周的具体锚定日,例如W-MON
锚定到周一,W-FRI
锚定到周五。 -
季度频率的锚定: 使用
(B)Q(S)-
后缀,可以指定季度的结束月份。例如QE-DEC
是年度结束于 12 月的季度频率,QE-MAR
是年度结束于 3 月的季度频率。 -
工作日频率: 使用
B
表示仅包括工作日,例如BQE-DEC
表示年度结束于 12 月,且只包含工作日。
节假日和节假日日历
在 Pandas 中,可以通过创建节假日日历类来定义一组特定的节假日规则。AbstractHolidayCalendar 类提供了必要的方法来返回节假日列表,而具体的规则则需要在特定的节假日日历类中定义。此外,start_date 和 end_date 类属性用于确定生成节假日的日期范围。应该在 AbstractHolidayCalendar 类上进行覆盖,以使这个范围适用于所有日历子类。
在 Pandas 中,已经预先定义了一些节假日日历,其中最常用的是 usFederalHolidayCalendar。它主要作为开发其他日历的示例。
对于那些在固定日期发生的节假日(例如美国的纪念日或7月4日),如果节假日恰逢周末或其他非工作日,则会根据一个观察规则来确定其观察日期。已定义的观察规则如下:
规则 | 描述 |
---|---|
nearest_workday | 将周六的节假日调整为前一个周五,将周日的节假日调整为后一个周一 |
sunday_to_monday | 将周日的节假日调整为后一个周一 |
next_monday_or_tuesday | 将周六调整为下一个周一,周日或周一调整为下一个周二 |
previous_friday | 将周六和周日的节假日都调整为前一个周五 |
next_monday | 将周六和周日的节假日都调整为下一个周一 |
解释:将周六的节假日调整为前一个周五,将周日的节假日调整为后一个周一
这个规则主要出现在时间序列管理和节假日日历的定义中,目的是为了确保节假日总是落在工作日(通常是周一至周五)内,避免因为节假日落在周末(周六或周日)而影响到业务场景或分析计算。
规则描述:
-
周六的节假日调整到前一个周五:
- 如果一个节假日原本是周六,就会将这个节假日的日期调整到它前面的最近一个工作日——也就是周五。
-
周日的节假日调整到后一个周一:
- 如果一个节假日原本是周日,就会将这个节假日的日期调整到它后面的最近一个工作日——也就是周一。
为什么要做这种调整?
在实际应用中,很多场景(如金融市场、公司考勤)只考虑工作日。如果一个节假日落在周末,通常需要将它调整到工作日,以确保数据处理或分析时不会漏掉重要的时间点。
例如:
- 在美国,如果独立日(7 月 4 日)是周六,会提前到周五(7 月 3 日)放假。
- 如果圣诞节(12 月 25 日)是周日,会延后到周一(12 月 26 日)放假。
在 pandas.tseries.holiday
中,nearest_workday
是 usFederalHolidayCalendar 的默认观察规则。即,当定义节假日日历时,默认规则会按照以下逻辑运行:
- 周六的节假日 → 调整到前一个周五
- 周日的节假日 → 调整到后一个周一
也可以通过修改 observance
参数,自定义节假日的调整规则。
from pandas.tseries.holiday import USFederalHolidayCalendar, nearest_workday# 创建美国联邦假日日历实例
calendar = USFederalHolidayCalendar()# 获取 2024 年的所有节假日
holidays = calendar.holidays(start='2024-01-01', end='2024-12-31')
print("2024 年美国联邦假日:")
print(holidays)
2024 年美国联邦假日:
DatetimeIndex(['2024-01-01', '2024-01-15', '2024-02-19', '2024-05-27','2024-06-19', '2024-07-04', '2024-09-02', '2024-10-14','2024-11-11', '2024-11-28', '2024-12-25'],dtype='datetime64[ns]', freq=None)
除了默认的 nearest_workday
规则,还可以指定其他观察规则,如 sunday_to_monday
。以下示例展示如何创建一个自定义的节假日日历,并调整观察规则。
#生成节假日
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday
class ChinaHolidaysCalendar(AbstractHolidayCalendar):rules = [Holiday('元旦', month=1, day=1),Holiday('元旦', month=1, day=2),Holiday('元旦', month=1, day=3),Holiday('春节', month=1, day=21),Holiday('春节', month=1, day=22),# 同样的方法添加其他节假日]start_date = '2023-01-01'
end_date = '2023-12-31'china_holidays_calendar = ChinaHolidaysCalendar()
holidays = china_holidays_calendar.holidays(start_date, end_date)
print(holidays)
DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-21','2023-01-22'],dtype='datetime64[ns]', freq=None)
知识扩充——holidays库
holidays 库支持世界大多数国家的节假日,并且可以轻松扩展自定义节假日。
!pip install holidays -i https://pypi.tuna.tsinghua.edu.cn/simple/
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple/
Collecting holidaysDownloading https://pypi.tuna.tsinghua.edu.cn/packages/90/9c/5235772fc9d2399f41401e6a054a26b4a993bd8a38e4ff849a6097a912a9/holidays-0.63-py3-none-any.whl (1.2 MB)
[2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m1.2/1.2 MB[0m [31m4.1 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m
[?25hRequirement already satisfied: python-dateutil in /opt/conda/lib/python3.11/site-packages (from holidays) (2.9.0)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.11/site-packages (from python-dateutil->holidays) (1.16.0)
Installing collected packages: holidays
Successfully installed holidays-0.63
import holidays# 定义中国的节假日
china_holidays = holidays.China(years=2024)
print("2024 年的节假日:")
for date, name in sorted(china_holidays.items()):print(f"{date}: {name}")
2024 年的节假日:
2024-01-01: New Year's Day
2024-02-10: Chinese New Year (Spring Festival)
2024-02-11: Chinese New Year (Spring Festival)
2024-02-12: Chinese New Year (Spring Festival)
2024-02-13: Chinese New Year (Spring Festival) (observed)
2024-02-14: Chinese New Year (Spring Festival) (observed)
2024-02-15: Day off (substituted from 02/04/2024)
2024-02-16: Day off (substituted from 02/18/2024)
2024-04-04: Tomb-Sweeping Day
2024-04-05: Day off (substituted from 04/07/2024)
2024-05-01: Labor Day
2024-05-02: Day off (substituted from 04/28/2024)
2024-05-03: Day off (substituted from 05/11/2024)
2024-06-10: Dragon Boat Festival
2024-09-16: Day off (substituted from 09/14/2024)
2024-09-17: Mid-Autumn Festival
2024-10-01: National Day
2024-10-02: National Day
2024-10-03: National Day
2024-10-04: Day off (substituted from 09/29/2024)
2024-10-07: Day off (substituted from 10/12/2024)
# 判断某日期是否为节假日
date = '2024-10-04'
is_holiday = date in china_holidays
print(f"{date} 是否为节假日: {is_holiday}")
2024-10-04 是否为节假日: True
# 添加自定义节假日
custom_holidays = holidays.China(years=2024)
custom_holidays.append({'2024-04-13': 'Water Splashing Festival'})
print("添加后的节假日:")
for date, name in sorted(custom_holidays.items()):print(f"{date}: {name}")
添加后的节假日:
2024-01-01: New Year's Day
2024-02-10: Chinese New Year (Spring Festival)
2024-02-11: Chinese New Year (Spring Festival)
2024-02-12: Chinese New Year (Spring Festival)
2024-02-13: Chinese New Year (Spring Festival) (observed)
2024-02-14: Chinese New Year (Spring Festival) (observed)
2024-02-15: Day off (substituted from 02/04/2024)
2024-02-16: Day off (substituted from 02/18/2024)
2024-04-04: Tomb-Sweeping Day
2024-04-05: Day off (substituted from 04/07/2024)
2024-04-13: Water Splashing Festival
2024-05-01: Labor Day
2024-05-02: Day off (substituted from 04/28/2024)
2024-05-03: Day off (substituted from 05/11/2024)
2024-06-10: Dragon Boat Festival
2024-09-16: Day off (substituted from 09/14/2024)
2024-09-17: Mid-Autumn Festival
2024-10-01: National Day
2024-10-02: National Day
2024-10-03: National Day
2024-10-04: Day off (substituted from 09/29/2024)
2024-10-07: Day off (substituted from 10/12/2024)
翻译后:
2024-01-01: 元旦
2024-02-10: 春节
2024-02-11: 春节
2024-02-12: 春节
2024-02-13: 春节(补假)
2024-02-14: 春节(补假)
2024-02-15: 调休日(调休自 2024-02-04)
2024-02-16: 调休日(调休自 2024-02-18)
2024-04-04: 清明节
2024-04-05: 调休日(调休自 2024-04-07)
2024-04-13: 泼水节
2024-05-01: 劳动节
2024-05-02: 调休日(调休自 2024-04-28)
2024-05-03: 调休日(调休自 2024-05-11)
2024-06-10: 端午节
2024-09-16: 调休日(调休自 2024-09-14)
2024-09-17: 中秋节
2024-10-01: 国庆节
2024-10-02: 国庆节
2024-10-03: 国庆节
2024-10-04: 调休日(调休自 2024-09-29)
2024-10-07: 调休日(调休自 2024-10-12)
四、period(周期)
pandas.Period
是 pandas
中用于表示**时间段(period)**的一个对象,它可以精确表示时间的某个区间(如年、季度、月、日等)。Period
不仅仅表示时间点(像 Timestamp
那样),还可以表示一个时间区间,如 “2023 年第一季度” 或 “2024 年 5 月”。
#创建表示特定月份的时间段
pd.Period('2016-01')
Period('2016-01', 'M')
思考:
上面的额外信息是什么?它是如何设置的?
#创建表示特定日期的时间段
pd.Period('2016-01-01')
Period('2016-01-01', 'D')
#增加细节 使时间段更细致
pd.Period('2016-01-01 10')
Period('2016-01-01 10:00', 'h')
#增加细节 使时间段更细致
pd.Period('2016-01-01 10:10')
Period('2016-01-01 10:10', 'min')
#再加细节
pd.Period('2016-01-01 10:10:10')
Period('2016-01-01 10:10:10', 's')
思考:
- 你能得到的最详细的时间段是什么?
- 如何制作多个时间段? 提示:寻找与上面 pd.date_range() 的类比
timedelta
#创建表示时间差的 Timedelta 对象,表示一天的时间跨度
pd.Timedelta('1 day')
Timedelta('1 days 00:00:00')
#将时间段和时间差相加,得到新的时间段
pd.Period('2016-01-01 10:10') + pd.Timedelta('1 day')
Period('2016-01-02 10:10', 'min')
#将时间戳和时间差相加,得到新的时间戳
pd.Timestamp('2016-01-01 10:10') + pd.Timedelta('1 day')
Timestamp('2016-01-02 10:10:00')
pd.Timestamp('2016-01-01 10:10') + pd.Timedelta('15 ns')
Timestamp('2016-01-01 10:10:00.000000015')
五、PeriodIndex(周期索引)
#花式频率设置
#在指定的时间范围内生成一系列时间段
#只要工作日
pd.period_range('2016-01-01 10:10',freq = 'B',periods = 10)
该方法,官方会提示警告:
/tmp/ipykernel_51/4134041981.py:4: FutureWarning: Period with BDay freq is deprecated and will be removed in a future version. Use a DatetimeIndex with BDay freq instead. pd.period_range('2016-01-01 10:10',freq = 'B',periods = 10)
/tmp/ipykernel_51/4134041981.py:4: FutureWarning: PeriodDtype[B] is deprecated and will be removed in a future version. Use a DatetimeIndex with freq='B' instead pd.period_range('2016-01-01 10:10',freq = 'B',periods = 10)
因为 pandas
中的 Period 对象使用 B
(工作日)频率已经被弃用(deprecated),并将在未来的版本中移除。官方建议改用 DatetimeIndex
配合 B
频率来生成工作日序列。
# 使用 DatetimeIndex 和工作日频率(B)
workday_index = pd.date_range('2016-01-01 10:10', freq='B', periods=10)
print(workday_index)
DatetimeIndex(['2016-01-01 10:10:00', '2016-01-04 10:10:00','2016-01-05 10:10:00', '2016-01-06 10:10:00','2016-01-07 10:10:00', '2016-01-08 10:10:00','2016-01-11 10:10:00', '2016-01-12 10:10:00','2016-01-13 10:10:00', '2016-01-14 10:10:00'],dtype='datetime64[ns]', freq='B')
# 可以组合频率。如果想每天提前 25 小时怎么办?有哪两种方法可以做到这一点?
p1 = pd.period_range('2016-01-01 10:10',freq = '25h',periods = 10)
p1
PeriodIndex(['2016-01-01 10:00', '2016-01-02 11:00', '2016-01-03 12:00','2016-01-04 13:00', '2016-01-05 14:00', '2016-01-06 15:00','2016-01-07 16:00', '2016-01-08 17:00', '2016-01-09 18:00','2016-01-10 19:00'],dtype='period[25h]')
p2 = pd.period_range('2016-01-01 10:10',freq = '1d1h',periods = 10)
p2
PeriodIndex(['2016-01-01 10:00', '2016-01-02 11:00', '2016-01-03 12:00','2016-01-04 13:00', '2016-01-05 14:00', '2016-01-06 15:00','2016-01-07 16:00', '2016-01-08 17:00', '2016-01-09 18:00','2016-01-10 19:00'],dtype='period[25h]')
# 使用 TIME 对象编制索引
# 创建包含 10 个日期的时间索引,然后创建以这些日期为索引、以对应位置的整数为值的序列
rng = pd.date_range('2016 Jul 1',periods = 10,freq = 'D')
rng
pd.Series(range(len(rng)),index = rng)
2016-07-01 0
2016-07-02 1
2016-07-03 2
2016-07-04 3
2016-07-05 4
2016-07-06 5
2016-07-07 6
2016-07-08 7
2016-07-09 8
2016-07-10 9
Freq: D, dtype: int64
# 也可以使用时间段索引
# 将索引视为一个时间跨度,而不是单个时间点
periods = [pd.Period('2016-01'),pd.Period('2016-02'),pd.Period('2016-03')]
ts = pd.Series(np.random.randn(len(periods)),index = periods)
ts
2016-01 0.448995
2016-02 0.798802
2016-03 1.033849
Freq: M, dtype: float64
# ts的索引类型
type(ts.index)
pandas.core.indexes.period.PeriodIndex
思考:
尝试各种索引 提示:ts[‘2016’] 有效吗?
# 提取 2016 年的所有时间段
print("ts['2016']:")
print(ts['2016'])
ts['2016']:
2016-01 0.448995
2016-02 0.798802
2016-03 1.033849
Freq: M, dtype: float64
ts[‘2016’] 有效,因为 PeriodIndex 支持基于较大的时间单位(如年份)的切片操作。这个特性使得你可以通过年份提取对应的时间段。
时间戳数据与周期索引的转换
# 时间戳数据可以转换为具有to_period的周期索引,反之亦然
ts = pd.Series(range(10),pd.date_range('07-10-16 8:00',periods = 10,freq = 'h'))
ts
2016-07-10 08:00:00 0
2016-07-10 09:00:00 1
2016-07-10 10:00:00 2
2016-07-10 11:00:00 3
2016-07-10 12:00:00 4
2016-07-10 13:00:00 5
2016-07-10 14:00:00 6
2016-07-10 15:00:00 7
2016-07-10 16:00:00 8
2016-07-10 17:00:00 9
Freq: h, dtype: int64
ts_period = ts.to_period()
ts_period
2016-07-10 08:00 0
2016-07-10 09:00 1
2016-07-10 10:00 2
2016-07-10 11:00 3
2016-07-10 12:00 4
2016-07-10 13:00 5
2016-07-10 14:00 6
2016-07-10 15:00 7
2016-07-10 16:00 8
2016-07-10 17:00 9
Freq: h, dtype: int64
六、数据切片
#使用时间段对时间序列数据进行切片操作
ts_period['2016-07-10 08:30':'2016-07-10 11:45']
2016-07-10 08:00 0
2016-07-10 09:00 1
2016-07-10 10:00 2
2016-07-10 11:00 3
Freq: h, dtype: int64
#使用时间戳对时间序列数据进行切片操作
ts['2016-07-10 08:30':'2016-07-10 11:45']
2016-07-10 09:00:00 1
2016-07-10 10:00:00 2
2016-07-10 11:00:00 3
Freq: h, dtype: int64
七、时区处理
#不设置时区
rng = pd.date_range('3/6/2012 00:00',periods = 15,freq = 'D')
rng.tz
#设置时区
rng_tz = pd.date_range('3/6/2012 00:00', periods = 15, freq = 'D', tz = 'Europe/London')
rng_tz.tz
<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
#使用pytz库来获取通用时区和所有时区的信息
from pytz import common_timezones, all_timezones
print(len(common_timezones))
print(len(all_timezones))
print(set(all_timezones) - set(common_timezones))
433
596
{'Europe/Kiev', 'Etc/GMT-12', 'America/Indianapolis', 'W-SU', 'MST7MDT', 'Mexico/BajaSur', 'Brazil/Acre', 'Singapore', 'Egypt', 'Etc/UCT', 'Portugal', 'Asia/Ulan_Bator', 'Etc/GMT+11', 'Etc/GMT+7', 'Australia/Canberra', 'Etc/GMT-3', 'America/Santa_Isabel', 'Atlantic/Faeroe', 'America/Buenos_Aires', 'Etc/GMT+4', 'MET', 'Zulu', 'America/Coral_Harbour', 'Etc/GMT0', 'America/Argentina/ComodRivadavia', 'Europe/Nicosia', 'GMT+0', 'Etc/GMT-0', 'GB', 'Libya', 'Iceland', 'Brazil/East', 'CET', 'Asia/Chongqing', 'America/Nipigon', 'Asia/Saigon', 'America/Rainy_River', 'GMT-0', 'Japan', 'Asia/Thimbu', 'Australia/Currie', 'Etc/GMT+2', 'Etc/GMT+5', 'Eire', 'PRC', 'Europe/Belfast', 'Etc/GMT+8', 'Canada/Saskatchewan', 'Asia/Tel_Aviv', 'Asia/Katmandu', 'Pacific/Yap', 'Israel', 'America/Jujuy', 'Etc/GMT-2', 'Greenwich', 'Europe/Zaporozhye', 'Australia/North', 'Etc/GMT+0', 'Asia/Kashgar', 'Australia/Yancowinna', 'Chile/Continental', 'America/Shiprock', 'Etc/Greenwich', 'Brazil/West', 'Pacific/Truk', 'WET', 'GB-Eire', 'Etc/GMT-14', 'America/Thunder_Bay', 'EST', 'Australia/ACT', 'Asia/Ujung_Pandang', 'America/Louisville', 'America/Catamarca', 'Etc/GMT+3', 'PST8PDT', 'Pacific/Johnston', 'Australia/LHI', 'Canada/Yukon', 'Etc/GMT', 'Iran', 'Mexico/General', 'Brazil/DeNoronha', 'US/Indiana-Starke', 'Atlantic/Jan_Mayen', 'UCT', 'Etc/GMT-4', 'Antarctica/South_Pole', 'Etc/Universal', 'America/Knox_IN', 'Etc/GMT-13', 'NZ-CHAT', 'Australia/Queensland', 'Etc/GMT-11', 'America/Ensenada', 'America/Fort_Wayne', 'America/Pangnirtung', 'Cuba', 'Africa/Asmera', 'Mexico/BajaNorte', 'Etc/GMT-6', 'America/Virgin', 'Europe/Tiraspol', 'EST5EDT', 'HST', 'America/Atka', 'Asia/Dacca', 'Africa/Timbuktu', 'Etc/GMT+10', 'Asia/Rangoon', 'ROK', 'US/Michigan', 'Hongkong', 'America/Cordoba', 'Poland', 'NZ', 'US/Samoa', 'Asia/Chungking', 'Etc/GMT-1', 'America/Mendoza', 'ROC', 'Australia/South', 'Australia/Victoria', 'Etc/GMT+6', 'CST6CDT', 'Etc/GMT-10', 'Etc/GMT-5', 'Etc/GMT+12', 'Turkey', 'Europe/Uzhgorod', 'Pacific/Samoa', 'Pacific/Enderbury', 'Universal', 'Etc/Zulu', 'America/Rosario', 'America/Porto_Acre', 'Etc/GMT-7', 'Kwajalein', 'Etc/GMT-9', 'Asia/Harbin', 'EET', 'Asia/Ashkhabad', 'Jamaica', 'Etc/UTC', 'America/Yellowknife', 'Etc/GMT+1', 'Asia/Macao', 'GMT0', 'Asia/Istanbul', 'MST', 'Australia/Tasmania', 'Australia/West', 'America/Godthab', 'Chile/EasterIsland', 'America/Montreal', 'US/Aleutian', 'Asia/Calcutta', 'US/East-Indiana', 'Etc/GMT-8', 'Navajo', 'Etc/GMT+9', 'Pacific/Ponape', 'Australia/NSW'}
#创建不带时区信息的时间戳
t_naive = pd.Timestamp('2016-07-10 08:50')
t_naive
Timestamp('2016-07-10 08:50:00')
#将本地时间转换为具有特定时区信息的时间
t = t_naive.tz_localize(tz = 'US/Central')
t
Timestamp('2016-07-10 08:50:00-0500', tz='US/Central')
#将已经具有时区信息的对象转换为另一个时区
t.tz_convert('Asia/Shanghai')
Timestamp('2016-07-10 21:50:00+0800', tz='Asia/Shanghai')
#如何处理夏令时?
#创建带有美国中部时区的日期时间索引,并将其用作一个时间序列的索引
rng = pd.date_range('2016-03-10', periods=10, tz='US/Central')
ts = pd.Series(range(10), index=rng)
ts
2016-03-10 00:00:00-06:00 0
2016-03-11 00:00:00-06:00 1
2016-03-12 00:00:00-06:00 2
2016-03-13 00:00:00-06:00 3
2016-03-14 00:00:00-05:00 4
2016-03-15 00:00:00-05:00 5
2016-03-16 00:00:00-05:00 6
2016-03-17 00:00:00-05:00 7
2016-03-18 00:00:00-05:00 8
2016-03-19 00:00:00-05:00 9
Freq: D, dtype: int64
1. 创建带有美国中部时区的时间序列
- 创建一个时间序列(
Series
),其中的时间点包含 美国中部时区(US/Central) 的时区信息。 - 注意,夏令时的切换会在 2016 年 3 月 13 日发生:
- 3 月 12 日之前的时间是标准时间(
-06:00
,UTC-6)。 - 从 3 月 13 日开始,切换为夏令时(
-05:00
,UTC-5)。
- 3 月 12 日之前的时间是标准时间(
- 这是为了说明
pandas
会自动处理夏令时的切换。在时间序列中,当时区发生变化(如进入夏令时),pandas
会自动调整时间偏移量。
#创建带有协调世界时时区的日期时间索引,并将其用作一个时间序列的索引
rng = pd.date_range('2016-03-10', periods=10, tz='utc')
ts = pd.Series(range(10), index=rng)
ts
2016-03-10 00:00:00+00:00 0
2016-03-11 00:00:00+00:00 1
2016-03-12 00:00:00+00:00 2
2016-03-13 00:00:00+00:00 3
2016-03-14 00:00:00+00:00 4
2016-03-15 00:00:00+00:00 5
2016-03-16 00:00:00+00:00 6
2016-03-17 00:00:00+00:00 7
2016-03-18 00:00:00+00:00 8
2016-03-19 00:00:00+00:00 9
Freq: D, dtype: int64
2. 创建带有协调世界时(UTC)的时间序列
- 创建一个时间序列,所有时间点都在 UTC(协调世界时)。
- UTC 不受夏令时的影响,所以每个时间点的时区偏移量始终是
+00:00
。 - 使用 UTC 作为时间序列的标准时区,可以避免时区和夏令时切换的问题。在许多跨时区的应用中,通常使用 UTC 作为基础时间戳。
#将时间序列中的时间点转换为特定的时区,以便在特定时区进行处理和分析。
ts.tz_convert('US/Central')
2016-03-09 18:00:00-06:00 0
2016-03-10 18:00:00-06:00 1
2016-03-11 18:00:00-06:00 2
2016-03-12 18:00:00-06:00 3
2016-03-13 19:00:00-05:00 4
2016-03-14 19:00:00-05:00 5
2016-03-15 19:00:00-05:00 6
2016-03-16 19:00:00-05:00 7
2016-03-17 19:00:00-05:00 8
2016-03-18 19:00:00-05:00 9
Freq: D, dtype: int64
3. 将时间序列从 UTC 转换为其他时区
- 将之前的 UTC 时间序列(
ts
)转换为 美国中部时区(US/Central)。 - 观察到时间序列的时间点发生了变化:
- 例如:
2016-03-10 00:00:00+00:00
转换为2016-03-09 18:00:00-06:00
。 - 这是因为美国中部时区相对于 UTC 是 UTC-6(标准时间),在夏令时期间是 UTC-5。
- 例如:
tz_convert
是用来转换时间序列的时区。- 转换后,时间点会自动调整到目标时区,并正确处理夏令时切换。
#创建包含12个时间点的日期时间索引,每个时间点相隔1小时,且带有美国东部时区的信息
pd.date_range('03-12-2016 22:00', periods = 12, freq = 'h', tz = 'US/Eastern')
/tmp/ipykernel_123/3463310447.py:2: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.pd.date_range('03-12-2016 22:00', periods = 12, freq = 'H', tz = 'US/Eastern')DatetimeIndex(['2016-03-12 22:00:00-05:00', '2016-03-12 23:00:00-05:00','2016-03-13 00:00:00-05:00', '2016-03-13 01:00:00-05:00','2016-03-13 03:00:00-04:00', '2016-03-13 04:00:00-04:00','2016-03-13 05:00:00-04:00', '2016-03-13 06:00:00-04:00','2016-03-13 07:00:00-04:00', '2016-03-13 08:00:00-04:00','2016-03-13 09:00:00-04:00', '2016-03-13 10:00:00-04:00'],dtype='datetime64[ns, US/Eastern]', freq='h')
4. 创建带有美国东部时区的时间序列
- 创建一个以小时为频率的时间序列,从 2016 年 3 月 12 日晚上 10 点开始,持续 12 小时。
- 包含 美国东部时区(US/Eastern) 的时区信息。
- 夏令时切换:
- 2016 年 3 月 13 日凌晨 2 点 -> 凌晨 3 点直接跳过,体现了夏令时的切换。
- 3 月 13 日之后的时间偏移变为
-04:00
,表示夏令时(UTC-4)。
思考:这段代码想干什么?
-
展示如何处理时区和夏令时:
-
说明时区转换的用法:
- 使用
tz_convert
方法将时间序列转换到其他时区,以便在特定时区下进行分析。 - 例如:UTC 是常见的基础时区,但数据可能需要在本地时区中呈现。
- 使用
-
模拟跨时区分析场景:
八、读取数据并制作数据框
#读取数据
data = pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = True, header = None,)
警告如下:
/tmp/ipykernel_51/101956276.py:2: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format. data = pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = True, header = None,)
警告的原因
infer_datetime_format=True
的作用:dateutil
的回退机制:- 如果不能推断出格式,
pandas
会使用 Python 内置的dateutil
模块逐个解析日期。这种方法会较慢且容易出错。
- 如果不能推断出格式,
# 指定日期时间格式
data = pd.read_fwf("climate.txt",parse_dates=[[0, 1]], # 合并第 0 列和第 1 列为日期时间date_format='%Y %m', # 指定日期时间格式header=None
)
#显示前几行数据
data.head()
0_1 | 2 | |
---|---|---|
0 | 1950-01-01 | -0.0603 |
1 | 1950-02-01 | 0.6268 |
2 | 1950-03-01 | -0.0081 |
3 | 1950-04-01 | 0.5551 |
4 | 1950-05-01 | 0.0716 |
data.columns = ['month','value'] #修改列名
data.index = data.month #将 'month' 列的值作为新的索引,替代原来的默认整数索引
data = data.drop('month',axis = 1) #删除原始的 'month' 列,避免出现重复列
#显示前几行数据
data.head()
value | |
---|---|
month | |
1950-01-01 | -0.0603 |
1950-02-01 | 0.6268 |
1950-03-01 | -0.0081 |
1950-04-01 | 0.5551 |
1950-05-01 | 0.0716 |
#进行切片操作,选择索引值在 '1950' 到 '1952' 之间的所有行
#注意日期范围
data['1950':'1952']
value | |
---|---|
month | |
1950-01-01 | -0.0603 |
1950-02-01 | 0.6268 |
1950-03-01 | -0.0081 |
1950-04-01 | 0.5551 |
1950-05-01 | 0.0716 |
1950-06-01 | 0.5386 |
1950-07-01 | -0.8025 |
1950-08-01 | -0.8510 |
1950-09-01 | 0.3580 |
1950-10-01 | -0.3789 |
1950-11-01 | -0.5151 |
1950-12-01 | -1.9281 |
1951-01-01 | -0.0850 |
1951-02-01 | -0.3999 |
1951-03-01 | -1.9341 |
1951-04-01 | -0.7765 |
1951-05-01 | -0.8628 |
1951-06-01 | -0.9179 |
1951-07-01 | 0.0900 |
1951-08-01 | -0.3774 |
1951-09-01 | -0.8178 |
1951-10-01 | -0.2129 |
1951-11-01 | -0.0685 |
1951-12-01 | 1.9872 |
1952-01-01 | 0.3682 |
1952-02-01 | -1.7472 |
1952-03-01 | -1.8595 |
1952-04-01 | 0.5385 |
1952-05-01 | -0.7735 |
1952-06-01 | -0.4409 |
1952-07-01 | 0.3831 |
1952-08-01 | -0.0304 |
1952-09-01 | -0.3834 |
1952-10-01 | -0.4372 |
1952-11-01 | -1.8909 |
1952-12-01 | -1.8267 |
#索引类型
type(data.index)
pandas.core.indexes.datetimes.DatetimeIndex
#进行切片操作,选择索引值在 '1951-11-11' 到 '1951-11-12' 之间的所有行
data['1951-11-11':'1951-11-12']
value | |
---|---|
month |
#获取周期索引
data_pd = data.to_period()
#切片
data_pd['1951-11-11':'1951-11-12']
value | |
---|---|
month | |
1951-11 | -0.0685 |
#切片
data_pd['1951-11-11':'1952-01-12']
value | |
---|---|
month | |
1951-11 | -0.0685 |
1951-12 | 1.9872 |
1952-01 | 0.3682 |
#各种数据加载是如何执行的?
#测试 Pandas 的 read_fwf() 函数在不同参数配置下的性能
import timeit print("infer_datetime_format = True, no date parser")
%timeit pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = True, header = None,) print("infer_datetime_format = False, no date parser")
%timeit pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = False, header = None,) print("infer_datetime_format = True, date parser provided")
dateparse = lambda x, y: pd.datetime.strptime('%s-%s'%(x,y), '%Y-%m')
%timeit pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = True, date_parser = dateparse, header = None,) print("infer_datetime_format = False, date parser provided")
dateparse = lambda x, y: pd.datetime.strptime('%s-%s'%(x,y), '%Y-%m')
%timeit pd.read_fwf("climate.txt", parse_dates = [[0, 1]], infer_datetime_format = False, date_parser = dateparse, header = None,)
这段代码,这里就不测试了,在新版中,date_parser
参数已经被标记为废弃(deprecated
),将会在未来的 pandas
版本中被移除。这里运行会报特别多的警告。
# 在已经有了数据框的情况下解析列
df = pd.DataFrame({'year': [2015, 2016],'month': [2, 3],'day': [4, 5],'hour': [2, 3]})
df
year | month | day | hour | |
---|---|---|---|---|
0 | 2015 | 2 | 4 | 2 |
1 | 2016 | 3 | 5 | 3 |
#将包含日期时间信息的数据转换为 Pandas 中的 datetime 类型
pd.to_datetime(df)
0 2015-02-04 02:00:00
1 2016-03-05 03:00:00
dtype: datetime64[ns]
#将 'year'、'month' 和 'day' 列的数据合并成一个日期时间列,并将结果转换为 Pandas 中的 datetime 类型
pd.to_datetime(df[['year', 'month', 'day']])
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
思考:
1. 是否适用于其他列名?
答案:可以适用,但需要调整列名或显式指定列的顺序。
-
默认行为: 如果列名为
'year'
、'month'
、'day'
,pd.to_datetime
可以自动识别并解析。 -
非默认列名: 如果列名不是默认的(如
['Year', 'Month', 'Day']
或其他),需要手动指定列的顺序,例如:df = pd.DataFrame({'Year': [2015, 2016], 'Month': [2, 3], 'Day': [4, 5]}) pd.to_datetime(df[['Year', 'Month', 'Day']].rename(columns={'Year': 'year', 'Month': 'month', 'Day': 'day'}))
或者,直接传递列值的数组:
pd.to_datetime({'year': df['Year'], 'month': df['Month'], 'day': df['Day']})
-
如果包含时间信息: 如果列还包含时间信息(如
'hour'
、'minute'
、'second'
),pd.to_datetime
也支持解析。例如:df = pd.DataFrame({ 'Year': [2015, 2016], 'Month': [2, 3], 'Day': [4, 5], 'Hour': [2, 3], 'Minute': [30, 45] }) pd.to_datetime({'year': df['Year'], 'month': df['Month'], 'day': df['Day'], 'hour': df['Hour'], 'minute': df['Minute']})
2. 获取你自己的时间序列数据并加载,能看到什么?
- 获取一个带有时间数据的数据集,可以是本地文件(如 CSV、Excel)或者在线数据源(如金融数据、气象数据)。
- 使用
pd.to_datetime
解析时间列,并探索数据的基本特性。
以一个带时间信息的 CSV 文件为例:
import pandas as pd # 示例数据
data = { 'date': ['2023-01-01', '2023-02-01', '2023-03-01'], 'value': [100, 150, 200]
}
df = pd.DataFrame(data) # 将 'date' 列解析为 datetime
df['date'] = pd.to_datetime(df['date']) # 查看数据
print(df.info())
print(df)
看到什么?
- 时间列的解析与数据类型:
'date'
列会被转换为datetime64[ns]
类型,方便进行时间序列分析。 - 时间列的索引作用: 可以将时间列设置为索引,便于按时间进行切片、筛选等操作:
df.set_index('date', inplace=True)
print(df)
3. 探索时间序列数据的三个任务
(1)绘制图表
- 使用
matplotlib
或pandas
的内置绘图功能:
import matplotlib.pyplot as plt # 示例数据
df = pd.DataFrame({ 'date': pd.date_range(start='2023-01-01', periods=12, freq='M'), 'value': [100, 110, 105, 115, 120, 130, 125, 135, 140, 150, 155, 160]
})
df.set_index('date', inplace=True) # 绘制时间序列图
df.plot(y='value', title='Time Series Plot')
plt.show()
(2)获取时间范围
- 获取时间序列的起止时间、时间跨度:
print("起始时间:", df.index.min())
print("结束时间:", df.index.max())
print("时间跨度:", df.index.max() - df.index.min())
(3)时间戳和时间段的转换
- 时间戳(
Timestamp
)转时间段(Period
): 时间段表示一个时间跨度(如一个月、一年)。
# 时间戳转换为时间段
df['period'] = df.index.to_period('M') # 'M' 表示月,'Y' 表示年
print(df)
- 时间段(
Period
)转时间戳(Timestamp
): 如果需要具体的起始或结束时间:
# 时间段转换为时间戳
df['start_of_period'] = df['period'].dt.start_time # 时间段的开始
df['end_of_period'] = df['period'].dt.end_time # 时间段的结束
print(df)
#创建时间序列,并对该时间序列进行了截取操作
ts = pd.Series(range(10), index = pd.date_range('7/31/2015', freq = 'M', periods = 10))
ts.truncate(before='10/31/2015', after='12/31/2015')
/tmp/ipykernel_123/1076020219.py:2: FutureWarning: 'M' is deprecated and will be removed in a future version, please use 'ME' instead.ts = pd.Series(range(10), index = pd.date_range('7/31/2015', freq = 'M', periods = 10))2015-10-31 3
2015-11-30 4
2015-12-31 5
Freq: ME, dtype: int64
#在截断时间序列时,可能会破坏时间序列的频率。需要谨慎选择截断方式,确保截断后的时间序列仍然保持为需要的频率。
#返回索引为 0、2 和 6 的数据点对应的时间索引
ts[[0, 2, 6]].index
/tmp/ipykernel_123/1690724394.py:3: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`ts[[0, 2, 6]].indexDatetimeIndex(['2015-07-31', '2015-09-30', '2016-01-31'], dtype='datetime64[ns]', freq=None)
#对时间序列 ts 进行按位置的切片操作
ts.iloc[0:10:2].index
DatetimeIndex(['2015-07-31', '2015-09-30', '2015-11-30', '2016-01-31','2016-03-31'],dtype='datetime64[ns]', freq='2ME')
总结
在本关卡中,我们详细介绍了Pandas在时间数据处理方面的多种功能和应用。通过一系列的示例和练习,我们学习了如何创建和操作时间戳(Timestamp)、日期时间索引(DatetimeIndex)、时间段(Period)、时间差(Timedelta)以及周期索引(PeriodIndex)。此外,我们还探讨了时区处理、节假日日历的创建和数据加载的技巧。