【解説】手を動かしながらグループ分け、最大最小値の抽出方法を学ぶ | データサイエンス100本ノック【問23〜問26 回答】

【解説】手を動かしながらグループ分け、最大最小値の抽出方法を学ぶ | データサイエンス100本ノック【問23〜問26 回答】

当ページのリンクには広告が含まれています。




目次

この記事の対象者


・ データサイエンティストを目指している人

・ Pythonのgroupby, agg, reset_index, min, maxメソッドの使い方を知りたい人

本記事で学ぶpythonのメソッドと関数



本記事では以下のメソッドを学びます。

  • groupbyメソッド: データフレームの特定の列のユニークな値でグルーピングするメソッド。
  • aggメソッド: aggはgroupbyでグルーピングされた列に対して統計量を取るためのメソッドです。
  • reset_indexメソッド: reset_indexメソッドは、インデックスが存在しないデータフレームにインデックスを付与するメソッドです。
  • min関数: 最小の要素を抽出する関数。
  • max関数: 最大の要素を抽出する関数。

では早速データサイエンス100本ノックの問題を題材に、これらの使い方を学んでいきたいと思います。

データサイエンス100本ノックの始め方は、以下の記事を参考にしていただければと思います。

>>データサイエンス100本ノックの始め方

第23問目: groupby()メソッド

P-023: レシート明細データフレーム(df_receipt)に対し、店舗コード(store_cd)ごとに売上金額(amount)と売上数量(quantity)を合計せよ。

まずはdf_receiptというデータフレーム全体を確認します。

1
df_receipt
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
14

sales_ymd sales_epoch store_cd receipt_no receipt_sub_no customer_id product_cd quantity amount
0 20181103 1541203200 S14006 112 1 CS006214000001 P070305012 1 158
1 20181118 1542499200 S13008 1132 2 CS008415000097 P070701017 1 81
2 20170712 1499817600 S14028 1102 1 CS028414000014 P060101005 1 170
3 20190205 1549324800 S14042 1132 1 ZZ000000000000 P050301001 1 25
4 20180821 1534809600 S14025 1102 2 CS025415000050 P060102007 1 90
... ... ... ... ... ... ... ... ... ...
104676 20180221 1519171200 S13043 1132 2 ZZ000000000000 P050101001 1 40
104677 20190911 1568160000 S14047 1132 2 ZZ000000000000 P071006005 1 218
104678 20170311 1489190400 S14040 1122 1 CS040513000195 P050405003 1 168
104679 20170331 1490918400 S13002 1142 1 CS002513000049 P060303001 1 148
104680 20190423 1555977600 S13016 1102 2 ZZ000000000000 P050601001 1 138
104681 rows × 9 columns

まずは、store_cdにおいて、ユニークな値でグルーピングをしてみます。

グルーピングをしたあと、ちゃんとグルーピングが出来ているかの確認をするために、グループ数がどの程度あるかを確認します。一旦以下のコードで、グループの数を確認します。

1
len(df_receipt['store_cd'].unique())

unique()メソッドを忘れてしまった方は、以下の記事で復習をしましょう。

>>uniqueメソッドの使い方

出力
1
52

52という数値が出力されました。

すなわちグループの数は52つであることが分かりました。

グループの数が確認できたので、早速groupby()メソッドを使ってstore_cdカラムのデータをグループ分けしたいと思います。

グループ分けしたいカラムは、store_cdなので、groupby()引数にstore_cdを指定してグルーピングしていきます。

最後にlen(df_group)のコードで、グルーピングの数を確認します。

1
2
df_group = df_receipt.groupby('store_cd')
len(df_group)
出力
1
52

52という出力が得られました。先程uniqueメソッドで調べたグループの数と一致しているので、グルーピングがうまく出来ていることが確認できました。

続いて、各グループごとに売上金額(amount)と売上数量(quantity)の合計を算出していきます。

各グループごとに統計量を取る方法は、agg()というメソッドを使うことで実現できます。

1
2
df_group_sum = df_group.agg({'amount': 'sum', 'quantity': 'sum'})
df_group_sum
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	amount	quantity
store_cd
S12007 638761 2099
S12013 787513 2425
S12014 725167 2358
S12029 794741 2555
S12030 684402 2403
S13001 811936 2347
S13002 727821 2340
S13003 764294 2197
S13004 779373 2390
S13005 629876 2004
S13008 809288 2491
S13009 808870 2486
S13015 780873 2248
S13016 793773 2432
S13017 748221 2376
S13018 790535 2562
S13019 827833 2541
S13020 796383 2383
S13031 705968 2336
S13032 790501 2491
S13035 715869 2219
S13037 693087 2344
S13038 708884 2337
S13039 611888 1981
S13041 728266 2233
S13043 587895 1881
S13044 520764 1729
S13051 107452 354
S13052 100314 250
S14006 712839 2284
S14010 790361 2290
S14011 805724 2434
S14012 720600 2412
S14021 699511 2231
S14022 651328 2047
S14023 727630 2258
S14024 736323 2417
S14025 755581 2394
S14026 824537 2503
S14027 714550 2303
S14028 786145 2458
S14033 725318 2282
S14034 653681 2024
S14036 203694 635
S14040 701858 2233
S14042 534689 1935
S14045 458484 1398
S14046 412646 1354
S14047 338329 1041
S14048 234276 769
S14049 230808 788
S14050 167090 580

一応これでstore_cd毎にamountとquantityの合計を得ることは出来ましたが、もともと各レコードに付いていたindex番号が失われていることが分かります。

各レコードにindex番号を割り当てるためには、reset_indexというメソッドを使用すればOKです。

1
df_group_sum.reset_index().head()
出力
1
2
3
4
5
6
7

store_cd amount quantity
0 S12007 638761 2099
1 S12013 787513 2425
2 S12014 725167 2358
3 S12029 794741 2555
4 S12030 684402 2403

これで完成です。

第24問目: max関数

P-024: レシート明細データフレーム(df_receipt)に対し、顧客ID(customer_id)ごとに最も新しい売上日(sales_ymd)を求め、10件表示せよ。

最も新しい売上日は、max関数を用いることで抽出できます。「最も新しい売上日」=「yyyymmdd」の値が最も大きい値と読み替えることができるわけですね。

1
2
df_max = df_receipt.groupby('customer_id').sales_ymd.max()
df_max
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
customer_id
CS001113000004 20190308
CS001114000005 20190731
CS001115000010 20190405
CS001205000004 20190625
CS001205000006 20190224
...
CS051513000004 20190719
CS051515000002 20191025
CS052212000002 20191017
CS052514000001 20190822
ZZ000000000000 20191031
Name: sales_ymd, Length: 8307, dtype: int64

こちらもindexが失われているのでreset_index()メソッドでindex番号を付与していきたいと思います。

1
df_max.reset_index().head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	customer_id	sales_ymd
0 CS001113000004 20190308
1 CS001114000005 20190731
2 CS001115000010 20190405
3 CS001205000004 20190625
4 CS001205000006 20190224
5 CS001211000025 20190322
6 CS001212000027 20170127
7 CS001212000031 20180906
8 CS001212000046 20170811
9 CS001212000070 20191018

これで完成です。

第25問目: min関数

P-025: レシート明細データフレーム(df_receipt)に対し、顧客ID(customer_id)ごとに最も古い売上日(sales_ymd)を求め、10件表示せよ。

この問題に関しては、第24問目で用いたmax関数を、minに変更すればOKです。

1
2
df_min = df_receipt.groupby('customer_id').sales_ymd.min()
df_min
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
customer_id
CS001113000004 20190308
CS001114000005 20180503
CS001115000010 20171228
CS001205000004 20170914
CS001205000006 20180207
...
CS051513000004 20190719
CS051515000002 20191025
CS052212000002 20191017
CS052514000001 20190822
ZZ000000000000 20170101
Name: sales_ymd, Length: 8307, dtype: int64

indexが失われているのでreset_index()メソッドでindex番号を付与していきたいと思います。

1
df_min.reset_index().head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
customer_id	sales_ymd
0 CS001113000004 20190308
1 CS001114000005 20180503
2 CS001115000010 20171228
3 CS001205000004 20170914
4 CS001205000006 20180207
5 CS001211000025 20190322
6 CS001212000027 20170127
7 CS001212000031 20180906
8 CS001212000046 20170811
9 CS001212000070 20191018

第26問目: 総合問題

P-026: レシート明細データフレーム(df_receipt)に対し、顧客ID(customer_id)ごとに最も新しい売上日(sales_ymd)と古い売上日を求め、両者が異なるデータを10件表示せよ。

まずは、問23のコードである、f_group_sum = df_group.agg({'amount': 'sum', 'quantity': 'sum'})を真似てコードを書いてみましょう。

1
df_receipt.groupby('customer_id').agg({'sales_ymd':'max', 'sales_ymd':'min'}).reset_index()
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
	customer_id	sales_ymd
0 CS001113000004 20190308
1 CS001114000005 20180503
2 CS001115000010 20171228
3 CS001205000004 20170914
4 CS001205000006 20180207
... ... ...
8302 CS051513000004 20190719
8303 CS051515000002 20191025
8304 CS052212000002 20191017
8305 CS052514000001 20190822
8306 ZZ000000000000 20170101
8307 rows × 2 columns

max列とmin列の2つが欲しかったのですが、minしか作成されませんでした。sales_ymdにおいて、max列とmin列の2つを設けたい場合は、以下のようにすればOKです。

1
2
df_salesymd_maxmin = df_receipt.groupby('customer_id').agg({'sales_ymd':['max', 'min']}).reset_index()
df_salesymd_maxmin
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	customer_id	sales_ymd
max  min
0 CS001113000004 20190308 20190308
1 CS001114000005 20190731 20180503
2 CS001115000010 20190405 20171228
3 CS001205000004 20190625 20170914
4 CS001205000006 20190224 20180207
... ... ... ...
8302 CS051513000004 20190719 20190719
8303 CS051515000002 20191025 20191025
8304 CS052212000002 20191017 20191017
8305 CS052514000001 20190822 20190822
8306 ZZ000000000000 20191031 20170101
8307 rows × 3 columns

出力をみると、sales_ymdの配下に、maxminというカラム名が設定されています。

これを、sales_ymd_maxsales_ymd_minという2つのカラム名に変更したいと思います。

まずは、このデータフレームからカラム名だけ抽出してみます。カラム名の抽出は、データフレームに対して.columnsを指定すればよかったですね。

第19問目でも使っているので、忘れてしまった方は復習しましょう。

>>第19問目の回答を確認する

1
df_salesymd_maxmin.columns
出力
1
2
3
4
MultiIndex([('customer_id',    ''),
( 'sales_ymd', 'max'),
( 'sales_ymd', 'min')],
)

マルチインデックスになっていることが分かります。

マルチインデックスを解除し、改めて各カラムに一意のカラム名を割り当てるために、for文とjoin関数を用いて、各インデックスを連結させたいと思います。

まずはdf_salesymd_maxmin.columnsをfor文で回して出力されるとどうなるのかを確認します。

1
2
for pair in df_salesymd_maxmin.columns:
print(pair)
出力
1
2
3
('customer_id', '')
('sales_ymd', 'max')
('sales_ymd', 'min')

ここで出力された各タプルを解除し、文字列を連結させて新たな配列に組み込んでいきたいと思います。

その前にjoin関数について説明します。

join関数の引数には、結合させたい文字列をそのまま設定するわけではなく、結合させたい文字列が格納されたリストまたはタプルを指定します。

今回でいうと、for文で1つ1つ抽出される各タプルをjoin関数の引数として指定して連結させていきます。

連結する文字列は_を指定します。

1
2
3
4
5
df_tmp = []
for pair in df_salesymd_maxmin.columns:
column = "_".join(pair)
df_tmp.append(column)
df_tmp
出力
1
['customer_id_', 'sales_ymd_max', 'sales_ymd_min']

作成できた新たなカラム名のリストであるdf_tmpを、df_salesymd_maxmin.columnsに代入したいと思います。

1
2
df_salesymd_maxmin.columns = df_tmp
df_salesymd_maxmin
出力
1
2
3
4
5
6
7
8
9
10
11
12
13
	customer_id_	sales_ymd_max	sales_ymd_min
0 CS001113000004 20190308 20190308
1 CS001114000005 20190731 20180503
2 CS001115000010 20190405 20171228
3 CS001205000004 20190625 20170914
4 CS001205000006 20190224 20180207
... ... ... ...
8302 CS051513000004 20190719 20190719
8303 CS051515000002 20191025 20191025
8304 CS052212000002 20191017 20191017
8305 CS052514000001 20190822 20190822
8306 ZZ000000000000 20191031 20170101
8307 rows × 3 columns

カラム名の修正が完了しました。

最後に、問題文に従って、最も新しい売上日であるsales_ymd_maxと古い売上日であるsales_ymd_minが異なるデータを10件表示します。これは以前取り組んだ.queryを使用します。

>>queryメソッドの使い方を復習する

1
df_salesymd_maxmin.query('sales_ymd_max != sales_ymd_min').head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	customer_id_	sales_ymd_max	sales_ymd_min
1 CS001114000005 20190731 20180503
2 CS001115000010 20190405 20171228
3 CS001205000004 20190625 20170914
4 CS001205000006 20190224 20180207
13 CS001214000009 20190902 20170306
14 CS001214000017 20191006 20180828
16 CS001214000048 20190929 20171109
17 CS001214000052 20190617 20180208
20 CS001215000005 20181021 20170206
21 CS001215000040 20171022 20170214

これで完成です。



まとめ: pythonのgroupby, agg, reset_index, min関数, max関数の使い方を学びました

本記事は、「【Python】手を動かしながらグループ分け、最大最小値の抽出方法を学ぶ | データサイエンス100本ノック【問23〜問26 回答】」というテーマでまとめました。

最後の問題は、今まで取り組んできた内容の総復習の位置づけにもなって、良い問題だったかなとおもいます。

少しずつデータの扱いを学び、スキルを伸ばしていきましょう。

>> 続きはこちら

コメント