摘要:下面讓我們開(kāi)始提速假設(shè)我們現(xiàn)在的電價(jià)是定值,不根據(jù)用電時(shí)間段來(lái)改變,那么中最快的方法那就是采用,這就是一個(gè)簡(jiǎn)單的矢量化操作示范。它基本是在中運(yùn)行最快的方式。
Pandas 加速
大家好,今天我們來(lái)看有關(guān)pandas加速的小技巧,不知道大家在剛剛接觸pandas的時(shí)候有沒(méi)有聽(tīng)過(guò)如下的說(shuō)法
pandas太慢了,運(yùn)行要等半天
其實(shí)我想說(shuō)的是,慢不是pandas的錯(cuò),大家要知道pandas本身是在Numpy上建立起來(lái)的包,在很多情況下是支持向量化運(yùn)算的,而且還有C的底層設(shè)計(jì),所以我今天
主要想從幾個(gè)方面和大家分享一下pandas加速的小技巧,與往常一樣,文章分成四部分,本文結(jié)構(gòu)如下:
使用datetime類型來(lái)處理和時(shí)間序列有關(guān)的數(shù)據(jù)
批量計(jì)算的技巧
通過(guò)HDFStore存儲(chǔ)數(shù)據(jù)節(jié)省時(shí)間
源碼,相關(guān)數(shù)據(jù)及GitHub地址
現(xiàn)在就讓我們開(kāi)始吧
1. 使用datetime類型來(lái)處理和時(shí)間序列有關(guān)的數(shù)據(jù)首先這里我們使用的數(shù)據(jù)源是一個(gè)電力消耗情況的數(shù)據(jù)(energy_cost.csv),非常貼近生活而且也是和時(shí)間息息相關(guān)的,用來(lái)做測(cè)試在合適不過(guò)了,這個(gè)csv文件大家可以在第四部分找到下載的地方哈
import os # 這兩行僅僅是切換路徑,方便我上傳Github,大家不用理會(huì),只要確認(rèn)csv文件和py文件再一起就行啦 os.chdir("F:Python教程segmentfaultpandas_sharePandas之旅_07 誰(shuí)說(shuō)pandas慢")
現(xiàn)在讓我們看看數(shù)據(jù)大概長(zhǎng)什么樣子
import numpy as np import pandas as pd f"Using {pd.__name__},{pd.__version__}"
"Using pandas,0.23.0"
df = pd.read_csv("energy_cost.csv",sep=",") df.head()
date_time | energy_kwh | |
---|---|---|
0 | 2001/1/13 0:00 | 0.586 |
1 | 2001/1/13 1:00 | 0.580 |
2 | 2001/1/13 2:00 | 0.572 |
3 | 2001/1/13 3:00 | 0.596 |
4 | 2001/1/13 4:00 | 0.592 |
現(xiàn)在我們看到初始數(shù)據(jù)的樣子了,主要有date_time和energy_kwh這兩列,來(lái)表示時(shí)間和消耗的電力,比較好理解,下面讓我們來(lái)看一下數(shù)據(jù)類型
df.dtypes >>> date_time object energy_kwh float64 dtype: object
type(df.iat[0,0]) >>> str
這里有個(gè)小問(wèn)題,Pandas和NumPy有dtypes(數(shù)據(jù)類型)的概念。如果未指定參數(shù),則date_time這一列的數(shù)據(jù)類型默認(rèn)object,所以為了之后運(yùn)算方便,我們可以把str類型的這一列轉(zhuǎn)化為timestamp類型:
df["date_time"] = pd.to_datetime(df["date_time"]) df.dtypes >>> date_time datetime64[ns] energy_kwh float64 dtype: object
先在大家可以發(fā)現(xiàn)我們通過(guò)用pd.to_datetime這個(gè)方法已經(jīng)成功的把date_time這一列轉(zhuǎn)化為了datetime64類型
df.head()
date_time | energy_kwh | |
---|---|---|
0 | 2001-01-13 00:00:00 | 0.586 |
1 | 2001-01-13 01:00:00 | 0.580 |
2 | 2001-01-13 02:00:00 | 0.572 |
3 | 2001-01-13 03:00:00 | 0.596 |
4 | 2001-01-13 04:00:00 | 0.592 |
現(xiàn)在再來(lái)看數(shù)據(jù), 發(fā)現(xiàn)已經(jīng)和剛才不同了,我們還可以通過(guò)指定format參數(shù)實(shí)現(xiàn)一樣的效果,速度上也會(huì)快一些
%%timeit -n 10 def convert_with_format(df, column_name): return pd.to_datetime(df[column_name],format="%Y/%m/%d %H:%M") df["date_time"]=convert_with_format(df, "date_time") >>>722 μs ± 334 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
有關(guān)具體的日期自定義相關(guān)方法,大家點(diǎn)擊這里查看
2. 批量計(jì)算的技巧首先,我們假設(shè)根據(jù)用電的時(shí)間段不同,電費(fèi)價(jià)目表如下:
Type | cents/kwh | periode |
---|---|---|
Peak | 28 | 17:00 to 24:00 |
Shoulder | 20 | 7:00 to 17:00 |
Off-Peak | 12 | 0:00 to 7:00 |
假設(shè)我們想要計(jì)算出電費(fèi),我們可以先寫(xiě)出一個(gè)根據(jù)時(shí)間動(dòng)態(tài)計(jì)算電費(fèi)的方法“apply_tariff“
def apply_tariff(kwh, hour): """Calculates cost of electricity for given hour.""" if 0 <= hour < 7: rate = 12 elif 7 <= hour < 17: rate = 20 elif 17 <= hour < 24: rate = 28 else: raise ValueError(f"Invalid hour: {hour}") return rate * kwh
好啦,現(xiàn)在我們想要在數(shù)據(jù)中新增一列 "cost_cents" 來(lái)表示總價(jià)錢,我們有很多選擇,首先能想到的方法便是iterrows(),它可以讓我們循環(huán)遍歷Dataframe的每一行,根據(jù)條件計(jì)算并賦值給新增的‘cost_cents’列
iterrows()首先我們能做的是循環(huán)遍歷流程,讓我們先用.iterrows()替代上面的方法來(lái)試試:
%%timeit -n 10 def apply_tariff_iterrows(df): energy_cost_list = [] for index, row in df.iterrows(): # Get electricity used and hour of day energy_used = row["energy_kwh"] hour = row["date_time"].hour # Append cost list energy_cost = apply_tariff(energy_used, hour) energy_cost_list.append(energy_cost) df["cost_cents"] = energy_cost_list apply_tariff_iterrows(df)
983 ms ± 65.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
我們?yōu)榱藴y(cè)試方便,所有的方法都會(huì)循環(huán)10次來(lái)比較耗時(shí),這里很明顯我們有很大的改進(jìn)空間,下面我們用apply方法來(lái)優(yōu)化
apply()%%timeit -n 10 def apply_tariff_withapply(df): df["cost_cents"] = df.apply( lambda row: apply_tariff( kwh=row["energy_kwh"], hour=row["date_time"].hour), axis=1) apply_tariff_withapply(df)
247 ms ± 24.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
這回速度得到了很大的提升,但是顯然我們還沒(méi)有g(shù)et到pandas加速的精髓:矢量化操作。下面讓我們開(kāi)始提速
isin()假設(shè)我們現(xiàn)在的電價(jià)是定值,不根據(jù)用電時(shí)間段來(lái)改變,那么pandas中最快的方法那就是采用(df["cost_cents"] = df["energy_kwh"] * price),這就是一個(gè)簡(jiǎn)單的矢量化操作示范。它基本是在Pandas中運(yùn)行最快的方式。
目前的問(wèn)題是我們的價(jià)格是動(dòng)態(tài)的,那么如何將條件判斷添加到Pandas中的矢量化運(yùn)算中呢?答案就是我們根據(jù)條件選擇和分組DataFrame,然后對(duì)每個(gè)選定的組應(yīng)用矢量化操作:
#先讓我們把時(shí)間序列作為索引 df.set_index("date_time", inplace=True)
%%timeit -n 10 def apply_tariff_isin(df): # Define hour range Boolean arrays peak_hours = df.index.hour.isin(range(17, 24)) shoulder_hours = df.index.hour.isin(range(7, 17)) off_peak_hours = df.index.hour.isin(range(0, 7)) # Apply tariffs to hour ranges df.loc[peak_hours, "cost_cents"] = df.loc[peak_hours, "energy_kwh"] * 28 df.loc[shoulder_hours,"cost_cents"] = df.loc[shoulder_hours, "energy_kwh"] * 20 df.loc[off_peak_hours,"cost_cents"] = df.loc[off_peak_hours, "energy_kwh"] * 12 apply_tariff_isin(df)
5.7 ms ± 871 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
這回我們發(fā)現(xiàn)速度是真正起飛了,首先我們根據(jù)用電的三個(gè)時(shí)段把df進(jìn)行分三組,再依次進(jìn)行三次矢量化操作,大家可以發(fā)現(xiàn)最后減少了很多時(shí)間,原理很簡(jiǎn)單:
在運(yùn)行的時(shí)候,.isin()方法返回一個(gè)布爾值數(shù)組,如下所示:
[False, False, False, ..., True, True, True]
接下來(lái)布爾數(shù)組傳遞給DataFrame的.loc索引器時(shí),我們獲得一個(gè)僅包含與3個(gè)用電時(shí)段匹配DataFrame切片。然后簡(jiǎn)單的進(jìn)行乘法操作就行了,這樣做的好處是我們已經(jīng)不需要?jiǎng)偛盘徇^(guò)的apply方法了,因?yàn)椴辉诖嬖诒闅v所有行的問(wèn)題
我們可以做的更好嗎?通過(guò)觀察可以發(fā)現(xiàn),在apply_tariff_isin()中,我們?nèi)匀辉谕ㄟ^(guò)調(diào)用df.loc和df.index.hour.isin()來(lái)進(jìn)行一些“手動(dòng)工作”。如果想要進(jìn)一步提速,我們可以使用cut方法
%%timeit -n 10 def apply_tariff_cut(df): cents_per_kwh = pd.cut(x=df.index.hour, bins=[0, 7, 17, 24], include_lowest=True, labels=[12, 20, 28]).astype(int) df["cost_cents"] = cents_per_kwh * df["energy_kwh"]
140 ns ± 29.9 ns per loop (mean ± std. dev. of 7 runs, 10 loops each)
效果依然鋒利,速度上有了成倍的提升
不要忘了用Numpy眾所周知,Pandas是在Numpy上建立起來(lái)的,所以在Numpy中當(dāng)然有類似cut的方法可以實(shí)現(xiàn)分組,從速度上來(lái)講差不太多
%%timeit -n 10 def apply_tariff_digitize(df): prices = np.array([12, 20, 28]) bins = np.digitize(df.index.hour.values, bins=[7, 17, 24]) df["cost_cents"] = prices[bins] * df["energy_kwh"].values
54.9 ns ± 19.3 ns per loop (mean ± std. dev. of 7 runs, 10 loops each)
正常情況下,以上的加速方法是能滿足日常需要的,如果有特殊的需求,大家可以上網(wǎng)看看有沒(méi)有相關(guān)的第三方加速包
3. 通過(guò)HDFStore存儲(chǔ)數(shù)據(jù)節(jié)省時(shí)間這里主要想強(qiáng)調(diào)的是節(jié)省預(yù)處理的時(shí)間,假設(shè)我們辛辛苦苦搭建了一些模型,但是每次運(yùn)行之前都要進(jìn)行一些預(yù)處理,比如類型轉(zhuǎn)換,用時(shí)間序列做索引等,如果不用HDFStore的話每次都會(huì)花去不少時(shí)間,這里Python提供了一種解決方案,可以把經(jīng)過(guò)預(yù)處理的數(shù)據(jù)存儲(chǔ)為HDF5格式,方便我們下次運(yùn)行時(shí)直接調(diào)用。
下面就讓我們把本篇文章的df通過(guò)HDF5來(lái)存儲(chǔ)一下:
# Create storage object with filename `processed_data` data_store = pd.HDFStore("processed_data.h5") # Put DataFrame into the object setting the key as "preprocessed_df" data_store["preprocessed_df"] = df data_store.close()
現(xiàn)在我們可以關(guān)機(jī)下班了,當(dāng)明天接著上班后,通過(guò)key("preprocessed_df")就可以直接使用經(jīng)過(guò)預(yù)處理的數(shù)據(jù)了
# Access data store data_store = pd.HDFStore("processed_data.h5") # Retrieve data using key preprocessed_df = data_store["preprocessed_df"] data_store.close()
preprocessed_df.head()
energy_kwh | cost_cents | |
---|---|---|
date_time | ||
2001-01-13 00:00:00 | 0.586 | 7.032 |
2001-01-13 01:00:00 | 0.580 | 6.960 |
2001-01-13 02:00:00 | 0.572 | 6.864 |
2001-01-13 03:00:00 | 0.596 | 7.152 |
2001-01-13 04:00:00 | 0.592 | 7.104 |
如上圖所示,現(xiàn)在我們可以發(fā)現(xiàn)date_time已經(jīng)是處理為index了
4. 源碼,相關(guān)數(shù)據(jù)及GitHub地址這一期為大家分享了一些pandas加速的實(shí)用技巧,希望可以幫到各位小伙伴,當(dāng)然,類似的技巧還有很多,但是核心思想應(yīng)該一直圍繞矢量化操作上,畢竟是基于Numpy上建立的包,如果大家有更好的辦法,希望可以在我的文章底下留言哈
我把這一期的ipynb文件,py文件以及我們用到的energy_cost.csv放到了Github上,大家可以點(diǎn)擊下面的鏈接來(lái)下載:
Github倉(cāng)庫(kù)地址: https://github.com/yaozeliang/pandas_share
希望大家能夠繼續(xù)支持我,這一篇文章已經(jīng)是Pandas系列的最后一篇了,雖然一共只寫(xiě)了7篇文章,但是我認(rèn)為從實(shí)用性上來(lái)講并沒(méi)有太遜色于收費(fèi)課程(除了少了很多漂亮的ppt),接下來(lái)我會(huì)再接再厲,分享一下我對(duì)R (ggplot2)或者matplotlib的學(xué)習(xí)經(jīng)驗(yàn)!!
Pandas之旅到此結(jié)束。撒花
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/43518.html
摘要:基于上的我們還可以實(shí)現(xiàn)幾個(gè)基于的,還是老樣子,先讓我們創(chuàng)建兩個(gè)好了,現(xiàn)在我們想要實(shí)現(xiàn)兩個(gè)的,但是條件是通過(guò)的和的這樣我們也可以得到結(jié)果。 Merge, Join, Concat 大家好,我有回來(lái)啦,這周更新的有點(diǎn)慢,主要是因?yàn)槲腋铝藗€(gè)人簡(jiǎn)歷哈哈,如果感興趣的朋友可以去看看哈: 我的主頁(yè) 個(gè)人認(rèn)為還是很漂亮的~,不得不說(shuō),很多時(shí)候老外的設(shè)計(jì)能力還是很強(qiáng)。 好了,有點(diǎn)扯遠(yuǎn)了,這一期我想和...
摘要:不為人知的七大實(shí)用技巧大家好,我今天勤快地回來(lái)了,這一期主要是和大家分享一些的實(shí)用技巧,會(huì)在日常生活中大大提升效率,希望可以幫助到大家還是老樣子,先給大家奉上這一期的章節(jié)目錄自定義選項(xiàng),設(shè)置實(shí)用中模塊構(gòu)建測(cè)試數(shù)據(jù)巧用訪問(wèn)器合并其他列拼接使用 Pandas不為人知的七大實(shí)用技巧 大家好,我今天勤快地回來(lái)了,這一期主要是和大家分享一些pandas的實(shí)用技巧,會(huì)在日常生活中大大提升效率,希望...
為什么你需要pandas 大家好,今天想和大家分享一下有關(guān)pandas的學(xué)習(xí)新的,我因工作需要,從去年12月開(kāi)始接觸這個(gè)非常好用的包,到現(xiàn)在為止也是算是熟悉了一些,因此發(fā)現(xiàn)了它的強(qiáng)大之處,特意想要和朋友們分享,特別是如果你每天和excel打交道,總是需要編寫(xiě)一些vba函數(shù)或者對(duì)行列進(jìn)行g(shù)roupby啊,merge,join啊之類的,相信我,pandas會(huì)讓你解脫的。 好啦,閑話少說(shuō),這篇文章的基礎(chǔ)...
摘要:數(shù)據(jù)清洗大家好,這一期我將為大家?guī)?lái)我的學(xué)習(xí)心得第二期數(shù)據(jù)清理。這一期我會(huì)和大家分享一些比較好用常見(jiàn)的清洗方法。首先還是讓我們來(lái)簡(jiǎn)單看一下本文將會(huì)用到的數(shù)據(jù)源這是一個(gè)超小型的房地產(chǎn)行業(yè)的數(shù)據(jù)集,大家會(huì)在文章最后找到下載地址。 數(shù)據(jù)清洗 大家好,這一期我將為大家?guī)?lái)我的pandas學(xué)習(xí)心得第二期:數(shù)據(jù)清理。這一步非常重要,一般在獲取數(shù)據(jù)源之后,我們緊接著就要開(kāi)始這一步,以便為了之后的各種...
摘要:有關(guān)字符串基本方法大家好,我又回來(lái)了之前的幾期我們已經(jīng)簡(jiǎn)單了解了的基礎(chǔ)操作,但是只要涉及到數(shù)據(jù),最常見(jiàn)的就是字符串類型,所以很多時(shí)候我們其實(shí)都在和字符串打交道,所以今天,我會(huì)把我自己總結(jié)的,有關(guān)字符串的常用方法分享給大家,希望能夠幫到各位小 有關(guān)字符串基本方法 大家好,我又回來(lái)了! 之前的幾期我們已經(jīng)簡(jiǎn)單了解了pandas的基礎(chǔ)操作,但是只要涉及到數(shù)據(jù),最常見(jiàn)的就是String(字符串...
閱讀 3342·2021-11-08 13:12
閱讀 2793·2021-10-15 09:41
閱讀 1485·2021-10-08 10:05
閱讀 3327·2021-10-08 10:04
閱讀 2151·2021-09-29 09:34
閱讀 2540·2019-08-30 15:55
閱讀 3008·2019-08-30 15:45
閱讀 2630·2019-08-29 14:17