🔧 STEP 13: 複雑なデータ変換の実践
apply、map、applymap を使いこなして、自由自在にデータを変換しよう
📋 このステップで学ぶこと
- apply、map、applymap の違いと使い分け
- lambda関数を使った効率的なデータ変換
- カスタム変換関数の作成方法
- 複雑なビジネスロジックの実装
- パフォーマンスを考慮した変換方法
⏱️ 学習時間の目安:1.5時間
📝 練習問題:10問(基礎4問・応用4問・発展2問)
🎯 1. apply、map、applymap の違い
1-1. 3つのメソッドを理解しよう
Pandasには、データを変換するための3つの主要なメソッドがあります。
それぞれ使う場面が違うので、しっかり理解しましょう!
📚 例え話:料理の作業
- map:材料リストを見て、「りんご」→「apple」に置き換える(1対1変換)
- apply:材料全体を見て、「これは煮る、あれは焼く」と判断する(複雑な処理)
- applymap:全ての材料を洗う(全セルに同じ処理)
3つのメソッドの比較
| メソッド | 対象 | 使い方 | よく使う場面 |
|---|---|---|---|
| map | 1つの列(Series) | 値を1つずつ変換 | カテゴリの変換、コードの読み替え |
| apply | 列全体または行全体 | 関数を適用 | 複雑な計算、条件分岐、複数列の組み合わせ |
| applymap | DataFrame全体 | 全セルに同じ処理 | 全体の書式統一、型変換(※pandas 2.1以降はmap) |
⚠️ pandas 2.1以降の注意
pandas 2.1以降では、applymapは非推奨になりました。
代わりにDataFrame.map()を使ってください。
🗺️ 2. map の使い方
2-1. mapの基本
mapは、1つの列の値を別の値に変換するときに使います。
辞書を使って、値を対応付けることができます。
# ===== mapの基本:辞書を使った変換 =====
import pandas as pd
df = pd.DataFrame({
‘名前’: [‘田中’, ‘佐藤’, ‘鈴木’, ‘高橋’],
‘性別コード’: [1, 2, 1, 2],
‘部署コード’: [‘A’, ‘B’, ‘A’, ‘C’]
})
print(“=== 元データ ===”)
print(df)
# 性別コードを文字列に変換
性別マップ = {1: ‘男性’, 2: ‘女性’}
df[‘性別’] = df[‘性別コード’].map(性別マップ)
# 部署コードを部署名に変換
部署マップ = {‘A’: ‘営業部’, ‘B’: ‘開発部’, ‘C’: ‘総務部’}
df[‘部署名’] = df[‘部署コード’].map(部署マップ)
print(“\n=== 変換後 ===”)
print(df)
【実行結果】
=== 元データ ===
名前 性別コード 部署コード
0 田中 1 A
1 佐藤 2 B
2 鈴木 1 A
3 高橋 2 C
=== 変換後 ===
名前 性別コード 部署コード 性別 部署名
0 田中 1 A 男性 営業部
1 佐藤 2 B 女性 開発部
2 鈴木 1 A 男性 営業部
3 高橋 2 C 女性 総務部
2-2. マッピングに存在しない値の扱い
# ===== 存在しない値はNaNになる =====
import pandas as pd
df = pd.DataFrame({
‘部署コード’: [‘A’, ‘B’, ‘D’, ‘E’] # DとEは辞書にない
})
部署マップ = {‘A’: ‘営業部’, ‘B’: ‘開発部’, ‘C’: ‘総務部’}
# マッピングに存在しない値はNaNになる
df[‘部署名’] = df[‘部署コード’].map(部署マップ)
print(“=== NaNが発生 ===”)
print(df)
# fillna()でデフォルト値を設定
df[‘部署名’] = df[‘部署コード’].map(部署マップ).fillna(‘未登録’)
print(“\n=== fillna()で対処 ===”)
print(df)
【実行結果】
=== NaNが発生 ===
部署コード 部署名
0 A 営業部
1 B 開発部
2 D NaN
3 E NaN
=== fillna()で対処 ===
部署コード 部署名
0 A 営業部
1 B 開発部
2 D 未登録
3 E 未登録
✅ mapのポイント
- 辞書のキーに存在しない値はNaNになる
fillna()でデフォルト値を設定できる- シンプルな値の変換に最適
- 実行速度が速い
🔧 3. apply の使い方
3-1. applyの基本
applyは、列全体や行全体に関数を適用するときに使います。
複雑な計算や条件分岐が必要なときに便利です。
# ===== 列に対してapply =====
import pandas as pd
df = pd.DataFrame({
‘名前’: [‘田中’, ‘佐藤’, ‘鈴木’, ‘高橋’],
‘点数’: [85, 65, 90, 55]
})
# 関数を定義
def 評価(点数):
if 点数 >= 80:
return ‘A’
elif 点数 >= 70:
return ‘B’
elif 点数 >= 60:
return ‘C’
else:
return ‘D’
# 列に対してapply
df[‘評価’] = df[‘点数’].apply(評価)
print(df)
【実行結果】
名前 点数 評価
0 田中 85 A
1 佐藤 65 C
2 鈴木 90 A
3 高橋 55 D
3-2. 行全体を使った計算(axis=1)
# ===== 行全体(複数列)を使った計算 =====
import pandas as pd
df = pd.DataFrame({
‘名前’: [‘田中’, ‘佐藤’, ‘鈴木’, ‘高橋’],
‘数学’: [80, 65, 90, 55],
‘英語’: [70, 85, 75, 60]
})
print(“=== 元データ ===”)
print(df)
# 複数列を使った関数
def 総合評価(row):
平均 = (row[‘数学’] + row[‘英語’]) / 2
# 両方とも80点以上
if row[‘数学’] >= 80 and row[‘英語’] >= 80:
return ‘優秀’
# 平均が70点以上
elif 平均 >= 70:
return ‘良好’
# 平均が60点以上
elif 平均 >= 60:
return ‘普通’
else:
return ‘要努力’
# axis=1 で行ごとに処理
df[‘総合評価’] = df.apply(総合評価, axis=1)
print(“\n=== 評価追加後 ===”)
print(df)
【実行結果】
=== 元データ ===
名前 数学 英語
0 田中 80 70
1 佐藤 65 85
2 鈴木 90 75
3 高橋 55 60
=== 評価追加後 ===
名前 数学 英語 総合評価
0 田中 80 70 良好
1 佐藤 65 85 良好
2 鈴木 90 75 良好
3 高橋 55 60 要努力
💡 axis パラメータ
- axis=0(デフォルト):列ごとに処理(縦方向)
- axis=1:行ごとに処理(複数列を使った計算)
覚え方:「複数列を使うならaxis=1」
⚡ 4. lambda関数の活用
4-1. lambda関数とは?
lambda関数は、名前のない簡単な関数を1行で書く方法です。
シンプルな処理をサッと書きたいときに便利です。
📝 lambda関数の書き方
lambda 引数: 処理
# 例:
lambda x: x * 2 # xを2倍にする
lambda x: x + 10 # xに10を足す
lambda x: ‘A’ if x >= 80 else ‘B’ # 条件分岐
4-2. lambda と apply の組み合わせ
# ===== lambda関数の活用 =====
import pandas as pd
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘バナナ’, ‘みかん’],
‘価格’: [150, 100, 80]
})
print(“=== 元データ ===”)
print(df)
# 通常の関数を使う場合
def 税込価格(価格):
return int(価格 * 1.1)
df[‘税込価格1’] = df[‘価格’].apply(税込価格)
# lambda関数を使う場合(1行で書ける!)
df[‘税込価格2’] = df[‘価格’].apply(lambda x: int(x * 1.1))
print(“\n=== 税込価格追加後 ===”)
print(df)
【実行結果】
=== 税込価格追加後 ===
商品名 価格 税込価格1 税込価格2
0 りんご 150 165 165
1 バナナ 100 110 110
2 みかん 80 88 88
4-3. 複数列を使ったlambda
# ===== 複数列を使った計算 =====
import pandas as pd
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘バナナ’, ‘みかん’],
‘単価’: [150, 100, 80],
‘個数’: [5, 10, 8]
})
# 合計金額を計算(axis=1で行ごと)
df[‘合計’] = df.apply(lambda row: row[‘単価’] * row[‘個数’], axis=1)
print(df)
【実行結果】
商品名 単価 個数 合計
0 りんご 150 5 750
1 バナナ 100 10 1000
2 みかん 80 8 640
⚠️ lambda関数の注意点
- 複雑な処理には向いていない(3行以上なら通常の関数を使おう)
- エラーが出たときにデバッグしにくい
- 読みにくくなるなら、通常の関数を使う方が良い
🎨 5. カスタム変換関数の作成
5-1. 実務でよく使うカスタム関数
# ===== 年齢層の分類 =====
import pandas as pd
import numpy as np
def 年齢層分類(年齢):
“””年齢を年齢層に分類”””
if pd.isna(年齢):
return ‘不明’
年齢 = int(年齢)
if 年齢 < 20:
return '10代以下'
elif 年齢 < 30:
return '20代'
elif 年齢 < 40:
return '30代'
elif 年齢 < 50:
return '40代'
elif 年齢 < 60:
return '50代'
else:
return '60代以上'
# サンプルデータ
df = pd.DataFrame({
'名前': ['田中', '佐藤', '鈴木', '高橋', '伊藤'],
'年齢': [25, 35, 18, 62, np.nan]
})
df['年齢層'] = df['年齢'].apply(年齢層分類)
print(df)
【実行結果】
名前 年齢 年齢層
0 田中 25.0 20代
1 佐藤 35.0 30代
2 鈴木 18.0 10代以下
3 高橋 62.0 60代以上
4 伊藤 NaN 不明
5-2. 複数列を使った会員ランク判定
# ===== 購入金額と回数から会員ランク判定 =====
import pandas as pd
def 会員ランク判定(row):
“””購入金額と購入回数から会員ランクを判定”””
金額 = row[‘購入金額合計’]
回数 = row[‘購入回数’]
# プラチナ会員: 金額10万円以上 かつ 回数10回以上
if 金額 >= 100000 and 回数 >= 10:
return ‘プラチナ’
# ゴールド会員: 金額5万円以上 かつ 回数5回以上
elif 金額 >= 50000 and 回数 >= 5:
return ‘ゴールド’
# シルバー会員: 金額1万円以上
elif 金額 >= 10000:
return ‘シルバー’
# ブロンズ会員: それ以外
else:
return ‘ブロンズ’
# サンプルデータ
df = pd.DataFrame({
‘顧客ID’: [1, 2, 3, 4, 5],
‘購入金額合計’: [120000, 65000, 25000, 8000, 150000],
‘購入回数’: [15, 8, 3, 2, 20]
})
df[‘会員ランク’] = df.apply(会員ランク判定, axis=1)
print(df)
【実行結果】
顧客ID 購入金額合計 購入回数 会員ランク
0 1 120000 15 プラチナ
1 2 65000 8 ゴールド
2 3 25000 3 シルバー
3 4 8000 2 ブロンズ
4 5 150000 20 プラチナ
⚡ 6. パフォーマンスを考慮した変換
6-1. 実行速度の比較
大量データを処理するときは、実行速度も重要です。
同じ結果でも、書き方によって速度が大きく変わります。
⚡ 速度の順位(速い順)
- ベクトル演算(最速) – Pandasの組み込み機能
- np.where / np.select – NumPyの条件分岐
- map – 辞書による変換
- apply – 関数の適用
- for ループ(最遅) – 避けるべき
6-2. ベクトル演算 vs apply
# ===== 速度比較:税込価格の計算 =====
import pandas as pd
import numpy as np
import time
# 10万件のデータを作成
df = pd.DataFrame({
‘価格’: np.random.randint(100, 10000, 100000)
})
# ❌ 遅い方法:apply を使う
start = time.time()
df[‘税込1’] = df[‘価格’].apply(lambda x: x * 1.1)
時間1 = time.time() – start
# ✅ 速い方法:ベクトル演算
start = time.time()
df[‘税込2’] = df[‘価格’] * 1.1
時間2 = time.time() – start
print(f”apply使用: {時間1:.4f}秒”)
print(f”ベクトル演算: {時間2:.4f}秒”)
print(f”速度差: {時間1/時間2:.1f}倍”)
【実行結果】
apply使用: 0.0892秒
ベクトル演算: 0.0012秒
速度差: 74.3倍
6-3. np.where で条件分岐を高速化
# ===== 条件分岐の高速化 =====
import pandas as pd
import numpy as np
import time
df = pd.DataFrame({
‘点数’: np.random.randint(0, 100, 100000)
})
# ❌ 遅い方法:apply
start = time.time()
df[‘合否1’] = df[‘点数’].apply(lambda x: ‘合格’ if x >= 60 else ‘不合格’)
時間1 = time.time() – start
# ✅ 速い方法:np.where
start = time.time()
df[‘合否2’] = np.where(df[‘点数’] >= 60, ‘合格’, ‘不合格’)
時間2 = time.time() – start
print(f”apply使用: {時間1:.4f}秒”)
print(f”np.where: {時間2:.4f}秒”)
print(f”速度差: {時間1/時間2:.1f}倍”)
【実行結果】
apply使用: 0.1145秒
np.where: 0.0034秒
速度差: 33.7倍
6-4. np.select で複数条件を高速化
# ===== np.select:複数条件の高速化 =====
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘点数’: [85, 72, 55, 90, 45]
})
# 条件リスト
conditions = [
df[‘点数’] >= 80,
df[‘点数’] >= 70,
df[‘点数’] >= 60
]
# 対応する値
choices = [‘A’, ‘B’, ‘C’]
# np.select(デフォルト値も指定可能)
df[‘評価’] = np.select(conditions, choices, default=’D’)
print(df)
【実行結果】
点数 評価
0 85 A
1 72 B
2 55 D
3 90 A
4 45 D
🎯 パフォーマンス最適化のコツ
- ベクトル演算が使えるか考える(四則演算、比較)
- 単純な変換にはmapを使う
- 条件分岐にはnp.whereやnp.selectを使う
- どうしても必要なときだけapplyを使う
- for ループは避ける
📝 STEP 13 のまとめ
✅ このステップで学んだこと
- map:1つの列の値を辞書で変換(シンプル&高速)
- apply:列や行に関数を適用(複雑な処理に対応)
- lambda関数:1行で簡単な関数を書ける
- カスタム関数:ビジネスロジックに合わせた変換
- パフォーマンス:ベクトル演算・np.whereが最速、applyは最後の手段
💡 使い分けの指針
| やりたいこと | 使うメソッド |
| コードを名前に変換 | map |
| 単純な四則演算 | ベクトル演算 |
| 2択の条件分岐 | np.where |
| 3択以上の条件分岐 | np.select |
| 複数列を使った複雑な処理 | apply(axis=1) |
🎯 次のステップの予告
次のSTEP 14では、「データベースへのロード」を学びます。
- to_sqlメソッドの使い方
- バルクインサート
- UPSERTの実装
- トランザクション管理
📝 練習問題
問題 1
基礎
商品カテゴリコードをカテゴリ名に変換してください。
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘にんじん’, ‘牛乳’, ‘パン’],
‘カテゴリコード’: [‘F’, ‘V’, ‘D’, ‘B’]
})
# カテゴリマップ
カテゴリ = {‘F’: ‘果物’, ‘V’: ‘野菜’, ‘D’: ‘乳製品’, ‘B’: ‘パン類’}
# ここにコードを書いてください
【解答】
df[‘カテゴリ名’] = df[‘カテゴリコード’].map(カテゴリ)
print(df)
問題 2
基礎
点数が80点以上なら「優」、60点以上なら「良」、それ以外は「可」と表示してください。
df = pd.DataFrame({
‘名前’: [‘田中’, ‘佐藤’, ‘鈴木’, ‘高橋’],
‘点数’: [85, 72, 55, 90]
})
# ここにコードを書いてください
【解答1】np.selectを使う方法(推奨)
import numpy as np
conditions = [df[‘点数’] >= 80, df[‘点数’] >= 60]
choices = [‘優’, ‘良’]
df[‘評価’] = np.select(conditions, choices, default=’可’)
print(df)
【解答2】applyを使う方法
def 評価(点数):
if 点数 >= 80:
return ‘優’
elif 点数 >= 60:
return ‘良’
else:
return ‘可’
df[‘評価’] = df[‘点数’].apply(評価)
print(df)
問題 3
基礎
価格を1.1倍して税込価格を計算してください(整数に変換)。
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘バナナ’, ‘みかん’],
‘価格’: [150, 100, 80]
})
# ここにコードを書いてください
【解答】ベクトル演算を使用(推奨)
df[‘税込価格’] = (df[‘価格’] * 1.1).astype(int)
print(df)
問題 4
基礎
単価と個数から合計金額を計算してください。
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘バナナ’, ‘みかん’],
‘単価’: [150, 100, 80],
‘個数’: [5, 10, 8]
})
# ここにコードを書いてください
【解答】ベクトル演算を使用(推奨)
df[‘合計’] = df[‘単価’] * df[‘個数’]
print(df)
問題 5
応用
メールアドレスからドメイン部分(@以降)を抽出してください。
df = pd.DataFrame({
‘ユーザー名’: [‘田中’, ‘佐藤’, ‘鈴木’],
‘メール’: [‘tanaka@example.com’, ‘sato@test.co.jp’, ‘suzuki@mail.org’]
})
# ここにコードを書いてください
【解答】文字列メソッドを使用(推奨)
df[‘ドメイン’] = df[‘メール’].str.split(‘@’).str[1]
print(df)
問題 6
応用
氏名から「姓」と「名」を分割してください。(スペース区切り)
df = pd.DataFrame({
‘氏名’: [‘田中 太郎’, ‘佐藤 花子’, ‘鈴木 一郎’]
})
# ここにコードを書いてください
【解答】
df[[‘姓’, ‘名’]] = df[‘氏名’].str.split(‘ ‘, expand=True)
print(df)
問題 7
応用
合計金額が1000円以上なら送料無料、未満なら送料500円を加算して最終金額を計算してください。
df = pd.DataFrame({
‘商品名’: [‘りんご’, ‘バナナ’, ‘みかん’],
‘単価’: [150, 100, 80],
‘個数’: [5, 3, 8]
})
# ここにコードを書いてください
【解答】np.whereを使用
import numpy as np
df[‘合計金額’] = df[‘単価’] * df[‘個数’]
df[‘送料’] = np.where(df[‘合計金額’] >= 1000, 0, 500)
df[‘最終金額’] = df[‘合計金額’] + df[‘送料’]
print(df)
問題 8
応用
日付から「曜日」と「週末フラグ」(土日ならTrue)を追加してください。
df = pd.DataFrame({
‘日付’: pd.date_range(‘2024-01-01’, periods=7)
})
# ここにコードを書いてください
【解答】
import numpy as np
# 曜日を追加(日本語)
曜日リスト = [‘月’, ‘火’, ‘水’, ‘木’, ‘金’, ‘土’, ‘日’]
df[‘曜日’] = df[‘日付’].dt.dayofweek.map(lambda x: 曜日リスト[x])
# 週末フラグを追加(土曜=5, 日曜=6)
df[‘週末’] = df[‘日付’].dt.dayofweek >= 5
print(df)
問題 9
発展
BMI(体格指数)を計算し、18.5未満「やせ」、18.5〜25未満「標準」、25〜30未満「肥満1度」、30以上「肥満2度以上」に分類してください。
BMI = 体重(kg) ÷ (身長(m) × 身長(m))
df = pd.DataFrame({
‘名前’: [‘田中’, ‘佐藤’, ‘鈴木’, ‘高橋’],
‘身長’: [170, 165, 180, 175], # cm
‘体重’: [65, 80, 70, 95] # kg
})
# ここにコードを書いてください
【解答】np.selectを使用
import numpy as np
# BMIを計算
df[‘BMI’] = (df[‘体重’] / ((df[‘身長’] / 100) ** 2)).round(2)
# 条件リスト
conditions = [
df[‘BMI’] < 18.5,
(df['BMI'] >= 18.5) & (df[‘BMI’] < 25),
(df['BMI'] >= 25) & (df[‘BMI’] < 30),
df['BMI'] >= 30
]
# 対応する分類
choices = [‘やせ’, ‘標準’, ‘肥満1度’, ‘肥満2度以上’]
df[‘体型分類’] = np.select(conditions, choices)
print(df)
問題 10
発展
購入金額と購入回数から会員ランクを判定してください。
購入金額と購入回数から会員ランクを判定してください。
・プラチナ:金額10万円以上 かつ 回数10回以上
・ゴールド:金額5万円以上 かつ 回数5回以上
・シルバー:金額1万円以上
・ブロンズ:それ以外
df = pd.DataFrame({
‘顧客ID’: [1, 2, 3, 4, 5],
‘購入金額合計’: [120000, 65000, 25000, 8000, 150000],
‘購入回数’: [15, 8, 3, 2, 20]
})
# ここにコードを書いてください
【解答】applyを使用(複数条件が複雑なため)
def 会員ランク判定(row):
金額 = row[‘購入金額合計’]
回数 = row[‘購入回数’]
if 金額 >= 100000 and 回数 >= 10:
return ‘プラチナ’
elif 金額 >= 50000 and 回数 >= 5:
return ‘ゴールド’
elif 金額 >= 10000:
return ‘シルバー’
else:
return ‘ブロンズ’
df[‘会員ランク’] = df.apply(会員ランク判定, axis=1)
print(df)
学習メモ
ETL・データパイプライン構築 - Step 13
📋 過去のメモ一覧
▼