Goodな生活

経済学修士→環境コンサル→データサイエンス

『Python実践データ分析100本ノック』(第1-5,10章)とりあえず2周やったので調べたこと全部書く

「とりあえずPython勉強しないとやばい」という焦りと勢いに任せ、100本ノック(正確には60本ノック)やりました。
第1,2章のデータ加工、第3-5章の機械学習、第10章の言語処理です(最適化と画像認識のところはおいおい)とりあえず2周やったので、調べたことメモします。

どういう風にやったかというと、

  • 説明文を読み、何も見ずにコードを書いてみる(たぶん全体の5%ぐらいしかできず)
  • 説明文を読み、ググったり参考書見ながらやってみる
  • 写経して何やってるか後追いで調べる
  • 写経しても分からないからスキップする(2周目にやる)

って感じです。

一応この本の題名「実践データ分析」にある通り、クラスタリングや決定木といった手法も扱います。しかし理論的なことはほとんど書かれていません。逆に言うと、理論はさておきデータ分析やるなら前処理が大事なんだよ/strong>ということが身をもって分かります。

それではメモです。所要時間はだいたい1章につき1周目は3,4時間、2周目は1時間ぐらいかかりました。

ノック1:カレントディレクトリの指定

まずこれをやらないとデータの読み込みができません。

#カレントディレクトリの指定
%cd C:\\Users
#カレントディレクトリの表示
%pwd

ノック10:データの可視化(.plot)

ピボットテーブルの形でグラフ用のデータを作るまではOK.

#グラフ用データ作成
graph_data = pd.pivot_table(join_data, index='payment_month', columns = 'item_name',values=['price'], aggfunc = 'sum')

ここであえてgraph_dataのインデックスを取得し、リスト化する意味が分からなかった。単純に.plotでオプション指定すればよいのではないか。

#グラフ用描画
graph_data.plot(grid=True, legend = True)

ノック15:Seriesを用いたデータの参照(.loc)

for文の中の.locの使い方が勉強になった。

#売上データのうち、価格が欠損値になっているデータを抽出
flg_is_null = uriage_data['item_price'].isnull()
flg_is_null.head()
0    False
1     True
2     True
3    False
4     True
Name: item_price, dtype: bool

flg_is_nullにはインデックスとFalse/Trueのブール値が含まれる。

#flg_is_null を売上データに渡し、価格が欠損値となっている商品名を抽出
uriage_data.loc[flg_is_null,'item_name']
1       商品S
2       商品A
4       商品A
6       商品A
14      商品A
       ... 
2987    商品K
2990    商品O
2992    商品C
2996    商品Q
2997    商品H
Name: item_name, Length: 387, dtype: object

すると、flg_is_nullでTrueとなったインデックスと同じ行のデータだけ引っ張ってこれる。どうやら作者はこの文法が好みらしい。他にも似たような書き方が出てくる。一旦flg_is_serialというインデックス付きのブール値が格納されたseriesを作成し、 locでこのseriesを参照する。するとTrueの箇所のインデックスだけを拾ってくる、という仕組み。

このflg_is_nullが条件式の意味なんだと理解した(違うかもしれない)。あるデータフレームdfの列column1が「column1 == condition」という条件を満たすときにcolumn2 の列を取り出す場合は、
df.loc[df['column'] == 'condition', 'column']となる。

loc。。。まだ使いこなせない

ノック17:.to_datetime

本来日付型として扱うべきデータが数値型になっているので直す問題。模範解答みたいにまどろっこしいことをせずとも、.to_datetimeを使えばよいのではないか。

kokyaku['登録日'] = pd.to_datetime(kokyaku['登録日'],format='%Y/%m/%d')
kokyaku['登録年月'] = kokyaku['登録日'].dt.strftime("%Y%m")

いずれにせよExcel, csvの段階で直せるならそっちで直した方がよい気がする。

ノック15と同じくここでもlocが大活躍。中に条件式を入れても使える。

fromString = pd.to_datetime(kokyaku_data.loc[flg_is_serial==False,'登録日'])

ノック23:カテゴリーごとの集計(.value_counts)

模範解答のように.groupbyで集計しても問題ない。.value_countsを使ってもカウントや割合を出力できる。

customer_join['class_name'].value_counts()
オールタイム    2045
ナイト       1128
デイタイム     1019
Name: class_name, dtype: int64
customer_join['class_name'].value_counts(normalize = True)
オールタイム    0.487834
ナイト       0.269084
デイタイム     0.243082
Name: class_name, dtype: float64

ノック23:.to_datetimeで使うデータ型

なんで.to_datetimeに文字列を入れるのか分からなかった。

customer_start = customer_join.loc[customer_join['start_date'] > pd.to_datetime("20180401")]

以下のページを参照して、とりあえずデータ型を揃えるものだと理解。
deepage.net

ノック26:条件指定(.where)(.mask)

模範解答だとwhereで条件しているが、maskを使っても可能。等号の向きが変わるのに注意。

#whereを用いる場合
uselog_weekday_max['routine_flg'] = 0
uselog_weekday_max['routine_flg'] = uselog_weekday_max['routine_flg'].where(uselog_weekday_max['count'] < 4,1)
#maskを用いる場合
uselog_weekday_max['routine_flg'] = 0
uselog_weekday_max['routine_flg'] = uselog_weekday_max['routine_flg'].mask(uselog_weekday_max['count'] >= 4,1)

ノック28:日付情報の計算(relativedelta)

relativedeltaを使う以外にも.TimedeltaやDateOffsetというものが使えるらしい。これは後々やろう。
sinhrks.hatenablog.com

ノック32:KMeans(random_state)

random_stateの初期値はランダムに設定される。結果の再現性を確保するため、random_state=0を設定する。

ノック34:PCA(n_components)

n_components = 次元の数、元の特徴量の空間(次元)よりも大きくなることはない。

ノック35:クロス集計(.crosstab)

クラスタリング結果を元に、groupbyで着実に集計した方がミスがない気もするが、クロス集計だけなら簡単に済ませられる。何より結果が見やすい。

#クラスターと退会有無のクロス集計
pd.crosstab(index=customer_clustering['cluster'], columns = customer_join['routine_flg'])
is_deleted	0	1
cluster		
0	821	19
1	1231	18
2	0	771
3	790	542
#クラスターとルーティーン有無のクロス集計
pd.crosstab(index=customer_clustering['cluster'], columns = customer_join['is_deleted'])
routine_flg	0	1
cluster		
0	52	788
1	2	1247
2	499	272
3	226	1106

ノック42:模範解答の誤り(.to_datetime)

たいした誤りではないです(でも解決するまでに2時間ぐらいかかった気がする)。
for文の中でexit_cstomer['exit_date’]のデータ型をdatetimeに変える1行を追加します。

for i in range(len(exit_customer)):
    exit_customer["exit_date"].iloc[i] = exit_customer["end_date"].iloc[i] - relativedelta(months=1)
#この1行を追加↓exit_customer['exit_date']をあらかじめpd型にしておく
exit_customer["exit_date"] = pd.to_datetime(exit_customer["exit_date"])
exit_customer["年月"] = exit_customer["exit_date"].dt.strftime("%Y%m")
uselog["年月"] = uselog["年月"].astype(str)
exit_uselog = pd.merge(uselog, exit_customer, on=["customer_id", "年月"], how="left")
print(len(uselog))
exit_uselog.head()

他の方の方法も試しました。こちらでは何故かうまくいきませんでした。
teratail.com

ノック46:ダミー変数化(.get_dummies)(.factorize)

素直にget_dummies使えばよい。別解としてカテゴリ変数を数値に変換する.factorizeも使える。これはカテゴリ変数が1列だけの場合だと書きやすいのか。いまいちどういう場面で有用か分からないけど参考まで。

ノック94:Mecabのインストール

コードを書くというよりMeCabのインストールが大変だった。
やった作業としては、

  • Pythonの最新版(3.9.6)のダウンロード
  • コマンドプロンプトでpythonを起動できることを確認
  • MeCabのダウンロード(mecab-0.996.exe, mecab-0.996-64.exe)
  • 環境変数の編集(Path, 編集, 新規, インストール先のパスを指定)

公式のMeCabだとうまく起動せず、野良ファイルだとうまくいった。なんでだろう。

ノック94:if文のインデント

ifの後にインデント空白があるとエラーになる。for文の中では for文よりも下げたインデントで始まり、そのレベルのインデントが続く限り、それはfor文がまわす対象範囲として認識される。 一見同じ空白に見えても片方はtab,もう片方はspaceでインデントされたいる場合もエラーになることもある。

pen.envr.tsukuba.ac.jp

ノック97:リストの拡張(.append)(.extend)

  • .append(): 引数に渡された値をリストの末尾に渡す
  • .extend() :引数に渡された別のリストを追加してリストを拡張する

qiita.com

参考書としてこちらの本も使いました。

やることは莫大にある。継続が大事。