apply、map、applymap — データ変換の3つのツール
このトピックを修了すると
apply、map、applymap の3つの関数の違いを明確に理解し、状況に応じた適切なツールを選択できるようになり、パフォーマンスの違いを理解できます。
なぜ3つも関数があるのか
pandasでデータを変換するための関数が3つもあるので、最初は混乱するかもしれません。それぞれ適用範囲が異なります。
| 関数 | 対象 | 適用単位 |
|---|---|---|
map() | Series (1次元) | 個別の値ごと |
apply() | Series または DataFrame | 行または列ごと |
applymap() | DataFrame (2次元) | 個別の値ごと |
一言で言うと:mapはSeriesの各値に、applymapはDataFrameの各値に、applyは行/列全体に関数を適用します。
map — Seriesの各値を変換
map()はSeries専用です。各値に関数を適用するか、辞書でマッピングします。
関数マッピング
import pandas as pd
df = pd.DataFrame({ "name": ["Alice", "Bob", "Carol"], "score": [85.7, 92.3, 78.1]})
# 各名前を大文字にdf["name"].map(str.upper)# 0 ALICE# 1 BOB# 2 CAROL
# 各スコアを四捨五入df["score"].map(round)# 0 86# 1 92# 2 78辞書マッピング
grade_map = { "A": "Excellent", "B": "Good", "C": "Average"}
grades = pd.Series(["A", "B", "C", "A", "B"])grades.map(grade_map)# 0 Excellent# 1 Good# 2 Average# 3 Excellent# 4 Good辞書にない値は NaN になります。これは、実務でカテゴリエンコーディングによく使用されます。
lambdaと組み合わせて
df["score"].map(lambda x: "Pass" if x >= 80 else "Fail")# 0 Pass# 1 Pass# 2 Failapply — 行または列単位で関数を適用
apply() は、SeriesとDataFrameの両方で使用できます。
Series.apply — mapと類似
df["score"].apply(lambda x: round(x, 1))# map() と同じ結果Seriesでは、map()とほぼ同じです。違いは、apply() が追加の引数を渡せることです。
DataFrame.apply — 列(または行)単位
df = pd.DataFrame({ "math": [85, 92, 78], "english": [90, 88, 95], "science": [88, 91, 82]})
# 各科目(列)の平均df.apply("mean") # axis=0 (デフォルト、列方向)# math 85.0# english 91.0# science 87.0
# 各生徒(行)の平均df.apply("mean", axis=1) # axis=1 (行方向)# 0 87.666667# 1 90.333333# 2 85.000000axis=0 は「上から下へ」(各列に適用)、axis=1 は「左から右へ」(各行に適用)です。
行単位で複数のカラムを参照
df = pd.DataFrame({ "name": ["Alice", "Bob", "Carol"], "math": [85, 92, 78], "english": [90, 88, 95]})
# 各生徒の2科目のうち、高い点数df.apply(lambda row: max(row["math"], row["english"]), axis=1)# 0 90# 1 92# 2 95行(axis=1)単位の apply で、row はその行の Series です。複数のカラムを参照して、複合的な計算を行うことができます。
applymap — DataFrameのすべての値を変換
applymap() はDataFrame専用で、すべてのセルに適用します。
df = pd.DataFrame({ "math": [85.7, 92.3, 78.1], "english": [90.2, 88.9, 95.4], "science": [88.1, 91.7, 82.3]})
# すべての値を整数にdf.applymap(int)# math english science# 0 85 90 88# 1 92 88 91# 2 78 95 82
# すべての値にフォーマットを適用df.applymap(lambda x: f"{x:.1f}%")# math english science# 0 85.7% 90.2% 88.1%# 1 92.3% 88.9% 91.7%# 2 78.1% 95.4% 82.3%注記: pandas 2.1 以降、
applymap()はmap()に統合されました。DataFrame.map()で同じ操作ができます。しかし、多くのコードベースではまだapplymap()を使用しているので、覚えておく必要があります。
3つの関数を比較 — 一目で
import pandas as pd
df = pd.DataFrame({ "A": [1, 2, 3], "B": [4, 5, 6]})
# map: Series の各値 → 値df["A"].map(lambda x: x * 10) # Series → Series# 0 10# 1 20# 2 30
# apply (Series): map と類似df["A"].apply(lambda x: x * 10) # Series → Series (同じ結果)
# apply (DataFrame, axis=0): 列単位df.apply(sum) # DataFrame → Series# A 6# B 15
# apply (DataFrame, axis=1): 行単位df.apply(sum, axis=1) # DataFrame → Series# 0 5# 1 7# 2 9
# applymap: DataFrame の各値 → 値df.applymap(lambda x: x * 10) # DataFrame → DataFrame# A B# 0 10 40# 1 20 50# 2 30 60パフォーマンス — ベクトル演算を優先
apply、map、applymap は、内部で Python のループを回します。pandas の組み込み演算(ベクトル演算)の方がはるかに高速です。
import numpy as np
df = pd.DataFrame({"value": range(1_000_000)})
# 遅い — apply (Python ループ)%timeit df["value"].apply(lambda x: x * 2)# ~200ms
# 高速 — ベクトル演算 (C で実行)%timeit df["value"] * 2# ~2ms (100倍高速)ルール: 単純な算術、比較、文字列メソッドはベクトル演算を使用します。apply() は、ベクトル演算で表現できない複雑なロジックでのみ使用します。
# 悪い — apply で条件分岐df["label"] = df["value"].apply(lambda x: "high" if x > 500000 else "low")
# 良い — np.where (ベクトル演算)df["label"] = np.where(df["value"] > 500000, "high", "low")
# 悪い — apply で文字列処理df["upper"] = df["name"].apply(str.upper)
# 良い — str accessor (ベクトル演算)df["upper"] = df["name"].str.upper()実践的なパターン — 複合変換
ベクトル演算で不可能な場合に apply を使用します。
df = pd.DataFrame({ "name": ["Alice Smith", "Bob Lee", "Carol Park"], "birth": ["1995-03-15", "1988-11-22", "2001-07-08"], "department": ["Sales", "Dev", "HR"]})
def create_employee_id(row): dept_code = row["department"][:2].upper() last_name = row["name"].split()[-1].upper() year = row["birth"][:4] return f"{dept_code}-{last_name}-{year}"
df["emp_id"] = df.apply(create_employee_id, axis=1)print(df["emp_id"])# 0 SA-SMITH-1995# 1 DE-LEE-1988# 2 HR-PARK-2001複数のカラムを参照して、複雑な文字列を作成する処理 — これはベクトル演算で表現することが難しく、apply(axis=1) が適切です。
まとめ
| 状況 | ツール |
|---|---|
| Series の各値を変換 | map() または apply() |
| 辞書で値をマッピング | map(dict) |
| DataFrame の各セルを変換 | applymap() (または pandas 2.1+ map()) |
| 列単位の集計 | apply(func, axis=0) |
| 行単位の複合計算 | apply(func, axis=1) |
| 単純な算術/比較 | ベクトル演算 (apply を使用しない) |
3つの関数を選ぶ順序:1) ベクトル演算でできるか? → ベクトル演算。2) Series の各値か? → map。3) DataFrame の各セルか? → applymap。4) 行/列単位の複合ロジックか? → apply。 この順序を覚えておけば、パフォーマンスと可読性の両方を高めることができます。
よくある間違い
| 間違い | 問題 | 解決 |
|---|---|---|
df.map(func) (pandas < 2.1) | AttributeError | df.applymap(func) または pandas をアップグレード |
df.apply(func) でスカラを返す | 期待と異なる結果 | axis を確認 — 0 は列、1 は行 |
| 単純な算術に apply を使用 | 100 倍遅くなる | ベクトル演算に置き換える |
| map にないキー | NaN が発生 | fillna() でデフォルト値を指定 |