Python Pandas DataFrameで各グループごとに最大値を持つ行を抽出する方法
はじめに
データ分析において最も基本的かつ頻繁に行われる操作のひとつが、「グループ内で特定の列が最大値となる行を選択する」ことです。本記事では、PandasのDataFrameを使って、各グループの中から最大値を持つ行を抽出する方法を、具体的なコード例とともにわかりやすく解説します。
課題の確認
まず、取り組むタスクを明確にしましょう。ここでは「映画データセット」を例に、各公開年ごとに最も人気(popularity)の高い映画をリストアップすることを目標とします。
実装手順
1. データの準備
データ分析用のデータセットは、Kaggleなどのサイトで自由に入手できます。ここではGitHub上に公開されている映画データセットを直接読み込みます。Kaggleからダウンロードした場合は、データ提供者に「いいね」を残して感謝を伝えるのも良いマナーです。
import pandas as pd
import numpy as np
movies = pd.read_csv("https://raw.githubusercontent.com/sasankac/TestDataSet/master/movies_data.csv")
# サンプルとして5行表示
print(f"Output \n\n*** {movies.sample(n=5)} ")
出力結果
*** budget id original_language original_title popularity \ 2028 22000000 235260 en Son of God 9.175762 2548 0 13411 en Malibu's Most Wanted 7.314796 3279 8000000 26306 en Prefontaine 8.717235 3627 5000000 10217 en The Sweet Hereafter 7.673124 4555 0 98568 en Enter Nowhere 3.637857 release_date revenue runtime status title \ 2028 28/02/2014 67800064 138.0 Released Son of God 2548 10/04/2003 0 86.0 Released Malibu's Most Wanted 3279 24/01/1997 589304 106.0 Released Prefontaine 3627 14/05/1997 3263585 112.0 Released The Sweet Hereafter 4555 22/10/2011 0 90.0 Released Enter Nowhere vote_average vote_count 2028 5.9 83 2548 4.7 77 3279 6.7 21 3627 6.8 103 4555 6.5 49
2. 基本的なデータ分析でデータを把握する
まずは各列のデータ型を確認します。
# データ型の確認
print(f"Output \n*** Datatypes are {movies.dtypes} ")
出力結果
*** Datatypes are budget int64 id int64 original_language object original_title object popularity float64 release_date object revenue int64 runtime float64 status object title object vote_average float64 vote_count int64 dtype: object
3. メモリ使用量を抑えるためのデータ型変換
メモリ消費を抑えたい場合は、float64やint64をより小さいサイズのデータ型へ変換できます。ただし、変換前には必ず値の範囲を確認し、桁あふれが起きないか検証することが重要です。
# 数値列の最大値を確認
print(f"Output \n *** maximum value for Numeric data type - {movies.select_dtypes(exclude=['object']).unstack().max()}")
# vote_count の最大値
print(f" *** Vote count maximum value - {movies[['vote_count']].unstack().max()}")
# runtime の最大値
print(f" *** Movie Id maximum value - {movies[['runtime']].unstack().max()}")
出力結果
*** maximum value for Numeric data type - 2787965087.0 *** Vote count maximum value - 13752 *** Movie Id maximum value - 338.0
たとえばint16の範囲は-32,768〜+32,767なので、vote_countやruntimeのような列はint16へ安全にダウンキャストできます。必要に応じて他の列も同様に処理してください。
4. 公開年の抽出
各年の最も人気のある映画を特定するには、SQLでいうところの次のようなクエリに相当する処理が必要です。
SELECT movie with max popularity FROM movies GROUP BY movie released year
しかし、現在のrelease_date列はobject型(文字列)です。そこで、日付型に変換したうえで「年」だけを格納する新しい列を作成します。このyear列をグルーピングのキーとして使います。
movies['year'] = pd.to_datetime(movies['release_date']).dt.year.astype('Int64')
print(f"Output \n ***{movies.sample(n=5)}")
出力結果
*** budget id original_language original_title popularity \ 757 0 87825 en Trouble with the Curve 18.587114 711 58000000 39514 en RED 41.430245 1945 13500000 152742 en La migliore offerta 30.058263 2763 13000000 16406 en Dick 4.742537 4595 350000 764 en The Evil Dead 35.037625 release_date revenue runtime status title \ 757 21/09/2012 0 111.0 Released Trouble with the Curve 711 13/10/2010 71664962 111.0 Released RED 1945 1/01/2013 19255873 124.0 Released The Best Offer 2763 4/08/1999 27500000 94.0 Released Dick 4595 15/10/1981 29400000 85.0 Released The Evil Dead vote_average vote_count year 757 6.6 366 2012 711 6.6 2808 2010 1945 7.7 704 2013 2763 5.7 67 1999 4595 7.3 894 1981
方法1:GroupByを使わないアプローチ
まず必要なのは、映画タイトル・公開年・人気度の3列だけです。これらの列を選択し、sort_valuesでyear順に並べ替えて結果を確認してみましょう。
print(f"Output \n *** Method 1- Without Using Group By")
movies[["title", "year", "popularity"]].sort_values("year", ascending=True)
出力結果
| title | year | popularity | |
|---|---|---|---|
| 4592 | Intolerance | 1916 | 3.232447 |
| 4661 | The Big Parade | 1925 | 0.785744 |
| 2638 | Metropolis | 1927 | 32.351527 |
| 4594 | The Broadway Melody | 1929 | 0.968865 |
| 4457 | Pandora's Box | 1929 | 1.824184 |
| ... | ... | ... | ... |
| 2109 | Me Before You | 2016 | 53.161905 |
| 3081 | The Forest | 2016 | 19.865989 |
| 2288 | Fight Valley | 2016 | 1.224105 |
| 4255 | Growing Up Smith | 2017 | 0.710870 |
| 4553 | America Is Still the Place | <NA> | 0.000000 |
4803行 × 3列
このままでは年ごとの並びだけで、人気度は考慮されていません。そこで、ソートキーとして複数の列をリストで渡し、ascending=Falseを指定して降順に並べ替えます。これにより、各年内で人気度の高い順に並びます。
movies[["title", "year", "popularity"]].sort_values(["year","popularity"], ascending=False)
| title | year | popularity | |
|---|---|---|---|
| 4255 | Growing Up Smith | 2017 | 0.710870 |
| 788 | Deadpool | 2016 | 514.569956 |
| 26 | Captain America: Civil War | 2016 | 198.372395 |
| 10 | Batman v Superman: Dawn of Justice | 2016 | 155.790452 |
| 64 | X-Men: Apocalypse | 2016 | 139.272042 |
| ... | ... | ... | ... |
| 4593 | The Broadway Melody | 1929 | 0.968865 |
| 2638 | Metropolis | 1927 | 32.351527 |
| 4660 | The Big Parade | 1925 | 0.785744 |
| 4591 | Intolerance | 1916 | 3.232447 |
| 4552 | America Is Still the Place | <NA> | 0.000000 |
4802行 × 3列
これでデータは完璧に並べ替えられました。次のステップは、各年の先頭行だけを残し、残りを削除することです。どうやって実現するか分かりますか?
答えは.drop_duplicatesメソッドを使うことです。subset引数に"year"を指定すれば、year列の重複を排除し、各年の最初の行(=最も人気の高い映画)だけが残ります。
movies[["title", "year", "popularity"]].sort_values(["year","popularity"], ascending=False).drop_duplicates(subset="year")
| title | year | popularity | |
|---|---|---|---|
| 4255 | Growing Up Smith | 2017 | 0.710870 |
| 788 | Deadpool | 2016 | 514.569956 |
| 546 | Minions | 2015 | 875.581305 |
| 95 | Interstellar | 2014 | 724.247784 |
| 124 | Frozen | 2013 | 165.125366 |
| ... | ... | ... | ... |
| 4456 | Pandora's Box | 1929 | 1.824184 |
| 2638 | Metropolis | 1927 | 32.351527 |
| 4660 | The Big Parade | 1925 | 0.785744 |
| 4591 | Intolerance | 1916 | 3.232447 |
| 4552 | America Is Still the Place | <NA> | 0.000000 |
91行 × 3列
方法2:GroupByを使うアプローチ
同じ結果はgroupbyを使っても得られます。考え方は前述のSQLとほぼ同じです。yearでグループ化し、各グループ内でpopularityの降順に並べ替えたうえで先頭の1行を取得します。
print(f"Output \n *** Method 2 - Using Group By")
movies[["title", "year", "popularity"]].groupby("year", as_index=False).apply(lambda df:df.sort_values("popularity", ascending=False)
.head(1)).droplevel(0).sort_values("year", ascending=False)
出力結果
| title | year | popularity | |
|---|---|---|---|
| 4255 | Growing Up Smith | 2017 | 0.710870 |
| 788 | Deadpool | 2016 | 514.569956 |
| 546 | Minions | 2015 | 875.581305 |
| 95 | Interstellar | 2014 | 724.247784 |
| 124 | Frozen | 2013 | 165.125366 |
| ... | ... | ... | ... |
| 3804 | Hell's Angels | 1930 | 8.484123 |
| 4457 | Pandora's Box | 1929 | 1.824184 |
| 2638 | Metropolis | 1927 | 32.351527 |
| 4661 | The Big Parade | 1925 | 0.785744 |
| 4592 | Intolerance | 1916 | 3.232447 |
90行 × 3列
まとめ
グループごとの最大値行の抽出には、大きく分けて2つの方法があります。
- 方法1(sort_values + drop_duplicates):シンプルで直感的。大量データでも高速に動作し、可読性も高い。
- 方法2(groupby + apply):柔軟性が高く、グループごとにより複雑な処理を行いたい場合に有効。
用途やデータ規模に応じて使い分けることで、効率的なデータ分析が可能になります。
-
【Python】Pandas DataFrameからnull(欠損値)行を削除する方法|dropna()の使い方
PandasのDataFrameからnull(NaN)行を削除するには、dropna()メソッドを使用します。この記事では、NaNを含むCSVファイルを読み込み、dropna()で欠損値を含む行を取り除くまでの手順を、サンプルコードと実行結果とともにわかりやすく解説します。前提:NaNを含むCSVファイル今回の例では、一部にNaN(null値)が含まれる以下のようなCSVファイル(CarRecords.csv)を使用します。手順1:read_csv()でCSVファイルを読み込むまずpandasをインポートし、read_csv()を使ってCSVファイルを読み込みます。ここではデスクトップ上に保存
-
Python Pandasでデータフレームの列の値をX軸ラベルとして設定する方法
PythonのPandasでデータフレームの列の値をX軸ラベルとして設定するには、plot()メソッドの引数にxticksを指定します。これにより、データの値をそのまま目盛りラベルとして表示できるため、グラフがより読みやすくなります。手順図のサイズを設定し、サブプロット間および周囲の余白(パディング)を調整します。Pandasを使って、column1というキーを持つデータフレームを作成します。plot()メソッドでデータフレームをプロットし、column1の値をX軸ラベルとして指定します。図を表示するには、show()メソッドを使用します。コード例import pandas as pd fro