Python | データ処理:pandas の apply

Python
スポンサーリンク

概要(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
Python

Series.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
Python

axis=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
Python

apply(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で渡される
Python

raw=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を早めに整える。この型を身につければ、初心者でも“短く、正確に、速く”データ加工ができるようになります。

タイトルとURLをコピーしました