STEP 13:複雑なデータ変換の実践

🔧 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. 実行速度の比較

大量データを処理するときは、実行速度も重要です。
同じ結果でも、書き方によって速度が大きく変わります。

⚡ 速度の順位(速い順)
  1. ベクトル演算(最速) – Pandasの組み込み機能
  2. np.where / np.select – NumPyの条件分岐
  3. map – 辞書による変換
  4. apply – 関数の適用
  5. 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
🎯 パフォーマンス最適化のコツ
  1. ベクトル演算が使えるか考える(四則演算、比較)
  2. 単純な変換にはmapを使う
  3. 条件分岐にはnp.wherenp.selectを使う
  4. どうしても必要なときだけapplyを使う
  5. 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

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE