重回帰分析で費用対効果を予測する。

さて今回のテーマは重回帰分析です。

データは広告費(テレビ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)

f:id:icchy333:20180218163906p:plain

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)

f:id:icchy333:20180218163926p:plain

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)

f:id:icchy333:20180218164704p:plain

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)

f:id:icchy333:20180218165104p:plain

雑誌の方が傾きが大きいですね。

さて、では重回帰分析してみます。

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()
OLS Regression Results
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')

f:id:icchy333:20180215193752p:plain

局所的だとまた原因を探る必要がありそうですが、全体的に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')

f:id:icchy333:20180215194550p:plain

以上、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')

f:id:icchy333:20180214180239p:plain

次は年齢別で違いがあるのか見たいと思います。

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')

f:id:icchy333:20180214180532p:plain

色は綺麗ですがまだちょっと見難いので、今度は"log_month"の方で層別化してみます。

sns.countplot("generation", data=all_df, hue='log_month')

f:id:icchy333:20180214180751p:plain

10〜30代の減少具合が高いような気もしますが、そもそものパイが大きいので、根本的な原因とは考えにくいかと思います。

実はこの章はクロス集計がテーマらしいので、それっぽいことをしていきます。

grouped = all_df.groupby(['log_month', 'gender', 'generation']).count()
grouped_df =  grouped.reset_index
grouped.log_date.plot(kind='bar')

f:id:icchy333:20180214181133p:plain

果たしてこの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')

f:id:icchy333:20180214181510p:plain

お?

これは明らかにおかしい結果が出てきました。

Androidユーザーの利用率が9月になってめちゃめちゃ下がってますね。

この辺に原因がありそうです。

性別、年代との関係があるか見てみます。

android_df = all_df[all_df['device_type'] == 'Android']
sns.countplot("generation", data=android_df, hue='log_month')

f:id:icchy333:20180214181815p:plain

sns.countplot("gender", data=android_df, hue='log_month')

f:id:icchy333:20180214181841p:plain

この辺に関係はなさそうですね。 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=["-", "--"])

f:id:icchy333:20180214182135p:plain

日付の文字が潰れてしまったので正確な日にちがわからなくなってしまいました。笑

しかし、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)

f:id:icchy333:20180212160548p:plain

お?

どうやら無課金ユーザーが多いので、そっちに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)

f:id:icchy333:20180212160829p:plain

今度はうまくいった!

結果

さて、青が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)

f:id:icchy333:20180212154725p:plain

結果

新規の登録者数が減っていることがわかった。

次の課題

新規の登録者数は減っているが、ユーザー層の変化の可能性が棄却できるわけではないので、それを調べたい。

先月からの売上減少を可視化してみる

とあるゲーム会社のソシャゲの売り上げが、先月と比べて落ちた。 市場的にも、ゲーム的にもまだまだ伸びると考えられるため、会社で大きな問題となり、原因を究明することに。

今回与えられているデータは三つ。

  • 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()

f:id:icchy333:20180211185344p:plain

結果

6月と7月の売り上げを新規/既存ユーザーで層別化したところ、既存ユーザーの合計課金額には変化が見られないが、新規ユーザー総合課金額が減っている。

次の課題

この課金額の減少の原因を調べるために、登録ユーザー数の推移やどの課金額の層が減っているかを見て行く。

卒コン幹事用プログラム

大学の研究室で、卒業生の先輩のためにコンパを開くから幹事をしろと言われた。

飲み会には興味皆無だが、先輩には好きな人も多いので、幹事を引き受けた。

毎年のことらしいので、去年の先輩から大体の集金額を聞いて諸々計算。。。

 

そこで気がついた。

めっちゃめんどくさい!!!!

そもそも計算もめんどくさいのだが、後から人数が変わったりするのがめちゃめちゃ厄介。

面倒になる最も大きな原因の一つは、卒業生からお金を一切もらわないことだ。

つまり、在校生と先生が卒業生の分の飲み代と簡単なプレゼント代も支払う。

それが結構高い。

当然欠席者もいるだろうから、とても高くなる。

その辺の値段設定まで考えると結構面倒。

なので、後で人数が変わってもイライラしないように(計算しなくていいように)、始めたてのpythonを使ってコードを書いてみることにした。

この記事はそのメモ。

学校が薬科大なので汎用性は全くないけどね!

 

条件はこんな感じ

研究室の配属は3年以上

卒業生は創薬学科の4年生と6年生、薬学科の6年生

欠席者からもプレゼント代として徴収する

変数が多いと難しいので、出席者4000円、欠席者2000円の徴収と、とりあえず固定した

去年の感じだとプレゼント代は908円(クオカードと花)

卒業生で、卒コン不参加の人へのプレゼントはクオカードのみ

飲み代は3300円(しかし貸切最低保障金額が18万円らしいので最低でも60人参加は絶対条件)

足りない分は先生から徴収したい(僕の希望)

 

コードはこちら

sannen_atend = 0
sannen_not_atend =0
yonen_yaku_atend = 0
yonen_yaku_not_atend =0
yonen_souyaku_atend = 0
yonen_souyaku_not_atend =0
gonen_atend = 0
gonen_not_atend = 0
rokunen_atend = 0
rokunen_not_atend =0
sensei_atend = 0
sensei_not_atend =0
sannen = 17
yonen_yaku= 14
yonen_souyaku = 3
gonen=16
rokunen = 19
sensei = 3
##入力
sannen_not_atend = 0
yonen_yaku_not_atend = 1
yonen_souyaku_not_atend = 1
gonen_not_atend = 6
rokunen_not_atend = 0
sensei_not_atend = 0
sannen_atend = sannen - sannen_not_atend
yonen_yaku_atend = yonen_yaku - yonen_yaku_not_atend
yonen_souyaku_atend = yonen_souyaku - yonen_souyaku_not_atend
gonen_atend = gonen - gonen_not_atend
rokunen_atend = rokunen - rokunen_not_atend
sensei_atend = sensei - sensei_not_atend
atend_pay = sannen_atend + yonen_yaku_atend + gonen_atend + sensei_atend
atend_not_pay = yonen_souyaku_atend + rokunen_atend
not_atend_pay = sannen_not_atend + yonen_yaku_not_atend + gonen_not_atend + sensei_not_atend
not_atend_not_pay = yonen_souyaku_not_atend + rokunen_not_atend
def check4000(sensei_pay_add):
  if atend_pay + atend_not_pay > 60:
  
    pay_all = atend_pay * 4000 + not_atend_pay * 2000
    spending = (atend_pay + atend_not_pay)*3300 + atend_not_pay*908 + not_atend_not_pay*530
    income = pay_all + 40600 + sensei_pay_add
    if income >= spending:
      print("多分大丈夫だよ!! ちなみに差額は{}だよ!!".format(income-spending))
    else:
      print("これじゃダメだよ!! 料金を考え直してね! ちなみに{}足りないよ! 頑張って!!!".format(spending - income))
      
  else:
    print("60人に足りないよ!! あと{}人!! 頑張って集めて!!".format(60 - atend_pay - atend_not_pay))
  
check4000(0)

そして実行。

これだと、先生から一銭ももらわない計算で4100円ほど足りないらしい。

先生から1500円ずつ余分にもらうことになりそうだ。。。