Pandasの旅(七)誰がpandasが遅いと言ったの?


Pandas加速
皆さん、こんにちは、今日はpandasの加速に関するテクニックを見てみましょう.pandasに触れたばかりの頃、次のような話を聞いたことがありますか.
pandasが遅すぎて、運行は半日待たなければなりません
実は私が言いたいのは、遅いのはpandasのせいではありません.pandas自体がNumpyで構築されたパッケージであることを知っておく必要があります.多くの場合、量子化演算をサポートしています.そして、Cの下位設計もあります.だから、今日は主にいくつかの面からpandasの加速のテクニックを共有したいと思っています.いつものように、文章は4つの部分に分かれています.
  • datetimeタイプを使用して、時系列に関するデータ
  • を処理する.
  • バッチ計算のテクニック
  • HDFStoreによるデータ格納による時間節約
  • .
  • ソースコード、関連データ及びGitHubアドレス
  • 今から始めましょう
    1.datetimeタイプを使用して、時間系列に関するデータを処理する
    まず、ここで私たちが使っているデータソースは電力消費状況のデータ(energy_cost.csv)で、生活に非常に近く、時間にも密接に関係しています.テストに適しています.このcsvファイルは第4部でダウンロードできる場所を見つけることができます.
    import os
    #           ,     Github,      ,    csv   py        
    os.chdir("F:\\Python  \\segmentfault\\pandas_share\\Pandas  _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の2列は、時間と消費電力を表しています.分かりやすいので、データ型を見てみましょう.
    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の列はdatetime 64タイプに変換されます
    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)
    
    

    今回は速度が大幅に向上したが,pandas加速の真髄であるベクトル化操作をgetしていないことは明らかである.次にスピードを上げましょう
    isin()
    我々の現在の電気価格が定値であり,電気使用期間によって変化しないと仮定すると,pandasにおける最も速い方法は(df['cost_cents']=df['energy_kwh']*price)を採用することであり,これは簡単なベクトル化動作の例である.基本的にはPandasで最も速く動作する方法です.
    現在の問題は私たちの価格が動的であることですが、Pandasのベクトル化演算に条件判断を追加するにはどうすればいいのでしょうか.条件に基づいてデータフレームを選択してグループ化し、選択したグループごとにベクトル化操作を適用します.
    #             
    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)
    
    

    今回は速度が本格的に離陸していることがわかりました.まず、電気の3つの時間帯に基づいてdfを3つのグループに分け、3回のベクトル化操作を順次行います.最後に多くの時間を減らしたことがわかります.原理は簡単です.
    運行中、.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)
    

    効果は依然として鋭く,速度は2倍に向上した.
    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は解決策を提供しています.前処理されたデータをHDF 5形式に保存することができ、次回の実行時に直接呼び出すことができます.
    次に、この記事のdfをHDF 5で保存します.
    # 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シリーズの最后の1篇で、全部で7篇の文章しか书いていませんが、私は実用性の上であまり有料の课程(多くのきれいなpptが少なくなったことを除いて)に遜色がないと思って、次に私は更に努力して、私のR(ggplot 2)あるいはmatplotlibに対する学习の経験を分かち合います!!
    Pandasの旅はこれで終わりです.花をまく