当ページのリンクには広告が含まれています。
目次
この記事の対象者
・ データサイエンティストを目指している人
・ Pythonで2値化の方法を学びたい人
以降はデータサイエンス100本ノックの問題を題材に、2値化の方法について学んでいきます。
データサイエンス100本ノックの始め方は、以下の記事を参考にしていただければと思います。
>>データサイエンス100本ノックの始め方を確認する
第52問目: サブクエリによるデータ抽出と2値化
P-052: レシート明細データフレーム(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計の上、売上金額合計に対して2000円以下を0、2000円超を1に2値化し、顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
流れとしては以下のように進めたいと思います。
顧客IDが”Z”から始まるもの以外を抽出する。
customer_id
でグループ化し、各合計を算出する。
2値化を行い、新たなカラムに追加。
10件表示する。
まずはZから始まるもの以外の顧客IDがを抽出します。 この方法は第34問目でやりましたね。
>> queryメソッドでデータを抽出する方法を復習する
Zから始まる顧客IDが以外のデータを抽出 1 2 df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python') df_sales_amount
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 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 4 20180821 1534809600 S14025 1102 2 CS025415000050 P060102007 1 90 5 20190605 1559692800 S13003 1112 1 CS003515000195 P050102002 1 138 ... ... ... ... ... ... ... ... ... ... 104671 20180131 1517356800 S14010 1102 1 CS010414000008 P060103003 1 150 104673 20181217 1545004800 S13004 1142 2 CS004515000066 P059001016 1 308 104674 20190911 1568160000 S14046 1182 1 CS046415000017 P070703003 1 98 104678 20170311 1489190400 S14040 1122 1 CS040513000195 P050405003 1 168 104679 20170331 1490918400 S13002 1142 1 CS002513000049 P060303001 1 148 65682 rows × 9 columns
合計値の出し方はsum
メソッドを使うやり方と、agg
メソッドを使いやり方2通りあります。
>> sumメソッドとaggメソッドの使い方を復習する
今回は、sumメソッドを使ったやり方で進めます。
customer_idでグループ化しsumメソッドで合計を算出 1 2 3 # customer_idでグループ化し、各合計を算出します。 df_sales_amount = df_sales_amount[['customer_id', 'amount']].groupby('customer_id').sum().reset_index() df_sales_amount
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 customer_id amount 0 CS001113000004 1298 1 CS001114000005 626 2 CS001115000010 3044 3 CS001205000004 1988 4 CS001205000006 3337 ... ... ... 8301 CS051212000001 336 8302 CS051513000004 551 8303 CS051515000002 265 8304 CS052212000002 192 8305 CS052514000001 178 8306 rows × 2 columns
2値化の処理を行います。
データフレームの列に対して何らかの関数を適用した処理をするメソッドとしてapply
メソッドがありました。
データ分析ではlambda式と併用して、df.apply(lambda 引数: 返り値)
という形式で使いましたね。
忘れてしまった方は、以下で復習しましょう。
>> applyメソッドとlambda式を復習する
2値化処理 1 2 df_sales_amount['sales_flag'] = df_sales_amount['amount'].apply(lambda x: 1 if x > 2000 else 0) df_sales_amount.head(10)
出力 1 2 3 4 5 6 7 8 9 10 11 12 customer_id amount sales_flag 0 CS001113000004 1298 0 1 CS001114000005 626 0 2 CS001115000010 3044 1 3 CS001205000004 1988 0 4 CS001205000006 3337 1 5 CS001211000025 456 0 6 CS001212000027 448 0 7 CS001212000031 296 0 8 CS001212000046 228 0 9 CS001212000070 456 0
本問はこれで完了です。
第53問目: データ型変換(str→int)と2値化
P-053: 顧客データフレーム(df_customer)の郵便番号(postal_cd)に対し、東京(先頭3桁が100〜209のもの)を1、それ以外のものを0に2値化せよ。さらにレシート明細データフレーム(df_receipt)と結合し、全期間において買い物実績のある顧客数を、作成した2値ごとにカウントせよ。
まずはdf_customer
の構造を確認します。
df_customerの構造を確認
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 customer_id customer_name gender_cd gender birth_day age postal_cd address application_store_cd application_date status_cd 0 CS021313000114 大野 あや子 1 女性 1981-04-29 37 259-1113 神奈川県伊勢原市粟窪********** S14021 20150905 0-00000000-0 1 CS037613000071 六角 雅彦 9 不明 1952-04-01 66 136-0076 東京都江東区南砂********** S13037 20150414 0-00000000-0 2 CS031415000172 宇多田 貴美子 1 女性 1976-10-04 42 151-0053 東京都渋谷区代々木********** S13031 20150529 D-20100325-C 3 CS028811000001 堀井 かおり 1 女性 1933-03-27 86 245-0016 神奈川県横浜市泉区和泉町********** S14028 20160115 0-00000000-0 4 CS001215000145 田崎 美紀 1 女性 1995-03-29 24 144-0055 東京都大田区仲六郷********** S13001 20170605 6-20090929-2 5 CS020401000016 宮下 達士 0 男性 1974-09-15 44 174-0065 東京都板橋区若木********** S13020 20150225 0-00000000-0 6 CS015414000103 奥野 陽子 1 女性 1977-08-09 41 136-0073 東京都江東区北砂********** S13015 20150722 B-20100609-B 7 CS029403000008 釈 人志 0 男性 1973-08-17 45 279-0003 千葉県浦安市海楽********** S12029 20150515 0-00000000-0 8 CS015804000004 松谷 米蔵 0 男性 1931-05-02 87 136-0073 東京都江東区北砂********** S13015 20150607 0-00000000-0 9 CS033513000180 安斎 遥 1 女性 1962-07-11 56 241-0823 神奈川県横浜市旭区善部町********** S14033 20150728 6-20080506-5 10 CS007403000016 依田 満 0 男性 1975-08-18 43 276-0022 千葉県八千代市上高野********** S12007 20150914 0-00000000-0 11 CS035614000014 板倉 菜々美 1 女性 1954-07-16 64 154-0015 東京都世田谷区桜新町********** S13035 20150804 0-00000000-0 12 CS011215000048 芦田 沙耶 1 女性 1992-02-01 27 223-0062 神奈川県横浜市港北区日吉本町********** S14011 20150228 C-20100421-9 13 CS009413000079 市川 コウ 1 女性 1975-12-28 43 158-0093 東京都世田谷区上野毛********** S13009 20151209 0-00000000-0 14 CS040412000191 川井 郁恵 1 女性 1977-01-05 42 226-0021 神奈川県横浜市緑区北八朔町********** S14040 20151101 1-20091025-4 15 CS029415000023 梅田 里穂 1 女性 1976-01-17 43 279-0043 千葉県浦安市富士見********** S12029 20150610 D-20100918-E 16 CS009315000023 皆川 文世 1 女性 1980-04-15 38 154-0012 東京都世田谷区駒沢********** S13009 20150319 5-20080322-1 17 CS040702000012 根本 六郎 0 男性 1939-07-02 79 226-0018 神奈川県横浜市緑区長津田みなみ台********** S14040 20150112 0-00000000-0 18 CS046615000013 河野 花 1 女性 1953-04-06 65 224-0026 神奈川県横浜市都筑区南山田町********** S14046 20181207 0-00000000-0 19 CS025412000147 堀口 陽子 1 女性 1974-10-22 44 242-0015 神奈川県大和市下和田********** S14025 20150417 0-00000000-0
そしてレシート明細データフレーム(df_receipt
)も確認しましょう。
df_receiptの構造を確認
出力 1 2 3 4 5 6 7 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
顧客データフレームの郵便番号に対して2値化した後、顧客数をカウントすることを踏まえ、customer_id
カラムとpostal_cd
カラムを抽出します。
customer_idカラムとpostal_cdカラムを抽出 1 2 df_tmp = df_customer[['customer_id', 'postal_cd']] df_tmp
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 customer_id postal_cd 0 CS021313000114 259-1113 1 CS037613000071 136-0076 2 CS031415000172 151-0053 3 CS028811000001 245-0016 4 CS001215000145 144-0055 ... ... ... 21966 CS002512000474 185-0034 21967 CS029414000065 279-0043 21968 CS012403000043 231-0825 21969 CS033512000184 245-0016 21970 CS009213000022 154-0012 21971 rows × 2 columns
ここで、新たなカラムとしてpostal_flg
というものを用意します。 その上で、postal_cd
の先頭3桁が100〜209となっているものは1とし、それ以外のものは0とする処理を施そうと思います。
``postal_cd``の先頭3桁が100〜209となっているものは1とし、それ以外のものは0とする処理ってどうやるの?
このように悩んだときは、具体的なデータを使って実験 してみると良いです。
ここでは擬似的なpostal_cd
として"123-4567"
というのを用いて実験してみます。
"123-4567"
という文字列から先頭の3桁を抽出するにはどうすればよいか考えましょう。
これは結構シンプルで、pythonのスライスを使えば抽出できます。
出力
しかし、このままでは、100〜209の範囲なのかどうかの判定はできません。
その理由は、”123”がint型ではなくstr型になっているからです。
出力
なのでこのデータをint型に変換する必要があります。
int型への変換は以下のようにすればOKです。
出力
ここまでの実験結果を踏まえて、applyメソッドを用いて以下のようなコードを書けば、postal_cd
の先頭3桁が100〜209なのか、そうではないのかが判定できることが理解できると思います。
1 df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0)
出力 1 2 3 4 5 6 7 8 9 10 11 12 0 0 1 1 2 1 3 0 4 1 .. 21966 1 21967 0 21968 0 21969 0 21970 1 Name: postal_cd, Length: 21971, dtype: int64
この結果については、のちのちdf_receipt
と結合することになるので、postal_flg
という新たなカラムに格納します。
1 2 df_tmp['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0) df_tmp
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <ipython-input-49-61507ef2b9a0>:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df_tmp['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0) customer_id postal_cd postal_flg 0 CS021313000114 259-1113 0 1 CS037613000071 136-0076 1 2 CS031415000172 151-0053 1 3 CS028811000001 245-0016 0 4 CS001215000145 144-0055 1 ... ... ... ... 21966 CS002512000474 185-0034 1 21967 CS029414000065 279-0043 0 21968 CS012403000043 231-0825 0 21969 CS033512000184 245-0016 0 21970 CS009213000022 154-0012 1 21971 rows × 3 columns
おっと、なにやらwarningが出てきました。
warningの内容 1 2 3 <ipython-input-49-61507ef2b9a0>:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead
warningの内容は、
warningの内容の日本語訳 1 2 DataFrameのスライスのコピーに値を設定しようとしています。 .loc[row_indexer,col_indexer] = valueを使用してください。
というわけで.locを使用してみます。
.locを適用 1 2 df_tmp.loc['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0) df_tmp
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /opt/conda/lib/python3.8/site-packages/pandas/core/indexing.py:692: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy iloc._setitem_with_indexer(indexer, value, self.name) customer_id postal_cd 0 CS021313000114 259-1113 1 CS037613000071 136-0076 2 CS031415000172 151-0053 3 CS028811000001 245-0016 4 CS001215000145 144-0055 ... ... ... 21967 CS029414000065 279-0043 21968 CS012403000043 231-0825 21969 CS033512000184 245-0016 21970 CS009213000022 154-0012 postal_flg NaN NaN 21972 rows × 2 columns
.loc
を用いてもだめでした。
こういうときはググりましょう。
すると以下の記事が見つかります。
https://qiita.com/HEM_SP/items/56cd62a1c000d342bd70
どうやら.copy
を用いてdf_tmpを最初にセットしなければいけなかったようです。改めて以下のコードを実施することで。warningが発生しないことが確認されました。
最初に.copyを用いて明示的にdf_customerのコピーであることを宣言 1 2 3 df_tmp = df_customer[['customer_id', 'postal_cd']].copy() df_tmp['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0) df_tmp
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 customer_id postal_cd postal_flg 0 CS021313000114 259-1113 0 1 CS037613000071 136-0076 1 2 CS031415000172 151-0053 1 3 CS028811000001 245-0016 0 4 CS001215000145 144-0055 1 ... ... ... ... 21966 CS002512000474 185-0034 1 21967 CS029414000065 279-0043 0 21968 CS012403000043 231-0825 0 21969 CS033512000184 245-0016 0 21970 CS009213000022 154-0012 1 21971 rows × 3 columns
次にdf_tmpとdf_receiptを結合します。
両者のデータフレームには共通するカラムとしてcustomer_id
が存在するので、merge
関数を用いて内部結合を行います。
mergeを用いて内部結合 1 2 df_merge = pd.merge(df_tmp, df_receipt, how='inner', on='customer_id') df_merge
出力 1 2 3 4 5 6 7 8 9 10 11 12 13 customer_id postal_cd postal_flg sales_ymd sales_epoch store_cd receipt_no receipt_sub_no product_cd quantity amount 0 CS031415000172 151-0053 1 20170507 1494115200 S13031 1102 1 P060103001 1 100 1 CS031415000172 151-0053 1 20171026 1508976000 S13031 1182 1 P090203004 1 320 2 CS031415000172 151-0053 1 20190325 1553472000 S13031 1192 1 P071401025 1 2400 3 CS031415000172 151-0053 1 20170111 1484092800 S13031 1132 2 P071203007 1 448 4 CS031415000172 151-0053 1 20190325 1553472000 S13031 1192 2 P070805011 1 258 ... ... ... ... ... ... ... ... ... ... ... ... 65677 CS029414000065 279-0043 0 20191028 1572220800 S12029 1182 1 P060102002 1 88 65678 CS029414000065 279-0043 0 20190806 1565049600 S12029 1132 2 P060101007 1 180 65679 CS029414000065 279-0043 0 20180611 1528675200 S12029 1162 2 P090204049 1 390 65680 CS029414000065 279-0043 0 20180305 1520208000 S12029 1132 2 P050602001 1 88 65681 CS029414000065 279-0043 0 20170306 1488758400 S12029 1112 2 P071401020 1 2200 65682 rows × 11 columns
また全期間において買い物実績のある顧客数を作成した2値ごとにカウントする必要があります。
まずはgroupby
メソッドを用いてpostal_flg
でグルーピングし、customer_id
の数を集計します。
集計方法はaggメソッドを使う方法と、nuniqueメソッドを使う方法がありますので両方紹介します。
パタン1:nunique()のパタン 1 df_merge.groupby('postal_flg')['customer_id'].nunique()
出力 1 2 3 4 postal_flg 0 3906 1 4400 Name: customer_id, dtype: int64
パタン2:aggメソッドのパタン 1 df_merge.groupby('postal_flg').agg({'customer_id':'nunique'})
出力 1 2 3 4 customer_id postal_flg 0 3906 1 4400
まとめ: 2値化の方法を学びました 本記事は、「【Python】2値化の方法を学ぶ | データサイエンス100本ノック【問52〜問53 回答】」というテーマでまとめました。
今回は、過去に学んだ事項の復習にもなったかと思います。
本記事で紹介した方法を元に、データサイエンティストとしての知見を深めていただければと思います。
なお、データサイエンティストに必要な知識は、TechAcademyのデータサイエンスコース での学習がおすすめです。