当ページのリンクには広告が含まれています。
 
目次  
この記事の対象者 
・ データサイエンティストを目指している人 
・ 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のデータサイエンスコース での学習がおすすめです。