【Pandas中級編】データ操作を極める!groupby集計、DataFrame結合、apply/map活用術
こんにちは! 前回の「【Pythonデータ分析入門】Pandasで表データ操作自由自在!」では、Pandasの基本的な使い方として、データの読み込みや基本的な集計方法をご紹介しました。Pandasの便利さを少し感じていただけたでしょうか?
今回は、その一歩先へ進み、Pandasを使ったより高度なデータ操作テクニックを深掘りしていきます。具体的には、
groupby()
を使った柔軟なデータ集計- 複数のDataFrameを自在に組み合わせる結合方法 (
merge
,concat
) apply()
やmap()
を使った効率的なデータ変換
などを、具体的なサンプルコードと共に解説します。これらのテクニックをマスターすれば、あなたのデータ分析スキルは格段に向上するはずです。それでは、Pandasのさらなる魅力に触れていきましょう!
1. より高度なデータ集計:groupby()
を使いこなす
前回の記事でも少し触れましたが、groupby()
は特定のキーに基づいてデータをグループ化し、それぞれのグループに対して集計処理を行うための非常に強力なメソッドです。
サンプルデータの準備
まずは、集計操作に使用するサンプルDataFrameを作成しましょう。
import pandas as pd
import numpy as np # NumPyも使うことがあるのでインポート
data = {
'部門': ['営業部', '開発部', '営業部', '人事部', '開発部', '営業部'],
'性別': ['男性', '女性', '女性', '男性', '男性', '男性'],
'給与': [500, 600, 450, 550, 700, 520],
'残業時間': [20, 10, 15, 5, 12, 25]
}
df_employee = pd.DataFrame(data)
print("--- 社員データ ---")
print(df_employee)
実行結果 (例):
--- 社員データ ---
部門 性別 給与 残業時間
0 営業部 男性 500 20
1 開発部 女性 600 10
2 営業部 女性 450 15
3 人事部 男性 550 5
4 開発部 男性 700 12
5 営業部 男性 520 25
1.1. 単一キーでのグループ化と集計
「部門」ごとに平均給与を計算してみましょう。
avg_salary_by_dept = df_employee.groupby('部門')['給与'].mean()
print("\n--- 部門ごとの平均給与 ---")
print(avg_salary_by_dept)
実行結果 (例):
--- 部門ごとの平均給与 ---
部門
営業部 490.0
人事部 550.0
開発部 650.0
Name: 給与, dtype: float64
1.2. 複数キーでのグループ化
「部門」と「性別」の両方でグループ化し、それぞれの残業時間の合計を計算します。
total_overtime_by_dept_gender = df_employee.groupby(['部門', '性別'])['残業時間'].sum()
print("\n--- 部門別・性別の合計残業時間 ---")
print(total_overtime_by_dept_gender)
実行結果 (例):
--- 部門別・性別の合計残業時間 ---
部門 性別
営業部 女性 15
男性 45
人事部 男性 5
開発部 女性 10
男性 12
Name: 残業時間, dtype: int64
このように階層的なインデックス(マルチインデックス)が作成されます。.reset_index()
を使うと、これらのインデックスを通常の列に戻すことができます。
print("\n--- reset_index()適用後 ---")
print(total_overtime_by_dept_gender.reset_index())
1.3. 複数の集計関数を一度に適用 (.agg()
)
.agg()
メソッドを使うと、1つのgroupby
操作で複数の集計を同時に行うことができます。
# 部門ごとに給与の平均と残業時間の合計、および人数(count)を計算
dept_summary = df_employee.groupby('部門').agg(
平均給与=('給与', 'mean'),
合計残業時間=('残業時間', 'sum'),
人数=('部門', 'count') # '部門'列でなくても、存在する列なら何でも良い
)
print("\n--- 部門ごとの集計サマリー ---")
print(dept_summary)
# 特定の列に対して複数の集計を行う場合
salary_stats_by_dept = df_employee.groupby('部門')['給与'].agg(['mean', 'min', 'max', 'std'])
print("\n--- 部門ごとの給与統計 ---")
print(salary_stats_by_dept)
実行結果 (例):
--- 部門ごとの集計サマリー ---
平均給与 合計残業時間 人数
部門
営業部 490.0 60 3
人事部 550.0 5 1
開発部 650.0 22 2
--- 部門ごとの給与統計 ---
mean min max std
部門
営業部 490.000000 450 520 36.055513
人事部 550.000000 550 550 NaN
開発部 650.000000 600 700 70.710678
.agg()
に渡す辞書では、新しい列名をキー、(集計対象列, 集計関数)のタプルを値として指定できます。
2. DataFrameの結合:データを一つにまとめる
実際のデータ分析では、複数のデータソースからの情報を組み合わせて分析することがよくあります。Pandasでは、pd.merge()
とpd.concat()
という2つの主要な関数を使ってDataFrameを結合できます。
サンプルDataFrameの準備
結合操作のために、2つのサンプルDataFrameを作成します。
df_customers = pd.DataFrame({
'顧客ID': ['C001', 'C002', 'C003', 'C004'],
'氏名': ['佐藤', '鈴木', '高橋', '田中']
})
print("--- 顧客データ ---")
print(df_customers)
df_orders = pd.DataFrame({
'注文ID': ['O001', 'O002', 'O003', 'O004', 'O005'],
'顧客ID': ['C001', 'C003', 'C001', 'C002', 'C005'], # C005は顧客データにいない
'商品': ['リンゴ', 'バナナ', 'ミカン', 'リンゴ', 'ブドウ'],
'価格': [100, 80, 120, 100, 200]
})
print("\n--- 注文データ ---")
print(df_orders)
2.1. pd.merge()
:特定の列をキーとして結合 (SQLのJOIN風)
pd.merge()
は、データベースのJOIN操作のように、共通の列(キー)を基準にDataFrameを結合します。
# 内部結合 (両方のDataFrameに存在するキーのみ)
df_merged_inner = pd.merge(df_customers, df_orders, on='顧客ID', how='inner')
print("\n--- 内部結合 (inner) ---")
print(df_merged_inner)
# 左外部結合 (左のDataFrameのキーは全て残す)
df_merged_left = pd.merge(df_customers, df_orders, on='顧客ID', how='left')
print("\n--- 左外部結合 (left) ---")
print(df_merged_left)
# 右外部結合 (右のDataFrameのキーは全て残す)
df_merged_right = pd.merge(df_customers, df_orders, on='顧客ID', how='right')
print("\n--- 右外部結合 (right) ---")
print(df_merged_right)
# 完全外部結合 (両方のDataFrameのキーを全て残す)
df_merged_outer = pd.merge(df_customers, df_orders, on='顧客ID', how='outer')
print("\n--- 完全外部結合 (outer) ---")
print(df_merged_outer)
on='顧客ID'
で「顧客ID」列をキーに指定し、how
パラメータで結合方法(inner
, left
, right
, outer
)を指定します。
もしキーとなる列名が左右のDataFrameで異なる場合は、left_on='左の列名'
, right_on='右の列名'
のように指定できます。
2.2. pd.concat()
:DataFrameを単純に連結 (縦積み・横並び)
pd.concat()
は、複数のDataFrameを単純に行方向(縦)または列方向(横)に連結します。
df1 = pd.DataFrame({'A': ['A0', 'A1'], 'B': ['B0', 'B1']})
df2 = pd.DataFrame({'A': ['A2', 'A3'], 'B': ['B2', 'B3']})
df3 = pd.DataFrame({'C': ['C0', 'C1'], 'D': ['D0', 'D1']})
# 行方向の連結 (デフォルト)
df_concat_rows = pd.concat([df1, df2])
print("\n--- 行方向の連結 ---")
print(df_concat_rows)
# ignore_index=True でインデックスを振り直す
df_concat_rows_reindex = pd.concat([df1, df2], ignore_index=True)
print("\n--- 行方向の連結 (インデックス振り直し) ---")
print(df_concat_rows_reindex)
# 列方向の連結 (axis=1 を指定)
df_concat_cols = pd.concat([df1, df3], axis=1)
print("\n--- 列方向の連結 ---")
print(df_concat_cols)
axis=0
(デフォルト)で行方向に、axis=1
で列方向に連結します。連結時に元のインデックスが重複する場合があるので、ignore_index=True
で新しい連番のインデックスを振ると便利なことがあります。
3. 効率的なデータ変換:apply()
と map()
DataFrameやSeriesの値を一括で変換したり、条件に基づいて新しい値を設定したりする際に便利なのがapply()
とmap()
(Seriesの場合)メソッドです。
サンプルデータの準備
df_products = pd.DataFrame({
'商品名': ['リンゴ', 'バナナ', 'ミカン', 'ブドウ'],
'価格(税抜)': [100, 80, 120, 200],
'分類': ['果物', '果物', '果物', '果物']
})
print("--- 商品データ ---")
print(df_products)
3.1. .apply()
:行または列に関数を適用
apply()
は、DataFrameの行全体または列全体に対して、自作の関数やラムダ式を適用できます。
# 消費税10%を加えた税込価格を計算する関数
def calculate_tax_included_price(price):
return price * 1.10
# '価格(税抜)'列に関数を適用して新しい列を作成
df_products['価格(税込)'] = df_products['価格(税抜)'].apply(calculate_tax_included_price)
print("\n--- 税込価格を追加 (applyで関数) ---")
print(df_products)
# ラムダ式を使って同様の処理
df_products['価格(税込)_lambda'] = df_products['価格(税抜)'].apply(lambda x: x * 1.10)
print("\n--- 税込価格を追加 (applyでラムダ式) ---")
print(df_products[['商品名', '価格(税抜)', '価格(税込)_lambda']])
# 行全体に適用して、価格が100円以上の商品にフラグを立てる
def is_expensive(row):
if row['価格(税抜)'] >= 100:
return '高価'
else:
return '手頃'
df_products['価格帯'] = df_products.apply(is_expensive, axis=1) # axis=1で行単位の処理
print("\n--- 価格帯を追加 (行にapply) ---")
print(df_products)
axis=0
(デフォルト)だと列単位、axis=1
だと行単位で関数が適用されます。
3.2. .map()
:Seriesの各要素を置換
map()
はSeriesの各要素に対して、辞書や関数を使って値を置き換えます。
# '分類'列の値を日本語から英語に変換する辞書
category_map = {
'果物': 'Fruit',
'野菜': 'Vegetable' # 今回のデータにはないが例として
}
df_products['分類(英)'] = df_products['分類'].map(category_map)
print("\n--- 分類を英語に変換 (mapで辞書) ---")
print(df_products)
# 価格(税抜)に応じてランク付けする関数
def price_to_rank(price):
if price >= 150:
return 'A'
elif price >= 100:
return 'B'
else:
return 'C'
df_products['価格ランク'] = df_products['価格(税抜)'].map(price_to_rank)
print("\n--- 価格ランク付け (mapで関数) ---")
print(df_products)
map()
はSeriesにしか使えませんが、特定の列の値を効率的に変換・置換するのに便利です。
まとめ:Pandasでデータ操作の達人を目指そう!
今回のPandas中級編では、
groupby()
による柔軟な集計(複数キー、複数集計関数)pd.merge()
によるキーに基づいたDataFrameの結合pd.concat()
による単純なDataFrameの連結.apply()
による行・列への関数適用.map()
によるSeriesの要素の置換
といった、より実践的なデータ操作テクニックを学びました。
これらの機能を組み合わせることで、複雑なデータも効率よく整形し、分析のための準備を整えることができます。Pandasにはまだまだ多くの強力な機能が備わっています。例えば、時系列データの扱いや、ピボットテーブルの作成、さらにはMatplotlibやSeabornといった可視化ライブラリとの連携など、学べば学ぶほどデータ分析の世界は広がっていきます。
ぜひ、今回学んだことを実際のデータで試し、Pandasの力を実感してみてください。あなたのデータ分析の旅が、さらにエキサイティングなものになることを願っています!
コメント
コメントを投稿