高速、柔軟、簡単、直観的:パンダプロジェクトをスピードアップする方法

高速、柔軟、簡単、直観的:パンダプロジェクトをスピードアップする方法

ビッグデータセットを使用する場合、https://pandas.pydata.org/pandas-docs/stable/[Pandas]ライブラリを発見したときのPythonの旅に沿った「aha」の瞬間を覚えているでしょう。 Pandasはhttps://realpython.com/tutorials/data-science/[データサイエンスと分析]のゲームチェンジャーです。特に、ExcelやVBAよりも強力なものを探していたためにPythonに来た場合はそうです。

それでは、私のようなデータサイエンティスト、アナリスト、エンジニアがいるパンダについてはどうですか? さて、Pandasのドキュメントでは、次のものを使用していると書かれています。

_ 「「高速」、「柔軟」、および「リレーショナル」または「ラベル付き」データの両方を「簡単に」「直感的に」操作できるように設計された表現力豊かなデータ構造。 _

高速、柔軟、簡単、直感的ですか? それはいいです! 仕事に複雑なデータモデルの構築が含まれる場合、開発にかかる時間の半分を、ビッグデータセットを介してモジュールが大量に発生するのを待つ必要はありません。 あまり強力ではないツールで苦労して手探りするのではなく、データの解釈に時間と頭脳を捧げたいと考えています。

しかし、パンダが遅いと聞きました…

最初にPandasを使用し始めたとき、データを分析するための優れたツールではありましたが、Pandasは統計モデリングツールとして使用するには遅すぎると言われました。 始めは、これが真実であることが証明されました。 親指をいじるのに数分以上を費やし、パンダがデータをめくるのを待ちました。

しかし、その後、PandasはNumPy配列構造の上に構築され、その操作の多くがNumPyまたはPandas自身のhttps://github.com/pandas-dev/pandas/を介してCで実行されることを知りましたCythonで記述されCにコンパイルされたPython拡張モジュールのtree/master/pandas/_libs [library] それで、パンダも速すぎてはいけませんか?

あなたがそれが意図された方法でそれを使用するならば、それは絶対にあるべきです!

パラドックスは、そうでなければ「https://stackoverflow.com/q/25011078/7954504[Pythonic]」コードが「見た目」である可能性があるものが、効率に関する限り、パンダでは準最適になる可能性があるということです。 NumPyと同様、https://realpython.com/numpy-array-programming/#what-is-vectorization [Pandasはベクトル化された操作用に設計されています]列全体またはデータセットを1回のスイープで操作します。 各「セル」または行について個別に考えることは、一般に最初ではなく最後の手段であるべきです。

このチュートリアル

明確にするために、これはPandasコードを過剰に最適化する方法に関するガイドではありません。 Pandasは、正しく使用すればすぐに実行できるように既に構築されています。 また、最適化とクリーンなコードの記述には大きな違いがあります。

これは、PandasをPythonで使用して、強力で使いやすい組み込み機能を最大限に活用するためのガイドです。 さらに、時間を節約するための実用的なヒントをいくつか学習するので、データを操作するたびに親指をいじる必要はありません。

このチュートリアルでは、以下について説明します。

  • 時系列で「+ datetime +」データを使用する利点

  • バッチ計算を行うための最も効率的なルート

  • HDFStoreでデータを保存して時間を節約する

これらのトピックを実証するために、時系列の電力消費量を調べる本業の例を取り上げます。 データを読み込んだ後、最終結果に到達するためのより効率的な方法を順を追って進めていきます。 ほとんどのパンダに当てはまる1つの格言は、AからBに到達する方法が複数あるということです。 ただし、これは、利用可能なすべてのオプションが、より大規模で要求の厳しいデータセットに等しく拡張できることを意味するものではありません。

いくつかの基本的なhttps://pandas.pydata.org/pandas-docs/stable/indexing.html[Pandasでのデータ選択]の実行方法を既に知っていると仮定して、始めましょう。

手元のタスク

この例の目標は、使用時間のエネルギー料金を適用して、1年間のエネルギー消費の総コストを見つけることです。 つまり、1日の時間帯によって電気の価格は異なるため、タスクは、1時間ごとに消費される電力に、消費された時間の正しい価格を掛けることです。

CSVファイルからデータを読み取りましょう。このカラムには、日付と時刻の2つの列があり、 1つはキロワット時(kWh)で消費される電気エネルギー用です。

CSV data、width = 502、height = 308

行には1時間ごとに使用される電力が含まれているため、年間で_365 x 24 = 8760_行があります。 各行は、その時点での「開始時間」の使用量を示しているため、1/1/13 0:00は、1月1日の最初の時間の使用量を示しています。

日時データで時間を節約する

最初に行う必要があるのは、PandasのI/O機能の1つを使用してCSVファイルからデータを読み取ることです。

>>>

>>> import pandas as pd
>>> pd.__version__
'0.23.1'

# Make sure that `demand_profile.csv` is in your
# current working directory.
>>> 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

これは一見問題ないように見えますが、小さな問題があります。 パンダとNumPyには、「+ dtypes 」(データ型)という概念があります。 引数が指定されていない場合、 ` date_time `は ` object +` dtypeを取ります:

>>>

>>> df.dtypes
date_time      object
energy_kwh    float64
dtype: object

>>> type(df.iat[0, 0])
str

これは理想的ではありません。 `+ object `は、 ` str +`だけでなく、1つのデータ型にうまく収まらない列のコンテナです。 文字列として日付を使用するのは困難で非効率的です。 (また、メモリ効率が悪いでしょう。)

時系列データを操作するには、 `+ date_time `列をdatetimeオブジェクトの配列としてフォーマットする必要があります。 (パンダはこれを「+タイムスタンプ」と呼びます。)パンダはここでの各ステップをかなり単純にします:

>>>

>>> df['date_time'] = pd.to_datetime(df['date_time'])
>>> df['date_time'].dtype
datetime64[ns]

(この場合、代わりにPandas https://pandas.pydata.org/pandas-docs/stable/generated/pandas.PeriodIndex.html [+ PeriodIndex +]を使用できることに注意してください。)

これで、CSVファイルによく似た「+ df +」というDataFrameができました。 2つの列と、行を参照するための数値インデックスがあります。

>>>

>>> df.head()
               date_time    energy_kwh
0    2013-01-01 00:00:00         0.586
1    2013-01-01 01:00:00         0.580
2    2013-01-01 02:00:00         0.572
3    2013-01-01 03:00:00         0.596
4    2013-01-01 04:00:00         0.592

上記のコードはシンプルで簡単ですが、どのくらい高速ですか? timing decoratorを使用してテストしてみましょう。 timeit + `。 このデコレータは、Pythonの標準ライブラリの `+ timeit.repeat()`をほぼ模倣していますが、関数自体の結果を返し、複数の試行からその平均実行時間を出力できます。 (Pythonの ` timeit.repeat()+`は、関数の結果ではなく、タイミングの結果を返します。)

関数を作成し、そのすぐ上に `+ @ timeit +`デコレータを配置すると、関数が呼び出されるたびに時間を計られることになります。 デコレータは、外側のループと内側のループを実行します。

>>>

>>> @timeit(repeat=3, number=10)
... def convert(df, column_name):
...     return pd.to_datetime(df[column_name])

>>> # Read in again so that we have `object` dtype to start
>>> df['date_time'] = convert(df, 'date_time')
Best of 3 trials with 10 function calls per trial:
Function `convert` ran in average of 1.610 seconds.

結果? 8760行のデータに対して1.6秒。 「素晴らしい」と言うかもしれませんが、「それはまったく時間ではありません。」しかし、より大きなデータセット、たとえば、1分間隔で1年間の電気使用が発生した場合はどうでしょうか。 これは60倍のデータなので、1分半ほど待つことになります。 それは容認しにくい音になり始めています。

実際、私は最近、330サイトからの10年間の1時間ごとの電力データを分析しました。 日時を変換するのに88分待ったと思いますか? 絶対違う!

これをどのようにスピードアップできますか? 一般的なルールとして、Pandasはデータを解釈する必要が少ないほどはるかに高速になります。 この場合、formatパラメーターを使用して、Pandaに時刻と日付のデータがどのように見えるかを伝えるだけで、速度が大幅に向上します。 これを行うには、http://strftime.org/[here]にある `+ strftime +`コードを使用し、次のように入力します。

>>>

>>> @timeit(repeat=3, number=100)
>>> def convert_with_format(df, column_name):
...     return pd.to_datetime(df[column_name],
...                           format='%d/%m/%y %H:%M')
Best of 3 trials with 100 function calls per trial:
Function `convert_with_format` ran in average of 0.032 seconds.

新しい結果は? 0.032秒、これは50倍高速です! 330サイトの処理時間を約86分節約できました。 悪くない改善!

より詳細な情報の1つは、CSVの日付時刻がhttps://en.wikipedia.org/wiki/ISO_8601[ISO 8601形式]にないことです。「+ YYYY-MM-DD HH:MM 」が必要です。 形式を指定しない場合、Pandasはhttps://dateutil.readthedocs.io/en/stable/[` dateutil +`]パッケージを使用して各文字列を日付に変換します。

逆に、生の日時データが既にISO 8601形式である場合、Pandasはhttps://github.com/pandas-dev/pandas/blob/08158c076d89177a962d00e4851649f1ef76d12f/pandas/_libs/tslib.pyx#L2129 [高速ルート]をすぐに使用できます日付を解析します。 これが、ここで形式を明示することが非常に有益な理由の1つです。 別のオプションは、 `+ infer_datetime_format = True +`パラメーターを渡すことですが、一般的には明示的であることが重要です。

:Pandasのhttps://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html [+ read_csv()+]では、ファイルの一部として日付を解析することもできますI/Oステップ。 + parse_dates ++ infer_datetime_format +、および `+ date_parser +`パラメーターを参照してください。

パンダデータの単純なループ

日付と時刻が便利な形式になったので、電気料金を計算する業務に取りかかる準備ができました。 コストは時間によって異なるため、1日の各時間に条件に応じてコスト係数を適用する必要があることに注意してください。 この例では、使用時間のコストは次のように定義されます。

Tariff Type Cents per kWh Time Range

Peak

28

17:00 to 24:00

Shoulder

20

7:00 to 17:00

Off-Peak

12

0:00 to 7:00

価格が1時間ごとに1 kWhあたり28セントと横ばいである場合、パンダに精通しているほとんどの人は、この計算が1行で達成できることを知っているでしょう。

>>>

>>> df['cost_cents'] = df['energy_kwh'] *28

これにより、その時間の電気代を含む新しい列が作成されます。

               date_time    energy_kwh       cost_cents
0    2013-01-01 00:00:00         0.586           16.408
1    2013-01-01 01:00:00         0.580           16.240
2    2013-01-01 02:00:00         0.572           16.016
3    2013-01-01 03:00:00         0.596           16.688
4    2013-01-01 04:00:00         0.592           16.576
# ...

ただし、コストの計算は時刻に依存します。 ここでは、多くの人々がパンダを意図していない方法で使用しています。条件付き計算を行うためのループを作成します。

このチュートリアルの残りの部分では、理想的ではないベースラインソリューションから開始し、Pandasを完全に活用するPythonicソリューションに進みます。

しかし、パンダの場合のPythonicとは何ですか? 皮肉なことに、C ++やJavaなどの他の(ユーザーフレンドリーではない)コーディング言語を経験しているのは、本能的に「ループで考える」ために特に影響を受けやすいということです。

Pythonでなく、多くの人がPandasがどのように使用されるように設計されているか知らないときに取るhttps://dbader.org/blog/pythonic-loops [ループアプローチ]を見てみましょう。 このアプローチの速さを確認するために、 `+ @ timeit +`を再度使用します。

まず、特定の時間に適切な関税を適用する関数を作成しましょう。

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

Pythonicでないループは、その栄光のすべてです:

>>>

>>> # NOTE: Don't do this!
>>> @timeit(repeat=3, number=100)
... def apply_tariff_loop(df):
...     """Calculate costs in loop.  Modifies `df` inplace."""
...     energy_cost_list = []
...     for i in range(len(df)):
...         # Get electricity used and hour of day
...         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.

しばらく前に「純粋なPython」を書いた後にパンダを手にした人にとって、この設計は自然に見えるかもしれません。典型的な「各_x_に、_y_を条件に、_z_を実行します」

ただし、このループは不格好です。 上記は、いくつかの理由でパンダの「アンチパターン」であると考えることができます。 まず、出力が記録されるリストを初期化する必要があります。

次に、不透明なオブジェクト `+ range(0、len(df))`を使用してループし、 ` apply_tariff()`を適用した後、作成に使用されるリストに結果を追加する必要があります新しいDataFrame列。 また、https://pandas.pydata.org/pandas-docs/stable/indexing.html#returning-a-view-versus-a-copy [連鎖インデックス]と呼ばれるものを、 ` df.iloc [i]で実行します['date_time'] + `。意図しない結果につながることがよくあります。

しかし、このアプローチの最大の問題は、計算の時間コストです。 私のマシンでは、このループは8760行のデータに対して3秒以上かかりました。 次に、Pandas構造を反復するためのいくつかの改善されたソリューションを見ていきます。

`+ .itertuples()`および ` .iterrows()+`によるループ

他にどのようなアプローチを取ることができますか? さて、Pandasは実際に `+ DataFrame.itertuples()`および ` DataFrame.iterrows()`メソッドを導入することで、 ` for i in range(len(df))`構文を冗長にしました。 これらは両方とも、一度に1行ずつ「 yield +」するジェネレーターメソッドです。

+ .itertuples()+`は、行のインデックス値を最初の要素として、各行にhttps://docs.python.org/3.6/library/collections.html#collections.namedtuple [+ namedtuple `]を生成しますタプルの。 ` nametuple `はPythonの ` collections +`モジュールからのデータ構造で、Pythonタプルのように動作しますが、属性ルックアップによってアクセス可能なフィールドがあります。

+ .iterrows()+`は、DataFrameの各行に対して(インデックス、 `+ Series +)のペア(タプル)を生成します。

「+ .itertuples()」は少し高速になる傾向がありますが、この例では「 nametuple 」に出会っていない読者もいるので、この例では「 .iterrows()+」を使用してみましょう。 これが達成するものを見てみましょう:

>>>

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

わずかな利益が得られました。 構文がより明確になり、行の値参照の混乱が少なくなるため、読みやすくなります。 時間の増加という点では、ほぼ5倍5倍高速です。

ただし、改善の余地があります。 まだ何らかの形式のPython for-loopを使用しています。つまり、Pandasの内部アーキテクチャに組み込まれたより高速な言語で理想的に実行できる場合、すべての関数呼び出しはPythonで実行されます。

パンダの + .apply()+

`+ .iterrows()`の代わりに ` .apply()`メソッドを使用して、この操作をさらに改善できます。 Pandasの ` .apply()`メソッドは関数(呼び出し可能)を受け取り、それらをDataFrameの軸(すべての行またはすべての列)に沿って適用します。 この例では、https://docs.python.org/3.6/tutorial/controlflow.html#lambda-expressions [lambda function]を使用すると、2つのデータ列を ` 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()+`メソッドの約半分でした。

ただし、これはまだ「非常に高速」ではありません。 1つの理由は、 `+ .apply()`が内部的にhttp://cython.org/[Cython]イテレータをループしようとすることです。 ただし、この場合、渡した「 lambda +」はCythonで処理できるものではないため、Pythonで呼び出されます。そのため、それほど高速ではありません。

330サイトの10年間の1時間ごとのデータに「+ .apply()+」を使用する場合、処理時間は約15分になります。 この計算がより大きなモデルの一部であることが意図されている場合、本当にスピードアップしたいと思うでしょう。 そこで、ベクトル化された操作が役立ちます。

`+ .isin()+`でデータを選択する

前に、単一の電気料金があれば、1行のコード( + df ['energy_kwh'] *28 +)ですべての電気消費データにその価格を適用できることを見ました。 この特定の操作は、ベクトル化された操作の例であり、パンダで物事を行う最速の方法です。

しかし、どのように条件計算をパンダのベクトル化された操作として適用できますか? 1つのコツは、条件に基づいてDataFrameを選択してグループ化し、選択した各グループにベクトル化された操作を適用することです。

次の例では、Pandasの `+ .isin()`メソッドを使用して行を選択し、ベクトル化された操作で適切な関税を適用する方法を説明します。 これを行う前に、DataFrameのインデックスとして「 date_time +」列を設定すると、少し便利になります。

df.set_index('date_time', inplace=True)

@timeit(repeat=3, number=100)
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)
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インデックス(日時)を識別します。 次に、これらのブール配列をDataFrameの「+ .loc +」インデクサーに渡すと、それらの時間に一致する行のみを含むDataFrameのスライスが取得されます。 その後は、スライスに適切な関税を掛けるだけで済み、これは高速なベクトル化操作です。

これは上記のループ操作と比較してどうですか? まず、すべての条件ロジックが行の選択に適用されるため、 `+ apply_tariff()+`が不要になったことにお気づきかもしれません。 そのため、記述する必要のあるコード行と、呼び出されるPythonコードが大幅に削減されます。

処理時間はどうですか? Pythonicでないループの315倍、 `+ .iterrows()`の約71倍、 ` .apply()+`の27倍高速です。 今、あなたはビッグデータセットを素晴らしく迅速に通過するために必要な速度で動いています。

もっと良くできますか?

`+ apply_tariff_isin()`では、 ` df.loc `と ` df.index.hour.isin()`をそれぞれ3回呼び出すことで、まだ「手動作業」を行っています。 より細かい範囲のタイムスロットがある場合、このソリューションはスケーラブルではないと主張できます。 (1時間ごとに異なるレートを設定するには、24個の ` .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()`は、各時間に属するビンに応じてラベルの配列(コスト)を適用しています。 ` include_lowest `パラメータは、最初の間隔を左に含めるかどうかを示すことに注意してください。 (グループに「 time = 0 +」を含める必要があります。)

これは、意図した結果を得るための完全にベクトル化された方法であり、タイミングの面でトップになります。

>>>

>>> 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.

これまで、300サイトのデータセット全体を処理するのに1時間以上かかっていた時間から1秒未満の時間をかけて作成しました。 悪くない! ただし、最後のオプションが1つあります。これは、NumPy関数を使用して、各DataFrameの基になるNumPy配列を操作し、結果をPandasデータ構造に統合することです。

NumPyを忘れないでください!

Pandasを使用しているときに忘れてはならない1つのポイントは、Pandas SeriesとDataFramesがNumPyライブラリの上に設計されていることです。 これにより、PandasはNumPyの配列および操作とシームレスに連携するため、計算の柔軟性がさらに高まります。

この次のケースでは、NumPyの `+ digitize()`関数を使用します。 Pandasの ` cut()+`と似ていますが、データはビン化されますが、今回は各時間に属するビンを表すインデックスの配列で表されます。 これらのインデックスは、価格配列に適用されます。

@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

`+ cut()+`関数のように、この構文は素晴らしく簡潔で読みやすいです。 しかし、速度はどのように比較されますか? どれどれ:

>>>

>>> 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.

この時点で、パフォーマンスはまだ改善されていますが、実際には限界に近づいています。 これはおそらく、コードの改善をハッキングして、全体像を考える日と呼ぶのに良い時期です。

Pandasを使用すると、ここで行ったようなバッチ計算を行うための優先オプションの「階層」を維持できます。 これらは通常、最も速いものから最も遅いものへとランク付けされます(そして最も柔軟なものから最も柔軟でないものへ):

  1. ベクトル化された操作を使用する:forループのないパンダのメソッドと関数。

  2. callableで `+ .apply()+`メソッドを使用します。

  3. + .itertuples()+`を使用します。Pythonの `+ collections +`モジュールからhttps://docs.python.org/3/library/collections.html#collections.namedtuple [+ namedtuples +`]としてDataFrame行を反復処理します。

  4. + .iterrows()+`を使用します。(Index、 `+ pd.Series +)のペアとしてDataFrame行を繰り返し処理します。 Pandasシリーズは柔軟なデータ構造ですが、各行をシリーズに構築してからアクセスするにはコストがかかる場合があります。

  5. ループには「要素ごと」を使用し、各セルまたは行を一度に1つずつ「+ df.loc 」または「 df.iloc 」で更新します。 (または、高速スカラーアクセスの場合は ` .at `/` .iat +`。)

*私の言葉を信じないでください:*上記の優先順位は、https://stackoverflow.com/a/24871316/7954504 [コアPandas開発者からのストレート]の提案です。

以下は、上記の「優先順位」を示しています。ここで作成した各機能は次のとおりです。

Function Runtime (seconds)

apply_tariff_loop()

3.152

apply_tariff_iterrows()

0.713

apply_tariff_withapply()

0.272

apply_tariff_isin()

0.010

apply_tariff_cut()

0.003

apply_tariff_digitize()

0.002

HDFStoreを使用した再処理の防止

Pandasのクイックデータプロセスを確認したので、https://pandas.pydata.org/pandas-docs/stable/io.html#hdf5-pytables [HDFStore]で再処理時間を完全に回避する方法を検討しましょう。最近パンダに統合されました。

多くの場合、複雑なデータモデルを構築する場合、データの前処理を行うと便利です。 たとえば、10年間の分単位の電力消費データがある場合、formatパラメーターを指定しても、日付と時刻をdatetimeに変換するだけで20分かかることがあります。 テストや分析のために、モデルを実行するたびにではなく、これを一度だけ実行する必要があります。

ここでできる非常に便利なことは、必要なときに使用するために、前処理を行い、データを処理済みの形式で保存することです。 しかし、データを再処理せずに適切な形式で保存するにはどうすればよいでしょうか? CSVとして保存すると、datetimeオブジェクトが失われ、再度アクセスするときに再処理する必要があります。

Pandasにはhttps://portal.hdfgroup.org/display/HDF5/HDF5[HDF5]を使用する組み込みのソリューションがあります。これは、データの表配列を保存するために特別に設計された高性能なストレージ形式です。 Pandasのhttps://pandas.pydata.org/pandas-docs/stable/api.html#hdfstore-pytables-hdf5 [+ HDFStore +]クラスを使用すると、DataFrameをHDF5ファイルに保存してアクセスできるようになります列タイプやその他のメタデータを保持しながら、効率的に。 これは辞書のようなクラスなので、Pythonの `+ dict +`オブジェクトの場合と同じように読み書きできます。

HDF5ファイルに前処理済みの電力消費量DataFrame、「+ df +」を保存する方法は次のとおりです。

# 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()

これで、コンピュータをシャットダウンし、戻ってきて、必要なときに処理済みのデータが待っていることを知って休憩できます。 再処理は必要ありません。 データタイプを保持したまま、HDF5ファイルからデータにアクセスする方法は次のとおりです。

# Access data store
data_store = pd.HDFStore('processed_data.h5')

# Retrieve data using key
preprocessed_df = data_store['preprocessed_df']
data_store.close()

データストアには、それぞれの名前をキーとして複数のテーブルを格納できます。

PandasでのHDFStoreの使用に関する注意:PyTables> = 3.0.0をインストールする必要があるため、Pandasをインストールした後、次のようにPyTablesを更新してください。

pip install --upgrade tables

結論

Pandasプロジェクトが「高速」、「柔軟」、「簡単」、「直感的」だと思わない場合は、ライブラリの使用方法を再考することを検討してください。

ここで検討した例は非常に単純ですが、Pandas機能を適切に適用することで、ランタイムとコードの可読性を大幅に向上させて起動できることを示しています。 次に、Pandasで大規模なデータセットを操作するときに適用できるいくつかの経験則を示します。

  • 可能な場合は、https://realpython.com/numpy-array-programming/#what-is-vectorization [vectorized operations]を使用して、「+ …​ for x df …​ +」メンタリティの問題に近づかないようにしてください。 コードに多くのforループが存在する場合、Pandasには多くのオーバーヘッドが伴うため、ネイティブPythonデータ構造での作業に適している可能性があります。

  • ベクトル化が単純に不可能であるか、効率的に解決するには難しすぎる、より複雑な操作がある場合は、 `+ .apply()+`メソッドを使用します。

  • 配列をループしなければならない場合(実際に起こります)、速度と構文を改善するために `+ .iterrows()`または ` .itertuples()+`を使用してください。

  • パンダには多くのオプションがあり、ほとんどの場合、AからBに到達する方法はいくつかあります。 これに留意して、さまざまなルートのパフォーマンスを比較し、プロジェクトのコンテキストで最適に機能するルートを選択してください。

  • データクリーニングスクリプトを作成したら、HDFStoreで中間結果を保存して再処理を避けます。

  • NumPyをPandas操作に統合すると、多くの場合、速度が向上し、構文が簡素化されます。