本次介紹pandas時間統(tǒng)計(jì)分析的一個高級用法--重采樣。
完整數(shù)據(jù)代碼可戳??《pandas進(jìn)階寶典》進(jìn)行了解。
重采樣指的是時間重采樣,就是將時間序列從一個頻率轉(zhuǎn)換到另一個頻率上,對應(yīng)數(shù)據(jù)也跟著頻率進(jìn)行變化。比如時間序列數(shù)據(jù)是以天為周期的,通過重采樣我們可以將其轉(zhuǎn)換為按分鐘、小時、周、月、季度等等的其他周期上。根據(jù)轉(zhuǎn)換的頻率精度可分為向上采樣和向下采樣。
pandas中時間重采樣的方法是resample()
,可以對series和dataframe對象操作。由于重采樣默認(rèn)對索引執(zhí)行變換,因此索引必須是時間類型,或者通過on指定要重采樣的時間類型的column列。
用法:
pandas.DataFrame.resample()
pandas.Series.resample()
------
返回:Resampler對象
參數(shù):
0
:代表索引1
:代表列M
,A
,Q
,BM
,BA
,BQ
,W
右閉合,其余均是左閉合left
:指定左閉合right
:指定右閉合M
,A
,Q
,BM
,BA
,BQ
,W
以右邊界為分組標(biāo)簽,其余均是以左邊界為分組標(biāo)簽left
:以左邊界為分組標(biāo)簽right
:以右邊界為分組標(biāo)簽timestamp
:將結(jié)果索引轉(zhuǎn)換為DateTimeIndex
period
:將結(jié)果索引轉(zhuǎn)換為PeriodIndex
int
:索引層級str
:索引層級名稱epoch
:1970-01-01start
:時間序列的第一個值start_day
:時間序列第一天的午夜end
:時間序列的最后一個值end_day
:最后一天的午夜.apply()
方法,默認(rèn)False不包含舉例:
resample
默認(rèn)只對索引對象操作,換句話說,默認(rèn)情況下索引必須是時間類型的數(shù)據(jù),否則執(zhí)行會報錯。
對于dataframe而言,如不想對索引重采樣,可以通過on
參數(shù)選擇一個column列代替索引進(jìn)行重采樣操作。
# 將時間類型索引重置,變?yōu)閏olumn列
df.reset_index(drop=False,inplace=True)
# 通過參數(shù)on指定時間類型的列名,也可以實(shí)現(xiàn)重采樣
df.resample('W', on='index')['C_0'].sum().head()
由于W
是默認(rèn)為右閉且取右邊界作為分組標(biāo)簽的,重采樣后結(jié)果如下。從1/3至1/9(綠色)是完整一周,因此之前非完整部分(黃色)自動歸為一周,后面依次按周統(tǒng)計(jì)。
通過closed
參數(shù)可以控制左右閉合的狀態(tài)。
默認(rèn)情況下,M
,A
,Q
,BM
,BA
,BQ
,W
是右閉合,其余頻率均是左閉合。
下面將天頻率轉(zhuǎn)為W
周頻率(默認(rèn)是右閉)。我們手動設(shè)置左、右閉合進(jìn)行對比,可以看出二者區(qū)別,對于求和結(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)
通過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)右邊界)。我們手動設(shè)置label為左、右進(jìn)行對比,可以看出第二個采樣分組下輸出標(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)
類似于groupby
和窗口的聚合方法, 重采樣也適用相關(guān)方法,參考pandas分組8個常用技巧!
以下是resample
采樣后可以支持的描述性統(tǒng)計(jì)和計(jì)算的內(nèi)置函數(shù)。
內(nèi)置方法下面例子中會舉例說明。
分為上采樣和下采樣。通過以下數(shù)據(jù)舉例說明。
# 生成時間索引的數(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ù)時間索引是以天為頻率的。
根據(jù)rule參數(shù)含義碼表,H
代表小時的意思,12H
也就是12小時。這是resample非常強(qiáng)大的地方,可以把采樣定位的非常精確。
下面將天的時間頻率轉(zhuǎn)換為12小時的頻率,并對新的頻率分組后求和。
df.resample('12H')['C_0'].sum().head(10)
比天顆粒度更小的還可以有分鐘、秒、毫秒、微秒、納秒,可根據(jù)實(shí)際情況自行設(shè)定頻率大小。
以上可以看到,上采樣的過程中由于頻率更高導(dǎo)致采樣后數(shù)據(jù)部分缺失。這時候可以使用上采樣的填充方法,方法如下:
只有一個參數(shù)limit
控制向前填充的數(shù)量。
下面將天為頻率的數(shù)據(jù)上采樣到8H
頻率,向前填充1行和2行的結(jié)果。
df.resample('8H')['C_0'].ffill(limit=1)
與向前填充用法一樣,下面向后填充1行和2行的結(jié)果。
df.resample('8H')['C_0'].bfill(limit=1)
該方法為就近填充,無確定方向,可能向前或者向后。參數(shù)也是limit對填充數(shù)量進(jìn)行控制。以下對缺失部分按最近數(shù)據(jù)填充1行,結(jié)果如下。
df.resample('8H')['C_0'].nearest(limit=1)
該方法是前三種方法的集合,參數(shù)method
可設(shè)置{'pad'/'ffill','bfill','nearest'}
三種,分別代表向前,向后、取最近,同時也可以設(shè)置limit
進(jìn)行數(shù)量控制,因此該方法可以取代前面三種。
df.resample('8H')['C_0'].fillna(method='pad', limit=1)
該方法可以指定固定值對所有缺失部分一次性填充,比如對缺失部分統(tǒng)一填充-999。
df.resample('8H')['C_0'].asfreq(-999)
該方法可以使用更高級的算法進(jìn)行填充。具體方法可通過參數(shù)method
設(shè)置,不詳細(xì)介紹,這里以linear
線性插值方法舉例。
df.resample('8H').interpolate(method='linear').applymap(lambda x:round(x,2))
如果想同時對多列的聚合,或者對單列賦予多個聚合函數(shù),可以使用agg()
聚合方法。
下面進(jìn)行下采樣,將天頻率降為周,并對多個變量進(jìn)行多種聚合操作。
df.resample('W').agg(
{
'C_0': ['sum', 'mean'],
'C_1': lambda x: np.std(x, ddof=1)
}
).head()
以上結(jié)果列名顯示了兩個層級,如果想去掉層級并自定義結(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()
使用apply
函數(shù)也可以達(dá)到agg
的聚合效果,以下對多個變量進(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()
transform
在分組系列中介紹過,會對原數(shù)據(jù)進(jìn)行分組內(nèi)轉(zhuǎn)換但不改變原索引結(jié)構(gòu),在重采樣中用法一樣。transform()
函數(shù)的使用方法可參考pandas transform 數(shù)據(jù)轉(zhuǎn)換的 4 個常用技巧!
以下對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)
pipe()
被稱為管道函數(shù),可以對重采樣后的resampler對象應(yīng)用帶參數(shù)的自定義函數(shù)。pipe()
函數(shù)的使用方法可參考pandas一個優(yōu)雅的高級應(yīng)用函數(shù)!
它最大的優(yōu)勢在于可以鏈?zhǔn)?/strong>使用,每次函數(shù)執(zhí)行后的輸出結(jié)果可以作為下一個函數(shù)的參數(shù),形式如:pipe(func1).pipe(func2)
,參數(shù)可以是series、dataFrames、groupBy對象、或者resampler對象。
通過pipe
的鏈?zhǔn)娇梢韵窆艿酪粯影错樞蛞来螆?zhí)行操作,并且只需要一行代碼即可,極大地提高了可讀性。
以下對下采樣后的C_0和C_1變量進(jìn)行累加求和操作,然后再對兩個求和作差。
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
一樣可以返回不改變原索引的結(jié)果。