高速、柔軟、簡単、直観的:パンダプロジェクトをスピードアップする方法
ビッグデータセットを扱う場合は、Pandasライブラリを発見したPythonの旅の「あはは」の瞬間を覚えているでしょう。 Pandasは、data science and analyticsのゲームチェンジャーです。特に、ExcelやVBAよりも強力なものを探していたためにPythonを使用した場合はそうです。
それでは、私のようなデータサイエンティスト、アナリスト、エンジニアがいるパンダについてはどうですか? さて、Pandasのドキュメントでは、次のものを使用していると書かれています。
「fast、flexible、およびeasyとintuitiveの両方の「リレーショナル」または「ラベル付き」データを処理するように設計された表現力豊かなデータ構造。」
高速、柔軟、簡単、直感的ですか? それはいいです! 仕事に複雑なデータモデルの構築が含まれる場合、開発にかかる時間の半分を、ビッグデータセットを介してモジュールが大量に発生するのを待つ必要はありません。 あまり強力ではないツールで苦労して手探りするのではなく、データの解釈に時間と頭脳を捧げたいと考えています。
しかし、パンダが遅いと聞きました…
最初にPandasを使用し始めたとき、データを分析するための優れたツールではありましたが、Pandasは統計モデリングツールとして使用するには遅すぎると言われました。 始めは、これが真実であることが証明されました。 親指をいじるのに数分以上を費やし、パンダがデータをめくるのを待ちました。
しかし、その後、PandasはNumPy配列構造の上に構築されており、その操作の多くは、NumPyを介して、またはCythonで記述されたPandas独自のlibraryのPython拡張モジュールを介してCで実行されることを学びました。そしてCにコンパイルされます。 それで、パンダも速すぎてはいけませんか?
あなたがそれが意図された方法でそれを使用するならば、それは絶対にあるべきです!
パラドックスは、効率に関する限り、Pythonicコードがパンダでは最適ではない可能性があるということです。 NumPyと同様に、1回のスイープで列全体またはデータセットを操作するPandas is designed for vectorized operations。 各「セル」または行について個別に考えることは、一般に最初ではなく最後の手段であるべきです。
このチュートリアル
明確にするために、これはPandasコードを過剰に最適化する方法に関するガイドではありません。 Pandasは、正しく使用すればすぐに実行できるように既に構築されています。 また、最適化とクリーンなコードの記述には大きな違いがあります。
これは、PandasをPythonで使用して、強力で使いやすい組み込み機能を最大限に活用するためのガイドです。 さらに、時間を節約するための実用的なヒントをいくつか学習するので、データを操作するたびに親指をいじる必要はありません。
このチュートリアルでは、以下について説明します。
-
時系列で
datetime
データを使用する利点 -
バッチ計算を行うための最も効率的なルート
-
HDFStoreでデータを保存して時間を節約する
これらのトピックを実証するために、時系列の電力消費量を調べる本業の例を取り上げます。 データを読み込んだ後、最終結果に到達するためのより効率的な方法を順を追って進めていきます。 ほとんどのパンダに当てはまる1つの格言は、AからBに到達する方法が複数あるということです。 ただし、これは、利用可能なすべてのオプションが、より大規模で要求の厳しいデータセットに等しく拡張できることを意味するものではありません。
基本的なdata selection in Pandasの実行方法をすでに知っていると仮定して、始めましょう。
手元のタスク
この例の目標は、使用時間のエネルギー料金を適用して、1年間のエネルギー消費の総コストを見つけることです。 つまり、1日の時間帯によって電気の価格は異なるため、タスクは、1時間ごとに消費される電力に、消費された時間の正しい価格を掛けることです。
2つの列があるCSV fileからデータを読み取ってみましょう。1つは日付と時刻、もう1つはキロワット時(kWh)で消費される電気エネルギーです。
行には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
これは一見問題ないように見えますが、小さな問題があります。 Pandasと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
列を日時オブジェクトの配列としてフォーマットする必要があります。 (PandasはこれをTimestamp
と呼んでいます。)Pandasは、ここでの各ステップをかなり単純にします。
>>>
>>> df['date_time'] = pd.to_datetime(df['date_time'])
>>> df['date_time'].dtype
datetime64[ns]
(この場合、代わりにPandasPeriodIndex
を使用できることに注意してください。)
これで、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
上記のコードはシンプルで簡単ですが、どのくらい高速ですか? もともと@timeit
と呼んでいたtiming decoratorを使ってテストしてみましょう。 このデコレータは、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.
結果? 1.6 seconds for 8760 rows of data. 「素晴らしい」と言うかもしれませんが、「それはまったく時間ではありません。」しかし、より大きなデータセット、たとえば、1分間隔で1年間の電気使用が発生した場合はどうでしょうか。 これは60倍のデータなので、1分半ほど待つことになります。 それは容認しにくい音になり始めています。
実際、私は最近、330サイトからの10年間の1時間ごとの電力データを分析しました。 日時を変換するのに88分待ったと思いますか? 絶対違う!
これをどのようにスピードアップできますか? 一般的なルールとして、Pandasはデータを解釈する必要が少ないほどはるかに高速になります。 この場合、formatパラメーターを使用して、Pandaに時刻と日付のデータがどのように見えるかを伝えるだけで、速度が大幅に向上します。 これを行うには、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 seconds, which is 50 times faster! 330サイトの処理時間を約86分節約できました。 悪くない改善!
より細かい詳細の1つは、CSVの日時がISO 8601 formatではないことです。YYYY-MM-DD HH:MM
が必要です。 形式を指定しない場合、Pandasはdateutil
パッケージを使用して各文字列を日付に変換します。
逆に、生の日時データがすでにISO 8601形式である場合、パンダはすぐにfast routeを使用して日付を解析できます。 これが、ここで形式を明示することが非常に有益な理由の1つです。 もう1つのオプションは、infer_datetime_format=True
パラメータを渡すことですが、通常は明示的にすることでメリットがあります。
Note:Pandasのread_csv()
を使用すると、ファイルI / Oステップの一部として日付を解析することもできます。 parse_dates
、infer_datetime_format
、およびdate_parser
パラメータを参照してください。
パンダデータの単純なループ
日付と時刻が便利な形式になったので、電気料金を計算する業務に取りかかる準備ができました。 コストは時間によって異なるため、1日の各時間に条件に応じてコスト係数を適用する必要があることに注意してください。 この例では、使用時間のコストは次のように定義されます。
料金タイプ | kWhあたりのセント | 時間範囲 |
---|---|---|
Peak |
28 |
17:00〜24:00 |
ショルダー |
20 |
7:00〜17:00 |
閑散時 |
12 |
0:00〜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などの他の(ユーザーフレンドリーではない)コーディング言語を経験しているのは、本能的に「ループで考える」ために特に影響を受けやすいということです。
Pythonicではなく、パンダがどのように使用されるように設計されているかを知らないときに多くの人が取るloop approachを見てみましょう。 @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」を書いた後でパンダを手にした人にとって、このデザインは自然に見えるかもしれません。「yを条件として、xごとに、z。」
ただし、このループは不格好です。 上記は、いくつかの理由でパンダの「アンチパターン」であると考えることができます。 まず、出力が記録されるリストを初期化する必要があります。
次に、不透明なオブジェクトrange(0, len(df))
を使用してループし、apply_tariff()
を適用した後、新しいDataFrame列の作成に使用されるリストに結果を追加する必要があります。 また、df.iloc[i]['date_time']
でchained indexingと呼ばれる処理を実行します。これにより、意図しない結果が生じることがよくあります。
しかし、このアプローチの最大の問題は、計算の時間コストです。 私のマシンでは、このループは8760行のデータに対して3秒以上かかりました。 次に、Pandas構造を反復するためのいくつかの改善されたソリューションを見ていきます。
.itertuples()
および.iterrows()
でループする
他にどのようなアプローチを取ることができますか? さて、パンダは実際にDataFrame.itertuples()
とDataFrame.iterrows()
メソッドを導入することにより、for i in range(len(df))
構文を冗長にしました。 これらは両方とも、一度に1行ずつyield
するジェネレータメソッドです。
.itertuples()
は、タプルの最初の要素として行のインデックス値を使用して、各行のnamedtuple
を生成します。 nametuple
は、Pythonのcollections
モジュールのデータ構造であり、Pythonタプルのように動作しますが、属性ルックアップによってアクセス可能なフィールドがあります。
.iterrows()
は、DataFrameの各行に対して(index、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の軸(すべての行またはすべての列)に沿って適用します。 この例では、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()
が内部でCythonイテレータをループしようとすることです。 ただし、この場合、渡したlambda
は、Cythonで処理できるものではないため、Pythonで呼び出されます。その結果、それほど高速ではありません。
330サイトの10年間の1時間ごとのデータに.apply()
を使用すると、約15分の処理時間がわかります。 この計算がより大きなモデルの一部であることが意図されている場合、本当にスピードアップしたいと思うでしょう。 そこで、ベクトル化された操作が役立ちます。
.isin()
でデータを選択する
以前、単一の電気料金がある場合、1行のコード(df['energy_kwh'] * 28
)ですべての電力消費データにその価格を適用できることを確認しました。 この特定の操作は、ベクトル化された操作の例であり、パンダで物事を行う最速の方法です。
しかし、どのように条件計算をパンダのベクトル化された操作として適用できますか? 1つのコツは、条件に基づいてDataFrameを選択してグループ化し、選択した各グループにベクトル化された操作を適用することです。
この次の例では、Pandasの.isin()
メソッドを使用して行を選択し、ベクトル化された操作で適切な料金を適用する方法を示します。 これを行う前に、date_time
列をDataFrameのインデックスとして設定すると、作業が少し便利になります。
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()
に似ていますが、今回は、1時間ごとにどのビンに属するかを表すインデックスの配列で表されます。 これらのインデックスは、価格配列に適用されます。
@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を使用すると、ここで行ったようなバッチ計算を行うための優先オプションの「階層」を維持できます。 これらは通常、最も速いものから最も遅いものへとランク付けされます(そして最も柔軟なものから最も柔軟でないものへ):
-
ベクトル化された操作を使用する:forループのないパンダのメソッドと関数。
-
呼び出し可能オブジェクトで
.apply()
メソッドを使用します。 -
.itertuples()
を使用します。Pythonのcollections
モジュールからnamedtuples
としてDataFrame行を反復処理します。 -
.iterrows()
を使用します。DataFrame行を(index、pd.Series
)ペアとして繰り返します。 Pandasシリーズは柔軟なデータ構造ですが、各行をシリーズに構築してからアクセスするにはコストがかかる場合があります。 -
ループには「要素ごと」を使用し、各セルまたは行を
df.loc
またはdf.iloc
で一度に1つずつ更新します。 (または、高速スカラーアクセスの場合は.at
/.iat
。)
Don’t Take My Word For It:上記の優先順位は、straight from a core Pandas developerの提案です。
以下は、上記の「優先順位」を示しています。ここで作成した各機能は次のとおりです。
関数 | ランタイム(秒) |
---|---|
|
3.152 |
|
0.713 |
|
0.272 |
|
0.010 |
|
0.003 |
|
0.002 |
HDFStoreを使用した再処理の防止
Pandasでの迅速なデータ処理について見てきたので、最近Pandasに統合されたHDFStoreを使用して再処理時間を完全に回避する方法を見ていきましょう。
多くの場合、複雑なデータモデルを構築する場合、データの前処理を行うと便利です。 たとえば、10年間の分単位の電力消費データがある場合、formatパラメーターを指定しても、日付と時刻をdatetimeに変換するだけで20分かかることがあります。 テストや分析のために、モデルを実行するたびにではなく、これを一度だけ実行する必要があります。
ここでできる非常に便利なことは、必要なときに使用するために、前処理を行い、データを処理済みの形式で保存することです。 しかし、データを再処理せずに適切な形式で保存するにはどうすればよいでしょうか? CSVとして保存すると、datetimeオブジェクトが失われ、再度アクセスするときに再処理する必要があります。
Pandasには、データの表形式の配列を格納するために特別に設計された高性能ストレージ形式であるHDF5を使用する組み込みソリューションがあります。 PandasのHDFStore
クラスを使用すると、DataFrameをHDF5ファイルに保存して、列タイプやその他のメタデータを保持しながら、効率的にアクセスできるようにすることができます。 これは辞書のようなクラスであるため、Pythonのdict
オブジェクトの場合と同じように読み取りと書き込みを行うことができます。
前処理された電力消費量DataFrame、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()
これで、コンピュータをシャットダウンし、戻ってきて、必要なときに処理済みのデータが待っていることを知って休憩できます。 再処理は必要ありません。 データタイプを保持したまま、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プロジェクトがfast、flexible、easy、およびintuitiveであると思わない場合は、ライブラリの使用方法を再考することを検討してください。
ここで検討した例は非常に単純ですが、Pandas機能を適切に適用することで、ランタイムとコードの可読性を大幅に向上させて起動できることを示しています。 次に、Pandasで大規模なデータセットを操作するときに適用できるいくつかの経験則を示します。
-
for x in df...
の考え方の問題に取り組むのではなく、可能な場合はvectorized operationsを使用するようにしてください。 コードに多くのforループが存在する場合、Pandasには多くのオーバーヘッドが伴うため、ネイティブPythonデータ構造での作業に適している可能性があります。 -
ベクトル化が単純に不可能であるか、効率的に実行するのが難しすぎる、より複雑な操作がある場合は、
.apply()
メソッドを使用します。 -
配列をループする必要がある場合(これは発生します)、速度と構文を改善するために
.iterrows()
または.itertuples()
を使用します。 -
パンダには多くのオプションがあり、ほとんどの場合、AからBに到達する方法はいくつかあります。 これに留意して、さまざまなルートのパフォーマンスを比較し、プロジェクトのコンテキストで最適に機能するルートを選択してください。
-
データクリーニングスクリプトを作成したら、HDFStoreで中間結果を保存して再処理を避けます。
-
NumPyをPandas操作に統合すると、多くの場合、速度が向上し、構文が簡素化されます。