web-dev-qa-db-ja.com

pandasシリーズの値を辞書経由で効率的に置換

Pandasシリーズsの値を辞書を介して置き換える方法dは、何度も尋ねられ、再尋ねられました。

推奨される方法( 12 、、 4 )は、s.replace(d)を使用するか、または、すべての系列値が辞書のキーで見つかった場合は、s.map(d)を使用することがあります。

ただし、s.replaceを使用したパフォーマンスは、多くの場合、不合理に遅く、単純なリスト内包よりも5〜10倍遅くなります。

代替のs.map(d)はパフォーマンスが優れていますが、ディクショナリですべてのキーが見つかった場合にのみお勧めします。

なぜs.replaceは非常に遅いのですか、またパフォーマンスをどのように改善できますか?

import pandas as pd, numpy as np

df = pd.DataFrame({'A': np.random.randint(0, 1000, 1000000)})
lst = df['A'].values.tolist()

##### TEST 1 #####

d = {i: i+1 for i in range(1000)}

%timeit df['A'].replace(d)                          # 1.98s
%timeit [d[i] for i in lst]                         # 134ms

##### TEST 2 #####

d = {i: i+1 for i in range(10)}

%timeit df['A'].replace(d)                          # 20.1ms
%timeit [d.get(i, i) for i in lst]                  # 243ms

注:この質問は、いつ使用するかについての具体的なアドバイスを探しているため、重複としてマークされていません異なるデータセットを与えられた異なるメソッド。これは答えで明示的であり、他の質問では通常扱われない側面です。

13
jpp

簡単な解決策の1つは、値がディクショナリキーによってどの程度完全にカバーされているかの推定値に依存する方法を選択することです。

一般的なケース

  • すべての値がマップされている場合はdf['A'].map(d)を使用します。または
  • 5%を超える値がマップされている場合は、df['A'].map(d).fillna(df['A']).astype(int)を使用します。

少数、たとえば5%未満、dの値

  • df['A'].replace(d)を使用します

約5%の「クロスオーバーポイント」は、以下のベンチマークに固有のものです。

興味深いことに、単純なリスト内包表記は通常、どちらのシナリオでもmapを下回ります。

ベンチマーク

_import pandas as pd, numpy as np

df = pd.DataFrame({'A': np.random.randint(0, 1000, 1000000)})
lst = df['A'].values.tolist()

##### TEST 1 - Full Map #####

d = {i: i+1 for i in range(1000)}

%timeit df['A'].replace(d)                          # 1.98s
%timeit df['A'].map(d)                              # 84.3ms
%timeit [d[i] for i in lst]                         # 134ms

##### TEST 2 - Partial Map #####

d = {i: i+1 for i in range(10)}

%timeit df['A'].replace(d)                          # 20.1ms
%timeit df['A'].map(d).fillna(df['A']).astype(int)  # 111ms
%timeit [d.get(i, i) for i in lst]                  # 243ms
_

説明

_s.replace_が非常に遅い理由は、単に辞書をマップするだけではありません。これは、いくつかのEdgeケースと間違いなくまれな状況を扱います。通常、どのケースでもより注意が必要です。

これは、 _pandas\generic.py_replace()からの抜粋です。

_items = list(compat.iteritems(to_replace))
keys, values = Zip(*items)
are_mappings = [is_dict_like(v) for v in values]

if any(are_mappings):
    # handling of nested dictionaries
else:
    to_replace, value = keys, values

return self.replace(to_replace, value, inplace=inplace,
                    limit=limit, regex=regex)
_

多くの手順が含まれているようです。

  • 辞書をリストに変換します。
  • リストを反復処理し、ネストされた辞書をチェックします。
  • キーと値のイテレータをreplace関数に渡す。

これは、 _pandas\series.py_map()のより無駄のないコードと比較できます。

_if isinstance(arg, (dict, Series)):
    if isinstance(arg, dict):
        arg = self._constructor(arg, index=arg.keys())

    indexer = arg.index.get_indexer(values)
    new_values = algos.take_1d(arg._values, indexer)
_
17
jpp