1億人のための統計解析をpythonで - case1_part1

詳しい内容は書籍「西内啓『1億人のための統計解析』」をご参照ください。

解析案件整理

解析要件定義

要件 定義
アウトカム 過去3か月間における夜間帯の総利用金額
解析単位 顧客
説明変数 アウトカム以外のすべての項目

解析前の準備

データを読み込み総利用金額の列を追加します。

CODE

import scipy
import pandas as pd
import numpy as np
from statsmodels.stats.weightstats import ttest_ind
import matplotlib.pyplot as plt

# matplotlib日本語文字化け対策
# plt.rcParams['font.family'] = 'sans-serif'
# plt.rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic', 'Meirio', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP']

# matplotlib日本語文字化け対策(colaboratory向け)
# !pip install japanize-matplotlib
# import japanize_matplotlib

plt.rcParams['font.size'] = 14

CODE

# ファイル読み込み
df = pd.read_excel('[ファイル設置パス]/case1.xlsx')

df['総利用金額'] = df['来店回数'] * df['利用金額']

Analysis 1: 顧客の性別や家族構成は売り上げに影響を与えるか

総利用金額の違いが男女によって生じるかどうかを検証する

男性・女性それぞれの平均総利用金額を算出する

CODE

dfa = df.groupby('性別').mean()
print(
  f"女性の平均総利用金額: {dfa.loc['女性']['総利用金額']}",
  f"男性の平均総利用金額: {dfa.loc['男性']['総利用金額']}",
  f"[女性]と[男性]の平均総利用金額の差: {dfa.loc['女性']['総利用金額'] - dfa.loc['男性']['総利用金額']}",
  sep='\n'
  )

OUTPUT

女性の平均総利用金額: 1322.9755178907722
男性の平均総利用金額: 2384.4349680170576
[女性]と[男性]の平均総利用金額の差: -1061.4594501262854

女性を基準に算出しているので、女性の方が総利用金額が1000円ほど少ないという結果となります。

この差は偶然起きるものなのか検定する

2つの母集団における差が偶然かどうかを検証する手法としてウェルチのt検定というものがあります。対象の母分散が等しくない(非等分散)ときに使う手法でありますが

これらを考慮していれば、母分散に関する情報がなくても利用することも考えられます。ここではそのまま利用する方針をとるとして

CODE

# ウェルチのt検定を実施
samplea = df[df['性別']=='女性']['総利用金額']
sampleb = df[df['性別']=='男性']['総利用金額']
t, p, dof = ttest_ind(samplea, sampleb, usevar='unequal')

print(
    f"自由度: {dof}",
    f"t値: {t}",
    f"p値: {p}",
    sep='\n'
    )

OUTPUT

自由度: 849.4363751215236
t値: -3.4347473789710277
p値: 0.000621842190808965

結果の読み取り

p値が0.0006強と算出されました。これは確率0.06%でこの差が得られるということを示しています。つまりたまたまこのような結果が得られる可能性は限りなく少なく、めずらしいことが起きているということです。

なお1000回に1回しか起きない事象の発生確率は0.1%で数量に置き換えると0.001となりますが、今回のp値の結果『0.0006強』を小数点第3位までを用いて<0.001と表すことが慣習としてあるようです。これは小数点第3位までとして0.000と表現してしまうと、確率が0であると示していることになり、正確性が損なわれます。実際は0.0006強であり、0ではないことを示す表現である方が正確であるということです。

またこのp値という値はイメージで理解する方が容易であると考えられ、下記のように確率密度関数によるグラフをイメージすると良いです。

# t分布の描画
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(12,6))

# 左側の図表
ax.set_title(f"自由度{dof:.0f}のt分布")
ax.set_ylabel('y')
ax.set_xlabel('x')
ax.grid(True)

x1 = np.linspace(-4, 4, 100)
y1 = scipy.stats.t.pdf(x1, dof)

ax.plot(x1, y1)

ax.annotate(f"{t:.3f}", xy=(t, scipy.stats.t.pdf(t, dof)),
            xytext=(-4,0.1),
            arrowprops=dict(width=1, facecolor='black'),
            )


# 右側の図表
ax2.set_title('左の図表の一部を拡大したもの')
ax2.set_ylabel('y')
ax2.set_xlabel('x')

x2 = np.linspace(-4, -3, 100)
y2 = scipy.stats.t.pdf(x2, dof)
ax2.plot(x2, y2)

ax2.annotate(f"{t:.3f}", xy=(t, scipy.stats.t.pdf(t, dof)),
            xytext=(-3.8, 0.003),
            arrowprops=dict(width=1, facecolor='black'),
            )

fill_x = np.linspace(-4, t, 25)
fill_y = scipy.stats.t.pdf(fill_x, dof)
ax2.fill_between(fill_x, fill_y, facecolor='cyan', alpha=0.3)

OUTPUT

CODE

# 累積分布関数(cumulative distribution function)
print(f"累積分布関数よりt値 {t:.3f} までの面積は {scipy.stats.t.cdf(t, dof)} となります。")

print(f"一方、p値は {p} となっており、一致していませんが、これは両側確率です。")

print(f"したがってt値より得られた片側確率の面積を2倍すれば {scipy.stats.t.cdf(t, dof) * 2} となり一致します。")

OUTPUT

累積分布関数よりt値 -3.435 までの面積は 0.0003109210954044825 となります。
一方、p値は 0.000621842190808965 となっており、一致していませんが、これは両側確率です。
したがってt値より得られた片側確率の面積を2倍すれば 0.000621842190808965 となり一致します。

総利用金額の違いが家族構成によって生じるかどうかを検証する

家族構成は

の3つの状態をとる説明変数。ウェルチのt検定による先ほどの比較手法は異なる2つの母集団における手法であり、3つの集団には適用できません。この場合の解決策の例は

などがあります。ここでは基準グループを決め比較を繰り返すかたちで検定します。

平均総利用金額の算出

各集団それぞれにおける総利用金額の平均は

CODE

samplea = df[df['家族構成']=='結婚していない']['総利用金額']
sampleb = df[df['家族構成']=='結婚している(子供あり)']['総利用金額']
samplec = df[df['家族構成']=='結婚している(子供なし)']['総利用金額']

print(
  f"結婚していないの平均総利用金額: {samplea.mean()}",
  f"結婚している(子供あり)の平均総利用金額: {sampleb.mean()}",
  f"結婚している(子供なし)の平均総利用金額: {samplec.mean()}",
  sep='\n'
  )

OUTPUT

結婚していないの平均総利用金額: 1476.2820512820513
結婚している(子供あり)の平均総利用金額: 2206.1032863849764
結婚している(子供なし)の平均総利用金額: 1793.3962264150944

今回は子供ありの集団が一番多いという結果が得られました。

これらの差が偶然発生するものかどうかを検証する

つづいて、この差が偶然によるものかどうかを検証します。

CODE

print(
  f"# [結婚していない]と[結婚している(子供あり)]",
  f"平均総利用金額の差: {samplea.mean() - sampleb.mean()}",
  sep='\n'
  )

t, p, dof = ttest_ind(samplea, sampleb, usevar='unequal')
print(
    f"自由度: {dof}",
    f"t値: {t}",
    f"p値: {p}",
    sep='\n'
    )

print('')

print(
  f"# [結婚していない]と[結婚している(子供なし)]",
  f"平均総利用金額の差: {samplea.mean() - samplec.mean()}",
  sep='\n'
  )

t, p, dof = ttest_ind(samplea, samplec, usevar='unequal')
print(
    f"自由度: {dof}",
    f"t値: {t}",
    f"p値: {p}",
    sep='\n'
    )

OUTPUT

# [結婚していない]と[結婚している(子供あり)]
平均総利用金額の差: -1061.4594501262854
自由度: 849.4363751215236
t値: -3.4347473789710277
p値: 0.000621842190808965

# [結婚していない]と[結婚している(子供なし)]
平均総利用金額の差: -470.4207085243222
自由度: 152.5549433878091
t値: -1.1183507093164164
p値: 0.2651751873825952

結果の読み取り

p値に着目すると下表のように整理できます。

比較対照 基準に対する平均総利用金額の差 p値 偶発的に得られるか
[結婚していない]と[結婚している(子供あり)] -729 0.028 x
[結婚していない]と[結婚している(子供なし)] -317 0.460 o

したがって、結婚していない顧客より

ということが分かりました。

続く・・・

参考文献