pandas.Seriesのmapメソッドで列の要素を置換

Modified: | Tags: Python, pandas

pandas.Seriesmap()メソッドに辞書dictを指定すると要素を置換できる。replace()メソッドでも同様に要素の置換ができるが、条件によってはmap()のほうが高速になる場合がある。

map()は任意の関数を各要素に適用する処理にも使われる。

本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。

import pandas as pd

print(pd.__version__)
# 2.1.2

要素の置換におけるmap()とreplace()の違い

map()に辞書dict{key: value)を指定すると、キーkeyと一致する要素が値valueに置き換えられる。

s = pd.Series(['A', 'B', 'C', 'A', 'B'])
print(s)
# 0    A
# 1    B
# 2    C
# 3    A
# 4    B
# dtype: object

print(s.map({'A': 'XX', 'B': 'YY', 'C': 'ZZ'}))
# 0    XX
# 1    YY
# 2    ZZ
# 3    XX
# 4    YY
# dtype: object

replace()にも辞書を指定できる。Seriesのすべての要素に対して置換が行われる場合はmap()と同様の結果となる。

print(s.replace({'A': 'XX', 'B': 'YY', 'C': 'ZZ'}))
# 0    XX
# 1    YY
# 2    ZZ
# 3    XX
# 4    YY
# dtype: object

辞書のキーがSeriesのすべての要素の値を網羅していない場合は結果が異なる。map()では置換されない値がNaNとなるが、replace()では元の値のまま。

print(s.map({'A': 'XX'}))
# 0     XX
# 1    NaN
# 2    NaN
# 3     XX
# 4    NaN
# dtype: object

print(s.replace({'A': 'XX'}))
# 0    XX
# 1     B
# 2     C
# 3    XX
# 4     B
# dtype: object

map()で置換されない要素を元の値のままにするには、NaNを埋めるfillna()メソッドに元のSeriesを指定する。

print(s.map({'A': 'XX'}).fillna(s))
# 0    XX
# 1     B
# 2     C
# 3    XX
# 4     B
# dtype: object

なお、replace()では正規表現を使って文字列の一部のみを置換したり、DataFrameの列ごとに異なる値に置換したりするなど、より複雑な処理が可能。詳細は以下の記事を参照。

速度比較

Jupyter Notebookのマジックコマンド%%timeitmap()replace()の実行時間を計測した結果を示す。Pythonコードとして実行しても計測できないので注意。

要素数100個のSeriesを例とする。

s = pd.Series(range(100))

すべての要素に対して置換が行われる場合はmap()のほうがreplace()よりも高速。

d_100 = {i: i * 10 for i in range(100)}

%%timeit
s.map(d_100)
# 70.7 µs ± 2.08 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

%%timeit
s.replace(d_100)
# 1.31 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

要素数50個の辞書で置換する場合もmap()fillna()のほうがreplace()よりも高速。

d_50 = {i: i * 10 for i in range(50)}

%%timeit
s.map(d_50).fillna(s)
# 108 µs ± 3.1 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

%%timeit
s.replace(d_50)
# 653 µs ± 3.73 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

要素数5個の辞書で置換する場合はreplace()のほうがmap()fillna()よりも高速。

d_5 = {i: i * 10 for i in range(5)}

%%timeit
s.map(d_5).fillna(s)
# 104 µs ± 3.85 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

%%timeit
s.replace(d_5)
# 78.5 µs ± 860 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

このように、replace()の実行時間は辞書のサイズに大きく依存する。

実行環境などによっても結果が変わる可能性もあるので、処理速度が重要な処理であれば実際に置換を行う条件でmap()replace()を試してみてからどちらを使うか決定するとよいだろう。

関連カテゴリー

関連記事