pandas.Seriesのmapメソッドで列の要素を置換
pandas.Seriesのmap()メソッドに辞書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のマジックコマンド%%timeitでmap()と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()を試してみてからどちらを使うか決定するとよいだろう。