概要(applyは「行や列ごとに関数を適用して柔軟に加工する」)
pandasのapplyは、SeriesやDataFrameに対して「各要素」「各行」「各列」へ関数を適用するための基本メソッドです。forループより短く読みやすく書け、複数列を使う条件分岐や新列作成に向いています。初心者は「Series.applyは要素ごと」「DataFrame.applyはaxisで行/列を選ぶ」「戻り値の形を意識する」の3点を押さえると迷いません。
基本の使い方(SeriesとDataFrameで“対象”が違う)
Series.apply(1列の要素単位で処理)
import pandas as pd
s = pd.Series([10, 20, 30])
out = s.apply(lambda x: x * 2) # 各要素を2倍
print(out)
# 0 20
# 1 40
# 2 60
PythonSeries.applyは“要素ごと”が基本。文字列の整形、数値の変換、カテゴリのマッピングなどで使います。
DataFrame.apply(axisで行/列を決める)
df = pd.DataFrame({"A": [1, 2, 3], "B": [10, 20, 30]})
col_twice = df.apply(lambda col: col * 2, axis=0) # 列ごと(axis=0)
row_sum = df.apply(lambda row: row["A"] + row["B"], axis=1) # 行ごと(axis=1)
print(col_twice)
# A B
# 0 2 20
# 1 4 40
# 2 6 60
print(row_sum)
# 0 11
# 1 22
# 2 33
Pythonaxis=0は“列を1本のSeries”として関数へ渡し、axis=1は“行を1本のSeries”として渡します。
axisの深掘り(列方向・行方向で関数の引数が変わる)
列方向(axis=0)で列単位の変換・検証
df = pd.DataFrame({"name": [" Taro ", "Hanako"], "age": [20, 25]})
def clean(col):
if col.name == "name":
return col.str.strip()
if col.name == "age":
return col.astype(int)
return col
cleaned = df.apply(clean, axis=0)
print(cleaned)
# name age
# 0 Taro 20
# 1 Hanako 25
Python列オブジェクト(Series)の名前はcol.nameで判別できます。列別の処理をまとめて書けます。
行方向(axis=1)で複数列を組み合わせる新列作成
df = pd.DataFrame({"name": ["taro", "hanako"], "score": [85, 92]})
df["label"] = df.apply(
lambda row: f"{row['name'].title()}({row['score']})",
axis=1
)
print(df)
# name score label
# 0 taro 85 Taro(85)
# 1 hanako 92 Hanako(92)
Python行オブジェクト(Series)から複数列を参照して合成・条件分岐ができます。新列作成の定番です。
引数・戻り値の制御(args/kwargs・result_type・raw)
関数へ追加引数を渡す(args/kwargs)
df = pd.DataFrame({"x": [1, 2, 3], "y": [10, 20, 30]})
def scale(col, k=1, b=0):
return col * k + b
scaled = df.apply(scale, axis=0, k=2, b=1) # kwargsで渡せる
print(scaled)
# x y
# 0 3 21
# 1 5 41
# 2 7 61
Pythonapply(func, axis, args=(), **kwargs)の形で関数にパラメータを供給できます。
result_typeで行方向の戻り値を整える
df = pd.DataFrame({"a": [1, 2], "b": [10, 20]})
# 各行で複数値を返し、列として並べたい場合
def stats(row):
return pd.Series({"sum": row.sum(), "mean": row.mean()})
out = df.apply(stats, axis=1) # Seriesを返すと列化される
print(out)
# sum mean
# 0 11 5.5
# 1 22 11.0
Python行方向で複数値を返すときは、辞書やSeriesを返すと列へ展開されます。タプルを返す場合はresult_type=”expand”を検討します。
raw=Trueで少し高速化(配列で渡す)
import numpy as np
df = pd.DataFrame({"x": [1, 2, 3], "y": [10, 20, 30]})
out = df.apply(lambda arr: np.sum(arr), axis=1, raw=True) # ndarrayで渡される
Pythonraw=Trueは行/列がndarrayとして渡され、純粋な数値計算ならやや速くなることがあります(ラベル参照はできません)。
実践例(前処理・条件分岐・複合ロジックの新列作成)
文字列前処理と型揃え(列方向で一括)
df = pd.DataFrame({"name": [" Taro", None], "email": ["taro@example.com ", ""]})
def normalize(col):
if col.name in ("name", "email"):
return col.fillna("").str.strip().str.lower()
return col
df = df.apply(normalize, axis=0)
print(df)
# name email
# 0 taro taro@example.com
# 1 (空)
Python行方向の条件分岐でカテゴリー付与
df = pd.DataFrame({"score": [45, 70, 92]})
df["grade"] = df.apply(
lambda r: ("A" if r["score"] >= 90 else "B" if r["score"] >= 60 else "C"),
axis=1
)
print(df)
# score grade
# 0 45 C
# 1 70 B
# 2 92 A
Python複数列から安全なラベル生成(欠損に強い)
df = pd.DataFrame({"first": ["Taro", None], "last": ["Yamada", "Suzuki"]})
def make_name(row):
f = (row["first"] or "").strip()
l = (row["last"] or "").strip()
return (f + " " + l).strip() or "unknown"
df["full_name"] = df.apply(make_name, axis=1)
print(df)
# first last full_name
# 0 Taro Yamada Taro Yamada
# 1 None Suzuki Suzuki
Pythonパフォーマンスと代替手段(速く・簡単にできる方法を選ぶ)
ベクトル化が最優先(applyより速い)
- 文字列処理: df[“name”] = df[“name”].str.strip().str.lower
- 条件代入: df[“grade”] = np.where(df[“score”] >= 90, “A”, …)
- 列演算: df[“sum”] = df[“A”] + df[“B”]
applyは柔軟ですが遅くなりがち。まず列演算・strアクセサ・NumPy関数で書けないかを検討しましょう。
Series.mapとDataFrame.applymapの使い分け
- Series.map: 1列の値を辞書や関数で変換する簡潔な方法。
- DataFrame.applymap: “各要素”に関数を当てる(要素単位)。列ごと・行ごではなく全要素に同じ処理をしたいときに限る。
groupby+agg/transformで集計やグループ内計算
- 集計: df.groupby(“key”).agg(total=(“x”, “sum”))
- グループ標準化: df[“zscore”] = df.groupby(“group”)[“x”].transform(lambda s: (s – s.mean())/s.std())
「集計・グループ内派生」はapplyよりagg/transformが表現力・速度ともに有利です。
つまずき対策(戻り値の形・NaN・SettingWithCopy・型)
戻り値の形を意識する
- 列方向: Seriesを返すと列として組み上がる。スカラーを返すと1列になる。
- 行方向: 辞書/Seriesを返すと複数列へ展開。タプルならresult_type=”expand”を考える。
NaNが混じるとdtypeがobjectへ落ちる
- 対策: 前処理でfillna、最後にastypeで型を確定する。日時はto_datetimeで早めに変換。
チェーン代入はしない(SettingWithCopyを避ける)
- 悪い例: df[df[“x”]>0][“y”] = 1
- 良い例: df.loc[df[“x”]>0, “y”] = 1 applyで作ったSeriesを既存列へ代入する時も、必ずlocで“一発指定”します。
まとめ(「Seriesは要素」「DataFrameはaxis」。戻り値の形と代替手段を選ぶ)
applyは、Seriesなら要素単位、DataFrameならaxisで列/行を選んで関数適用する道具です。行方向では複数列を組み合わせた新列作成が得意、列方向では列別の前処理を一括で記述できる。戻り値の形(スカラー/Series/辞書)を意識し、速度が必要な処理はベクトル化・strアクセサ・NumPy・groupbyで代替する。SettingWithCopyを避け、NaNとdtypeを早めに整える。この型を身につければ、初心者でも“短く、正確に、速く”データ加工ができるようになります。
