pandas处理时间序列(2):DatetimeIndex、索引和选择、含有重复索引的时间序列、日期范围与频率和移位、时间区间和区间算术
一、时间序列基础
1. 时间戳索引DatetimeIndex
生成20个DatetimeIndex
1 2 3 | from datetime import datetime dates = pd.date_range(start = '2019-04-01' ,periods = 20 ) dates |
用这20个索引作为ts的索引
1 2 | ts = pd.Series(np.random.randn( 20 ),index = dates) ts |
不同索引的时间序列之间的算术运算在日期上自动对齐
1 | ts + ts[:: 2 ] |
pandas使用numpy的datetime64数据类型在纳秒级的分辨率下存储时间戳
1 | ts.index.dtype |
DatetimeIndex中的标量值是pandas的Timestamp对象
1 2 | stamp = ts.index[ 0 ] stamp |
2. 索引、选择
(1) 索引
ts是一个series;stamp是索引为2的时间戳,Timestamp('2019-04-03 00:00:00', freq='D')
1 2 3 | stamp = ts.index[ 2 ] ts[stamp] |
为了方便,可以传递一个能解释为日期的字符串
(2) 选择
[1]对于长的时间序列,可以传递一个年份或一个年份和月份来选择数据的切片
1 2 | longer_ts = pd.Series(np.random.randn( 10 ),index = pd.date_range( '4/1/2019' ,periods = 10 )) longer_ts |
选择2019年4月份的所有数据
1 | longer_ts.loc[ '2019-4' ] #可以写成'2019/04',不能写成'201904' |
选择2019年的所有数据
1 | longer_ts[ '2019' ] |
[2]选择一段时间内的数据
1 | ts[datetime( 2019 , 1 , 1 ):] |
1 | ts[ '1/4/2019' : '4/10/2019' ] |
truncate也可以实现在两个日期间对Series进行切片
1 | ts.truncate(after = '4/3/2019' ) |
以上操作也都适用于DataFrame
3. 含有重复索引的时间序列
在某些应用中,可能会有多个数据观察值落在特定的时间戳中。
1 2 3 4 | dates = pd.DatetimeIndex([ '4/1/2019' , '4/2/2019' , '4/2/2019' , '4/2/2019' , '4/3/2019' ]) dup_ts = pd.Series(np.arange( 5 ),index = dates) dup_ts |
通过检查索引的is_unique属性,我们可以看出索引并不是唯一的。
1 | dup_ts.index.is_unique |
对上面的Series进行索引,结果是标量值还是Series切片取决于是否有时间戳是重复的。
假设你想要聚合含有非唯一时间戳的数据,一种方式就是使用groupby并传递level=0
二、日期范围、频率和移位
有些应用中经常需要处理固定频率的场景,例如每日的、每月的或每10分钟,这意味着我们甚至需要在必要的时候向时间序列中引入缺失值。pandas可以帮助我们重新采样、推断频率以及生成固定频率的数据范围。
1. 生成日期范围pd.date_range()、pd.period_range()
常用函数:
- pd.date_range(),生成的是DatetimeIndex格式的日期序列;
- pd.period_range(),生成PeriodIndex的时期日期序列。
(1)pd.date_range()
参数:起始时间,结束时间,freq,periods (四选三)
开始日期和结束日期严格定义了生成日期索引的边界, 周时间序列,默认以sunday周日作为一周最后一日;若要改成周一作为第一天,freq='W-SAT'
freq='M'月,'D'天,'W',周,'Y'年。默认情况下,date_range生成的是每日的时间戳,如果只传递一个起始或结尾日期,就必须要传递一个用于生成范围的数字,如 “BM” 代表 bussiness end of month
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #使用date_range生成日期序列 #如要详细了解该函数,可以使用help(pd.date_range) #参数四选三:起始时间,结束时间,freq,periods #freq='M'月,'D'天,'W',周,'Y'年 #生成月时间序列 dm = pd.date_range( '2018/01/01' , freq = 'M' , periods = 12 ) print (f '生成月时间序列:\n{dm}' ) #算头不算尾 #生成年时间序列,默认是以12月结尾,freq='Y-DEC' dy = pd.date_range( '2008-01-01' , '2019-01-10' ,freq = 'Y' ) print (f '生成年时间序列:\n{dy}' ) #生成日时间序列 dd = pd.date_range( '2018-01-01' ,freq = 'D' ,periods = 10 ) print (f '生成日时间序列:\n{dd}' ) #生成周时间序列,默认以sunday周日作为一周最后一日 #如要改成周一作为第一天,freq='W-SAT' dw = pd.date_range( '2018-01-01' ,freq = 'W' ,periods = 10 ) print (f '生成周时间序列:\n{dw}' ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import matplotlib.pyplot as plt plt.rcParams[ 'font.sans-serif' ] = [ 'SimHei' ] plt.rcParams[ 'axes.unicode_minus' ] = False #画以时间为x轴的图,pandas的DataFrame自动将index列作为x轴 np.random.seed( 2 ) #生成日期序列 x = pd.date_range( '2018/01/01' , '2019/12/31' , freq = 'd' ) #x=pd.period_range('2018/01/01','2019/12/31', freq='d') #标准正态分布时间序列 y = np.random.standard_normal( len (x)) #将二者转换为pandas的数据格式 df = pd.DataFrame(y,columns = [ '标准正态分布' ],index = x) df.plot(figsize = ( 12 , 6 )) plt.title( '模拟标准正态分布随机数' ) ax = plt.gca() ax.spines[ 'right' ].set_color( 'none' ) ax.spines[ 'top' ].set_color( 'none' ) plt.show() |
(2)pd.period_range()
具体period见第五节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #使用period_range生成日期序列 #参数四选三:起始时间,结束时间,freq,periods #freq='M'月,'D'天,'W',周,'Y'年 #生成月时期序列 dpm = pd.period_range( '2019/01/01' , freq = 'M' , periods = 12 ) print (f '生成月时间序列:\n{dpm}' ) #生成年时期序列,默认是以12月结尾,freq='Y-DEC' dpy = pd.period_range( '2008-01-01' , '2019-01-10' ,freq = 'Y' ) print (f '生成年时间序列:\n{dpy}' ) #生成日时期序列 dpd = pd.period_range( '2018-01-01' ,freq = 'D' ,periods = 10 ) print (f '生成日时间序列:\n{dpd}' ) #生成周时期序列,默认以sunday周日作为一周最后一日 #如要改成周一作为第一天,freq='W-SAT' dpw = pd.period_range( '2018-01-01' ,freq = 'W-SUN' ,periods = 10 ) print (f '生成周时间序列:\n{dpw}' ) |
其他的一些freq频率值,见下图
有时候会获得包含时间信息的开始日期或结束日期,但是想要生成的是标准化为零点的时间戳。用normalize=True就可以解决
1 | pd.date_range(start = '2019-4-1 12:45:23' ,periods = 5 ) #不加normalize |
1 | pd.date_range(start = '2019-4-1 12:45:23' ,periods = 5 ,normalize = True ) |
2. 频率和日期偏置
pandas中的频率是由基础频率和倍数组成的。基础频率通常会有字符串别名,例如'M'代表每月,'H'代表每小时。对于每个基础频率,都有一个对象可以被用于定义日期偏置。例如,每小时的频率可以使用Hour类来表示。
1 2 3 | from pandas.tseries.offsets import Hour,Minute hour = Hour() hour |
1 2 | four_hours = Hour( 4 ) four_hours |
1 | pd.date_range(start = '2019-4-1' ,periods = 5 ,freq = '4h' ) |
1 | pd.date_range(start = '2019-4-1' ,periods = 5 ,freq = '1h30min' ) |
月中某星期的日期week of month
例子:每月第三个星期五
1 2 | rng = pd.date_range( '2019-4-1' , '2019-7-5' ,freq = 'WOM-3FRI' ) list (rng) |
3. 移位(前向和后向)日期
“移位”是指将日期按时间向前移动或向后移动。Series和DataFrame都有一个shift方法用于进行简单的前向或后向移位,而不改变索引。
(1)shift
1 2 3 | ts = pd.Series(np.random.randn( 4 ), index = pd.date_range( '4/1/2019' ,periods = 4 ,freq = 'M' )) ts |
1 | ts.shift( 2 ) |
1 | ts.shift( - 2 ) |
由于简单移位并不改变索引,一些数据会被丢弃。因此,如果频率是已知的,则可以将频率传递给shift来推移时间戳而不是简单的数据:
1 | ts.shift( 2 ,freq = 'D' ) |
1 | ts.shift( 2 ,freq = 'M' ) #'M'日历月末 |
1 | ts.shift( 2 ,freq = 'BM' ) #'BM'月内最后工作日 |
(2)使用偏置进行移位日期
1 2 3 | from pandas.tseries.offsets import Day,MonthEnd now = datetime.now() now |
1 | now + 3 * Day() |
如果添加锚定偏置量,比如MonthEnd,根据频率规则,第一个增量会将日期“前滚”到下一个日期:
1 | now + MonthEnd() |
1 | now + MonthEnd( 2 ) |
锚定偏置可以使用rollforward和rollback分别显示地将日期向前或向后“滚动”;
1 2 | offset = MonthEnd() offset.rollforward(now) |
1 | offset.rollback(now) |
将移位方法与groupby一起使用是日期偏置的一种创造性用法:
1 2 | ts = pd.Series(np.random.randn( 20 ),index = pd.date_range( '4/1/2019' ,periods = 20 ,freq = '4d' )) ts |
1 | ts.groupby(offset.rollforward).mean() |
resample可以得到同样的结果
1 | ts.resample( 'M' ).mean() |
三、时区处理
待用到时候再添加
四、时间区间和区间算术
1. Period、period_range
(1)Period
时间区间Period表示的是时间范围,比如数日、数月、数季、数年等。
定义一个时间区间Period
1 2 | p = pd.Period( 2019 ,freq = 'A-DEC' ) p |
通过加减整数可以实现对Period的移动
如果两个Period对象拥有相同频率,则它们的差就是它们之间的单位数量
1 | pd.Period( '2018' ,freq = 'A-DEC' ) - p |
(2)period_range
参数:起始时间,结束时间,freq,periods(四选三)
freq='M'月,'D'天,'W',周,'Y'年
1 2 3 4 5 6 7 8 9 10 11 12 13 | #生成月时期序列 dpm = pd.period_range( '2019/01/01' , freq = 'M' , periods = 12 ) print (f '生成<strong>月</strong>时间序列:\n{dpm}' ) #生成年时期序列,默认是以12月结尾,freq='Y-DEC' dpy = pd.period_range( '2008-01-01' , '2019-01-10' ,freq = 'Y' ) print (f '生成<strong>年</strong>时间序列:\n{dpy}' ) #生成日时期序列 dpd = pd.period_range( '2018-01-01' ,freq = 'D' ,periods = 10 ) print (f '生成<strong>日</strong>时间序列:\n{dpd}' ) #生成周时期序列,默认以sunday周日作为一周最后一日 #如要改成周一作为第一天,freq='W-SAT' dpw = pd.period_range( '2018-01-01' ,freq = '<strong>W-SUN</strong>' ,periods = 10 ) print (f '生成<strong>周</strong>时间序列:\n{dpw}' ) |
(3)PeriodIndex
PeriodIndex类的构造函数允许直接使用一组字符串表示一段时期
1 2 3 | values = [ '2001Q3' , '2002Q2' , '2003Q1' ] index = pd.PeriodIndex(values,freq = 'Q-DEC' ) index |
2. 区间频率转换
- 高频数据向低频数据转换;
- 低频数据向高频数据转换。
主要函数:df.resample(),df代表pandas的DataFrame格式数据,resample方法的参数参数中,freq表示重采样频率,例如‘M’、‘5min’,Second(15),用于产生聚合值的函数名或数组函数,例如‘mean’、‘ohlc’、np.max等,默认是‘mean’,其他常用的有:‘first’、‘last’、‘median’、‘max’、‘min’axis=0默认是纵轴,横轴设置axis=1
(1)asfreq转换频率
Period和PeriodIndex对象可以通过asfreq方法被转换成别的频率。
假设有一个年度时期,希望将其转换为当年年初或年末的一个月度时期:
频率为‘A-DEC’表示一年的开始到结尾的每一条,'start'表示年初,'end'表示年末
【1】Period
对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了:
【2】PeriodIndex或TimeSeries的频率转换
举例:频率转换
1 2 3 4 5 | #frq='W'代表周 df = pd.DataFrame(np.random.randn( 5 , 4 ), index = pd.date_range( '1/4/2019' ,periods = 5 ,freq = 'W' ), columns = [ 'GZ' , 'BJ' , 'SH' , 'SZ' ]) df |
低频数据向高频数据转换【周-日】
1 2 3 4 | #将上述样本转换为日序列,缺失值使用前值补上 #如使用后值则用bfill() df_daily = df.resample( 'D' ).ffill() df_daily.head( 10 ) |
高频数据向低频数据转化【日-月】
1 2 | df_daily1 = df.resample( 'M' ).ffill() df_daily1.head( 10 ) |
根据period来重采样
1 2 3 4 5 | #根据period来重采样 df1 = pd.DataFrame(np.random.randn( 5 , 4 ), index = pd.period_range( '1/1/2017' ,periods = 5 ,freq = 'W' ), columns = [ 'GZ' , 'BJ' , 'SH' , 'SZ' ]) df1.head() |
1 2 3 4 | df2 = pd.DataFrame(np.random.randn( 2 , 4 ), index = pd.period_range( '1-2017' , '12-2018' ,freq = 'A' ), columns = [ 'GZ' , 'BJ' , 'SH' , 'SZ' ]) df2.head() |
3. 季度区间频率
季度型数据在会计、金融等领域中很常见,许多季度型数据都会涉及“财年末”的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期“2012Q4”根据财年末的不同会有不同的含义。pandas支持12种可能的季度型频率,即Q-JAN到Q-DEC:
2012Q4指的是2011年第四个季度,也就是2011.11.01-2012-01.31
在以1月结束的财年中,2012Q4是从11月到1月(将其转换为日型频率就明白了)
例如:要获取该季度倒数第二个工作日下午4点的时间戳:
period_range可以生成季度型范围。季度型范围的算术运算也跟上面是一样的:
4. 时间戳Timestamp与Period区间的转换
(1)to_period()
由于时期指的是非重叠时间区间,因此对于给定的频率,一个给定的时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中语序存在重复时期:
(2)to_timestamp()
5. 通过数组创建PeriodIndex
固定频率的数据集通常会将时间信息分开存放在多个列中。例如:年度和季度就分布存放在不同的列中:
将这两个数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引:
参考文献:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!