本次介紹pandas時(shí)間統(tǒng)計(jì)分析的一個(gè)高級(jí)用法--重采樣。
完整數(shù)據(jù)代碼可戳??《pandas進(jìn)階寶典》進(jìn)行了解。
重采樣指的是時(shí)間重采樣,就是將時(shí)間序列從一個(gè)頻率轉(zhuǎn)換到另一個(gè)頻率上,對(duì)應(yīng)數(shù)據(jù)也跟著頻率進(jìn)行變化。比如時(shí)間序列數(shù)據(jù)是以天為周期的,通過重采樣我們可以將其轉(zhuǎn)換為按分鐘、小時(shí)、周、月、季度等等的其他周期上。根據(jù)轉(zhuǎn)換的頻率精度可分為向上采樣和向下采樣。
- 向上采樣:轉(zhuǎn)換到更細(xì)顆粒度的頻率,比如將天轉(zhuǎn)為小時(shí)、分鐘、秒等
- 向下采樣:轉(zhuǎn)換到更粗顆粒度的頻率,比如將天轉(zhuǎn)為周、月、季度、年等
resample用法
pandas中時(shí)間重采樣的方法是resample()
,可以對(duì)series和dataframe對(duì)象操作。由于重采樣默認(rèn)對(duì)索引執(zhí)行變換,因此索引必須是時(shí)間類型,或者通過on指定要重采樣的時(shí)間類型的column列。
用法:
pandas.DataFrame.resample()
pandas.Series.resample()
------
返回:Resampler對(duì)象
參數(shù):
- rule:定義重采樣的規(guī)則,DateOffset,Timedelta或str類型,當(dāng)為str類型時(shí),其參數(shù)及含義如下表所示
- axis:指定軸方向,str類型,默認(rèn)為0
- closed:指定時(shí)間頻率分組的左右閉合狀態(tài),默認(rèn)
M
,A
,Q
,BM
,BA
,BQ
,W
右閉合,其余均是左閉合 - label:指定左或右邊界作為分組標(biāo)簽,默認(rèn)
M
,A
,Q
,BM
,BA
,BQ
,W
以右邊界為分組標(biāo)簽,其余均是以左邊界為分組標(biāo)簽 - kind:將結(jié)果索引轉(zhuǎn)化為指定的時(shí)間類型
timestamp
:將結(jié)果索引轉(zhuǎn)換為DateTimeIndex
period
:將結(jié)果索引轉(zhuǎn)換為PeriodIndex
- on:對(duì)于dataframe,指定被重采樣的列,且列必須是時(shí)間類型
- level:對(duì)于多級(jí)索引,指定要被重采樣的索引層級(jí),int或str類型。
- origin:調(diào)整時(shí)間分組的起點(diǎn)。Timestamp或str類型,當(dāng)為str時(shí):
start_day
:時(shí)間序列第一天的午夜
- offset:對(duì)origin添加的偏移量,Timedelta或str類型
- group_keys:指定是否在結(jié)果索引包含分組keys,當(dāng)采樣對(duì)象使用了
.apply()
方法,默認(rèn)False不包含

舉例:
1)指定列名
resample
默認(rèn)只對(duì)索引對(duì)象操作,換句話說,默認(rèn)情況下索引必須是時(shí)間類型的數(shù)據(jù),否則執(zhí)行會(huì)報(bào)錯(cuò)。
對(duì)于dataframe而言,如不想對(duì)索引重采樣,可以通過on
參數(shù)選擇一個(gè)column列代替索引進(jìn)行重采樣操作。
# 將時(shí)間類型索引重置,變?yōu)閏olumn列
df.reset_index(drop=False,inplace=True)
# 通過參數(shù)on指定時(shí)間類型的列名,也可以實(shí)現(xiàn)重采樣
df.resample('W', on='index')['C_0'].sum().head()
由于W
是默認(rèn)為右閉且取右邊界作為分組標(biāo)簽的,重采樣后結(jié)果如下。從1/3至1/9(綠色)是完整一周,因此之前非完整部分(黃色)自動(dòng)歸為一周,后面依次按周統(tǒng)計(jì)。

2)開閉區(qū)間指定
通過closed
參數(shù)可以控制左右閉合的狀態(tài)。
默認(rèn)情況下,M
,A
,Q
,BM
,BA
,BQ
,W
是右閉合,其余頻率均是左閉合。
下面將天頻率轉(zhuǎn)為W
周頻率(默認(rèn)是右閉)。我們手動(dòng)設(shè)置左、右閉合進(jìn)行對(duì)比,可以看出二者區(qū)別,對(duì)于求和結(jié)果的影響。
df=generate_sample_data_datetime()
pd.concat([df.resample('W', closed='left')['C_0'].sum().to_frame(name='left_clsd'),
df.resample('W', closed='right')['C_0'].sum().to_frame(name='right_clsd')],
axis=1).head(5)

3)輸出結(jié)果控制
通過label
參數(shù)可以控制輸出結(jié)果的標(biāo)簽。
默認(rèn)情況下,M
,A
,Q
,BM
,BA
,BQ
,W
以分組內(nèi)右側(cè)邊界為輸出的標(biāo)簽,其余均是以分組內(nèi)左邊界為標(biāo)簽。
下面將天頻率轉(zhuǎn)為W
周頻率(label默認(rèn)右邊界)。我們手動(dòng)設(shè)置label為左、右進(jìn)行對(duì)比,可以看出第二個(gè)采樣分組下輸出標(biāo)簽的區(qū)別。
df=generate_sample_data_datetime()
df.resample('W', label='left')['C_0'].sum().to_frame(name='left_bnd').head(5)
df.resample('W', label='right')['C_0'].sum().to_frame(name='right_bnd').head(5)

4)聚合統(tǒng)計(jì)
類似于groupby
和窗口的聚合方法, 重采樣也適用相關(guān)方法,參考pandas分組8個(gè)常用技巧!
以下是resample
采樣后可以支持的描述性統(tǒng)計(jì)和計(jì)算的內(nèi)置函數(shù)。

內(nèi)置方法下面例子中會(huì)舉例說明。
上采樣
分為上采樣和下采樣。通過以下數(shù)據(jù)舉例說明。
# 生成時(shí)間索引的數(shù)據(jù)
def generate_sample_data_datetime():
np.random.seed(123)
number_or_rows = 365*2
num_cols = 5
start_date = '2022-01-01'
cols = ['C_0', 'C_1', 'C_2', 'C_3', 'C_4']
df = pd.DataFrame(np.random.randint(1, 100, size = (number_or_rows, num_cols)), columns=cols)
df.index = pd.date_range(start=start_date, periods=number_or_rows)
return df
df=generate_sample_data_datetime()
以上生成數(shù)據(jù)時(shí)間索引是以天為頻率的。
根據(jù)rule參數(shù)含義碼表,H
代表小時(shí)的意思,12H
也就是12小時(shí)。這是resample非常強(qiáng)大的地方,可以把采樣定位的非常精確。
下面將天的時(shí)間頻率轉(zhuǎn)換為12小時(shí)的頻率,并對(duì)新的頻率分組后求和。
df.resample('12H')['C_0'].sum().head(10)

比天顆粒度更小的還可以有分鐘、秒、毫秒、微秒、納秒,可根據(jù)實(shí)際情況自行設(shè)定頻率大小。
以上可以看到,上采樣的過程中由于頻率更高導(dǎo)致采樣后數(shù)據(jù)部分缺失。這時(shí)候可以使用上采樣的填充方法,方法如下:

1)ffill
只有一個(gè)參數(shù)limit
控制向前填充的數(shù)量。
下面將天為頻率的數(shù)據(jù)上采樣到8H
頻率,向前填充1行和2行的結(jié)果。
df.resample('8H')['C_0'].ffill(limit=1)

2)bfill
與向前填充用法一樣,下面向后填充1行和2行的結(jié)果。
df.resample('8H')['C_0'].bfill(limit=1)

3)nearest
該方法為就近填充,無確定方向,可能向前或者向后。參數(shù)也是limit對(duì)填充數(shù)量進(jìn)行控制。以下對(duì)缺失部分按最近數(shù)據(jù)填充1行,結(jié)果如下。
df.resample('8H')['C_0'].nearest(limit=1)

4)fillna
該方法是前三種方法的集合,參數(shù)method
可設(shè)置{'pad'/'ffill','bfill','nearest'}
三種,分別代表向前,向后、取最近,同時(shí)也可以設(shè)置limit
進(jìn)行數(shù)量控制,因此該方法可以取代前面三種。
df.resample('8H')['C_0'].fillna(method='pad', limit=1)
5)asfreq
該方法可以指定固定值對(duì)所有缺失部分一次性填充,比如對(duì)缺失部分統(tǒng)一填充-999。
df.resample('8H')['C_0'].asfreq(-999)

6)interpolate
該方法可以使用更高級(jí)的算法進(jìn)行填充。具體方法可通過參數(shù)method
設(shè)置,不詳細(xì)介紹,這里以linear
線性插值方法舉例。
df.resample('8H').interpolate(method='linear').applymap(lambda x:round(x,2))

應(yīng)用函數(shù)
1)agg
如果想同時(shí)對(duì)多列的聚合,或者對(duì)單列賦予多個(gè)聚合函數(shù),可以使用agg()
聚合方法。
下面進(jìn)行下采樣,將天頻率降為周,并對(duì)多個(gè)變量進(jìn)行多種聚合操作。
df.resample('W').agg(
{
'C_0': ['sum', 'mean'],
'C_1': lambda x: np.std(x, ddof=1)
}
).head()

以上結(jié)果列名顯示了兩個(gè)層級(jí),如果想去掉層級(jí)并自定義結(jié)果中的變量名,可通過以下代碼實(shí)現(xiàn)。
df=generate_sample_data_datetime()
df.resample('W').agg(
C_0_sum=('C_0','sum'),
C_0_avg=('C_0','mean'),
C_1_delta=('C_1', lambda x:x.max()-x.min())
).head()

2)apply
使用apply
函數(shù)也可以達(dá)到agg
的聚合效果,以下對(duì)多個(gè)變量進(jìn)行不同的聚合函數(shù),其中也可以自定義函數(shù)。
def agg_func(x):
names = {
'C_0_mean': round(x['C_0'].mean(),2),
'C_1_sum': x['C_1'].sum(),
'C_2_max': x['C_2'].max(),
'C_3_mean_plus1': round(x['C_3'].mean()+1,2),
}
return pd.Series(names, index=[ key for key in names.keys()])
df.resample('W').apply(agg_func).head()

3)transform
transform
在分組系列中介紹過,會(huì)對(duì)原數(shù)據(jù)進(jìn)行分組內(nèi)轉(zhuǎn)換但不改變?cè)饕Y(jié)構(gòu),在重采樣中用法一樣。transform()
函數(shù)的使用方法可參考pandas transform 數(shù)據(jù)轉(zhuǎn)換的 4 個(gè)常用技巧!
以下對(duì)C_0變量進(jìn)行采樣分組內(nèi)的累加和排序操作。
df['C_0_cumsum'] = df.resample('W')['C_0'].transform('cumsum')
df['C_0_rank'] = df.resample('W')['C_0'].transform('rank')
df.head(10)

4)pipe
pipe()
被稱為管道函數(shù),可以對(duì)重采樣后的resampler對(duì)象應(yīng)用帶參數(shù)的自定義函數(shù)。pipe()
函數(shù)的使用方法可參考pandas一個(gè)優(yōu)雅的高級(jí)應(yīng)用函數(shù)!
它最大的優(yōu)勢(shì)在于可以鏈?zhǔn)?/strong>使用,每次函數(shù)執(zhí)行后的輸出結(jié)果可以作為下一個(gè)函數(shù)的參數(shù),形式如:pipe(func1).pipe(func2)
,參數(shù)可以是series、dataFrames、groupBy對(duì)象、或者resampler對(duì)象。
通過pipe
的鏈?zhǔn)娇梢韵窆艿酪粯影错樞蛞来螆?zhí)行操作,并且只需要一行代碼即可,極大地提高了可讀性。
以下對(duì)下采樣后的C_0和C_1變量進(jìn)行累加求和操作,然后再對(duì)兩個(gè)求和作差。
df['cumsum_delta'] = df.resample('W')['C_0','C_1'] \
.pipe(lambda x:x.cumsum()) \
.pipe(lambda x:x['C_1']-x['C_0'])
df.head(10)

這里當(dāng)pipe
應(yīng)用了cumsum()
函數(shù)后,與transform
一樣可以返回不改變?cè)饕慕Y(jié)果。