一文帶你掌握常見的 Pandas 效能最佳化方法,讓你的 pandas 飛起來!

Harry_03發表於2020-01-15

微信公眾號:「Python讀數」

如有問題或建議,請公眾號留言

Pandas是Python中用於資料處理與分析的屠龍刀,想必大家也都不陌生,但Pandas在使用上有一些技巧和需要注意的地方,尤其是對於較大的資料集而言,如果你沒有適當地使用,那麼可能會導致Pandas的執行速度非常慢。

對於程式猿/媛而言,時間就是生命,這篇文章給大家總結了一些pandas常見的效能最佳化方法,希望能對你有所幫助!

一、資料讀取的最佳化

讀取資料是進行資料分析前的一個必經環節,pandas中也內建了許多資料讀取的函式,最常見的就是用pd.read_csv()函式從csv檔案讀取資料,那不同格式的檔案讀取起來有什麼區別呢?哪種方式速度更快呢?我們做個實驗對比一下。

這裡採用的資料共59萬行,分別儲存為xlsx、csv、hdf以及pkl格式,每種格式進行10次讀取測試,得到下面的結果。

image

可以看到,對同一份資料,pkl格式的資料的讀取速度最快,是讀取csv格式資料的近6倍,其次是hdf格式的資料,速度最慘不忍睹的是讀取xlsx格式的資料(這僅僅是一份只有15M左右大小的資料集呀)。

所以對於日常的資料集(大多為csv格式),可以先用pandas讀入,然後將資料轉存為pkl或者hdf格式,之後每次讀取資料時候,便可以節省一些時間。程式碼如下:

 import pandas as pd
 #讀取csv
 df = pd.read_csv('xxx.csv')

 #pkl格式
 df.to_pickle('xxx.pkl') #格式另存
 df = pd.read_pickle('xxx.pkl') #讀取

 #hdf格式
df.to_hdf('xxx.hdf','df') #格式另存
df = pd.read_hdf('xxx.pkl','df') #讀取

二、進行聚合操作時的最佳化

在使用aggtransform進行操作時,儘量使用Python的內建函式,能夠提高執行效率。(資料用的還是上面的測試用例)

1、agg+Python內建函式

image

2、agg+非內建函式

image
可以看到對 agg 方法,使用內建函式時執行效率提升了60%。

3、transform+Python內建函式

image

4、transform+非內建函式

image

transform方法而言,使用內建函式時執行效率提升了兩倍。

三、對資料進行逐行操作時的最佳化

假設我們現在有這樣一個電力消耗資料集,以及對應時段的電費價格,如下圖所示:

image

image

資料集記錄著每小時的電力消耗,如第一行代表2001年1月13日零點消耗了0.586kwh的電。不同使用時段的電費價格不一樣,我們現在的目的是求出總的電費,那麼就需要將對應時段的單位電費×消耗電量。下面給出了三種寫法,我們分別測試這三種處理方式,對比一下這三種寫法有什麼不同,程式碼效率上有什麼差異。

#編寫求得相應結果的函式
def get_cost(kwh, hour):
    if 0 <= hour < 7:
        rate = 0.6
    elif 7 <= hour < 17:
        rate = 0.68
    elif 17 <= hour < 24:
        rate = 0.75
    else:
        raise ValueError(f'Invalid hour: {hour}')
    return rate * kwh

#方法一:簡單迴圈
def loop(df):
    cost_list = []
    for i in range(len(df)):
        energy_used = df.iloc[i]['energy_kwh']
        hour = df.iloc[i]['date_time'].hour
        energy_cost = get_cost(energy_used, hour)
        cost_list.append(energy_cost)
    df['cost'] = cost_list

#方法二:apply方法
def apply_method(df):
     df['cost'] = df.apply(
         lambda row: get_cost(
             kwh=row['energy_kwh'],
             hour=row['date_time'].hour),
         axis=1)

#方法三:採用isin篩選出各時段,分段處理
df.set_index('date_time', inplace=True)
def isin_method(df):
    peak_hours = df.index.hour.isin(range(17, 24))
    simple_hours = df.index.hour.isin(range(7, 17))
    off_peak_hours = df.index.hour.isin(range(0, 7))

    df.loc[peak_hours, 'cost'] = df.loc[peak_hours, 'energy_kwh'] * 0.75
    df.loc[simple_hours,'cost'] = df.loc[simple_hours, 'energy_kwh'] * 0.68
    df.loc[off_peak_hours,'cost'] = df.loc[off_peak_hours, 'energy_kwh'] * 0.6

測試結果:

image

可以看到,採用isin() 篩選出對應資料後分開計算的速度是簡單迴圈的近606倍,這並不是說 isin() 有多厲害,方法三速度快是因為它採用了向量化的資料處理方式(這裡的isin() 是其中一種方式,還有其他方式,大家可以嘗試一下) ,這才是重點。什麼意思呢?

這裡簡單畫了個圖,大家可以結合這個圖和程式碼好好體會是一個一個處理快,還是把能進行相同操作的分開然後批次處理快。

image

四、使用numba進行加

如果在你的資料處理過程涉及到了大量的數值計算,那麼使用numba可以大大加快程式碼的執行效率,numba使用起來也很簡單,下面給大家演示一下。(程式碼處理不具有實際意義,只是展示一下效果)

首先需要安裝numba模組

>>>pip install numba

我們用一個簡單的例子測試一下numba的提速效果

import numba

@numba.vectorize
def f_with_numba(x): 
    return x * 2

def f_without_numba(x): 
    return x * 2

#方法一:apply逐行操作
df["double_energy"] = df.energy_kwh.apply(f_without_numba)

#方法二:向量化執行
df["double_energy"] = df.energy_kwh*2

#方法三:運用numba加速
#需要以numpy陣列的形式傳入
#否則會報錯
df["double_energy"] = f_with_numba(df.energy_kwh.to_numpy())

image

從測試結果來看,再次凸顯出向量化處理的優勢,同時numba對原本速度已經很快的向量化處理也能提高一倍多的效率。更多numba的使用方法請參考numba的使用文件。

參考資料:

1、https://pandas.pydata.org/pandasdocs/stabl...

2、https://realpython.com/fast-flexible-panda...

3、https://www.cnblogs.com/wkang/p/9794678.ht...

原創不易,如果覺得有點用,希望可以隨手點個贊,拜謝各位老鐵。

掃碼關注公眾號「Python讀數」,第一時間獲取乾貨,還可以加Python學習交流群!!
picture.png

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章