【解説】データサイエンス100本ノック【問79〜83 回答】

【解説】データサイエンス100本ノック【問79〜83 回答】

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


目次


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


>>データサイエンス100本ノックの始め方を確認する


第79〜83問目を解くために必要な基礎知識

問題を解く前に欠損値が存在する場合の補完方法に関する基礎をまとめておきます。

データフレームの中に欠損値が含まれるかもしれないとき、「確認」と「補完」または「削除」を行います。

「補完」には、様々な補完方法が存在します。

例えば、「平均値で補完」だったり「中央値で補完」といったものがあります。

繰り返しになりますが、問題を解いていく前に代表的な手法を簡単なデータフレームを用いて紹介したいと思います。

以下のような欠損値を含むデータフレームを用いて解説します。

1
2
3
4
5
6
import pandas as pd
import numpy as np
columns = ['product', 'price']
data = [['apple', np.nan], ['banana', 200], ['peach', 120], ['tomato', np.nan], [np.nan, 700]]
df = pd.DataFrame(data, columns=columns)
df
出力
1
2
3
4
5
6
	product	price
0 apple NaN
1 banana 200.0
2 peach 120.0
3 tomato NaN
4 NaN 700.0

欠損の確認方法

DataFrame, Seriesにおいて、各要素に対して欠損値を確認するには、isnullメソッドを使用します。

1
df.isnull()
出力
1
2
3
4
5
6
7

product price
0 False True
1 False False
2 False False
3 False True
4 True False

上記出力のように、欠損値が存在する箇所がTrueとなります。

欠損レコードの削除方法

欠損がある行や列を全部削除する場合は、dropnaメソッドを使用します。

1
df.dropna()
出力
1
2
3
	product	price
1 banana 200.0
2 peach 120.0

上記の出力の用にprice列において欠損があった行は、まるごと削除されていることがわかります。

これを、リストワイズ削除といいます。

欠損の補完方法

続いて補完の方法です。

欠損値にデータを補完する際はfillnaメソッドと主に使用します。

fillnaメソッドは、辞書型で引数を指定することで、カラムごとにデータを補完することができます。

ここでは「平均値」、「中央値」で補完する方法を紹介します。

平均値で補完する方法

欠損値が含まれている箇所を平均値で補完するには、numpy.nanmean関数を使用します。

ここでは、fillnaメソッドを用いてproductカラムにはmelonpriceには今のprice列に存在する価格である200, 120, 700の平均値を補完したいと思います

1
df.fillna({'product':'melon', 'price':np.nanmean(df['price'])})
出力
1
2
3
4
5
6
7

product price
0 apple 340.0
1 banana 200.0
2 peach 120.0
3 tomato 340.0
4 melon 700.0

上記のように、productにはmelon、そしてpriceには200, 120, 700の平均値である340が補完されています。

中央値で補完する方法

欠損値が含まれている箇所を中央値で補完するには、numpy.nanmedian関数を使用します。

こちらも同様にfillnaメソッドを用いてproductカラムにはmelon, priceには今のprice列に存在する価格である200, 120, 700の中央値を補完したいと思います

1
df
出力
1
2
3
4
5
6
	product	price
0 apple NaN
1 banana 200.0
2 peach 120.0
3 tomato NaN
4 NaN 700.0
1
df.fillna({'product':'melon', 'price':np.nanmedian(df['price'])})
出力
1
2
3
4
5
6
	product	price
0 apple 200.0
1 banana 200.0
2 peach 120.0
3 tomato 200.0
4 melon 700.0

上記のように、productにはmelon、そしてpriceには200, 120, 700の中央値である200が補完されています。

(応用)applyを用いた細かい数値処理と補完

本データフレームにおいて、たとえば欠損値であれば中央値を代入し、それ以外は30%値上げさせる処理をしたい場合を考えましょう。

やり方としては、applyメソッドとnumpy.isnan関数をを活用することで補完が可能です。

1
2
3
4
5


df_tmp = df.fillna({'product':'melon'})
df_tmp['price'] = df_tmp['price'].apply(lambda x : np.nanmedian(df['price']) if np.isnan(x) else x*1.3)
df_tmp
出力
1
2
3
4
5
6
	product	price
0 apple 200.0
1 banana 260.0
2 peach 156.0
3 tomato 200.0
4 melon 910.0

上記のように、productにはmelonが補完されています。その上でpriceカラムの欠損値には中央値、既に価格が設定されていた値段は3割増しになっています。

欠損値の確認と補完方法の基礎については以上です。

ここまでの知識をもとに問題を解いていきましょう。

 第79問目: 欠損列状況の確認

P-079: 商品データフレーム(df_product)の各項目に対し、欠損数を確認せよ。

DataFrame, Seriesでは、各要素に対して欠損値を確認するためには、isnullメソッドを使用します。

実際に本問を解きながら使い方を確認しましょう。

1
df_product.isnull().sum()
出力
1
2
3
4
5
6
7
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 7
unit_cost 7
dtype: int64

このように、欠損値を確認するためにはisnullメソッドを使用します。

その上で、各カラムに対して、合計を出す場合sumメソッドを使用することで、欠損値の判定とともに各項目ごとの数を出力できます。

第80問目: 欠損レコードの削除

P-080: 商品データフレーム(df_product)のいずれかの項目に欠損が発生しているレコードを全て削除した新たなdf_product_1を作成せよ。なお、削除前後の件数を表示させ、前設問で確認した件数だけ減少していることも確認すること。

データ欠損のある行や列を全て削除することを、「リストワイズ削除」といいます。

dropnaメソッドを用いることでNaNを含む全ての行を全て取り除けます。

本問を解きながらdropnaメソッドの使い方を学びましょう。

まずは、削除前の件数を出力します。

1
2
df_product_1 = df_product.copy()
print("削除前:", len(df_product_1))
1
削除前: 10030

欠損値を削除しデータの件数を出力します。

1
2
df_product_1.dropna(inplace=True)
print('削除語:', len(df_product_1))
出力
1
削除語: 10023

削除前が10030であるのに対して削除後は10023と件数が減少していることが確認できました。

第81問目: 数値補完(平均値)

P-081: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの平均値で補完した新たなdf_product_2を作成せよ。なお、平均値について1円未満は四捨五入とし、0.5については偶数寄せでかまわない。補完実施後、各項目について欠損が生じていないことも確認すること。

欠損値NaNにデータを保管するにはfillnaメソッドを使用します。

引数は辞書型で指定し、カラムごとに代替データを変更することも可能です。

問題を解きながら使い方を学んでいきましょう。

早速平均値で欠損値を補完します。

なお、欠損値が含まれている状態で平均値を求める場合は、numpy.nenmean関数を使用します。

参考: https://note.nkmk.me/python-numpy-nansum/

四捨五入はnumpy.round関数で行います。SeriesデータにNaNが存在する場合、通常のround関数を用いるとエラーになるので注意しましょう。

参考: https://omathin.com/100knock-66-68/

1
df_product.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	product_cd	category_major_cd	category_medium_cd	category_small_cd	unit_price	unit_cost
0 P040101001 04 0401 040101 198.0 149.0
1 P040101002 04 0401 040101 218.0 164.0
2 P040101003 04 0401 040101 230.0 173.0
3 P040101004 04 0401 040101 248.0 186.0
4 P040101005 04 0401 040101 268.0 201.0
5 P040101006 04 0401 040101 298.0 224.0
6 P040101007 04 0401 040101 338.0 254.0
7 P040101008 04 0401 040101 420.0 315.0
8 P040101009 04 0401 040101 498.0 374.0
9 P040101010 04 0401 040101 580.0 435.0
1
2
df_product_2 = df_product.fillna({'unit_price':np.round(np.nanmean(df_product['unit_price'])), 'unit_cost':np.round(np.nanmean(df_product['unit_cost']))})
df_product_2.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	product_cd	category_major_cd	category_medium_cd	category_small_cd	unit_price	unit_cost
0 P040101001 04 0401 040101 198.0 149.0
1 P040101002 04 0401 040101 218.0 164.0
2 P040101003 04 0401 040101 230.0 173.0
3 P040101004 04 0401 040101 248.0 186.0
4 P040101005 04 0401 040101 268.0 201.0
5 P040101006 04 0401 040101 298.0 224.0
6 P040101007 04 0401 040101 338.0 254.0
7 P040101008 04 0401 040101 420.0 315.0
8 P040101009 04 0401 040101 498.0 374.0
9 P040101010 04 0401 040101 580.0 435.0

各項目について欠損が生じていないことを確認します。

1
df_product_2.isnull().sum()
出力
1
2
3
4
5
6
7
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 0
unit_cost 0
dtype: int64

欠損値が存在していないのでOKです。

第82問目:数値補完(中央値)

P-082: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの中央値で補完した新たなdf_product_3を作成せよ。なお、中央値について1円未満は四捨五入とし、0.5については偶数寄せでかまわない。補完実施後、各項目について欠損が生じていないことも確認すること。

この問題も同様に、欠損値の補完なのでfillnaメソッドを使用します。

今回の問題では中央値を使用します。

データに欠損値が含まれた状態で中央値を求める場合は、numpy.nanmedian関数を使用します。

中央値で欠損値を補完します。

1
2
df_product_3 = df_product.fillna({'unit_price': np.round(np.nanmedian(df_product['unit_price'])), 'unit_cost': np.round(np.nanmedian(df_product['unit_cost']))})
df_product_3.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	product_cd	category_major_cd	category_medium_cd	category_small_cd	unit_price	unit_cost
0 P040101001 04 0401 040101 198.0 149.0
1 P040101002 04 0401 040101 218.0 164.0
2 P040101003 04 0401 040101 230.0 173.0
3 P040101004 04 0401 040101 248.0 186.0
4 P040101005 04 0401 040101 268.0 201.0
5 P040101006 04 0401 040101 298.0 224.0
6 P040101007 04 0401 040101 338.0 254.0
7 P040101008 04 0401 040101 420.0 315.0
8 P040101009 04 0401 040101 498.0 374.0
9 P040101010 04 0401 040101 580.0 435.0

補完が完了したので、各項目について欠損が生じていないことを確認します。

1
df_product_3.isnull().sum()
出力
1
2
3
4
5
6
7
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 0
unit_cost 0
dtype: int64

欠損値が存在していないことが確認できたのでOKです。

第83問目: 数値補完(カテゴリごとの中央値)

P-083: 単価(unit_price)と原価(unit_cost)の欠損値について、各商品の小区分(category_small_cd)ごとに算出した中央値で補完した新たなdf_product_4を作成せよ。なお、中央値について1円未満は四捨五入とし、0.5については偶数寄せでかまわない。補完実施後、各項目について欠損が生じていないことも確認すること。

まずは、df_productの構造を確認します。

1
df_product.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	product_cd	category_major_cd	category_medium_cd	category_small_cd	unit_price	unit_cost
0 P040101001 04 0401 040101 198.0 149.0
1 P040101002 04 0401 040101 218.0 164.0
2 P040101003 04 0401 040101 230.0 173.0
3 P040101004 04 0401 040101 248.0 186.0
4 P040101005 04 0401 040101 268.0 201.0
5 P040101006 04 0401 040101 298.0 224.0
6 P040101007 04 0401 040101 338.0 254.0
7 P040101008 04 0401 040101 420.0 315.0
8 P040101009 04 0401 040101 498.0 374.0
9 P040101010 04 0401 040101 580.0 435.0

欠損値の有無を確認します。

1
df_product.isnull().sum()
出力
1
2
3
4
5
6
7
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 7
unit_cost 7
dtype: int64

unit_priceとunit_costに欠損があることがわかりました。

まずは問題文の通り各商品の小区分(category_cd)ごとにunit_priceとunit_costの中央値を算出した磯思います。

1
2
df_tmp = df_product.groupby('category_small_cd').agg({'unit_price':'median', 'unit_cost':'median'}).reset_index()
df_tmp.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	category_small_cd	unit_price	unit_cost
0 040101 283.0 212.5
1 040102 378.0 284.0
2 040201 223.0 167.5
3 040202 178.0 134.0
4 040203 308.0 231.0
5 040204 198.0 149.0
6 040301 278.0 209.0
7 040401 288.0 216.5
8 040402 228.0 171.0
9 040403 248.0 186.0

各商品の小区分ごとにunit_priceとunit_costの中央値を算出することができました。

カラム名ががunit_price, unit_costとなっていますが、これでは各商品の小区分ごとの中央値であることが分か理にくいので、カラム名を変更します。

1
2
df_tmp.columns = ['category_small_cd', 'median_price', 'median_cost']
df_tmp.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	category_small_cd	median_price	median_cost
0 040101 283.0 212.5
1 040102 378.0 284.0
2 040201 223.0 167.5
3 040202 178.0 134.0
4 040203 308.0 231.0
5 040204 198.0 149.0
6 040301 278.0 209.0
7 040401 288.0 216.5
8 040402 228.0 171.0
9 040403 248.0 186.0

それではdf_productとdf_tmpを内部結合していきます。

内部結合はmergeメソッドで可能です。

1
2
df_product_4 = pd.merge(df_product, df_tmp, how='inner', on='category_small_cd')
df_product_4.head(10)
出力
1
2
3
4
5
6
7
8
9
10
11
	product_cd	category_major_cd	category_medium_cd	category_small_cd	unit_price	unit_cost	median_price	median_cost
0 P040101001 04 0401 040101 198.0 149.0 283.0 212.5
1 P040101002 04 0401 040101 218.0 164.0 283.0 212.5
2 P040101003 04 0401 040101 230.0 173.0 283.0 212.5
3 P040101004 04 0401 040101 248.0 186.0 283.0 212.5
4 P040101005 04 0401 040101 268.0 201.0 283.0 212.5
5 P040101006 04 0401 040101 298.0 224.0 283.0 212.5
6 P040101007 04 0401 040101 338.0 254.0 283.0 212.5
7 P040101008 04 0401 040101 420.0 315.0 283.0 212.5
8 P040101009 04 0401 040101 498.0 374.0 283.0 212.5
9 P040101010 04 0401 040101 580.0 435.0 283.0 212.5

df_product_4の欠損値の有無を確認します。

1
df_product_4.isnull().sum()
出力
1
2
3
4
5
6
7
8
9
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 7
unit_cost 7
median_price 0
median_cost 0
dtype: int64

unit_priceとunit_costに欠損値があることが確認できます。

この欠損値に対して各商品の小区分ごとに算出した中央値で補完をします。

欠損値がある場合のみ中央値を適用したいので、applyメソッドとlambda式を用いて補完していきます。

1
2
3
4
5
6
7
8
# unit_price(下記コードではx[0]がunit_price)に欠損値があった場合は、median_price(下記コードではx[1]がmedian_price)を適用し、そうでない場合は。unit_priceそのまま
# axis=1にすることで行方向への処理にさせる。
df_product_4['unit_price'] = df_product_4[['unit_price', 'median_price']].apply(lambda x: np.round(x[1]) if np.isnan(x[0]) else x[0], axis=1)
# unit_cost(下記コードではx[0]がunit_cost)に欠損値があった場合は、median_cost(下記コードではx[1]がmedian_cost)を適用し、そうでない場合は。unit_costそのまま
# axis=1にすることで行方向への処理にさせる。
df_product_4['unit_cost'] = df_product_4[['unit_cost', 'median_cost']].apply(lambda x : np.round(x[1]) if np.isnan(x[0]) else x[0], axis=1)

df_product_4.isnull().sum()
出力
1
2
3
4
5
6
7
8
9
product_cd            0
category_major_cd 0
category_medium_cd 0
category_small_cd 0
unit_price 0
unit_cost 0
median_price 0
median_cost 0
dtype: int64

まとめ:データサイエンス100本ノック【問79〜83 回答】で欠陥値の扱いを学びました

本記事で紹介した方法を元にデータサイエンティストとしての知見を深めていただければと思います。

なお、データサイエンティストに必要な知識は、TechAcademyのデータサイエンスコースでの学習がおすすめです。

コメント