【データ変換】map・apply・pipe・replace・assign・transformの使い方
データクリーニングの最終ステップは「変換」です。生データを分析に必要な形に整形します。スコアをグレードに変換したり、価格を割引に変換したり、複数ステップの処理をチェーンでつなげたりします。Pandasの map / apply / pipe / replace / assign / transform は、単一要素からテーブル全体まで対応する多段階の変換ツールキットを構成しています。本セクションでは、各メソッドのユースケースとパフォーマンスの落とし穴を解説します。
⚠️ 注意: 以下のコードはローカルのPython環境で実行してください。
1. 学習内容
- ❶ map による要素マッピング
- ❷ apply による行・列関数
- ❸ pipe によるチェーン処理
- ❹ replace による値の置換
- ❺ assign による新しい列の追加
- ❻ transform によるグループ変換
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 呼び出しの中で先に作成した列を参照できます(例:total が discounted_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つの変換メソッド早見表
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は避けてください。📖 まとめ
- map はSeriesの1対1マッピングに使用。辞書マッピングが最速。一致しない値はNaNになる
- apply は行・列ごとの複雑なロジックに対応。柔軟だが低速。可能な限りベクトル化操作を優先する
- pipe は複数ステップのパイプラインを構築し、ネスト呼び出しを上から下へ読めるチェーンに変換する
- replace は特定の値を置換しつつ、一致しない値はそのまま保持。正規表現もサポート
- assign はチェーンで列を追加。lambdaで同じステップ内で作成した列を参照可能
- transform はグループ変換を実行しつつ元の形状を保持。標準化・補完・ランキングに最適
- パフォーマンス順位:ベクトル化 > map(辞書) > replace > map(関数) > apply
📝 練習問題
- 基礎(難易度 ⭐):スコアのSeries(0〜100)を作成してください。map + 辞書でスコアを High(≥70)/ Medium(≥50)/ Low(<50)に分類し、次に replace で Medium を Mid に変更してください。
- 応用(難易度 ⭐⭐):従業員DataFrame(名前/給与/部署)を作成してください。pipeで3ステップのパイプラインを構築してください:名前のクリーニング(strip+title)→ 税引き後給与の列を追加 → 各従業員の給与が部署平均を超えているかどうかのフラグを立てる。
- チャレンジ(難易度 ⭐⭐⭐):20行の商品データセットを作成してください。assignで3つの列(売上/利益率/価格帯)を一度に追加し、groupby+transformでカテゴリ内のZスコアを計算し、最後にpipeですべてのステップを1つのパイプラインにまとめてください。



