摘要:下面讓我們開始提速假設我們現在的電價是定值,不根據用電時間段來改變,那么中最快的方法那就是采用,這就是一個簡單的矢量化操作示范。它基本是在中運行最快的方式。
Pandas 加速
大家好,今天我們來看有關pandas加速的小技巧,不知道大家在剛剛接觸pandas的時候有沒有聽過如下的說法
pandas太慢了,運行要等半天
其實我想說的是,慢不是pandas的錯,大家要知道pandas本身是在Numpy上建立起來的包,在很多情況下是支持向量化運算的,而且還有C的底層設計,所以我今天
主要想從幾個方面和大家分享一下pandas加速的小技巧,與往常一樣,文章分成四部分,本文結構如下:
使用datetime類型來處理和時間序列有關的數據
批量計算的技巧
通過HDFStore存儲數據節省時間
源碼,相關數據及GitHub地址
現在就讓我們開始吧
1. 使用datetime類型來處理和時間序列有關的數據首先這里我們使用的數據源是一個電力消耗情況的數據(energy_cost.csv),非常貼近生活而且也是和時間息息相關的,用來做測試在合適不過了,這個csv文件大家可以在第四部分找到下載的地方哈
import os # 這兩行僅僅是切換路徑,方便我上傳Github,大家不用理會,只要確認csv文件和py文件再一起就行啦 os.chdir("F:Python教程segmentfaultpandas_sharePandas之旅_07 誰說pandas慢")
現在讓我們看看數據大概長什么樣子
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 |
現在我們看到初始數據的樣子了,主要有date_time和energy_kwh這兩列,來表示時間和消耗的電力,比較好理解,下面讓我們來看一下數據類型
df.dtypes >>> date_time object energy_kwh float64 dtype: object
type(df.iat[0,0]) >>> str
這里有個小問題,Pandas和NumPy有dtypes(數據類型)的概念。如果未指定參數,則date_time這一列的數據類型默認object,所以為了之后運算方便,我們可以把str類型的這一列轉化為timestamp類型:
df["date_time"] = pd.to_datetime(df["date_time"]) df.dtypes >>> date_time datetime64[ns] energy_kwh float64 dtype: object
先在大家可以發現我們通過用pd.to_datetime這個方法已經成功的把date_time這一列轉化為了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 |
現在再來看數據, 發現已經和剛才不同了,我們還可以通過指定format參數實現一樣的效果,速度上也會快一些
%%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)
有關具體的日期自定義相關方法,大家點擊這里查看
2. 批量計算的技巧首先,我們假設根據用電的時間段不同,電費價目表如下:
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 |
假設我們想要計算出電費,我們可以先寫出一個根據時間動態計算電費的方法“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
好啦,現在我們想要在數據中新增一列 "cost_cents" 來表示總價錢,我們有很多選擇,首先能想到的方法便是iterrows(),它可以讓我們循環遍歷Dataframe的每一行,根據條件計算并賦值給新增的‘cost_cents’列
iterrows()首先我們能做的是循環遍歷流程,讓我們先用.iterrows()替代上面的方法來試試:
%%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)
我們為了測試方便,所有的方法都會循環10次來比較耗時,這里很明顯我們有很大的改進空間,下面我們用apply方法來優化
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)
這回速度得到了很大的提升,但是顯然我們還沒有get到pandas加速的精髓:矢量化操作。下面讓我們開始提速
isin()假設我們現在的電價是定值,不根據用電時間段來改變,那么pandas中最快的方法那就是采用(df["cost_cents"] = df["energy_kwh"] * price),這就是一個簡單的矢量化操作示范。它基本是在Pandas中運行最快的方式。
目前的問題是我們的價格是動態的,那么如何將條件判斷添加到Pandas中的矢量化運算中呢?答案就是我們根據條件選擇和分組DataFrame,然后對每個選定的組應用矢量化操作:
#先讓我們把時間序列作為索引 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)
這回我們發現速度是真正起飛了,首先我們根據用電的三個時段把df進行分三組,再依次進行三次矢量化操作,大家可以發現最后減少了很多時間,原理很簡單:
在運行的時候,.isin()方法返回一個布爾值數組,如下所示:
[False, False, False, ..., True, True, True]
接下來布爾數組傳遞給DataFrame的.loc索引器時,我們獲得一個僅包含與3個用電時段匹配DataFrame切片。然后簡單的進行乘法操作就行了,這樣做的好處是我們已經不需要剛才提過的apply方法了,因為不在存在遍歷所有行的問題
我們可以做的更好嗎?通過觀察可以發現,在apply_tariff_isin()中,我們仍然在通過調用df.loc和df.index.hour.isin()來進行一些“手動工作”。如果想要進一步提速,我們可以使用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上建立起來的,所以在Numpy中當然有類似cut的方法可以實現分組,從速度上來講差不太多
%%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)
正常情況下,以上的加速方法是能滿足日常需要的,如果有特殊的需求,大家可以上網看看有沒有相關的第三方加速包
3. 通過HDFStore存儲數據節省時間這里主要想強調的是節省預處理的時間,假設我們辛辛苦苦搭建了一些模型,但是每次運行之前都要進行一些預處理,比如類型轉換,用時間序列做索引等,如果不用HDFStore的話每次都會花去不少時間,這里Python提供了一種解決方案,可以把經過預處理的數據存儲為HDF5格式,方便我們下次運行時直接調用。
下面就讓我們把本篇文章的df通過HDF5來存儲一下:
# 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()
現在我們可以關機下班了,當明天接著上班后,通過key("preprocessed_df")就可以直接使用經過預處理的數據了
# 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 |
如上圖所示,現在我們可以發現date_time已經是處理為index了
4. 源碼,相關數據及GitHub地址這一期為大家分享了一些pandas加速的實用技巧,希望可以幫到各位小伙伴,當然,類似的技巧還有很多,但是核心思想應該一直圍繞矢量化操作上,畢竟是基于Numpy上建立的包,如果大家有更好的辦法,希望可以在我的文章底下留言哈
我把這一期的ipynb文件,py文件以及我們用到的energy_cost.csv放到了Github上,大家可以點擊下面的鏈接來下載:
Github倉庫地址: https://github.com/yaozeliang/pandas_share
希望大家能夠繼續支持我,這一篇文章已經是Pandas系列的最后一篇了,雖然一共只寫了7篇文章,但是我認為從實用性上來講并沒有太遜色于收費課程(除了少了很多漂亮的ppt),接下來我會再接再厲,分享一下我對R (ggplot2)或者matplotlib的學習經驗!!
Pandas之旅到此結束。撒花
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/43518.html
摘要:基于上的我們還可以實現幾個基于的,還是老樣子,先讓我們創建兩個好了,現在我們想要實現兩個的,但是條件是通過的和的這樣我們也可以得到結果。 Merge, Join, Concat 大家好,我有回來啦,這周更新的有點慢,主要是因為我更新了個人簡歷哈哈,如果感興趣的朋友可以去看看哈: 我的主頁 個人認為還是很漂亮的~,不得不說,很多時候老外的設計能力還是很強。 好了,有點扯遠了,這一期我想和...
摘要:不為人知的七大實用技巧大家好,我今天勤快地回來了,這一期主要是和大家分享一些的實用技巧,會在日常生活中大大提升效率,希望可以幫助到大家還是老樣子,先給大家奉上這一期的章節目錄自定義選項,設置實用中模塊構建測試數據巧用訪問器合并其他列拼接使用 Pandas不為人知的七大實用技巧 大家好,我今天勤快地回來了,這一期主要是和大家分享一些pandas的實用技巧,會在日常生活中大大提升效率,希望...
為什么你需要pandas 大家好,今天想和大家分享一下有關pandas的學習新的,我因工作需要,從去年12月開始接觸這個非常好用的包,到現在為止也是算是熟悉了一些,因此發現了它的強大之處,特意想要和朋友們分享,特別是如果你每天和excel打交道,總是需要編寫一些vba函數或者對行列進行groupby啊,merge,join啊之類的,相信我,pandas會讓你解脫的。 好啦,閑話少說,這篇文章的基礎...
摘要:數據清洗大家好,這一期我將為大家帶來我的學習心得第二期數據清理。這一期我會和大家分享一些比較好用常見的清洗方法。首先還是讓我們來簡單看一下本文將會用到的數據源這是一個超小型的房地產行業的數據集,大家會在文章最后找到下載地址。 數據清洗 大家好,這一期我將為大家帶來我的pandas學習心得第二期:數據清理。這一步非常重要,一般在獲取數據源之后,我們緊接著就要開始這一步,以便為了之后的各種...
摘要:有關字符串基本方法大家好,我又回來了之前的幾期我們已經簡單了解了的基礎操作,但是只要涉及到數據,最常見的就是字符串類型,所以很多時候我們其實都在和字符串打交道,所以今天,我會把我自己總結的,有關字符串的常用方法分享給大家,希望能夠幫到各位小 有關字符串基本方法 大家好,我又回來了! 之前的幾期我們已經簡單了解了pandas的基礎操作,但是只要涉及到數據,最常見的就是String(字符串...
閱讀 3319·2021-11-08 13:12
閱讀 2756·2021-10-15 09:41
閱讀 1451·2021-10-08 10:05
閱讀 3300·2021-10-08 10:04
閱讀 2102·2021-09-29 09:34
閱讀 2472·2019-08-30 15:55
閱讀 2979·2019-08-30 15:45
閱讀 2577·2019-08-29 14:17