イシューからはじめるデータ分析②データ加工編

# はじめに
こちら[イシューからはじめるデータ分析①イシュー編]()の続きとなります。
イシューからはじめるとは全く関係ないため**読み飛ばして頂いて構いません！**
**リーク**などに気をつけながらデータを加工していきます。

# 全体の流れ
2019年1月31日までを学習データ。2019年2月1日以降をテストデータとします。
そのため**2019年2月に退会申請するかどうか**を予測することを目的とし、データを加工していきます。
1. 顧客データを加工する
2. ログデータを加工する
3. コホート分析

# データ加工
### 使用ライブラリ
```py
# standard
import datetime as dt
from dateutil.relativedelta import relativedelta

# third-party
import numpy as np
import pandas as pd
pd.set_option("max_rows", 500, "max_colwidth", 500, "max_columns", 500)
pd.options.plotting.backend = "plotly"
```
### 顧客データを加工する
まず顧客データから読み込んでいきます。

```py
customer = pd.read_csv("./customer_master.csv", parse_dates=["start_date", "end_date"])
# 退会を決めた月は退会前月の末日とする
customer["decide_end_date"] = customer.end_date.apply(lambda d: d.replace(day=1) - relativedelta(days=1) if not pd.isna(d) else d)
print("df_shape", customer.shape)
customer.head()
```
![スクリーンショット 2021-06-17 8.26.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/a6385225-8b57-cc7f-ffb6-994b053e1201.png)
日付のフォーマットが整っていて助かりました(笑)
ここで気になるのが2015年5月1日に入会して、**未だに辞めていない**人がいることですね。調べてみましょう。

```py
# 2015年内に入会したユーザー
customers_2015 = customer.query("start_date < '2016-01-01'")
print(customers_2015.end_date.isnull().sum())
print(customers_2015.end_date.notnull().sum())
```

> 継続中 492
退会済み 0

調べてみると2015年に入会したユーザーは1人も退会していないようですね(驚き)
[Prtimes: ON DIARYシステム](https://prtimes.jp/main/html/rd/p/000000004.000035967.html)によると一般的なフィットネスクラブでは、
入会から半年で**約70%**が退会、1年後には**約90%**が退会するそうです。
つまり2015年のデータは**欠損**(入力忘れ等)もしくはデータが**人工的**に作られている可能性が高いですね。
他の年も調べてみましょう。

```py
# 年ごとに退会割合計算
customer["start_year"] = customer.start_date.dt.year
customer_year =  customer.groupby("start_year")[["is_deleted"]].apply(lambda group: group.sum() / group.count() *100) 
# 年ごとの退会率を棒グラフに
fig = customer_year.plot.bar(title="入会年ごとの退会率")
fig.update_xaxes(title="入会年")
fig.update_yaxes(ticksuffix="%", title="退会率")

```
![退会プロット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/4ddf07e4-c49b-0af9-6832-4100164fbbcf.png)
2015年と2016年のデータが怪しいですね。。
(秀和システムさんに「このデータは実際のジムのデータでしょうか？」「入力漏れなどはありますか？」とお問い合わせしたところ返信が返って来ませんでした泣。そりゃそうですよね。。)
今回は真偽が分からないため、**2017年1月1日以降**に入会したユーザーを分析対象とし加工してきます。

```py
# 2017年以降の顧客を分析する
customer = customer.query("start_year >= 2017")
# キャンペーンやクラスの情報を結合する
customer = customer.merge(pd.read_csv("./campaign_master.csv"), how="left", on="campaign_id")
customer = customer.merge(pd.read_csv("./class_master.csv"), how="left", on="class")
# 使わない列を消す
customer.drop(columns=["class", "campaign_id", "is_deleted", "start_year"], inplace=True)
customer.head()
```
![スクリーンショット 2021-06-17 8.43.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/0e4e6b48-6eb4-252e-5633-1ae0762b7009.png)
最後にリークしないよう2019年2月1日以降に知るの情報を**MASK**し、学習データとします。

```py
# 2019年2月1日以降の知る情報をMASK
customer.loc[customer.decide_end_date >= "2019-02-01", ["end_date", "decide_end_date"]] = np.nan
# 学習データとして書き出し
customer.query("start_date < '2019-02-01'").to_csv("customer_train.csv", index=None)
# 読み込めるか確認
customer = pd.read_csv("./customer_train.csv", parse_dates=["start_date", "end_date", "decide_end_date"])
customer.head()
```

### ログデータを加工する
ログデータを読み込んでいきます。

```py
logs = pd.read_csv("./use_log.csv", parse_dates=["usedate"])
logs.rename(columns={"usedate": "use_date"}, inplace=True)
# 月の初日を抽出
logs["use_month"] = logs.use_date.apply(lambda d: d.replace(day=1))
print(logs.shape)
logs.head()
```
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/4926b2b9-c5fd-7e9c-2bc0-a0431e31a6b2.png">
先ほど分析対象として指定したユーザー以外のログをドロップします。

```py
# 分析対象以外のユーザーログをドロップする
logs = logs.loc[logs.customer_id.isin(customer.customer_id.unique())]
print("logs_shape", logs.shape)
```
> logs_shape (125864, 4)

30%ほどログデータがドロップされました。
最後に2019年1月31日以前を学習データとして書き出します。

```py
logs.query("use_date < '2019-02-01'").to_csv("./logs_train.csv", index=None)
# 読み込めるか確認
logs = pd.read_csv("./logs_train.csv", parse_dates=["use_date", "use_month"])
```
### コホート分析
なんで急に？といった感じですが、
「もしかしたらデータが人工的に作られているのでは？」という仮設を検証していきます。
まずログデータを月毎にまとめ、顧客データと結合していきます。

```py
# あとで会員期間を計算するために使う
def calc_period(s: pd.Series):
    return (s.use_month.year - s.start_month.year) * 12 + (s.use_month.month - s.start_month.month) + 1

monthly_logs = logs.groupby(["customer_id", "use_month"]).log_id.count().reset_index()
monthly_logs = monthly_logs.merge(customer[["customer_id", "start_date"]], how="left", on="customer_id")
monthly_logs["start_month"] = monthly_logs.start_date.apply(lambda d: d.replace(day=1))
monthly_logs["period"] = monthly_logs.apply(calc_period, axis=1)
monthly_logs.head()
```
![sc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/a34f7858-b5d8-d294-3c38-165c5a5e1c6d.png)
2018年4月1日からのログデータしかないため、それ以降に入会した顧客を対象にします。

```py
cohort = monthly_logs.query("start_month >= '2018-04-01'")
cohort = cohort.pivot_table(index="start_month", columns="period", values="customer_id", aggfunc="count")
# 継続率を計算
cohort = cohort.divide(cohort.max(axis=1), axis=0).round(3) * 100
cohort
```
![sc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/f4ee3e9c-3f16-b3bc-e281-eac50434a746.png)
最後にヒートマップで可視化します。

```py
fig = px.imshow(cohort)
fig.update_xaxes(title="会員期間")
fig.update_yaxes(title="入会月")
```
![newplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225073/50a2da1f-d02c-c688-87ba-edecbef94a21.png)
入会月に関係なく**綺麗に**継続率が下がっていますね。
「似たような継続率になるようにデータが作られたのか？」
「入会月に関係なく共通した要因から顧客が退会しているのか？」
などいろいろ考えられますが、
データが**人工的**に作られている可能性が濃厚そうです。。
ただ断定はできないため、頭の片隅に入れて分析していきます。

# まとめ
今回は分析する前のデータ加工を行なっていきました。
次回から加工した学習データを用いて分析をしていきます。
続き: [イシューからはじめるデータ分析③データ分析編]()