404 Not Found

404 Not Found


nginx

【データ変換】map・apply・pipe・replace・assign・transformの使い方

データクリーニングの最終ステップは「変換」です。生データを分析に必要な形に整形します。スコアをグレードに変換したり、価格を割引に変換したり、複数ステップの処理をチェーンでつなげたりします。Pandasの map / apply / pipe / replace / assign / transform は、単一要素からテーブル全体まで対応する多段階の変換ツールキットを構成しています。本セクションでは、各メソッドのユースケースとパフォーマンスの落とし穴を解説します。

⚠️ 注意: 以下のコードはローカルのPython環境で実行してください。

1. 学習内容



2. アリスの成績変換

(1) 問題:0〜100のスコアをABCDグレードに変換する

アリスは100人の学生のスコア(0〜100スケール)を持っており、これを文字グレードに変換する必要があります:

PYTHON
import pandas as pd

scores = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'score': [92, 78, 65, 88, 45]
})
# 0-100 → A/B/C/D/F に変換するには?
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) 解決策:map + 辞書マッピング

▶ サンプル:map による辞書マッピング

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:map による辞書マッピング(難易度 ⭐)

PYTHON
import pandas as pd

scores = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'score': [92, 78, 65, 88, 45]
})

# ステップ1:グレードマッピング関数を定義
def score_to_grade(s):
    if s >= 90: return 'A'
    elif s >= 80: return 'B'
    elif s >= 70: return 'C'
    elif s >= 60: return 'D'
    else: return 'F'

# ステップ2:mapで適用
scores['grade'] = scores['score'].map(score_to_grade)
print(scores)
#      name  score grade
# 0   Alice     92     A
# 1     Bob     78     C
# 2 Charlie     65     D
# 3   Carol     88     B
# 4   David     45     F
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。


3. map による要素マッピング

(1) map の3つの使い方

▶ サンプル:map の3つのマッピング方法

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:map による3つのマッピング方法(難易度 ⭐⭐)

PYTHON
import pandas as pd

s = pd.Series([1, 2, 3, 4, 5])

# 1. 辞書マッピング
category_map = {1: 'Low', 2: 'Low', 3: 'Medium', 4: 'High', 5: 'High'}
print(s.map(category_map))
# 0      Low
# 1      Low
# 2   Medium
# 3     High
# 4     High

# 2. 関数マッピング
print(s.map(lambda x: x ** 2))
# 0     1
# 1     4
# 2     9
# 3    16
# 4    25

# 3. Seriesマッピング(インデックスで整列)
other = pd.Series({1: 'One', 2: 'Two', 3: 'Three'})
print(s.map(other))
# 0    NaN   ← 1はotherに存在 → 'One'
# 1   Two
# 2 Three
# 3    NaN  ← 4はotherに存在しない → NaN
# 4    NaN
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) map を使うべき場面

機能 map apply
操作対象 Seriesの個別要素 Series / DataFrameの行・列
入力 辞書/関数/Series 関数
戻り値 Series(1対1マッピング) 任意の型
パフォーマンス 高速(辞書検索 O(1)) 低速(要素・行・列ごとの呼び出し)
最適な用途 1対1マッピング 複雑なロジック・複数列の操作


4. apply による行・列関数

(1) Series の apply

▶ サンプル:Series の apply

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:Series の apply(難易度 ⭐⭐)

PYTHON
import pandas as pd

s = pd.Series(['alice@example.com', 'bob@company.org', 'charlie@uni.edu'])

# メールアドレスからドメインを抽出
domains = s.apply(lambda email: email.split('@')[1])
print(domains)
# 0    example.com
# 1    company.org
# 2       uni.edu

# 複雑なロジック:メールの種類を分類
def classify_email(email):
    domain = email.split('@')[1]
    if '.edu' in domain: return 'Academic'
    elif '.org' in domain: return 'Non-profit'
    else: return 'Commercial'

email_type = s.apply(classify_email)
print(email_type)
# 0    Commercial
# 1    Non-profit
# 2     Academic
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) DataFrame の apply

▶ サンプル:DataFrame の apply による行・列操作

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:DataFrame の apply による行・列操作(難易度 ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'math': [85, 92, 78, 88, 65],
    'english': [90, 85, 82, 75, 70],
    'science': [88, 95, 80, 90, 60]
}, index=['Alice', 'Bob', 'Charlie', 'Carol', 'David'])

# 各列に関数を適用(axis=0、デフォルト)
print(df.apply(np.mean))
# math       81.6
# english    80.4
# science    82.6

# 各行に関数を適用(axis=1)
df['average'] = df.apply(lambda row: row.mean(), axis=1)
print(df['average'])
# Alice      87.67
# Bob        90.67
# Charlie    80.00
# Carol      84.33
# David      65.00

# 行ごとにSeriesを返す(より構造化)
def score_report(row):
    return pd.Series({
        'total': row.sum(),
        'avg': row.mean(),
        'max': row.max(),
        'min': row.min()
    })

report = df[['math', 'english', 'science']].apply(score_report, axis=1)
print(report)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
⚠️ 注意: apply はベクトル化された操作ではありません。大きなDataFrameに対して行ごとに関数を呼び出すと非常に遅くなります。ベクトル化された操作(算術演算、strメソッド)で実現できる場合は、applyを使わないでください。applyは「他の方法では実現できない場合の最終手段」です。



5. pipe によるチェーン処理

(1) チェーンによるデータフロー

▶ サンプル:pipe によるパイプライン操作

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:pipe によるパイプライン操作(難易度 ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# 生データ
df = pd.DataFrame({
    'name': ['  Alice  ', 'BOB', 'Charlie', '  carol  ', 'David'],
    'age': [28, -1, 25, 30, 999],
    'salary': [75000, np.nan, 68000, 88000, np.nan]
})

# pipeを使わない場合(ネスト呼び出し)
# result = assign_dept(replace_invalid(clean_names(df)))

# pipeを使うと — 左から右へ読めるフローになる
def clean_names(df):
    df = df.copy()
    df['name'] = df['name'].str.strip().str.title()
    return df

def replace_invalid(df):
    df = df.copy()
    df.loc[df['age'] < 0, 'age'] = np.nan
    df.loc[df['age'] > 150, 'age'] = np.nan
    return df

def fill_salary(df):
    df = df.copy()
    df['salary'] = df['salary'].fillna(df['salary'].median())
    return df

result = (df
    .pipe(clean_names)
    .pipe(replace_invalid)
    .pipe(fill_salary)
)
print(result)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) pipe と apply の比較

機能 pipe apply
入力 DataFrame全体 行・列・Series
戻り値 DataFrame(変換後) 任意の型
目的 複数ステップのパイプライン 単一ステップの要素・行・列操作
チェーン ✅ ネイティブサポート ❌ ネストが必要
可読性 高い(上から下へ読む) 中程度


6. replace による値の置換

(1) 完全一致置換

▶ サンプル:replace による値の置換

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:replace による値の置換(難易度 ⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'status': ['active', 'inactive', 'pending', 'active', 'unknown'],
    'priority': [1, 2, 3, 1, 99]
})

# 単一値の置換
df['status'] = df['status'].replace('unknown', 'inactive')
print(df['status'])

# 複数値の置換(辞書)
df['status'] = df['status'].replace({
    'active': 'Active',
    'inactive': 'Inactive',
    'pending': 'Pending'
})
print(df['status'])

# 無効なセンチネル値の置換
df['priority'] = df['priority'].replace(99, np.nan)
print(df['priority'])

# 正規表現による置換
text = pd.Series(['Phone: 555-0100', 'Email: test@example.com'])
cleaned = text.str.replace(r'[\d-]+', '[REDACTED]', regex=True)
print(cleaned)
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) replace と map の比較

機能 replace map
一致しない値 そのまま保持 NaNになる
複数値の置換 ✅ 辞書 ✅ 辞書
正規表現 ✅ regex=True
最適な用途 特定の値の置換 完全マッピング
🔥 よくある間違い: map では辞書に含まれない値がNaNになります!replace では一致しない値はそのまま保持されます。「一部の値だけを置換して残りはそのままにしたい」場合は replace を使います。「すべての値をマッピングし、マッピングされない値は破棄したい」場合は map を使います。



7. assign による新しい列の追加

(1) 複数の新しい列をチェーンで追加

▶ サンプル:assign による新しい列の追加

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:assign による新しい列の追加(難易度 ⭐⭐)

PYTHON
import pandas as pd

df = pd.DataFrame({
    'price': [100, 200, 150, 80, 300],
    'quantity': [5, 3, 10, 8, 2],
    'discount': [0.1, 0.2, 0.0, 0.15, 0.05]
})

# 1つのチェーンで複数の列を追加
result = (df
    .assign(
        discounted_price=lambda x: x['price'] * (1 - x['discount']),
        total=lambda x: x['discounted_price'] * x['quantity'],
        is_bulk=lambda x: x['quantity'] >= 5
    )
)
print(result)
#   price  quantity  discount  discounted_price  total  is_bulk
# 0   100         5      0.10              90.0  450.0     True
# 1   200         3      0.20             160.0  480.0    False
# 2   150        10      0.00             150.0 1500.0     True
# 3    80         8      0.15              68.0  544.0     True
# 4   300         2      0.05             285.0  570.0    False
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。
💡 ヒント: assign の中では lambda x: を使って現在のDataFrameを参照します。同じ assign 呼び出しの中で先に作成した列を参照できます(例:totaldiscounted_price を参照)。これにより、df['new_col'] = ... 構文よりもチェーン処理に適しています。



8. transform によるグループ変換

(1) transform は元の形状を保持する

▶ サンプル:transform によるグループ標準化

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:transform によるグループ標準化(難易度 ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'student': ['Alice', 'Alice', 'Bob', 'Bob', 'Charlie', 'Charlie'],
    'subject': ['Math', 'English', 'Math', 'English', 'Math', 'English'],
    'score': [85, 90, 92, 88, 78, 82]
})

# 各学生内でZスコア正規化
df['z_score'] = df.groupby('student')['score'].transform(
    lambda x: (x - x.mean()) / x.std()
)
print(df)

# グループ平均で埋める(全体平均ではなく)
df['score_filled'] = df.groupby('student')['score'].transform('mean')
print(df[['student', 'score', 'score_filled']])

# グループ内ランク
df['rank_in_class'] = df.groupby('subject')['score'].transform('rank', method='min')
print(df[['student', 'subject', 'score', 'rank_in_class']])
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) transform と apply の比較

機能 transform apply
出力の形状 入力と同じ 変わりうる
groupby後 グループごとに同じ長さの結果を返す グループごとに任意の結果を返す
集約 ❌ 削減不可 ✅ 可能
ブロードキャスト ✅ 元の行に自動ブロードキャスト
一般的な用途 標準化・補完・ランキング 集約・複雑な計算


9. 適切な変換メソッドの選び方

(1) 6つの変換メソッド早見表

100%
graph TB
    Q["データを変換したい?"] --> TYPE{"変換の種類は?"}
    TYPE -->|"1対1マッピング"| MAP["map()"]
    TYPE -->|"行・列関数"| APPLY["apply()"]
    TYPE -->|"複数ステップのパイプライン"| PIPE["pipe()"]
    TYPE -->|"特定の値の置換"| REPLACE["replace()"]
    TYPE -->|"新しい列の追加"| ASSIGN["assign()"]
    TYPE -->|"グループレベルの変換"| TRANS["transform()"]
    MAP --> NOTE1["✅ 辞書/関数<br>❌ 未マッピング → NaN"]
    APPLY --> NOTE2["⚠️ 大きなDFでは低速<br>✅ 柔軟"]
    PIPE --> NOTE3["✅ 可読性の高いチェーン<br>✅ 再利用可能なステップ"]
    REPLACE --> NOTE4["✅ 不一致は保持<br>✅ 正規表現対応"]
    ASSIGN --> NOTE5["✅ チェーン向き<br>✅ 複数列対応"]
    TRANS --> NOTE6["✅ 同じ形状で出力<br>✅ グループブロードキャスト"]
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

(2) パフォーマンスランキング

順位 メソッド パフォーマンス 理由
1 ベクトル化操作 最速 Cレベルで実行、Pythonループなし
2 map(辞書) 高速 O(1) 検索
3 replace 高速 内部で最適化済み
4 map(関数) 中程度 要素ごとの関数呼び出し
5 apply 低速 行・列ごとのPython呼び出し
6 apply + 複雑なロジック 最遅 関数オーバーヘッドが累積
💡 ヒント: df['col'] * 2 と書ける場合は、df['col'].apply(lambda x: x * 2) と書かないでください。ベクトル化操作は apply より10〜100倍高速です。



10. 総合例:ECサイトデータの変換パイプライン

▶ サンプル:データ変換パイプライン全体

TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

:データ変換パイプライン全体(難易度 ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# 総合例:ECサイトデータの変換パイプライン
# ============================================

# 1. 生データ
np.random.seed(42)
df = pd.DataFrame({
    'product': [f'Item_{i:03d}' for i in range(1, 21)],
    'category': np.random.choice(['Electronics', 'Clothing', 'Home'], 20),
    'price': np.round(np.random.uniform(10, 500, 20), 2),
    'cost': np.round(np.random.uniform(5, 250, 20), 2),
    'quantity_sold': np.random.randint(1, 100, 20),
    'rating': np.round(np.random.uniform(2.0, 5.0, 20), 1)
})

# 2. pipeによるステップごとのパイプライン
def add_margin(df):
    df = df.copy()
    df['margin_pct'] = ((df['price'] - df['cost']) / df['price'] * 100).round(1)
    return df

def categorize_price(df):
    df = df.copy()
    df['price_tier'] = df['price'].map(
        lambda p: 'Budget' if p < 50 else ('Mid' if p < 200 else 'Premium')
    )
    return df

def add_revenue(df):
    return df.assign(
        revenue=lambda x: x['price'] * x['quantity_sold'],
        is_top_rated=lambda x: x['rating'] >= 4.0
    )

def normalize_rating(df):
    df = df.copy()
    df['rating_zscore'] = df.groupby('category')['rating'].transform(
        lambda x: (x - x.mean()) / x.std()
    )
    return df

# 3. パイプラインの実行
result = (df
    .pipe(add_margin)
    .pipe(categorize_price)
    .pipe(add_revenue)
    .pipe(normalize_rating)
)

# 4. サマリー
print("=== 変換結果 ===")
print(result[['product', 'category', 'price_tier', 'margin_pct', 'revenue', 'is_top_rated']].head(10))
print(f"\n総売上: ${result['revenue'].sum():,.2f}")
print(f"高評価アイテム数: {result['is_top_rated'].sum()}")
print(f"価格帯の分布:\n{result['price_tier'].value_counts()}")
TEXT
> 出力: ローカルのPython環境(pandas 2.x)で実行してください。Pistonサーバーにはpandasがプリインストールされていません。ローカルにインストール(`pip install pandas`)して一緒に進めてください。実際の値はpandasのバージョンによって若干異なる場合があります。

❓ よくある質問

Q map と apply の違いは何ですか?
A map はSeriesのみで動作し、各要素に対して1対1のマッピングを行います(辞書または関数を受け付けます)。マッピングされない値はNaNになります。apply はSeriesとDataFrameの両方で動作し、行・列レベルの操作(axis=0/1)をサポートし、戻り値の型も柔軟です。単純なマッピングには map(高速)、複雑なロジックには apply(柔軟)を使います。
Q なぜ apply は遅いのですか?
A apply は本質的にPythonループです。行・列ごとに1回ずつPython関数を呼び出すため、関数呼び出しのオーバーヘッドが非常に大きくなります。ベクトル化操作はCレベルで実行されるため、このオーバーヘッドがありません。経験則として、df['a'] + df['b'] と書ける場合は、df.apply(lambda r: r['a']+r['b'], axis=1) と書かないでください。applyは最終手段です。
Q pipe と apply の違いは何ですか?
A pipe はDataFrame全体に対して動作し、DataFrameを受け取ってDataFrameを返します。複数ステップのパイプラインのために設計されています。apply は行・列・Seriesに対して動作し、要素ごとまたは行ごとの計算を行います。pipe の価値は可読性です。f3(f2(f1(df)))df.pipe(f1).pipe(f2).pipe(f3) に変換し、上から下へ読むだけで実行順序がわかります。
Q replace と map の違いは何ですか?
A 最大の違いは一致しない値の扱いです。replace は元の値をそのまま保持しますが、map は一致しない値をNaNにします。特定の値だけを置換したい場合は replace を使い、すべての値を変換する完全マッピングには map を使います。replace は正規表現による置換(regex=True)もサポートしていますが、map はサポートしていません。
Q assign と df['col']=... の違いは何ですか?
A assign は新しいDataFrameを返すため(元を変更しない)、チェーン処理に最適です。df['col']=... はその場で変更します。assign の中では lambda を使って同じステップで作成した列を参照できますが、直接代入ではできません。推奨:チェーン操作には assign を使い、単純な場合は直接代入で問題ありません。
Q groupby後の transform と apply の違いは何ですか?
A transform は元のDataFrameと同じ長さの結果を返します(グループ内でブロードキャスト)。一方、apply は任意の形状を返せます。例:groupby + transform('mean') はすべての行にグループ平均を返します(長さは不変)。groupby + apply('mean') はグループごとに1つの平均を返します(長さ = グループ数)。元の形状を保持したい場合は transform を、集約したい場合は apply/agg を使います。
Q 「ベクトル化」とは具体的に何を意味しますか?
A ベクトル化とは、Pythonループの代わりにPandas/NumPyの組み込み操作を使うことです。df['a'] + df['b'] はベクトル化されています(配列全体に対するCレベルの計算)。一方、df.apply(lambda r: r['a']+r['b'], axis=1) はベクトル化されていません(Pythonが1行ずつ呼び出します)。ベクトル化操作は10〜100倍高速です。可能な限り組み込み操作を使い、applyは避けてください。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):スコアのSeries(0〜100)を作成してください。map + 辞書でスコアを High(≥70)/ Medium(≥50)/ Low(<50)に分類し、次に replace で Medium を Mid に変更してください。
  2. 応用(難易度 ⭐⭐):従業員DataFrame(名前/給与/部署)を作成してください。pipeで3ステップのパイプラインを構築してください:名前のクリーニング(strip+title)→ 税引き後給与の列を追加 → 各従業員の給与が部署平均を超えているかどうかのフラグを立てる。
  3. チャレンジ(難易度 ⭐⭐⭐):20行の商品データセットを作成してください。assignで3つの列(売上/利益率/価格帯)を一度に追加し、groupby+transformでカテゴリ内のZスコアを計算し、最後にpipeですべてのステップを1つのパイプラインにまとめてください。

← 前へ:重複データの処理 · 次へ:グループ集約 →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%