时间序列 | pandas时间序列基础
时间序列(time series
)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每15秒、每5分钟、每月出现一次
)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种:
时间戳(
timestamp
),特定的时刻。固定时期(
period
),如2008年1月或2020年全年。时间间隔(
interval
),由起始和结束时间戳表示。时期(period
)可以被看做间隔(interval
)的特例。
本文内容包括,索引、选取、子集构造,日期的范围、频率以及移动基础等。
首先回顾下datetime模块
>>> from datetime import datetime
>>> dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),
... datetime(2011, 1, 7), datetime(2011, 1, 8),
... datetime(2011, 1, 10), datetime(2011, 1, 12)]
>>> ts = pd.Series(np.random.randn(6),index = dates)
>>> ts
2011-01-02 -0.162712
2011-01-05 1.876604
2011-01-07 -0.016393
2011-01-08 0.239276
2011-01-10 -0.704732
2011-01-12 -1.502936
dtype: float64
# 这些datetime对象实际上是被放在一个DatetimeIndex中
>>> ts.index
DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07',
'2011-01-08','2011-01-10', '2011-01-12']
,dtype='datetime64[ns]', freq=None)
# DatetimeIndex中的各个标量值是pandas的Timestamp对象
>>> stamp = ts.index[0]
>>> stamp
Timestamp('2011-01-02 00:00:00')
索引、选取、子集构造
根据标签索引
>>> stamp = ts.index[2]
>>> stamp
Timestamp('2011-01-07 00:00:00')
传入一个可以被解释为日期的字符串
>>> ts['1/10/2011']
-0.7047322514407551
>>> ts['20110110']
-0.7047322514407551
对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片
>>> long_ts = pd.Series(np.random.randn(1000)
,index = pd.date_range('01/01/2000'
,periods = 1000))
>>> long_ts
2000-01-01 1.054099
2000-01-02 -1.554446
2000-01-03 0.576552
2000-01-04 0.785388
2000-01-05 -1.674210
...
2002-09-22 -0.836620
2002-09-23 0.561945
2002-09-24 -0.196348
2002-09-25 -0.937816
2002-09-26 -0.431839
Freq: D, Length: 1000, dtype: float64
>>> long_ts['2002']
2002-01-01 -1.084543
2002-01-02 -2.452728
2002-01-03 0.415411
2002-01-04 1.284758
2002-01-05 -0.449588
...
2002-09-22 -0.836620
2002-09-23 0.561945
2002-09-24 -0.196348
2002-09-25 -0.937816
2002-09-26 -0.431839
Freq: D, Length: 269, dtype: float64
>>> long_ts['2002-08']
2002-08-01 1.061041
2002-08-02 0.181386
2002-08-03 0.697954
2002-08-04 0.729076
2002-08-05 -0.573171
...
2002-08-27 -1.240861
2002-08-28 0.397729
2002-08-29 -2.765702
2002-08-30 -2.228761
2002-08-31 -0.386724
Freq: D, dtype: float64
datetime对象也可以进行切
>>> long_ts[datetime(2001,5,10)::100]
2001-05-10 -1.412274
2001-08-18 -0.051782
2001-11-26 -0.948275
2002-03-06 0.565756
2002-06-14 0.040260
2002-09-22 -0.836620
Freq: 100D, dtype: float64
>>> '''由于大部分时间序列数据都是按照时间先后排序的,
... 因此你也可以用不存在于该时间序列中的时间戳对其进行切片(即范围查询)'''
>>> ts['1/6/2011':'1/11/2011']
2011-01-07 -0.016393
2011-01-08 0.239276
2011-01-10 -0.704732
dtype: float64
truncate()等价的实例方法也可以截取两个日期之间TimeSeries:
>>> ts.truncate(after = '1/9/2011')
2011-01-02 -0.162712
2011-01-05 1.876604
2011-01-07 -0.016393
2011-01-08 0.239276
dtype: float64
对DataFrame的行进行索引
>>> dates = pd.date_range('2/1/2020', periods = 10, freq = 'W-WED')
>>> dates_1 = pd.date_range('2/1/2020', periods = 10, freq = 'W-SUN')
>>> dates
DatetimeIndex(['2020-02-05', '2020-02-12', '2020-02-19', '2020-02-26',
'2020-03-04', '2020-03-11', '2020-03-18', '2020-03-25',
'2020-04-01', '2020-04-08'],
dtype='datetime64[ns]', freq='W-WED')
>>> dates_1
DatetimeIndex(['2020-02-02', '2020-02-09', '2020-02-16', '2020-02-23',
'2020-03-01', '2020-03-08', '2020-03-15', '2020-03-22',
'2020-03-29', '2020-04-05'],
dtype='datetime64[ns]', freq='W-SUN')
>>> long_df = pd.DataFrame(np.random.randn(10, 4)
, index=dates
, columns=['Colorado', 'Texas','New York', 'Ohio'])
>>> long_df
Colorado | TexasNew York | New YorkOhio | Ohio | |
---|---|---|---|---|
2020-02-02 | -0.029801 | 0.514341 | -0.141655 | 1.715595 |
2020-02-09 | -0.824540 | -0.296288 | -0.827877 | 0.245774 |
2020-02-16 | -0.084484 | 0.126171 | 0.423891 | -1.709223 |
2020-02-23 | -1.688280 | 1.032951 | -0.062944 | -0.521174 |
2020-03-01 | 0.322167 | 0.226218 | 0.515736 | 0.098784 |
2020-03-08 | 2.117617 | 1.840786 | -0.283062 | -1.414009 |
2020-03-15 | -0.853829 | 0.101525 | 0.395773 | -0.938408 |
2020-03-22 | 0.022767 | -1.761823 | 0.361345 | -1.015794 |
2020-03-29 | -1.075985 | -0.403549 | 2.053879 | -0.075490 |
2020-04-05 | -0.468759 | 1.591693 | 0.012494 | 1.013237 |
同样只需传入“年”或“年月”即可轻松选取数据的切片
long_df.loc['3-2020']
Colorado | Texas | New York | Ohio | |
---|---|---|---|---|
2020-03-01 | -0.983091 | -0.359375 | 0.014695 | 1.123173 |
2020-03-08 | 0.506222 | -0.188421 | -1.298069 | -1.769189 |
2020-03-15 | -1.773529 | -1.664837 | 0.045604 | 0.365199 |
2020-03-22 | -2.059035 | -1.386787 | 0.402163 | -1.967558 |
2020-03-29 | -1.254208 | -0.272317 | -1.595532 | 1.349384 |
带有重复索引的时间序列
>>> dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000','1/2/2000', '1/3/2000'])
>>> dup_ts = pd.Series(np.arange(5), index = dates)
>>> dup_ts
2000-01-01 0
2000-01-02 1
2000-01-02 2
2000-01-02 3
2000-01-03 4
dtype: int64
>>> dup_ts.index.is_unique
False
>>> dup_ts['1/2/2000']
2000-01-02 1
2000-01-02 2
2000-01-02 3
dtype: int64
>>> """假设你想要对具有非唯一时间戳的数据进行聚合。
... 一个办法是使用groupby,并传入level=0 """
>>> group = dup_ts.groupby(level=0)
>>> group.mean()
2000-01-01 0
2000-01-02 2
2000-01-03 4
dtype: int64
日期的范围、频率以及移动
pandas
中的原生时间序列一般被认为是不规则的,也就是说,它们没有固定的频率。对于大部分应用程序而言,这是无所谓的。但是,它常常需要以某种相对固定 的频率进行分析,比如每日、每月、每15分钟等(这样自然会在时间序列中引入缺失值)。幸运的是,pandas
有一整套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。例如,我们可以将之前那个时间序列转换为一 个具有固定频率(每日)的时间序列,只需调用resample
即可
pandas.date_range() 生成日期范围
pandas.date_range
可用于根据指定的频率生成指定长度的DatetimeIndex
默认情况下, date_range
会产生按天计算的时间点。如果只传入起始或结束日期,那就还得传入一个表示一段时间的数字,起始和结束日期定义了日期索引的严格边界
>>> pd.date_range(start='2012-04-01', periods=20)
DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03', '2012-04-04',
'2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08',
'2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12',
'2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16',
'2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20'],
dtype='datetime64[ns]', freq='D')
>>> pd.date_range(end ='2012-04-01', periods=20)
DatetimeIndex(['2012-03-13', '2012-03-14', '2012-03-15', '2012-03-16',
'2012-03-17', '2012-03-18', '2012-03-19', '2012-03-20',
'2012-03-21', '2012-03-22', '2012-03-23', '2012-03-24',
'2012-03-25', '2012-03-26', '2012-03-27', '2012-03-28',
'2012-03-29', '2012-03-30', '2012-03-31', '2012-04-01'],
dtype='datetime64[ns]', freq='D')
如果你想要生成一个由每月最后一个工作日组成的日期索引,可以传入"BM"频率(表示business end of month
,下表是频率列表),这样就只会包含时间间隔内(或刚好在边界上的)符合频率要求的日期:
别名 | 便宜量类型 | 说明 |
---|---|---|
D | Day | 每日历日 |
B | BusinessDay | 每工作日 |
H | Hour | 每小时 |
T 或 min | Minute | 每分 |
S | Second | 每秒 |
L或ms | Milli | 每毫秒(即千万分之一秒) |
U | Micro | 每微秒(即百万分之一秒) |
M | MonthEnd | 每月最后一个日历日 |
BM | BusinessMonthEnd | 每月最后一个工作日 |
MS | MonthBegin | 每月第一个日历日 |
BMS | BusinessMonthBegin | 每月第一个工作日 |
W-MON、W-TUE ... | Week | 从指定的星期几(MON、TUE、WED、THU、FRI、SAT、SUN)开始算起,每周 |
WON-1MON、WOM-2MON... | WeekOfMonth | 产生每月第一、第二、第三或第四周的星期即。例如,WOM-3FRI表示每月第3个星期五 |
Q-JAN、Q-FEB... | QuarterEnd | 对于以指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)结束的年度,每季度最后一个月的最后一个日历日 |
BQ-JAN、BQ-FEB... | BusinessQuarterEnd | 对于以指定月份结束的年度,每季度最后一月的最后一个工作日 |
QS-JAN、QS-FEB... | QuarterBegin | 对于以指定月份结束的年度、每季度最后一月的第一个日历日 |
BQS-JAN、BQS-FEB... | BusinessQuarterBegin | 对于以指定月份结束的年度、每季度最后一月的第一个工作日 |
A-JAN、A-FEB... | YearEnd | 每年指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)的最后一个日历日 |
BA-JAN、BA-FEB... | BusinessYearEnd | 每年指定月份的最后一个工作日 |
AS-JAN、As-FEB | YearBegin | 每年指定月份的第一个日历日 |
BAS-JAN、BAS-FEB... | BusinessYearnBegin | 每年指定月份的第一个工作日 |
有时,虽然起始和结束日期带有时间信息,但你希望产生一组被规范化 (normalize
)到午夜的时间戳。normalize
选项即可实现该功能:
>>> pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True)
>>> DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
'2012-05-06'],
dtype='datetime64[ns]', freq='D')
频率和日期偏移量
pandas
中的频率是由一个基础频率(base frequency
)和一个乘数组成的。基础频 率通常以一个字符串别名表示,比如"M"
表示每月,"H"
表示每小时。对于每个基础 频率,都有一个被称为日期偏移量(date offset
)的对象与之对应
>>> from pandas.tseries import offsets
>>> offsets.Hour()
<Hour>
# 传入一个整数即可定义偏移量的倍数:
>>> offsets.Hour(2)
<2 * Hours>
# 大部分偏移量对象都可通过加法进行连接
>>> Hour(2) + Minute(30)
<150 * Minutes>
#在创建日期范围时,给freq传入参数即可实现偏移频率
>>> pd.date_range('2000-01-01', periods=10, freq='1h30min')
DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00',
'2000-01-01 03:00:00', '2000-01-01 04:30:00',
'2000-01-01 06:00:00', '2000-01-01 07:30:00',
'2000-01-01 09:00:00', '2000-01-01 10:30:00',
'2000-01-01 12:00:00', '2000-01-01 13:30:00'],
dtype='datetime64[ns]', freq='90T')
WOM 日期 (Week Of Month)
WOM(Week Of Month
)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期:
>>> rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI') # 每月第3个星期五
>>> list(rng)
[Timestamp('2012-01-20 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-02-17 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-03-16 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-04-20 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-05-18 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-06-15 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-07-20 00:00:00', freq='WOM-3FRI'),
Timestamp('2012-08-17 00:00:00', freq='WOM-3FRI')]
shfit() -- 移动(超前和滞后)数据
移动(shifting
)指的是沿着时间轴将数据前移或后移。Series
和DataFrame
都有一个shift
方法用于执行单纯的前移或后移操作,保持索引不变:
>>> ts
2011-01-02 -0.162712
2011-01-05 1.876604
2011-01-07 -0.016393
2011-01-08 0.239276
2011-01-10 -0.704732
2011-01-12 -1.502936
dtype: float64
# 往前移动2格,索引不变
>>> ts.shift(2)
2011-01-02 NaN
2011-01-05 NaN
2011-01-07 -0.162712
2011-01-08 1.876604
2011-01-10 -0.016393
2011-01-12 0.239276
dtype: float64
>>> ts.shift(-2)
2011-01-02 -0.016393
2011-01-05 0.239276
2011-01-07 -0.704732
2011-01-08 -1.502936
2011-01-10 NaN
2011-01-12 NaN
dtype: float64
shift
通常用于计算一个时间序列或多个时间序列(如DataFrame
的列)中的百分比变化。
>>> ts/ts.shift(1)-1
2011-01-02 NaN
2011-01-05 -12.533276
2011-01-07 -1.008735
2011-01-08 -15.596514
2011-01-10 -3.945270
2011-01-12 1.132634
dtype: float64
>>> ts
2011-01-02 -0.162712
2011-01-05 1.876604
2011-01-07 -0.016393
2011-01-08 0.239276
2011-01-10 -0.704732
2011-01-12 -1.502936
dtype: float64
>>> ts.shift(2)
2011-01-02 NaN
2011-01-05 NaN
2011-01-07 -0.162712
2011-01-08 1.876604
2011-01-10 -0.016393
2011-01-12 0.239276
dtype: float64
>>> ts.shift(2,freq = 'D')
2011-01-04 -0.162712
2011-01-07 1.876604
2011-01-09 -0.016393
2011-01-10 0.239276
2011-01-12 -0.704732
2011-01-14 -1.502936
dtype: float64
>>> ts.shift(2, freq = 'BM')
2011-02-28 -0.162712
2011-02-28 1.876604
2011-02-28 -0.016393
2011-02-28 0.239276
2011-02-28 -0.704732
2011-02-28 -1.502936
dtype: float64
通过偏移量对日期进行位移
>>> from pandas.tseries.offsets import *
>>> now = datetime(2020,2,20)
>>> now + 3 * Day()
Timestamp('2020-02-23 00:00:00')
如果加的是锚点偏移量(比如MonthEnd
),第一次增量会将原日期向前滚动到符合频率规则的下一个日期:
>>> now + MonthEnd()
Timestamp('2020-02-29 00:00:00')
通过锚点偏移量的rollforward
和rollback
方法,可明确地将日期向前或向后“滚动”:
>>> now
datetime.datetime(2020, 2, 20, 0, 0)
>>> me_offset = MonthEnd()
>>> MonthEnd().rollforward(now)
Timestamp('2020-02-29 00:00:00')
>>> me_offset.rollback(now)
Timestamp('2020-01-31 00:00:00')
结合groupby
运用‘滚动’
>>> ts = pd.Series(np.random.randn(20), index = pd.date_range('1/15/2020',periods = 20, freq = '5d'))
>>> ts
2020-01-15 1.036827
2020-01-20 0.013990
2020-01-25 0.833330
2020-01-30 0.377223
2020-02-04 -0.275511
2020-02-09 -0.331296
2020-02-14 -0.433451
2020-02-19 -0.518679
2020-02-24 0.828722
2020-02-29 0.158464
2020-03-05 0.737144
2020-03-10 0.318404
2020-03-15 -0.240118
2020-03-20 -0.910773
2020-03-25 0.416353
2020-03-30 -1.265480
2020-04-04 0.882785
2020-04-09 2.628588
2020-04-14 0.777044
2020-04-19 -1.821877
Freq: 5D, dtype: float64
>>> ts.groupby(MonthEnd().rollforward).mean()
2020-01-31 0.565343
2020-02-29 -0.095292
2020-03-31 -0.157412
2020-04-30 0.616635
dtype: float64
更简单、更快速地实现该功能的办法是使用resample
。
>>> ts.resample('M').mean()
2020-01-31 0.565343
2020-02-29 -0.095292
2020-03-31 -0.157412
2020-04-30 0.616635
Freq: M, dtype: float64
推荐阅读-- 数据STUDIO --