重回帰分析で費用対効果を予測する。
さて今回のテーマは重回帰分析です。
データは広告費(テレビCMと雑誌)とアプリのインストール数のcsvファイルです。
確認してみます。
ad_result_df = pd.read_csv('ad_result.csv')
ad_result_df
month | tvcm | magazine | install | |
---|---|---|---|---|
0 | 2013-01 | 6358 | 5955 | 53948 |
1 | 2013-02 | 8176 | 6069 | 57300 |
2 | 2013-03 | 6853 | 5862 | 52057 |
3 | 2013-04 | 5271 | 5247 | 44044 |
4 | 2013-05 | 6473 | 6365 | 54063 |
5 | 2013-06 | 7682 | 6555 | 58097 |
6 | 2013-07 | 5666 | 5546 | 47407 |
7 | 2013-08 | 6659 | 6066 | 53333 |
8 | 2013-09 | 6066 | 5646 | 49918 |
9 | 2013-10 | 10090 | 6545 | 59963 |
何にもわからないので、それぞれの散布図を描いてみます。
custom_style = {'axes.labelcolor': 'white', 'xtick.color': 'black', 'ytick.color': 'black'} sns.set_style("darkgrid", rc=custom_style) sns.jointplot('tvcm', 'install', data=ad_result_df)
custom_style = {'axes.labelcolor': 'white', 'xtick.color': 'black', 'ytick.color': 'black'} sns.set_style("darkgrid", rc=custom_style) sns.jointplot('magazine', 'install', data=ad_result_df)
custom_styleはジュピターで表示するとき用のコードなので気にしにないでください。
さて二つの散布図を見ると何やら相関がありそうですね。
回帰直線を当てはめてプロットしてみます。
custom_style = {'axes.labelcolor': 'white', 'xtick.color': 'black', 'ytick.color': 'black'} sns.set_style("darkgrid", rc=custom_style) sns.regplot('tvcm', 'install', data=ad_result_df)
custom_style = {'axes.labelcolor': 'white', 'xtick.color': 'black', 'ytick.color': 'black'} sns.set_style("darkgrid", rc=custom_style) sns.regplot('magazine', 'install', data=ad_result_df)
雑誌の方が傾きが大きいですね。
さて、では重回帰分析してみます。
from sklearn import linear_model
model = linear_model.LinearRegression() x_multi = ad_result_df.drop(['install','month'], axis=1) y_target = ad_result_df.install model.fit(x_multi, y_target)
model.coef_
array([ 1.3609213 , 7.24980915])
model.intercept_
188.17427483039501
TVCMの係数は1.36、雑誌広告の係数は7.25と、やはり雑誌広告の方が大きい値になっていますね。
.score関数でR2を出すことが出来るようなのでやってみます。
model.score(x_multi, y_target)
0.93790143010444693
まあまあの値ではないでしょうか。
ちなみにstatsmodelsを使うとp値なんかも出て来ます。
import statsmodels.formula.api as sm
models = sm.OLS(y_target, x_multi) results = models.fit() results.summary()
Dep. Variable: | install | R-squared: | 1.000 |
---|---|---|---|
Model: | OLS | Adj. R-squared: | 0.999 |
Method: | Least Squares | F-statistic: | 8403. |
Date: | Sun, 18 Feb 2018 | Prob (F-statistic): | 5.12e-14 |
Time: | 15:54:45 | Log-Likelihood: | -84.758 |
No. Observations: | 10 | AIC: | 173.5 |
Df Residuals: | 8 | BIC: | 174.1 |
Df Model: | 2 | ||
Covariance Type: | nonrobust |
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
tvcm | 1.3540 | 0.405 | 3.347 | 0.010 | 0.421 | 2.287 |
magazine | 7.2892 | 0.476 | 15.320 | 0.000 | 6.192 | 8.386 |
Omnibus: | 1.009 | Durbin-Watson: | 0.876 |
---|---|---|---|
Prob(Omnibus): | 0.604 | Jarque-Bera (JB): | 0.804 |
Skew: | 0.539 | Prob(JB): | 0.669 |
Kurtosis: | 2.123 | Cond. No. | 14.0 |
さて、最後に予測をしてみます。
本にはTVCMは4200万、雑誌広告は7500万で予測すると書いてあるので、その数字を入れてみます。
x_pre = [4200, 7500] x = np.reshape(x_pre, (1, -1)) model.predict(x)
array([ 60277.61237361])
60278人がインストールすると予測できました。
A/Bテストの結果を解析する
今回はA/Bテストの結果を解析していきます。
与えられたデータは2つ。
imp_df = pd.read_csv('section5-ab_test_imp.csv') goal_df = pd.read_csv('section5-ab_test_goal.csv')
imp_df.head()
log_date | app_name | test_name | test_case | user_id | transaction_id | |
---|---|---|---|---|---|---|
0 | 2013-10-01 | game-01 | sales_test | B | 36703 | 25622 |
1 | 2013-10-01 | game-01 | sales_test | A | 44339 | 25623 |
2 | 2013-10-01 | game-01 | sales_test | B | 32087 | 25624 |
3 | 2013-10-01 | game-01 | sales_test | B | 10160 | 25625 |
4 | 2013-10-01 | game-01 | sales_test | B | 46113 | 25626 |
goal_df.head()
log_date | app_name | test_name | test_case | user_id | transaction_id | |
---|---|---|---|---|---|---|
0 | 2013-10-01 | game-01 | sales_test | B | 15021 | 25638 |
1 | 2013-10-01 | game-01 | sales_test | B | 351 | 25704 |
2 | 2013-10-01 | game-01 | sales_test | B | 8276 | 25739 |
3 | 2013-10-01 | game-01 | sales_test | B | 1230 | 25742 |
4 | 2013-10-01 | game-01 | sales_test | B | 17471 | 25743 |
いつも通り結合していきます。
all_df = pd.merge(imp_df, goal_df, on='transaction_id', how='left')
all_df = all_df.drop(['app_name_x', 'test_name_x', 'log_date_y', 'app_name_y', 'test_name_y', 'test_case_y'], axis=1)
all_df.columns = ['log_date_x', 'test_case_x', 'user_id_x', 'transaction_id', 'result']
all_df = all_df.fillna(0)
check=[] for k in range(len(all_df['result'])): if all_df.result[k] == 0 : check.append(0) else: check.append(1) all_df['results'] = check
all_df.head(50)
log_date_x | test_case_x | user_id_x | transaction_id | result | results | |
---|---|---|---|---|---|---|
0 | 2013-10-01 | B | 36703 | 25622 | 0.0 | 0 |
1 | 2013-10-01 | A | 44339 | 25623 | 0.0 | 0 |
2 | 2013-10-01 | B | 32087 | 25624 | 0.0 | 0 |
3 | 2013-10-01 | B | 10160 | 25625 | 0.0 | 0 |
4 | 2013-10-01 | B | 46113 | 25626 | 0.0 | 0 |
5 | 2013-10-01 | A | 6605 | 25627 | 0.0 | 0 |
6 | 2013-10-01 | A | 346 | 25628 | 0.0 | 0 |
7 | 2013-10-01 | A | 42710 | 25629 | 0.0 | 0 |
8 | 2013-10-01 | A | 37194 | 25630 | 0.0 | 0 |
9 | 2013-10-01 | A | 123 | 25631 | 0.0 | 0 |
10 | 2013-10-01 | A | 41638 | 25632 | 0.0 | 0 |
11 | 2013-10-01 | B | 49024 | 25633 | 0.0 | 0 |
12 | 2013-10-01 | B | 42199 | 25634 | 0.0 | 0 |
13 | 2013-10-01 | B | 33444 | 25635 | 0.0 | 0 |
14 | 2013-10-01 | B | 9347 | 25636 | 0.0 | 0 |
15 | 2013-10-01 | B | 39412 | 25637 | 0.0 | 0 |
16 | 2013-10-01 | B | 15021 | 25638 | 15021.0 | 1 |
17 | 2013-10-01 | B | 118 | 25639 | 0.0 | 0 |
18 | 2013-10-01 | A | 42885 | 25640 | 0.0 | 0 |
19 | 2013-10-01 | B | 9498 | 25641 | 0.0 | 0 |
20 | 2013-10-01 | B | 16920 | 25642 | 0.0 | 0 |
21 | 2013-10-01 | A | 31438 | 25643 | 0.0 | 0 |
22 | 2013-10-01 | A | 37050 | 25644 | 0.0 | 0 |
23 | 2013-10-01 | B | 47484 | 25645 | 0.0 | 0 |
24 | 2013-10-01 | A | 49528 | 25646 | 0.0 | 0 |
25 | 2013-10-01 | A | 45125 | 25647 | 0.0 | 0 |
26 | 2013-10-01 | A | 35315 | 25648 | 0.0 | 0 |
27 | 2013-10-01 | A | 37195 | 25649 | 0.0 | 0 |
28 | 2013-10-01 | A | 32304 | 25650 | 0.0 | 0 |
29 | 2013-10-01 | A | 45970 | 25651 | 0.0 | 0 |
30 | 2013-10-01 | A | 40298 | 25652 | 0.0 | 0 |
31 | 2013-10-01 | A | 49527 | 25653 | 0.0 | 0 |
32 | 2013-10-01 | A | 45340 | 25654 | 0.0 | 0 |
33 | 2013-10-01 | A | 596 | 25655 | 596.0 | 1 |
34 | 2013-10-01 | A | 5525 | 25656 | 0.0 | 0 |
35 | 2013-10-01 | B | 30727 | 25657 | 0.0 | 0 |
36 | 2013-10-01 | B | 40985 | 25658 | 0.0 | 0 |
37 | 2013-10-01 | B | 45778 | 25659 | 0.0 | 0 |
38 | 2013-10-01 | A | 593 | 25660 | 0.0 | 0 |
39 | 2013-10-01 | B | 49529 | 25661 | 0.0 | 0 |
40 | 2013-10-01 | B | 49530 | 25662 | 0.0 | 0 |
41 | 2013-10-01 | A | 38019 | 25663 | 0.0 | 0 |
42 | 2013-10-01 | B | 18254 | 25664 | 0.0 | 0 |
43 | 2013-10-01 | B | 15521 | 25665 | 0.0 | 0 |
44 | 2013-10-01 | A | 41259 | 25666 | 0.0 | 0 |
45 | 2013-10-01 | B | 122 | 25667 | 0.0 | 0 |
46 | 2013-10-01 | B | 3133 | 25668 | 0.0 | 0 |
47 | 2013-10-01 | A | 1532 | 25669 | 0.0 | 0 |
48 | 2013-10-01 | B | 49531 | 25670 | 0.0 | 0 |
49 | 2013-10-01 | A | 49532 | 25671 | 0.0 | 0 |
all_df = all_df.drop('result', axis=1)
all_df.head()
log_date_x | test_case_x | user_id_x | transaction_id | results | |
---|---|---|---|---|---|
0 | 2013-10-01 | B | 36703 | 25622 | 0 |
1 | 2013-10-01 | A | 44339 | 25623 | 0 |
2 | 2013-10-01 | B | 32087 | 25624 | 0 |
3 | 2013-10-01 | B | 10160 | 25625 | 0 |
4 | 2013-10-01 | B | 46113 | 25626 | 0 |
さて、綺麗になりました。
ちなみに、resultsとして、実際のクリックを1、クリックしていない場合を0で表しています。
まずはクリック率を見てみます。
child = all_df.pivot_table("results", aggfunc='sum', columns='test_case_x')
parent = all_df.pivot_table("results", aggfunc='count', columns='test_case_x')
rato = child/parent
rato
test_case_x | A | B |
---|---|---|
results | 0.080256 | 0.11546 |
なるほど。Bの方が高いのですね。
ちなみに、他の種類のアプリのクリック率は12%程(本に書いてあった)ということなので、Bには期待できそうです。
しかし、この結果は単なるクリック率ですので、まだ安心できません。
カイ二乗検定を行なって、Bの結果が偶然よかったという帰無仮説を棄却していきたいです。
もう一度、全体を確認。
all_df.head()
log_date_x | test_case_x | user_id_x | transaction_id | results | |
---|---|---|---|---|---|
0 | 2013-10-01 | B | 36703 | 25622 | 0 |
1 | 2013-10-01 | A | 44339 | 25623 | 0 |
2 | 2013-10-01 | B | 32087 | 25624 | 0 |
3 | 2013-10-01 | B | 10160 | 25625 | 0 |
4 | 2013-10-01 | B | 46113 | 25626 | 0 |
resultsとtest_case_xでクロス集計します。
cross = pd.crosstab(all_df.test_case_x, all_df.results)
cross
results | 0 | 1 |
---|---|---|
test_case_x | ||
A | 40592 | 3542 |
B | 38734 | 5056 |
scipyのstatsは一発でカイ二乗検定を行うことができるので、それを利用。
stats.chi2_contingency(cross)
(308.37505289322877,
4.9341396337856319e-69,
1,
array([[ 39818.18029207, 4315.81970793],
[ 39507.81970793, 4282.18029207]]))
2つめの4.9341396337856319e-69がp値となります。
余裕で0.05は下回っていますね。
つまり、Bの結果が偶然良いものになった可能性は棄却できそうです。
一応、データの詳細も見ておきます。
日毎のクリック率を可視化したいと思います。
まずはgroupbyしてcount値をとります。
grouped = all_df.groupby(['test_case_x', 'log_date_x', 'results']).count()
grouped = grouped.drop('transaction_id', axis=1)
カラム名を変更。
grouped.columns = ['count']
クリック率/非クリック率(こっちはついで)を追加します。
list=[] for k in range(len(grouped)): if k % 2 == 0: a = grouped["count"][k] / (grouped["count"][k] + grouped["count"][k+1]) list.append(a) else: b = grouped["count"][k] / (grouped["count"][k] + grouped["count"][k-1]) list.append(b) grouped['d_rato'] = list
grouped_df = grouped.reset_index()
click_df = grouped_df[grouped_df['results'] == 1]
クリック率だけ抜き出します。 そして可視化。
sns.set() sns.set_context("notebook") plt.figure(figsize=(24, 12)) sns.pointplot(x='log_date_x', y='d_rato', data=click_df, hue='test_case_x')
局所的だとまた原因を探る必要がありそうですが、全体的にBの方が高そうなのでよかったです。
せっかくなので平均線も入れておきます。 赤色は全体のクリック率です。
avg = click_df.d_rato.mean()
sns.set() sns.set_context("notebook") plt.figure(figsize=(24, 12)) sns.pointplot(x='log_date_x', y='d_rato', data=click_df, hue='test_case_x') plt.hlines(avg, -5, 30, "red", linestyles='dashed') plt.hlines(click_df[click_df['test_case_x']=='A'].d_rato.mean(), -5, 30, "blue", linestyles='dashed') plt.hlines(click_df[click_df['test_case_x']=='B'].d_rato.mean(), -5, 30, "green", linestyles='dashed')
以上、A/Bテスト分析でした!!
どの属性の顧客が離脱しているのか?
さて、今回は探索型のデータ分析です。
先月と比べて、なぜか落ちてしまったユーザー数の原因を探索します。
まず、与えられているデータを確認。
dau_df = pd.read_csv("section4-dau.csv") user_info_df = pd.read_csv("section4-user_info.csv")
dau_df.head()
log_date | app_name | user_id | |
---|---|---|---|
0 | 2013-08-01 | game-01 | 33754 |
1 | 2013-08-01 | game-01 | 28598 |
2 | 2013-08-01 | game-01 | 30306 |
3 | 2013-08-01 | game-01 | 117 |
4 | 2013-08-01 | game-01 | 6605 |
user_info_df.head()
install_date | app_name | user_id | gender | generation | device_type | |
---|---|---|---|---|---|---|
0 | 2013-04-15 | game-01 | 1 | M | 40 | iOS |
1 | 2013-04-15 | game-01 | 2 | M | 10 | Android |
2 | 2013-04-15 | game-01 | 3 | F | 40 | iOS |
3 | 2013-04-15 | game-01 | 4 | M | 10 | Android |
4 | 2013-04-15 | game-01 | 5 | M | 40 | iOS |
keyをuser_idとして結合します。
all_df = pd.merge(dau_df, user_info_df, on='user_id', how='left')
all_df.head()
log_date | app_name_x | user_id | install_date | app_name_y | gender | generation | device_type | |
---|---|---|---|---|---|---|---|---|
0 | 2013-08-01 | game-01 | 33754 | 2013-08-01 | game-01 | M | 20 | iOS |
1 | 2013-08-01 | game-01 | 28598 | 2013-07-16 | game-01 | M | 50 | iOS |
2 | 2013-08-01 | game-01 | 30306 | 2013-07-20 | game-01 | F | 30 | iOS |
3 | 2013-08-01 | game-01 | 117 | 2013-04-17 | game-01 | F | 20 | iOS |
4 | 2013-08-01 | game-01 | 6605 | 2013-05-02 | game-01 | M | 20 | iOS |
先月との比較を行いたいので、月の情報を追加します。
log_month =[] for k in range(len(all_df['log_date'])): str = all_df['log_date'][k] str_list = list(str) month = str_list[5:7] month = ''.join(month) log_month.append(month) all_df["log_month"] = log_month
all_df.head()
log_date | user_id | install_date | gender | generation | device_type | log_month | |
---|---|---|---|---|---|---|---|
0 | 2013-08-01 | 33754 | 2013-08-01 | M | 20 | iOS | 08 |
1 | 2013-08-01 | 28598 | 2013-07-16 | M | 50 | iOS | 08 |
2 | 2013-08-01 | 30306 | 2013-07-20 | F | 30 | iOS | 08 |
3 | 2013-08-01 | 117 | 2013-04-17 | F | 20 | iOS | 08 |
4 | 2013-08-01 | 6605 | 2013-05-02 | M | 20 | iOS | 08 |
さてここからデータをみていきます。 まずは、性別で違いがあるのかみていきます。
all_df.groupby(['log_month', 'gender']).count()
log_date | user_id | install_date | generation | device_type | ||
---|---|---|---|---|---|---|
log_month | gender | |||||
08 | F | 47343 | 47343 | 47343 | 47343 | 47343 |
M | 46842 | 46842 | 46842 | 46842 | 46842 | |
09 | F | 38027 | 38027 | 38027 | 38027 | 38027 |
M | 38148 | 38148 | 38148 | 38148 | 38148 |
全然違いはなさそうですね。 一応plotしておきます。
sns.countplot("log_month", data=all_df, hue='gender')
次は年齢別で違いがあるのか見たいと思います。
all_df.groupby(['log_month', 'generation']).count()
log_date | user_id | install_date | gender | device_type | ||
---|---|---|---|---|---|---|
log_month | generation | |||||
08 | 10 | 18785 | 18785 | 18785 | 18785 | 18785 |
20 | 33671 | 33671 | 33671 | 33671 | 33671 | |
30 | 28072 | 28072 | 28072 | 28072 | 28072 | |
40 | 8828 | 8828 | 8828 | 8828 | 8828 | |
50 | 4829 | 4829 | 4829 | 4829 | 4829 | |
09 | 10 | 15391 | 15391 | 15391 | 15391 | 15391 |
20 | 27229 | 27229 | 27229 | 27229 | 27229 | |
30 | 22226 | 22226 | 22226 | 22226 | 22226 | |
40 | 7494 | 7494 | 7494 | 7494 | 7494 | |
50 | 3835 | 3835 | 3835 | 3835 | 3835 |
とても見難いので、これはplotする意味がありそうです。
sns.countplot("log_month", data=all_df, hue='generation')
色は綺麗ですがまだちょっと見難いので、今度は"log_month"の方で層別化してみます。
sns.countplot("generation", data=all_df, hue='log_month')
10〜30代の減少具合が高いような気もしますが、そもそものパイが大きいので、根本的な原因とは考えにくいかと思います。
実はこの章はクロス集計がテーマらしいので、それっぽいことをしていきます。
grouped = all_df.groupby(['log_month', 'gender', 'generation']).count()
grouped_df = grouped.reset_index
grouped.log_date.plot(kind='bar')
果たしてこのplotが見やすいかは謎ですが、これを見る限り、特別大きな原因があるようには思えません。。。
もう一度、全体のデータを確認します。
all_df.head()
log_date | user_id | install_date | gender | generation | device_type | log_month | |
---|---|---|---|---|---|---|---|
0 | 2013-08-01 | 33754 | 2013-08-01 | M | 20 | iOS | 08 |
1 | 2013-08-01 | 28598 | 2013-07-16 | M | 50 | iOS | 08 |
2 | 2013-08-01 | 30306 | 2013-07-20 | F | 30 | iOS | 08 |
3 | 2013-08-01 | 117 | 2013-04-17 | F | 20 | iOS | 08 |
4 | 2013-08-01 | 6605 | 2013-05-02 | M | 20 | iOS | 08 |
'device_type'の情報が含まれています。 こっちも分析してみましょう。
sns.countplot("device_type", data=all_df, hue='log_month')
お?
これは明らかにおかしい結果が出てきました。
Androidユーザーの利用率が9月になってめちゃめちゃ下がってますね。
この辺に原因がありそうです。
性別、年代との関係があるか見てみます。
android_df = all_df[all_df['device_type'] == 'Android']
sns.countplot("generation", data=android_df, hue='log_month')
sns.countplot("gender", data=android_df, hue='log_month')
この辺に関係はなさそうですね。 Android用のアプリに異常があったことは間違いなさそうなので、月ごとではなく、日毎でplotしてみます。
device_count = all_df.groupby(["device_type", 'log_date']).count()
device_count = device_count.drop(['install_date', 'gender', 'generation', 'log_month'],axis=1)
device_count = device_count.reset_index()
device_count.columns = ['device_type', 'log_date', 'count']
sns.set() sns.set_context("notebook") plt.figure(figsize=(24, 12)) sns.pointplot(x='log_date', y='count', hue='device_type', data=device_count, markers=["^", "o"], linestyles=["-", "--"])
日付の文字が潰れてしまったので正確な日にちがわからなくなってしまいました。笑
しかし、Androidユーザー数が0になったわけではないので、Androidの全ユーザーに支障があったわけではなさそうです。
と、ここまで分析できたので、本の方をみてみます。 すると、AndroidOSのアップデートがあったと答えが書いてありました。 古いバージョンのAndroidOSではアプリの起動確認をしてなかったそうです。
それであれば世代別で見たときに、上の世代のユーザー減少率が下がりそうなものですが、最近の人たちはテクノロジーリテラシーが高いのでしょうか笑 練習用のデータなので、綺麗に結果が出るようにしてくれているのかもしれません。
※最初に二つのデータを結合した後、'app_name_x','app_name_y'の列を削除してますが、コード載せるの忘れてました。
先月からの売上減少を可視化してみる3
さて、前回までで、新規ユーザーの全体課金額が減少していて、その原因の一つとして、そもそも新規ユーザー登録数が減少しているのではないか、というところまで進んだ。
今回は、6,7月の新規ユーザーに課金額の違いがあるのかを見ていく。(ユーザー層が変化した可能性をみたい)
まずは全体の確認。
all_df.head()
log_date | user_id | install_date | payment | log_month | install_month | new(0)/old(1) | |
---|---|---|---|---|---|---|---|
0 | 2013-06-01 | 116 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
1 | 2013-06-01 | 13491 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
2 | 2013-06-01 | 7006 | 2013-05-03 | 0.0 | 06 | 05 | 1 |
3 | 2013-06-01 | 117 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
4 | 2013-06-01 | 13492 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
色々覚えてきて、ここから一発でグラフ化できそうだなと思ったので、やってみた。
sns.set() sns.set_context("notebook") plt.figure(figsize=(24, 12)) sns.distplot(all_df[all_df['log_month'] == "06"].payment) sns.distplot(all_df[all_df['log_month']== "07"].payment)
お?
どうやら無課金ユーザーが多いので、そっちにplotが引っ張られてしまったようだ。 課金勢のみを抽出して再度可視化。
all_df = all_df[all_df.payment != 0]
sns.set() sns.set_context("notebook") plt.figure(figsize=(24, 12)) sns.distplot(all_df[all_df['log_month'] == "06"].payment) sns.distplot(all_df[all_df['log_month']== "07"].payment)
今度はうまくいった!
結果
さて、青が6月新規勢で、緑が7月新規勢だが、kdeplotの部分もほぼ一緒なのでこれは単に新規登録人数が減ったと考えられそう。
追記
本を確認すると、2000円以下の課金数が減ったと書いてあった。 まあ確かにplotを見てもその通りなのだが、3つのplotピークの差がちょっとずつ減少傾向にある。そのため、課金額0をスタートとして、正規分布しているのだろうと勝手に判断した。(なので問題ないかなと)
理想は、この判断を数学的に行いたいが、まだその辺はよくわかってないので、勉強した時に、振り返りたい。
先月からの売上減少を可視化してみる2
前回の記事の続きです。 前回までで、新規ユーザーの総合課金額が減少してることがわかりました。 今回は、この減少が、ユーザー層の変化によるものなのか、単に登録ユーザーによるものなのかを見ていきます。
まずは全体の確認。
all_df.head()
log_date | user_id | install_date | payment | log_month | install_month | new(0)/old(1) | |
---|---|---|---|---|---|---|---|
0 | 2013-06-01 | 116 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
1 | 2013-06-01 | 13491 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
2 | 2013-06-01 | 7006 | 2013-05-03 | 0.0 | 06 | 05 | 1 |
3 | 2013-06-01 | 117 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
4 | 2013-06-01 | 13492 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
ここから6、7月新規のデータだけ取り出します。 ここでは.isin()を使ってますね。色々調べながらやったからこうなったんでしょう。 今なら == で書くと思います。
new_user_df = all_df[all_df['new(0)/old(1)'].isin(['0'])]
同じidでも複数の行で書かれているのでgroupbyでまとめて、reset_indexでデータフレーム に戻します。
drop = ["log_date", "install_date", "log_month", "new(0)/old(1)"] new_user_df2 = new_user_df.drop(drop, axis=1)
grouped = new_user_df2.groupby(["install_month", "user_id"]).sum()
grouped.head()
payment | ||
---|---|---|
install_month | user_id | |
06 | 13491 | 0.0 |
13492 | 0.0 | |
13493 | 0.0 | |
13494 | 0.0 | |
13495 | 0.0 |
new_grouped_df = grouped.reset_index()
可視化
sns.countplot("install_month", data=new_grouped_df)
結果
新規の登録者数が減っていることがわかった。
次の課題
新規の登録者数は減っているが、ユーザー層の変化の可能性が棄却できるわけではないので、それを調べたい。
先月からの売上減少を可視化してみる
とあるゲーム会社のソシャゲの売り上げが、先月と比べて落ちた。 市場的にも、ゲーム的にもまだまだ伸びると考えられるため、会社で大きな問題となり、原因を究明することに。
今回与えられているデータは三つ。
DAU(Daily Active User:1日1回以上アクセスしたユーザーのデータ)
DPU(Daily Payment User:1日1円以上課金したユーザーのデータ)
Install(ユーザーごとにゲームを利用開始した日付が記録されたデータ)
データを確認
dau_df.head()
log_date | app_name | user_id | |
---|---|---|---|
0 | 2013-06-01 | game-01 | 116 |
1 | 2013-06-01 | game-01 | 13491 |
2 | 2013-06-01 | game-01 | 7006 |
3 | 2013-06-01 | game-01 | 117 |
4 | 2013-06-01 | game-01 | 13492 |
dpu_df.head()
log_date | app_name | user_id | payment | |
---|---|---|---|---|
0 | 2013-06-01 | game-01 | 351 | 1333 |
1 | 2013-06-01 | game-01 | 12796 | 81 |
2 | 2013-06-01 | game-01 | 364 | 571 |
3 | 2013-06-01 | game-01 | 13212 | 648 |
4 | 2013-06-01 | game-01 | 13212 | 1142 |
install_df.head()
install_date | app_name | user_id | |
---|---|---|---|
0 | 2013-04-15 | game-01 | 1 |
1 | 2013-04-15 | game-01 | 2 |
2 | 2013-04-15 | game-01 | 3 |
3 | 2013-04-15 | game-01 | 4 |
4 | 2013-04-15 | game-01 | 5 |
三つのデータフレームをuser_idで結合させる。 この時、dpuは全ユーザーデータではないので、データが欠損しないように気をつける。
dau_install_df = pd.merge(dau_df, install_df, on='user_id', how='left')
all_df = pd.merge(dau_install_df, dpu_df, on=['user_id', 'log_date'], how='outer')
確認
all_df.head()
log_date | app_name_x | user_id | install_date | app_name_y | app_name | payment | |
---|---|---|---|---|---|---|---|
0 | 2013-06-01 | game-01 | 116 | 2013-04-17 | game-01 | NaN | NaN |
1 | 2013-06-01 | game-01 | 13491 | 2013-06-01 | game-01 | NaN | NaN |
2 | 2013-06-01 | game-01 | 7006 | 2013-05-03 | game-01 | NaN | NaN |
3 | 2013-06-01 | game-01 | 117 | 2013-04-17 | game-01 | NaN | NaN |
4 | 2013-06-01 | game-01 | 13492 | 2013-06-01 | game-01 | NaN | NaN |
さて、これを見るとNot a Numberが存在していて、さらにapp_nameも複数存在してしまっているので、これを処理していく(NaNは0で埋める)
app_names = ['app_name', 'app_name_x', 'app_name_y']
all_df = all_df.drop(app_names, axis=1)
all_df = all_df.fillna(0)
確認してみる
all_df.head()
log_date | user_id | install_date | payment | |
---|---|---|---|---|
0 | 2013-06-01 | 116 | 2013-04-17 | 0.0 |
1 | 2013-06-01 | 13491 | 2013-06-01 | 0.0 |
2 | 2013-06-01 | 7006 | 2013-05-03 | 0.0 |
3 | 2013-06-01 | 117 | 2013-04-17 | 0.0 |
4 | 2013-06-01 | 13492 | 2013-06-01 | 0.0 |
スッキリしました。
さて、本によると、先月と今月の違いを見たいため、log_date、install_dateを月次に変更する。さらに、それらが一致するかしないかで、ユーザーが、新規か既存かの情報を加えたいとのこと。
logmonth = [] for k in range(len(all_df["log_date"])): str = all_df["log_date"][k] str_list = list(str) str_changed = str_list[5]+str_list[6] fin_str = ''.join(str_changed) logmonth.append(fin_str) all_df["log_month"] = logmonth
installmonth = [] for k in range(len(all_df["install_date"])): str = all_df["install_date"][k] str_list = list(str) str_changed = str_list[5]+str_list[6] fin_str = ''.join(str_changed) installmonth.append(fin_str) all_df["install_month"] = installmonth
new_one = [] for i in range(len(all_df["log_date"])): if all_df["log_month"][i] == all_df["install_month"][i]: new_one.append(0) else: new_one.append(1)
all_df["new(0)/old(1)"] = new_one
all_df.head()
log_date | user_id | install_date | payment | log_month | install_month | new(0)/old(1) | |
---|---|---|---|---|---|---|---|
0 | 2013-06-01 | 116 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
1 | 2013-06-01 | 13491 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
2 | 2013-06-01 | 7006 | 2013-05-03 | 0.0 | 06 | 05 | 1 |
3 | 2013-06-01 | 117 | 2013-04-17 | 0.0 | 06 | 04 | 1 |
4 | 2013-06-01 | 13492 | 2013-06-01 | 0.0 | 06 | 06 | 0 |
多分もっと簡単な方法があるのだろうとは思いつつも、何しろ初学者なので強引に行ってしまった。
さて。本によると、この後、アクセスした月と新規か既存かの情報で、合計課金額を集計している。
payment_df = all_df.sort_values(by="user_id").groupby(["log_month", "new(0)/old(1)"]).sum()
payment_df
user_id | payment | ||
---|---|---|---|
log_month | new(0)/old(1) | ||
06 | 0 | 604911660 | 49837.0 |
1 | 307222419 | 177886.0 | |
07 | 0 | 592521566 | 29199.0 |
1 | 507666653 | 177886.0 |
user_idまで合計されてしまった。笑 取り除く。
payment_df = payment_df.drop("user_id", axis=1)
payment_df
payment | ||
---|---|---|
log_month | new(0)/old(1) | |
06 | 0 | 49837.0 |
1 | 177886.0 | |
07 | 0 | 29199.0 |
1 | 177886.0 |
さて、集計が終わったので、可視化してみる。
payment_df.plot(kind='bar', stacked=True) plt.show()
結果
6月と7月の売り上げを新規/既存ユーザーで層別化したところ、既存ユーザーの合計課金額には変化が見られないが、新規ユーザー総合課金額が減っている。
次の課題
この課金額の減少の原因を調べるために、登録ユーザー数の推移やどの課金額の層が減っているかを見て行く。
卒コン幹事用プログラム
大学の研究室で、卒業生の先輩のためにコンパを開くから幹事をしろと言われた。
飲み会には興味皆無だが、先輩には好きな人も多いので、幹事を引き受けた。
毎年のことらしいので、去年の先輩から大体の集金額を聞いて諸々計算。。。
そこで気がついた。
めっちゃめんどくさい!!!!
そもそも計算もめんどくさいのだが、後から人数が変わったりするのがめちゃめちゃ厄介。
面倒になる最も大きな原因の一つは、卒業生からお金を一切もらわないことだ。
つまり、在校生と先生が卒業生の分の飲み代と簡単なプレゼント代も支払う。
それが結構高い。
当然欠席者もいるだろうから、とても高くなる。
その辺の値段設定まで考えると結構面倒。
なので、後で人数が変わってもイライラしないように(計算しなくていいように)、始めたてのpythonを使ってコードを書いてみることにした。
この記事はそのメモ。
学校が薬科大なので汎用性は全くないけどね!
条件はこんな感じ
研究室の配属は3年以上
卒業生は創薬学科の4年生と6年生、薬学科の6年生
欠席者からもプレゼント代として徴収する
変数が多いと難しいので、出席者4000円、欠席者2000円の徴収と、とりあえず固定した
去年の感じだとプレゼント代は908円(クオカードと花)
卒業生で、卒コン不参加の人へのプレゼントはクオカードのみ
飲み代は3300円(しかし貸切最低保障金額が18万円らしいので最低でも60人参加は絶対条件)
足りない分は先生から徴収したい(僕の希望)
コードはこちら
そして実行。
これだと、先生から一銭ももらわない計算で4100円ほど足りないらしい。
先生から1500円ずつ余分にもらうことになりそうだ。。。