呆鸟云:“数据分析就像是夜里行军,业务是灯塔,是地图,没灯塔你不知道方向,没地图你不知道该怎么走。技术是你的交通工具,你用11路,还是骑自行车,还是开跑车,交通工具越好,你实现目标的速度越快 。”
实例方法移位与延迟频率转换向前与向后填充转换 Python 日期与时间重采样基础知识上采样稀疏重采样聚合分组迭代 移位与延迟有时,需要整体向前或向后移动时间序列里的值,这就是移位与延迟。实现这一操作的方法是 shift() ,该方法适用于所有 Pandas 对象。 In [272]: ts = pd.Series(range(len(rng)), index=rng)
In [273]: ts = ts[:5]
In [274]: ts.shift(1) Out[274]: 2012-01-01 NaN 2012-01-02 0.0 2012-01-03 1.0 Freq: D, dtype: float64
shift 方法支持 freq 参数,可以把 DateOffset 、timedelta 对象、偏移量别名 作为参数值:
In [275]: ts.shift(5, freq=pd.offsets.BDay()) Out[275]: 2012-01-06 0 2012-01-09 1 2012-01-10 2 Freq: B, dtype: int64
In [276]: ts.shift(5, freq='BM') Out[276]: 2012-05-31 0 2012-05-31 1 2012-05-31 2 Freq: D, dtype: int64
除更改数据与索引的对齐方式外,DataFrame 与 Series 对象还提供了 tshift() 便捷方法,可以指定偏移量修改索引日期。 In [277]: ts.tshift(5, freq='D') Out[277]: 2012-01-06 0 2012-01-07 1 2012-01-08 2 Freq: D, dtype: int64
注意,使用 tshift() 时,因为数据没有重对齐,NaN 不会排在前面。 频率转换改变频率的函数主要是 asfreq() 。对于 DatetimeIndex ,这就是一个调用 reindex() ,并生成 date_range 的便捷打包器。 In [278]: dr = pd.date_range('1/1/2010', periods=3, freq=3 * pd.offsets.BDay())
In [279]: ts = pd.Series(np.random.randn(3), index=dr)
In [280]: ts Out[280]: 2010-01-01 1.494522 2010-01-06 -0.778425 2010-01-11 -0.253355 Freq: 3B, dtype: float64
In [281]: ts.asfreq(pd.offsets.BDay()) Out[281]: 2010-01-01 1.494522 2010-01-04 NaN 2010-01-05 NaN 2010-01-06 -0.778425 2010-01-07 NaN 2010-01-08 NaN 2010-01-11 -0.253355 Freq: B, dtype: float64
asfreq 用起来很方便,可以为频率转化后出现的任意间隔指定插值方法。
In [282]: ts.asfreq(pd.offsets.BDay(), method='pad') Out[282]: 2010-01-01 1.494522 2010-01-04 1.494522 2010-01-05 1.494522 2010-01-06 -0.778425 2010-01-07 -0.778425 2010-01-08 -0.778425 2010-01-11 -0.253355 Freq: B, dtype: float64
向前与向后填充与 asfreq 与 reindex 相关的是 fillna() ,有关文档请参阅缺失值。 转换 Python 日期与时间用 to_datetime 方法可以把DatetimeIndex 转换为 Python 原生 datetime.datetime 对象数组。 重采样0.18.0 版修改了 .resample 接口,现在的 .resample 更灵活,更像 groupby。参阅更新文档 ,对比新旧版本操作的区别。
Pandas 有一个虽然简单,但却强大、高效的功能,可在频率转换时执行重采样,如,将秒数据转换为 5 分钟数据,这种操作在金融等领域里的应用非常广泛。 resample() 是基于时间的分组操作,每个组都遵循归纳方法。参阅 Cookbook 示例了解高级应用。
从 0.18.0 版开始,resample() 可以直接用于 DataFrameGroupBy 对象,参阅 groupby 文档。 .resample() 类似于基于时间偏移量的 rolling() 操作,请参阅这里的讨论。
基础知识In [283]: rng = pd.date_range('1/1/2012', periods=100, freq='S')
In [284]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
In [285]: ts.resample('5Min').sum() Out[285]: 2012-01-01 25103 Freq: 5T, dtype: int64
resample 函数非常灵活,可以指定多种频率转换与重采样参数。
任何支持派送(dispatch)的函数都可用于 resample 返回对象,包括 sum 、mean 、std 、sem 、max 、min 、mid 、median 、first 、last 、ohlc : In [286]: ts.resample('5Min').mean() Out[286]: 2012-01-01 251.03 Freq: 5T, dtype: float64
In [287]: ts.resample('5Min').ohlc() Out[287]: open high low close 2012-01-01 308 460 9 205
In [288]: ts.resample('5Min').max() Out[288]: 2012-01-01 460 Freq: 5T, dtype: int64
对于下采样,closed 可以设置为left 或 right ,用于指定关闭哪一端间隔: In [289]: ts.resample('5Min', closed='right').mean() Out[289]: 2011-12-31 23:55:00 308.000000 2012-01-01 00:00:00 250.454545 Freq: 5T, dtype: float64
In [290]: ts.resample('5Min', closed='left').mean() Out[290]: 2012-01-01 251.03 Freq: 5T, dtype: float64
label 、loffset 等参数用于生成标签。label 指定生成的结果是否要为间隔标注起始时间。loffset 调整输出标签的时间。
In [291]: ts.resample('5Min').mean() # 默认为 label='left' Out[291]: 2012-01-01 251.03 Freq: 5T, dtype: float64
In [292]: ts.resample('5Min', label='left').mean() Out[292]: 2012-01-01 251.03 Freq: 5T, dtype: float64
In [293]: ts.resample('5Min', label='left', loffset='1s').mean() Out[293]: 2012-01-01 00:00:01 251.03 dtype: float64
除了 M 、A 、Q 、BM 、BA 、BQ 、W 的默认值是 right 外,其它频率偏移量的 label 与 closed 默认值都是 left 。 这种操作可能会导致时间回溯,即后面的时间会被拉回到前面的时间,如下例的 BusinessDay 频率所示。
In [294]: s = pd.date_range('2000-01-01', '2000-01-05').to_series()
In [295]: s.iloc[2] = pd.NaT
In [296]: s.dt.weekday_name Out[296]: 2000-01-01 Saturday 2000-01-02 Sunday 2000-01-03 NaN 2000-01-04 Tuesday 2000-01-05 Wednesday Freq: D, dtype: object
# 默认为:label='left', closed='left' In [297]: s.resample('B').last().dt.weekday_name Out[297]: 1999-12-31 Sunday 2000-01-03 NaN 2000-01-04 Tuesday 2000-01-05 Wednesday Freq: B, dtype: object
看到了吗?星期日被拉回到了上一个星期五。要想把星期日移至星期一,改用以下代码:
In [298]: s.resample('B', label='right', closed='right').last().dt.weekday_name Out[298]: 2000-01-03 Sunday 2000-01-04 Tuesday 2000-01-05 Wednesday Freq: B, dtype: object
axis 参数的值为 0 或 1 ,并可指定 DataFrame 重采样的轴。
kind 参数可以是 timestamp 或 period ,转换为时间戳或时间段形式的索引。resample 默认保留输入的日期时间形式。
重采样 period 数据时(详情见下文),convention 可以设置为 start 或 end 。指定低频时间段如何转换为高频时间段。 上采样上采样可以指定上采样的方式及插入时间间隔的 limit 参数: # 从秒到每 250 毫秒 In [299]: ts[:2].resample('250L').asfreq() Out[299]: 2012-01-01 00:00:00.000 308.0 2012-01-01 00:00:00.250 NaN 2012-01-01 00:00:00.500 NaN 2012-01-01 00:00:00.750 NaN 2012-01-01 00:00:01.000 204.0 Freq: 250L, dtype: float64
In [300]: ts[:2].resample('250L').ffill() Out[300]: 2012-01-01 00:00:00.000 308 2012-01-01 00:00:00.250 308 2012-01-01 00:00:00.500 308 2012-01-01 00:00:00.750 308 2012-01-01 00:00:01.000 204 Freq: 250L, dtype: int64
In [301]: ts[:2].resample('250L').ffill(limit=2) Out[301]: 2012-01-01 00:00:00.000 308.0 2012-01-01 00:00:00.250 308.0 2012-01-01 00:00:00.500 308.0 2012-01-01 00:00:00.750 NaN 2012-01-01 00:00:01.000 204.0 Freq: 250L, dtype: float64
稀疏重采样相对于时间点总量,稀疏时间序列重采样的点要少很多。单纯上采样稀疏系列可能会生成很多中间值。未指定填充值,即 fill_method 是 None 时,中间值将填充为 NaN 。 鉴于 resample 是基于时间的分组,下列这种方法可以有效重采样,只是分组不是都为 NaN 。 In [302]: rng = pd.date_range('2014-1-1', periods=100, freq='D') + pd.Timedelta('1s')
In [303]: ts = pd.Series(range(100), index=rng)
对 Series 全范围重采样。 In [304]: ts.resample('3T').sum() Out[304]: 2014-01-01 00:00:00 0 2014-01-01 00:03:00 0 2014-01-01 00:06:00 0 2014-01-01 00:09:00 0 2014-01-01 00:12:00 0 .. 2014-04-09 23:48:00 0 2014-04-09 23:51:00 0 2014-04-09 23:54:00 0 2014-04-09 23:57:00 0 2014-04-10 00:00:00 99 Freq: 3T, Length: 47521, dtype: int64
对以下包含点的分组重采样: In [305]: from functools import partial
In [306]: from pandas.tseries.frequencies import to_offset
In [307]: def round(t, freq): .....: freq = to_offset(freq) .....: return pd.Timestamp((t.value // freq.delta.value) * freq.delta.value) .....:
In [308]: ts.groupby(partial(round, freq='3T')).sum() Out[308]: 2014-01-01 0 2014-01-02 1 2014-01-03 2 2014-01-04 3 2014-01-05 4 .. 2014-04-06 95 2014-04-07 96 2014-04-08 97 2014-04-09 98 2014-04-10 99 Length: 100, dtype: int64
聚合类似于聚合 API,Groupby API 及窗口函数 API,Resampler 可以有选择地重采样。 DataFrame 重采样,默认用相同函数操作所有列。
In [309]: df = pd.DataFrame(np.random.randn(1000, 3), .....: index=pd.date_range('1/1/2012', freq='S', periods=1000), .....: columns=['A', 'B', 'C']) .....:
In [310]: r = df.resample('3T')
In [311]: r.mean() Out[311]: A B C 2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447 2012-01-01 00:03:00 0.056909 0.146731 -0.024320 2012-01-01 00:06:00 -0.058837 0.047046 -0.052021 2012-01-01 00:09:00 0.063123 -0.026158 -0.066533 2012-01-01 00:12:00 0.186340 -0.003144 0.074752 2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
标准 getitem 操作可以指定的一列或多列。 In [312]: r['A'].mean() Out[312]: 2012-01-01 00:00:00 -0.033823 2012-01-01 00:03:00 0.056909 2012-01-01 00:06:00 -0.058837 2012-01-01 00:09:00 0.063123 2012-01-01 00:12:00 0.186340 2012-01-01 00:15:00 -0.085954 Freq: 3T, Name: A, dtype: float64
In [313]: r[['A', 'B']].mean() Out[313]: A B 2012-01-01 00:00:00 -0.033823 -0.121514 2012-01-01 00:03:00 0.056909 0.146731 2012-01-01 00:06:00 -0.058837 0.047046 2012-01-01 00:09:00 0.063123 -0.026158 2012-01-01 00:12:00 0.186340 -0.003144 2012-01-01 00:15:00 -0.085954 -0.016287
聚合还支持函数列表与字典,输出的是 DataFrame 。 In [314]: r['A'].agg([np.sum, np.mean, np.std]) Out[314]: sum mean std 2012-01-01 00:00:00 -6.088060 -0.033823 1.043263 2012-01-01 00:03:00 10.243678 0.056909 1.058534 2012-01-01 00:06:00 -10.590584 -0.058837 0.949264 2012-01-01 00:09:00 11.362228 0.063123 1.028096 2012-01-01 00:12:00 33.541257 0.186340 0.884586 2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
重采样后的 DataFrame ,可以为每列指定函数列表,生成结构化索引的聚合结果: In [315]: r.agg([np.sum, np.mean]) Out[315]: A B C sum mean sum mean sum mean 2012-01-01 00:00:00 -6.088060 -0.033823 -21.872530 -0.121514 -14.660515 -0.081447 2012-01-01 00:03:00 10.243678 0.056909 26.411633 0.146731 -4.377642 -0.024320 2012-01-01 00:06:00 -10.590584 -0.058837 8.468289 0.047046 -9.363825 -0.052021 2012-01-01 00:09:00 11.362228 0.063123 -4.708526 -0.026158 -11.975895 -0.066533 2012-01-01 00:12:00 33.541257 0.186340 -0.565895 -0.003144 13.455299 0.074752 2012-01-01 00:15:00 -8.595393 -0.085954 -1.628689 -0.016287 -5.004580 -0.050046
把字典传递给 aggregate ,可以为 DataFrame 里不同的列应用不同聚合函数。 In [316]: r.agg({'A': np.sum, .....: 'B': lambda x: np.std(x, ddof=1)}) .....: Out[316]: A B 2012-01-01 00:00:00 -6.088060 1.001294 2012-01-01 00:03:00 10.243678 1.074597 2012-01-01 00:06:00 -10.590584 0.987309 2012-01-01 00:09:00 11.362228 0.944953 2012-01-01 00:12:00 33.541257 1.095025 2012-01-01 00:15:00 -8.595393 1.035312
还可以用字符串代替函数名。为了让字符串有效,必须在重采样对象上操作: In [317]: r.agg({'A': 'sum', 'B': 'std'}) Out[317]: A B 2012-01-01 00:00:00 -6.088060 1.001294 2012-01-01 00:03:00 10.243678 1.074597 2012-01-01 00:06:00 -10.590584 0.987309 2012-01-01 00:09:00 11.362228 0.944953 2012-01-01 00:12:00 33.541257 1.095025 2012-01-01 00:15:00 -8.595393 1.035312
甚至还可以为每列单独多个聚合函数。 In [318]: r.agg({'A': ['sum', 'std'], 'B': ['mean', 'std']}) Out[318]: A B sum std mean std 2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294 2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597 2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309 2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953 2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025 2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
如果 DataFrame 用的不是 datetime 型索引,则可以基于 datetime 数据列重采样,用关键字 on 控制。 In [319]: df = pd.DataFrame({'date': pd.date_range('2015-01-01', freq='W', periods=5), .....: 'a': np.arange(5)}, .....: index=pd.MultiIndex.from_arrays([ .....: [1, 2, 3, 4, 5], .....: pd.date_range('2015-01-01', freq='W', periods=5)], .....: names=['v', 'd'])) .....:
In [320]: df Out[320]: date a v d 1 2015-01-04 2015-01-04 0 2 2015-01-11 2015-01-11 1 3 2015-01-18 2015-01-18 2 4 2015-01-25 2015-01-25 3 5 2015-02-01 2015-02-01 4
In [321]: df.resample('M', on='date').sum() Out[321]: a date 2015-01-31 6 2015-02-28 4
同样,还可以对 datetime MultiIndex 重采样,通过关键字 level 传递名字与位置。 In [322]: df.resample('M', level='d').sum() Out[322]: a d 2015-01-31 6 2015-02-28 4
分组迭代Resampler 对象迭代分组数据的操作非常自然,类似于 itertools.groupby() :
In [323]: small = pd.Series( .....: range(6), .....: index=pd.to_datetime(['2017-01-01T00:00:00', .....: '2017-01-01T00:30:00', .....: '2017-01-01T00:31:00', .....: '2017-01-01T01:00:00', .....: '2017-01-01T03:00:00', .....: '2017-01-01T03:05:00']) .....: ) .....:
In [324]: resampled = small.resample('H')
In [325]: for name, group in resampled: .....: print("Group: ", name) .....: print("-" * 27) .....: print(group, end="\n\n") .....: Group: 2017-01-01 00:00:00 --------------------------- 2017-01-01 00:00:00 0 2017-01-01 00:30:00 1 2017-01-01 00:31:00 2 dtype: int64
Group: 2017-01-01 01:00:00 --------------------------- 2017-01-01 01:00:00 3 dtype: int64
Group: 2017-01-01 02:00:00 --------------------------- Series([], dtype: int64)
Group: 2017-01-01 03:00:00 --------------------------- 2017-01-01 03:00:00 4 2017-01-01 03:05:00 5 dtype: int64
了解更多详情,请参阅分组迭代或 itertools.groupby() 。
|