警告を出さない「データ分析100本ノック」の回答【5章】

[前回](https://qiita.com/mickie895/items/c97aef07d73cffb8ecb1)に引き続き、データ分析100本ノックの別解を作っていく。

前回で思い知ったのと、今回も本文には言及のない邪悪なコード（```warnings.filterwarnings('ignore')```）がサンプルコードの頭に仕込まれているため本文のコードは一切信用せず作っていく。

# ノック41

いつも通り各行をfor文で処理しているので、pandasのAPIを使ってfor文を消そう。こんな感じに。
なおノック42で参照したい列を引き継がせるために1列だけ書籍のデータより増えている。

```py
year_months_source = uselog_months["年月"].unique()
year_month_target = pd.DataFrame(data={"年月":year_months_source[1:], "先月":year_months_source[:-1]})

uselog = uselog_months\
.join(year_month_target.set_index("年月"), on="年月", how='inner')\
.merge(uselog_months[["年月", "count", "customer_id"]], left_on=["先月", "customer_id"], right_on=["年月", "customer_id"], suffixes=('', '_1'), how='left')\
.drop(axis='columns', labels=['年月_1'])\
.rename(columns={"count": "count_0"})

uselog.head()
```

# ノック42

ここもメソッドチェーンを使って、わかりやすくしていこう。
結合方法を正しく定めておけば「手動で外部結合してから欠損が含まれているデータを削除する」とか言う穴をほってから穴を埋める作業が不要になるのもポイント。

```py
exit_customer = customer.loc[customer["is_deleted"] == 1]
exit_customer = exit_customer.assign(退会月=exit_customer.apply(lambda x: int(x.end_date[:4]+x.end_date[5:7]), axis=1))

# 今回の最終的に表示される「年月」は退会を決めた年月で、退会した月の前の月
exit_uselog = pd.merge(year_month_target, exit_customer, left_on=["年月"], right_on=["退会月"], how='inner')\
.drop(axis='columns', labels="年月")\
.rename(columns={"先月": "年月"})\
.merge(uselog, on=['年月', 'customer_id'], how='inner')\
.drop(columns=["退会月", "先月"])

print(len(exit_uselog))

exit_uselog.head()
```

# ノック43

ノック42と同様に最初から内部結合させておけばデータの欠落は出ない。
ただ、入会直後で先月のデータが欠落している会員も若干いるため、
本のデータより正確になると考えてそこも取り除いておく。
（本でも別のタイミングで行っている。）

とは言っても```uselog```を```uselog.dropna()```に変える程度なのでどちらも大した変更ではないから、ここでのサンプルは省略する。

# ノック44

前回と同様に、各行の処理を関数にして処理しよう。
前回の記事を作っているあたりでどこらへんが無駄なデータになっているかのあたりがついてきたため、コード量も減ってきた。

```py
from dateutil.relativedelta import relativedelta
from datetime import datetime


def calc_period(x):
    end_date = datetime(year=x.年月 // 100, month=x.年月 % 100, day=28)
    start_date = datetime.strptime(x.start_date, "%Y-%m-%d")
    return relativedelta(end_date, start_date).months


predict_data = predict_data.assign(
    period=predict_data[["年月", "start_date"]].apply(calc_period, axis=1)
)

predict_data.head()
```

# ノック46

ダミー変数って排他関係の場合、一つの変数に複数個入れたら駄目なのかな。例えば職業のとき、会社員は1,主婦は2,学生は3のように。
今APIに無いということは精度が上がらないなどの原因で別に無くてもいいなという結論になったんだろうな。

というわけでツッコミどころは一箇所。
特に複数行を一気に消去する場合、Pythonのdel命令よりdrop関数のほうがパフォーマンス上有利なようだ。そうじゃなくてもより柔軟に使えるのでdropを使うものだと覚えよう。
ノック46のコード全般はこんな感じになる。

なおget_dummiesでdtypeの指定を行うさい、最初の指定(デフォルト：bool)から変わらないという症状が起きた。
多分ダミー行がすでに存在しているとかでテーブルの更新が起きなかったから、とかだと思われる。

```py
target_col = ["campaign_name", "class_name", "gender", "count_1", "routine_flg", "period", "is_deleted"]
predict_values = predict_data[target_col]
predict_values.head()
# セルの区切り
predict_values = pd.get_dummies(predict_values, dtype=int)
print(predict_values.dtypes)
predict_values.head()
# セルの区切り
predict_values.drop(columns=["campaign_name_通常","class_name_ナイト","gender_M"], inplace=True)
predict_values.head()
```

コードの質とかとは関係ないが、「オールタイム」を「デイタイム」かつ「ナイトタイム」に変換すると、「夜に来る客」と「昼に来る客」でクラスタリングできるから最終的な結果が変わってくるのかな、とはちょっと思ってる。

# 5章全体を通じて

今回の章は前回と同様に、APIの使い方がおかしい部分がメインだった。
今後も自分でコードを書きながら動作の確認を取るが、よっぽど新しいネタが出ないと記事の形でまとめることは無いと思う。