再見 for 循環!pandas 提速 315 倍~

2021-03-02 Python知識圈

點擊上方Python知識圈,設為星標

來源:Python數據科學

作者:東哥起飛

for是所有程式語言的基礎語法,初學者為了快速實現功能,依懶性較強。但如果從運算時間性能上考慮可能不是特別好的選擇。本次介紹幾個常見的提速方法,一個比一個快,了解pandas本質,才能知道如何提速。

>>> import pandas as pd
# 導入數據集
>>> df = pd.read_csv('demand_profile.csv')
>>> df.head()
     date_time  energy_kwh
0  1/1/13 0:00       0.586
1  1/1/13 1:00       0.580
2  1/1/13 2:00       0.572
3  1/1/13 3:00       0.596
4  1/1/13 4:00       0.592

基於上面的數據,我們現在要增加一個新的特徵,但這個新的特徵是基於一些時間條件生成的,根據時長(小時)而變化,如下:

因此,如果你不知道如何提速,那正常第一想法可能就是用apply方法寫一個函數,函數裡面寫好時間條件的邏輯代碼。

def apply_tariff(kwh, 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

然後使用for循環來遍歷df,根據apply函數邏輯添加新的特徵,如下:

>>> # 不贊同這種操作
>>> @timeit(repeat=3, number=100)
... def apply_tariff_loop(df):
...     """用for循環計算enery cost,並添加到列表"""
...     energy_cost_list = []
...     for i in range(len(df)):
...         # 獲取用電量和時間(小時)
...         energy_used = df.iloc[i]['energy_kwh']
...         hour = df.iloc[i]['date_time'].hour
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
... 
>>> apply_tariff_loop(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_loop` ran in average of 3.152 seconds.

對於那些寫Pythonic風格的人來說,這個設計看起來很自然。然而,這個循環將會嚴重影響效率。原因有幾個:其次,它使用不透明對象範圍(0,len(df))循環,然後再應用apply_tariff()之後,它必須將結果附加到用於創建新DataFrame列的列表中。另外,還使用df.iloc [i]['date_time']執行所謂的鏈式索引,這通常會導致意外的結果。這種方法的最大問題是計算的時間成本。對於8760行數據,此循環花費了3秒鐘。一、使用 iterrows循環第一種可以通過pandas引入iterrows方法讓效率更高。這些都是一次產生一行的生成器方法,類似scrapy中使用的yield用法。.itertuples為每一行產生一個namedtuple,並且行的索引值作為元組的第一個元素。nametuple是Python的collections模塊中的一種數據結構,其行為類似於Python元組,但具有可通過屬性查找訪問的欄位。.iterrows為DataFrame中的每一行產生(index,series)這樣的元組。在這個例子中使用.iterrows,我們看看這使用iterrows後效果如何。

>>> @timeit(repeat=3, number=100)
... def apply_tariff_iterrows(df):
...     energy_cost_list = []
...     for index, row in df.iterrows():
...         # 獲取用電量和時間(小時)
...         energy_used = row['energy_kwh']
...         hour = row['date_time'].hour
...         # 添加cost列表
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
...
>>> apply_tariff_iterrows(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_iterrows` ran in average of 0.713 seconds.

這樣的語法更明確,並且行值引用中的混亂更少,因此它更具可讀性。但是,還有更多的改進空間,理想情況是可以用pandas內置更快的方法完成。二、pandas的apply方法我們可以使用.apply方法而不是.iterrows進一步改進此操作。pandas的.apply方法接受函數callables並沿DataFrame的軸(所有行或所有列)應用。下面代碼中,lambda函數將兩列數據傳遞給apply_tariff():

>>> @timeit(repeat=3, number=100)
... 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)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_withapply` ran in average of 0.272 seconds.

apply的語法優點很明顯,行數少,代碼可讀性高。在這種情況下,所花費的時間大約是iterrows方法的一半。但是,這還不是「非常快」。一個原因是apply()將在內部嘗試循環遍歷Cython迭代器。但是在這種情況下,傳遞的lambda不是可以在Cython中處理的東西,因此它在Python中調用並不是那麼快。如果我們使用apply()方法獲取10年的小時數據,那麼將需要大約15分鐘的處理時間。如果這個計算只是大規模計算的一小部分,那麼真的應該提速了。這也就是矢量化操作派上用場的地方。三、矢量化操作:使用.isin選擇數據如果你不基於一些條件,而是可以在一行代碼中將所有電力消耗數據應用於該價格:df ['energy_kwh'] * 28,類似這種。那麼這個特定的操作就是矢量化操作的一個例子,它是在pandas中執行的最快方法。但是如何將條件計算應用為pandas中的矢量化運算?一個技巧是:根據你的條件,選擇和分組DataFrame,然後對每個選定的組應用矢量化操作。在下面代碼中,我們將看到如何使用pandas的.isin()方法選擇行,然後在矢量化操作中實現新特徵的添加。在執行此操作之前,如果將date_time列設置為DataFrame的索引,會更方便:

# 將date_time列設置為DataFrame的索引
df.set_index('date_time', inplace=True)

@timeit(repeat=3, number=100)
def apply_tariff_isin(df):
    # 定義小時範圍Boolean數組
    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_traffic函數中的定義
    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)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_isin` ran in average of 0.010 seconds.

提示,上面.isin()方法返回的是一個布爾值數組,如下:

[False, False, False, ..., True, True, True]

布爾值標識了DataFrame索引datetimes是否落在了指定的小時範圍內。然後把這些布爾數組傳遞給DataFrame的.loc,將獲得一個與這些小時匹配的DataFrame切片。然後再將切片乘以適當的費率,這就是一種快速的矢量化操作了。上面的方法完全取代了我們最開始自定義的函數apply_tariff(),代碼大大減少,同時速度起飛。運行時間比Pythonic的for循環快315倍,比iterrows快71倍,比apply快27倍!四、還能更快?在上面apply_tariff_isin中,我們通過調用df.loc和df.index.hour.isin三次來進行一些手動調整。如果我們有更精細的時間範圍,你可能會說這個解決方案是不可擴展的。但在這種情況下,我們可以使用pandas的pd.cut()函數來自動完成切割:

@timeit(repeat=3, number=100)
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']

上面代碼pd.cut()會根據bin列表應用分組。其中include_lowest參數表示第一個間隔是否應該是包含左邊的。

>>> apply_tariff_cut(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_cut` ran in average of 0.003 seconds.

到目前為止,使用pandas處理的時間上基本快達到極限了!只需要花費不到一秒的時間即可處理完整的10年的小時數據集。但是,最後一個其它選擇,就是使用 NumPy,還可以更快!五、使用Numpy繼續加速使用pandas時不應忘記的一點是Pandas的Series和DataFrames是在NumPy庫之上設計的。並且,pandas可以與NumPy陣列和操作無縫銜接。下面我們使用NumPy的 digitize()函數更進一步。它類似於上面pandas的cut(),因為數據將被分箱,但這次它將由一個索引數組表示,這些索引表示每小時所屬的bin。然後將這些索引應用於價格數組:

@timeit(repeat=3, number=100)
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

>>> apply_tariff_digitize(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_digitize` ran in average of 0.002 seconds.

0.002秒! 雖然仍有性能提升,但已經很邊緣化了。

添加pk哥個人微信即送Python資料

Python知識點100題的PDF

Python相關的電子書10本

記得備註:「100題」

↓點擊閱讀原文查看pk哥原創視頻

相關焦點

  • pandas100個騷操作:再見 for 循環!速度提升315倍!
    本篇是pandas100個騷操作系列的第 11 篇:再見 for 循環!速度提升315倍!系列內容,請看👉「pandas100個騷操作」話題,訂閱後文章更新可第一時間推送至訂閱號。for是所有程式語言的基礎語法,初學者為了快速實現功能,依懶性較強。
  • 數據處理必看:如何讓你的 pandas 循環加快 71803 倍
    雷鋒網 AI 開發者按,如果你使用 python 和 pandas 進行數據分析,那麼不久你就會第一次使用循環了。然而,即使是對小型數據集,使用標準循環也很費時,你很快就會意識到大型數據幀可能需要很長的時間。當我第一次等了半個多小時來執行代碼時,我找到了接下來想與你共享的替代方案。標準循環數據幀是具有行和列的 pandas 對象。
  • 僅需添加一行代碼,即可讓Pandas加速四倍 | Pandas on Ray
    import pandas as pds = time.time()df = pd.read_csv("esea_master_dmg_demos.part1.csv")e = time.time()print("Pandas Loading Time = {}".format(e-s))import modin.pandas as pds
  • 這是 Pandas 最詳細教程了
    pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。pandas 相當於 python 中 excel:它使用表(也就是 dataframe),能在數據上做各種變換,但還有其他很多功能。
  • 史上最全Pandas 教程!
    pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。pandas 相當於 python 中 excel:它使用表(也就是 dataframe),能在數據上做各種變換,但還有其他很多功能。如果你早已熟知 python 的使用,可以直接跳到第三段。
  • 數據科學 | 從 Pandas 小白到 Pandas 能手
    在本文中,作者從 Pandas 的簡介開始,一步一步講解了 Pandas 的發展現狀、內存優化等問題。這是一篇最佳實踐教程,既適合用過 Pandas 的讀者,也適合沒用過但想要上手的小白。通過本文,你將有望發現一到多種用 pandas 編碼的新方法。Pandas 發展現狀;內存優化;索引;方法鏈;隨機提示。
  • Expo pandas arrive in Shanghai
    Ten giant pandas selected for the world expo by the Chinese giant panda protection and research center were flown to Shanghai on Jan. 5 from the center's Bifengxia base in Ya'an City, southwest China's
  • 一行代碼讓 pandas 的 apply 速度飆到極致!
    來源:Python數據科學作者:東哥起飛1. pandas
  • 別找了,這是 Pandas 最詳細教程了
    pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。pandas 相當於 python 中 excel:它使用表(也就是 dataframe),能在數據上做各種變換,但還有其他很多功能。如果你早已熟知 python 的使用,可以直接跳到第三段。
  • Python 數據分析:Pandas 進階
    import numpy as npfrom pandas import Series, DataFrames = Series(['1', '2', np.nan, '3'])df = DataFrame([['1', '2'], ['3', np.nan], [np.nan, 4]])print(s)print(df)# 清除缺失項
  • 一日二技:Pandas 與 Docker 的使用技巧
    pandas 讀取無頭 CSV我們知道,CSV 一般是長這樣的:
  • Laid-back pandas ease into new home
    The two pandas - 4-year-old female Meng Meng and 7-year-old male Jiao Qing - arrived in the German capital last month.
  • JY:再見pandakill!天涯未遠,江湖再見!
    微博中JY寫到:再見pandakill!天涯未遠,江湖再見!裡面提到的pandakill是由直播平臺打造的狼人殺真人秀活動,作為狼人殺界的大神,能夠參與平臺支持的節目那是最好不過的事情了。然而此時JY卻忽然間宣布退出對自己這麼重要的節目,圍觀群眾無論如何也想不明白。
  • Six pandas transferred from Wolong center
    A total of six pandas were taken away on Friday from a major panda base in Wolong of southwest China's Sichuan Province because of damaged shelters and food shortage after
  • 【pandas 基礎】
    構建的含有更高級數據結構和工具的數據分析包類似於 Numpy 的核心是 ndarray,pandas 也是圍繞著 Series 和 DataFrame 兩個核心數據結構展開的 。pandas 約定俗成的導入方法如下:lang:pythonfrom pandas import Series,DataFrameimport pandas as pd<br />SeriesSeries 可以看做一個定長的有序字典。
  • Pandas face tough time this winter
    CHENGDU -- Giant pandas living in the wild in Sichuan will face a struggle to survive this winter because of the damage caused to their environment by the May 12 earthquake
  • It’s a first in the world! A giant panda recovers from...
    Maotao going for a leisurely walk before the illness「At present, occurrence of pylorus or duodenal obstruction in giant pandas
  • Red pandas enjoy their life at Panda World in Fuzhou
    One of a triplet red pandas (ailurus fulgens) is seen at the Panda World in Fuzhou, capital of southeast China's
  • 英語小作文:Pandas 熊貓
    新東方網>英語>英語學習>英語寫作>中小學英語作文>正文英語小作文:Pandas 熊貓 2013-01-15 17:29 來源:恆星英語 作者:
  • Debut, weaning and breeding: pandas get busy
    Giant panda Bao Bao was a star at the Smithsonian's National Zoo before she left for China on Feb 21. And it turns out that she is also a star back in China.