統計学基礎完全マスター!データ分析に必要な統計の知識を初心者向けに解説

「データ分析を始めたいけれど、統計学って数学が苦手な私にも理解できるの?」「統計学の用語や数式が難しくて、どこから手をつければいいかわからない」
データ分析の学習を始めると、必ずぶつかる壁が「統計学」です。確かに、専門書を開くと複雑な数式が並んでいて、数学に苦手意識がある方には敷居が高く感じられるかもしれません。
しかし、**データ分析に必要な統計学の基礎は、数学が苦手な方でも必ず理解できます。**重要なのは「なぜその統計手法を使うのか」「結果をどう解釈するのか」という実用的な理解です。
本記事では、データ分析の現場で実際に使われる統計学の基礎知識を、具体例とPythonコードを交えながら、初心者の方にも分かりやすく解説します。数式の暗記ではなく、「なぜ」と「どう使うか」に焦点を当てることで、実践的な統計学の力が身につくはずです。
統計学がデータ分析に不可欠な理由
データから正しい結論を導くために
日常業務でデータを扱う際、私たちは常に「このデータから何が言えるのか?」という疑問と向き合います。例えば、以下のような場面を考えてみてください:
- 新商品の売上データを見て「好調」と言えるのか?
- A/Bテストの結果から「改善効果あり」と判断できるのか?
- 顧客満足度の調査結果から「サービス品質向上」と結論できるのか?
これらの疑問に答えるために必要なのが統計学の知識です。統計学を知らずにデータを見ると、偶然の変動を重要な傾向と誤解したり、実際には意味のある変化を見逃したりする可能性があります。
統計学の3つの役割
1. データの要約・整理 大量のデータから重要な特徴を抽出し、分かりやすく整理する方法を提供します。これが「記述統計」の領域です。
2. 不確実性の定量化 「どの程度確実なのか」を数値で表現し、リスクを含めた意思決定を可能にします。これが「推測統計」の核心です。
3. 因果関係の探索 単なる相関関係と因果関係を区別し、「なぜそうなるのか」を科学的に探求する枠組みを提供します。
これらの役割により、統計学は「データに基づく意思決定」を支える基盤となっています。感覚や経験だけに頼らず、客観的で再現性のある分析を行うために、統計学の理解は欠かせません。
記述統計:データの特徴を掴む
中心傾向の測定
データの「真ん中あたり」がどこにあるかを知ることは、データ理解の第一歩です。しかし、「真ん中」を表す方法は1つではありません。それぞれに特徴があり、データの性質によって使い分けることが重要です。
平均値(Mean)
概念: すべてのデータを足して、データ数で割った値
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# サンプルデータ:ある店舗の日別売上(万円)
daily_sales = [120, 150, 180, 130, 200, 160, 140, 170, 190, 155]
# 平均値の計算
mean_sales = np.mean(daily_sales)
print(f"平均売上: {mean_sales:.1f}万円") # 159.5万円
平均値は最もよく使われる指標ですが、外れ値に大きく影響されるという特徴があります。例えば、上記のデータに「500万円」という日があったとしたら、平均値は大きく上昇し、「典型的な売上」を表さなくなってしまいます。
このような場合、店舗オーナーが「平均的には190万円売れている」と考えて在庫計画を立てると、多くの日で在庫過多になってしまう可能性があります。
中央値(Median)
概念: データを小さい順に並べたときの真ん中の値
# 中央値の計算
median_sales = np.median(daily_sales)
print(f"中央値: {median_sales:.1f}万円") # 157.5万円
# 外れ値を含む場合の比較
sales_with_outlier = daily_sales + [500] # 異常に高い売上日を追加
mean_with_outlier = np.mean(sales_with_outlier)
median_with_outlier = np.median(sales_with_outlier)
print(f"外れ値ありの平均: {mean_with_outlier:.1f}万円") # 190.0万円
print(f"外れ値ありの中央値: {median_with_outlier:.1f}万円") # 160.0万円
中央値は外れ値に影響されにくいという特徴があります。上記の例では、異常に高い売上日があっても中央値はほとんど変わりません。このため、「典型的な売上」を知りたい場合は、中央値の方が適していることが多いのです。
最頻値(Mode)
概念: 最も頻繁に現れる値
from scipy import stats
# カテゴリデータの例:顧客の年代別来店数
age_groups = ['20代', '30代', '20代', '40代', '30代', '20代', '50代', '30代', '20代']
# 最頻値の計算
mode_result = stats.mode(age_groups, keepdims=True)
print(f"最頻値: {mode_result.mode[0]}") # 20代
# 数値データでの最頻値
scores = [85, 90, 85, 92, 88, 85, 91, 89, 85, 87]
mode_score = stats.mode(scores, keepdims=True)
print(f"最頻値の点数: {mode_score.mode[0]}") # 85
最頻値は特にカテゴリデータで有用です。「どの年代の顧客が最も多いか」「どの商品が最も人気か」といった質問に答える際に使われます。
散らばりの測定
データの中心がわかったら、次に知りたいのは「データがどの程度ばらついているか」です。同じ平均値でも、データのばらつきが異なれば、ビジネス上の意味は大きく変わります。
分散と標準偏差
概念: データが平均からどの程度離れているかの平均的な度合い
# 2つの店舗の売上データ
store_a = [150, 155, 145, 160, 140, 165, 135, 170, 130, 150] # 安定した売上
store_b = [100, 200, 120, 180, 110, 190, 105, 195, 115, 185] # 変動の大きい売上
# 平均、分散、標準偏差の計算
print("店舗A:")
print(f" 平均: {np.mean(store_a):.1f}万円")
print(f" 分散: {np.var(store_a, ddof=1):.1f}")
print(f" 標準偏差: {np.std(store_a, ddof=1):.1f}万円")
print("\n店舗B:")
print(f" 平均: {np.mean(store_b):.1f}万円")
print(f" 分散: {np.var(store_b, ddof=1):.1f}")
print(f" 標準偏差: {np.std(store_b, ddof=1):.1f}万円")
実行結果を見ると、両店舗の平均売上は同じでも、標準偏差(ばらつきの程度)は大きく異なることがわかります。店舗Aは安定した売上パターンで予測しやすく、店舗Bは変動が大きく予測が困難です。
この違いは経営戦略に大きな影響を与えます。店舗Aなら安定した在庫管理が可能ですが、店舗Bは変動に対応できる柔軟な仕組みが必要になります。
標準偏差の実用的な解釈
標準偏差には重要な性質があります。正規分布に従うデータでは:
- 約68%のデータが「平均 ± 1標準偏差」の範囲に入る
- 約95%のデータが「平均 ± 2標準偏差」の範囲に入る
# 標準偏差を使った売上予測範囲の計算
mean_a = np.mean(store_a)
std_a = np.std(store_a, ddof=1)
# 68%の確率で売上が入る範囲
lower_68 = mean_a - std_a
upper_68 = mean_a + std_a
# 95%の確率で売上が入る範囲
lower_95 = mean_a - 2*std_a
upper_95 = mean_a + 2*std_a
print(f"店舗Aの売上予測:")
print(f" 68%の確率で {lower_68:.1f}〜{upper_68:.1f}万円")
print(f" 95%の確率で {lower_95:.1f}〜{upper_95:.1f}万円")
この性質を使うことで、「来月の売上は95%の確率で○○万円〜○○万円の範囲に入る」といった予測ができるようになります。
データの可視化と分布の理解
数値だけではわからないデータの特徴を、グラフで視覚的に理解することは非常に重要です。
ヒストグラムによる分布の確認
# データの可視化
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 正規分布に近いデータ
normal_data = np.random.normal(100, 15, 1000)
axes[0].hist(normal_data, bins=30, alpha=0.7, color='skyblue')
axes[0].set_title('正規分布(ベル型)')
axes[0].set_xlabel('値')
axes[0].set_ylabel('頻度')
# 右に偏ったデータ(年収などでよく見られる)
skewed_data = np.random.exponential(2, 1000)
axes[1].hist(skewed_data, bins=30, alpha=0.7, color='lightcoral')
axes[1].set_title('右偏分布')
axes[1].set_xlabel('値')
axes[1].set_ylabel('頻度')
# 一様分布(均等に分散)
uniform_data = np.random.uniform(0, 10, 1000)
axes[2].hist(uniform_data, bins=30, alpha=0.7, color='lightgreen')
axes[2].set_title('一様分布')
axes[2].set_xlabel('値')
axes[2].set_ylabel('頻度')
plt.tight_layout()
plt.show()
データの分布の形を理解することで、適切な統計手法を選択できるようになります。例えば:
- 正規分布: 平均値と標準偏差で特徴づけられ、多くの統計手法が適用可能
- 右偏分布: 中央値の方が平均値より実態を表すことが多い
- 一様分布: すべての値が等しく現れるため、特定の傾向を読み取りにくい
確率分布の基礎
正規分布:最も重要な確率分布
正規分布は統計学において最も重要な確率分布です。なぜなら、自然界や社会の多くの現象が正規分布に従うか、近似できるからです。
正規分布の特徴
正規分布は以下の特徴を持ちます:
- 平均を中心として左右対称
- 平均、中央値、最頻値がすべて同じ
- 平均と標準偏差の2つのパラメータで完全に決まる
from scipy import stats
import numpy as np
# 異なるパラメータの正規分布
x = np.linspace(-10, 20, 1000)
# パラメータの違いによる分布の変化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 平均が異なる場合
ax1.plot(x, stats.norm.pdf(x, 0, 2), label='平均=0, 標準偏差=2')
ax1.plot(x, stats.norm.pdf(x, 5, 2), label='平均=5, 標準偏差=2')
ax1.plot(x, stats.norm.pdf(x, 10, 2), label='平均=10, 標準偏差=2')
ax1.set_title('平均の違い')
ax1.legend()
# 標準偏差が異なる場合
ax2.plot(x, stats.norm.pdf(x, 5, 1), label='平均=5, 標準偏差=1')
ax2.plot(x, stats.norm.pdf(x, 5, 2), label='平均=5, 標準偏差=2')
ax2.plot(x, stats.norm.pdf(x, 5, 3), label='平均=5, 標準偏差=3')
ax2.set_title('標準偏差の違い')
ax2.legend()
plt.tight_layout()
plt.show()
標準正規分布とZ値
標準正規分布(平均0、標準偏差1の正規分布)を基準として、任意の正規分布のデータを比較する方法がZ値(標準化)です。
# Z値の計算例:テストの点数を標準化
test_scores = [85, 92, 78, 88, 95, 82, 90, 87, 91, 84]
mean_score = np.mean(test_scores)
std_score = np.std(test_scores, ddof=1)
# Z値の計算
z_scores = [(score - mean_score) / std_score for score in test_scores]
print("元の点数 → Z値")
for score, z in zip(test_scores, z_scores):
print(f"{score:3d}点 → {z:5.2f}")
Z値を使うことで、「平均より何標準偏差分高い(低い)か」がわかります。これにより、異なる試験や異なる集団間での成績比較が可能になります。
例えば、Z値が1.5の学生は「平均より1.5標準偏差分高い成績」で、これは上位約7%に相当することがわかります。このように、Z値は相対的な位置を客観的に評価する強力なツールです。
その他の重要な確率分布
二項分布:成功/失敗の確率
使用場面: 一定の成功確率を持つ試行を繰り返す場合
# 二項分布の例:広告のクリック率分析
# クリック率5%の広告を100回表示した場合のクリック数分布
n = 100 # 試行回数
p = 0.05 # 成功確率(クリック率)
# 二項分布による確率計算
x = np.arange(0, 15)
probabilities = stats.binom.pmf(x, n, p)
plt.figure(figsize=(10, 6))
plt.bar(x, probabilities, alpha=0.7)
plt.xlabel('クリック数')
plt.ylabel('確率')
plt.title(f'広告クリック数の分布 (n={n}, p={p})')
plt.show()
# 具体的な確率の計算
print(f"ちょうど5回クリックされる確率: {stats.binom.pmf(5, n, p):.3f}")
print(f"3回以上クリックされる確率: {1 - stats.binom.cdf(2, n, p):.3f}")
この分析により、「100回表示して3回以上クリックされる確率は○○%」といった予測ができます。マーケティング担当者は、この情報を基に予算計画や目標設定を行うことができます。
ポアソン分布:稀な事象の発生頻度
使用場面: 一定期間での事象発生回数(コールセンターへの問い合わせ、システム障害など)
# ポアソン分布の例:1日当たりのサーバーエラー発生数
lambda_param = 3 # 1日平均3回のエラー
x = np.arange(0, 12)
probabilities = stats.poisson.pmf(x, lambda_param)
plt.figure(figsize=(10, 6))
plt.bar(x, probabilities, alpha=0.7, color='orange')
plt.xlabel('エラー発生回数')
plt.ylabel('確率')
plt.title(f'1日当たりのエラー発生回数分布 (λ={lambda_param})')
plt.show()
# 具体的な確率計算
print(f"1日でエラーが0回の確率: {stats.poisson.pmf(0, lambda_param):.3f}")
print(f"1日でエラーが5回以上の確率: {1 - stats.poisson.cdf(4, lambda_param):.3f}")
この分析により、システム管理者は「1日でエラーが5回以上発生する確率は○○%なので、この場合は緊急対応が必要」といった判断基準を設けることができます。
推測統計の基礎
母集団と標本の関係
推測統計の核心は、「限られたデータ(標本)から全体(母集団)について何が言えるか」という問題です。この概念は、ビジネスにおけるデータ分析で常に直面する課題です。
なぜ標本調査が必要なのか
全数調査(母集団全体を調べること)は理想的ですが、現実的には困難な場合がほとんどです:
- コストの問題: 全顧客にアンケートを送るのは費用がかかりすぎる
- 時間の問題: 全商品の品質検査をしていたら納期に間に合わない
- 物理的制約: 破壊検査では全数検査は不可能
# 標本調査のシミュレーション
np.random.seed(42)
# 仮想的な母集団(全顧客の満足度、平均7.5点)
population = np.random.normal(7.5, 1.5, 10000) # 10,000人の顧客
# 異なる標本サイズでの標本平均の変動
sample_sizes = [10, 50, 100, 500]
num_samples = 1000 # 各サイズで1000回サンプリング
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.ravel()
for i, n in enumerate(sample_sizes):
sample_means = []
for _ in range(num_samples):
sample = np.random.choice(population, size=n, replace=False)
sample_means.append(np.mean(sample))
axes[i].hist(sample_means, bins=30, alpha=0.7, density=True)
axes[i].axvline(np.mean(population), color='red', linestyle='--',
label=f'母集団平均: {np.mean(population):.2f}')
axes[i].set_title(f'標本サイズ: {n}')
axes[i].set_xlabel('標本平均')
axes[i].set_ylabel('密度')
axes[i].legend()
plt.tight_layout()
plt.show()
このシミュレーションから重要な発見があります:
- 標本サイズが大きくなるほど、標本平均は母集団平均に近づく
- 標本サイズが大きくなるほど、標本平均のばらつきが小さくなる
これが中心極限定理の実例です。十分大きな標本サイズであれば、母集団の分布に関係なく、標本平均は正規分布に近づきます。
信頼区間:推定の不確実性を表現する
「標本から母集団を推測する」際に重要なのは、推定値だけでなく「どの程度確からしいか」も示すことです。これが信頼区間の役割です。
信頼区間の意味と計算
from scipy import stats
# 顧客満足度調査の例
np.random.seed(123)
sample_data = np.random.normal(7.2, 1.8, 100) # 100人の回答
# 標本統計量
sample_mean = np.mean(sample_data)
sample_std = np.std(sample_data, ddof=1)
n = len(sample_data)
# 95%信頼区間の計算
confidence_level = 0.95
alpha = 1 - confidence_level
t_value = stats.t.ppf(1 - alpha/2, df=n-1)
margin_error = t_value * (sample_std / np.sqrt(n))
ci_lower = sample_mean - margin_error
ci_upper = sample_mean + margin_error
print(f"標本平均: {sample_mean:.2f}")
print(f"95%信頼区間: [{ci_lower:.2f}, {ci_upper:.2f}]")
print(f"\n解釈: 母集団の平均満足度は95%の確率で{ci_lower:.2f}点から{ci_upper:.2f}点の間にある")
この結果は「母集団の真の平均満足度は95%の確率で○○点から○○点の間にある」と解釈されます。つまり、同じ方法で100回調査を行ったとすれば、そのうち95回は真の母集団平均がこの区間に含まれることを意味します。
信頼区間の幅に影響する要因
信頼区間の幅は以下の要因で決まります:
# 信頼区間の幅に影響する要因の可視化
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 1. 標本サイズの影響
sample_sizes = [20, 50, 100, 200, 500]
widths = []
for n in sample_sizes:
t_val = stats.t.ppf(0.975, df=n-1)
width = 2 * t_val * (1.8 / np.sqrt(n)) # 標準偏差1.8と仮定
widths.append(width)
axes[0].plot(sample_sizes, widths, 'bo-')
axes[0].set_xlabel('標本サイズ')
axes[0].set_ylabel('95%信頼区間の幅')
axes[0].set_title('標本サイズの影響')
axes[0].grid(True, alpha=0.3)
# 2. 信頼度の影響
confidence_levels = [0.80, 0.85, 0.90, 0.95, 0.99]
widths_conf = []
for conf in confidence_levels:
alpha = 1 - conf
t_val = stats.t.ppf(1 - alpha/2, df=99)
width = 2 * t_val * (1.8 / np.sqrt(100))
widths_conf.append(width)
axes[1].plot([c*100 for c in confidence_levels], widths_conf, 'ro-')
axes[1].set_xlabel('信頼度 (%)')
axes[1].set_ylabel('信頼区間の幅')
axes[1].set_title('信頼度の影響')
axes[1].grid(True, alpha=0.3)
# 3. 標準偏差の影響
std_devs = [0.5, 1.0, 1.5, 2.0, 2.5]
widths_std = []
for std in std_devs:
t_val = stats.t.ppf(0.975, df=99)
width = 2 * t_val * (std / np.sqrt(100))
widths_std.append(width)
axes[2].plot(std_devs, widths_std, 'go-')
axes[2].set_xlabel('標準偏差')
axes[2].set_ylabel('95%信頼区間の幅')
axes[2].set_title('標準偏差の影響')
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
このグラフから以下のことがわかります:
- 標本サイズが大きくなるほど信頼区間は狭くなる(より精密な推定)
- 信頼度を高くするほど信頼区間は広くなる(より確実だが精度は下がる)
- データのばらつきが大きいほど信頼区間は広くなる(推定が困難)
実務では、これらの関係を理解して適切な調査設計を行うことが重要です。
仮説検定の実践
仮説検定の考え方
仮説検定は「偶然では説明できないほど大きな違いがあるかどうか」を統計的に判断する手法です。ビジネスの現場では、以下のような場面で頻繁に使われます:
- 新しいマーケティング施策に効果があったか?
- 商品改良により顧客満足度が向上したか?
- 地域によって売上に差があるか?
仮説検定の基本ステップ
Step 1: 帰無仮説と対立仮説の設定
- 帰無仮説(H₀): 「差がない」「効果がない」という仮説
- 対立仮説(H₁): 「差がある」「効果がある」という仮説
Step 2: 有意水準の設定
- 通常は5%(0.05)を使用
- 「偶然でこの結果が起こる確率が5%以下なら、偶然ではないと判断する」
Step 3: 統計量の計算とp値の算出
Step 4: 結論の導出
- p値 ≤ 有意水準 → 帰無仮説を棄却(差があると判断)
- p値 > 有意水準 → 帰無仮説を採択(差があるとは言えない)
t検定:平均の比較
一標本t検定:基準値との比較
# 一標本t検定の例:新商品の満足度が基準値(7.0)より高いか?
np.random.seed(456)
new_product_scores = np.random.normal(7.3, 1.2, 50) # 新商品の満足度データ
# 基準値
target_value = 7.0
# 一標本t検定の実行
t_statistic, p_value = stats.ttest_1samp(new_product_scores, target_value)
print("=== 一標本t検定の結果 ===")
print(f"標本平均: {np.mean(new_product_scores):.3f}")
print(f"基準値: {target_value}")
print(f"t統計量: {t_statistic:.3f}")
print(f"p値: {p_value:.3f}")
# 結果の解釈
alpha = 0.05
if p_value < alpha:
print(f"\n結論: p値({p_value:.3f}) < 有意水準({alpha})")
print("→ 新商品の満足度は基準値より有意に高い")
else:
print(f"\n結論: p値({p_value:.3f}) ≥ 有意水準({alpha})")
print("→ 新商品の満足度が基準値より高いとは言えない")
二標本t検定:2つのグループの比較
# 二標本t検定の例:A/Bテストの効果検証
np.random.seed(789)
# A群(従来版)とB群(新版)のコンバージョン率
group_a = np.random.binomial(1, 0.12, 1000) # 従来版のCV率12%
group_b = np.random.binomial(1, 0.15, 1000) # 新版のCV率15%
# 実際にはコンバージョン率の差を検定する場合は比率の検定を使うべきですが、
# ここではt検定の例として説明します
# 独立二標本t検定
t_statistic, p_value = stats.ttest_ind(group_a, group_b)
print("=== 二標本t検定の結果 ===")
print(f"A群の平均: {np.mean(group_a):.3f}")
print(f"B群の平均: {np.mean(group_b):.3f}")
print(f"差: {np.mean(group_b) - np.mean(group_a):.3f}")
print(f"t統計量: {t_statistic:.3f}")
print(f"p値: {p_value:.3f}")
# 効果量(Cohen's d)の計算
pooled_std = np.sqrt(((len(group_a)-1)*np.var(group_a, ddof=1) +
(len(group_b)-1)*np.var(group_b, ddof=1)) /
(len(group_a) + len(group_b) - 2))
cohens_d = (np.mean(group_b) - np.mean(group_a)) / pooled_std
print(f"効果量(Cohen's d): {cohens_d:.3f}")
# 結果の解釈
if p_value < 0.05:
print(f"\n結論: 新版は従来版より有意に効果が高い")
if abs(cohens_d) > 0.8:
effect_size = "大"
elif abs(cohens_d) > 0.5:
effect_size = "中"
elif abs(cohens_d) > 0.2:
effect_size = "小"
else:
effect_size = "ほとんどなし"
print(f"効果の大きさ: {effect_size}")
else:
print(f"\n結論: 新版の効果があるとは言えない")
カイ二乗検定:独立性の検定
カテゴリデータの関連性を調べる際に使用される検定です。
# カイ二乗検定の例:性別と商品選好の関連性
# クロス集計表の作成
observed = np.array([
[120, 80], # 男性:商品A好み, 商品B好み
[90, 110] # 女性:商品A好み, 商品B好み
])
# カイ二乗検定の実行
chi2_statistic, p_value, dof, expected = stats.chi2_contingency(observed)
print("=== カイ二乗検定の結果 ===")
print("観測値:")
print(observed)
print("\n期待値:")
print(expected.round(1))
print(f"\nカイ二乗統計量: {chi2_statistic:.3f}")
print(f"p値: {p_value:.3f}")
print(f"自由度: {dof}")
# 結果の解釈
if p_value < 0.05:
print(f"\n結論: 性別と商品選好には有意な関連性がある")
else:
print(f"\n結論: 性別と商品選好に関連性があるとは言えない")
# 関連の強さ(Cramér's V)
n = np.sum(observed)
cramers_v = np.sqrt(chi2_statistic / (n * (min(observed.shape) - 1)))
print(f"関連の強さ(Cramér's V): {cramers_v:.3f}")
相関分析
相関係数の種類と使い分け
相関分析は2つの変数間の関係の強さと方向を調べる手法です。ビジネスでは「価格と売上」「広告費と認知度」「気温と飲料売上」などの関係を分析する際に使われます。
ピアソンの相関係数
使用条件: 両方の変数が連続値で、線形関係がある場合
# ピアソン相関の例:広告費と売上の関係
np.random.seed(100)
advertising_cost = np.random.uniform(50, 500, 100) # 広告費(万円)
# 売上は広告費に比例 + ランダムノイズ
sales = 2.5 * advertising_cost + np.random.normal(0, 50, 100) + 200
# ピアソン相関係数の計算
pearson_corr, p_value = stats.pearsonr(advertising_cost, sales)
print(f"ピアソン相関係数: {pearson_corr:.3f}")
print(f"p値: {p_value:.3f}")
# 可視化
plt.figure(figsize=(10, 6))
plt.scatter(advertising_cost, sales, alpha=0.7)
plt.xlabel('広告費(万円)')
plt.ylabel('売上(万円)')
plt.title(f'広告費と売上の関係 (r = {pearson_corr:.3f})')
# 回帰直線の追加
z = np.polyfit(advertising_cost, sales, 1)
p = np.poly1d(z)
plt.plot(advertising_cost, p(advertising_cost), "r--", alpha=0.8)
plt.grid(True, alpha=0.3)
plt.show()
スピアマンの順位相関係数
使用条件: 変数が順序データの場合、または単調関係(必ずしも線形でない)がある場合
# スピアマン相関の例:顧客ランクと購入金額
customer_rank = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 1=VIP, 10=一般
# 非線形だが単調な関係
purchase_amount = [1000, 800, 600, 500, 350, 250, 180, 120, 80, 50]
# ピアソンとスピアマンの比較
pearson_corr, _ = stats.pearsonr(customer_rank, purchase_amount)
spearman_corr, _ = stats.spearmanr(customer_rank, purchase_amount)
print(f"ピアソン相関係数: {pearson_corr:.3f}")
print(f"スピアマン相関係数: {spearman_corr:.3f}")
# 可視化
plt.figure(figsize=(10, 6))
plt.scatter(customer_rank, purchase_amount, s=100, alpha=0.7)
plt.xlabel('顧客ランク(1=VIP, 10=一般)')
plt.ylabel('月間購入金額(万円)')
plt.title('顧客ランクと購入金額の関係')
plt.grid(True, alpha=0.3)
plt.show()
print("\n解釈:")
print("スピアマン相関係数の方が高い理由:")
print("→ 関係は単調だが非線形なため、順位相関の方が適している")
相関と因果の違い
相関分析で最も重要な注意点は「相関があっても因果関係があるとは限らない」ことです。
# 擬似相関の例:アイスクリーム売上と水難事故の関係
np.random.seed(200)
months = np.arange(1, 13)
temperature = [2, 4, 8, 15, 20, 25, 28, 27, 22, 16, 9, 4] # 月別平均気温
# 気温に依存する2つの変数
ice_cream_sales = [temp * 2 + np.random.normal(0, 5) for temp in temperature]
drowning_accidents = [temp * 0.3 + np.random.normal(0, 2) for temp in temperature]
# 相関計算
corr_ice_drowning, p_val = stats.pearsonr(ice_cream_sales, drowning_accidents)
print(f"アイスクリーム売上と水難事故の相関: {corr_ice_drowning:.3f}")
print(f"p値: {p_val:.3f}")
# 可視化
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
# 気温とアイスクリーム
ax1.scatter(temperature, ice_cream_sales)
ax1.set_xlabel('気温(℃)')
ax1.set_ylabel('アイスクリーム売上')
ax1.set_title('気温 vs アイスクリーム売上')
# 気温と水難事故
ax2.scatter(temperature, drowning_accidents)
ax2.set_xlabel('気温(℃)')
ax2.set_ylabel('水難事故件数')
ax2.set_title('気温 vs 水難事故')
# アイスクリームと水難事故(擬似相関)
ax3.scatter(ice_cream_sales, drowning_accidents, color='red')
ax3.set_xlabel('アイスクリーム売上')
ax3.set_ylabel('水難事故件数')
ax3.set_title('アイスクリーム vs 水難事故\n(擬似相関)')
plt.tight_layout()
plt.show()
print("\n重要なポイント:")
print("1. アイスクリーム売上と水難事故に相関があっても、因果関係はない")
print("2. 真の原因は「気温」という第3の変数")
print("3. ビジネス分析でも同様の擬似相関に注意が必要")
実際のビジネス課題での統計分析例
ケーススタディ1:売上データの分析
# 実際のビジネス問題:月別売上の分析と予測
np.random.seed(300)
# 2年間の月別売上データを生成(季節性とトレンドを含む)
months = np.arange(1, 25) # 24ヶ月
base_sales = 1000
trend = months * 20 # 月次20万円のトレンド
seasonality = 200 * np.sin(2 * np.pi * months / 12) # 季節性
noise = np.random.normal(0, 100, 24) # ランダムノイズ
monthly_sales = base_sales + trend + seasonality + noise
# データフレームの作成
import pandas as pd
sales_df = pd.DataFrame({
'month': months,
'sales': monthly_sales,
'year': [1 if m <= 12 else 2 for m in months],
'season': ['spring' if m%12 in [3,4,5] else
'summer' if m%12 in [6,7,8] else
'autumn' if m%12 in [9,10,11] else
'winter' for m in months]
})
print("=== 売上データ分析 ===")
print(f"全期間平均売上: {sales_df['sales'].mean():.0f}万円")
print(f"売上の標準偏差: {sales_df['sales'].std():.0f}万円")
print(f"最高売上月: {sales_df['sales'].max():.0f}万円")
print(f"最低売上月: {sales_df['sales'].min():.0f}万円")
# 季節性の分析
seasonal_stats = sales_df.groupby('season')['sales'].agg(['mean', 'std'])
print(f"\n季節別売上統計:")
print(seasonal_stats.round(0))
# 年次比較
yearly_stats = sales_df.groupby('year')['sales'].agg(['mean', 'std'])
print(f"\n年次売上統計:")
print(yearly_stats.round(0))
# t検定による年次差の検定
year1_sales = sales_df[sales_df['year'] == 1]['sales']
year2_sales = sales_df[sales_df['year'] == 2]['sales']
t_stat, p_val = stats.ttest_ind(year1_sales, year2_sales)
print(f"\n年次差の検定:")
print(f"t統計量: {t_stat:.3f}")
print(f"p値: {p_val:.3f}")
if p_val < 0.05:
print("→ 2年目の売上は1年目と有意に異なる")
else:
print("→ 2年目の売上は1年目と有意な差はない")
# 可視化
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# 時系列プロット
axes[0,0].plot(months, monthly_sales, 'bo-')
axes[0,0].set_xlabel('月')
axes[0,0].set_ylabel('売上(万円)')
axes[0,0].set_title('月別売上推移')
axes[0,0].grid(True, alpha=0.3)
# 季節別ボックスプロット
season_order = ['spring', 'summer', 'autumn', 'winter']
sales_df_ordered = sales_df.copy()
sales_df_ordered['season'] = pd.Categorical(sales_df_ordered['season'],
categories=season_order, ordered=True)
import seaborn as sns
sns.boxplot(data=sales_df_ordered, x='season', y='sales', ax=axes[0,1])
axes[0,1].set_title('季節別売上分布')
# 年次比較
sns.boxplot(data=sales_df, x='year', y='sales', ax=axes[1,0])
axes[1,0].set_title('年次別売上分布')
# ヒストグラム
axes[1,1].hist(monthly_sales, bins=10, alpha=0.7, edgecolor='black')
axes[1,1].set_xlabel('売上(万円)')
axes[1,1].set_ylabel('頻度')
axes[1,1].set_title('売上分布')
plt.tight_layout()
plt.show()
ケーススタディ2:A/Bテストの分析
# A/Bテストの例:ウェブサイトのデザイン変更効果
np.random.seed(400)
# A群(現行デザイン)とB群(新デザイン)のデータ
n_users_a = 1000
n_users_b = 1000
conversion_rate_a = 0.08 # 現行デザインのCV率8%
conversion_rate_b = 0.10 # 新デザインのCV率10%
# コンバージョンデータの生成
conversions_a = np.random.binomial(1, conversion_rate_a, n_users_a)
conversions_b = np.random.binomial(1, conversion_rate_b, n_users_b)
# 基本統計
cv_rate_a = np.mean(conversions_a)
cv_rate_b = np.mean(conversions_b)
improvement = (cv_rate_b - cv_rate_a) / cv_rate_a * 100
print("=== A/Bテスト結果 ===")
print(f"A群(現行)CV率: {cv_rate_a:.3f} ({cv_rate_a*100:.1f}%)")
print(f"B群(新版)CV率: {cv_rate_b:.3f} ({cv_rate_b*100:.1f}%)")
print(f"改善率: {improvement:.1f}%")
# 統計的有意性の検定(二項比率の検定)
from statsmodels.stats.proportion import proportions_ztest
counts = [np.sum(conversions_b), np.sum(conversions_a)]
nobs = [n_users_b, n_users_a]
z_stat, p_value = proportions_ztest(counts, nobs)
print(f"\nz統計量: {z_stat:.3f}")
print(f"p値: {p_value:.3f}")
# 信頼区間の計算
from statsmodels.stats.proportion import proportion_confint
ci_a = proportion_confint(np.sum(conversions_a), n_users_a, alpha=0.05)
ci_b = proportion_confint(np.sum(conversions_b), n_users_b, alpha=0.05)
print(f"\nA群の95%信頼区間: [{ci_a[0]:.3f}, {ci_a[1]:.3f}]")
print(f"B群の95%信頼区間: [{ci_b[0]:.3f}, {ci_b[1]:.3f}]")
# 結果の解釈
if p_value < 0.05:
print(f"\n結論: 新デザインは統計的に有意な改善効果がある(p < 0.05)")
# 実用的意義の評価
if improvement > 10:
practical_significance = "大きな改善"
elif improvement > 5:
practical_significance = "中程度の改善"
elif improvement > 1:
practical_significance = "小さな改善"
else:
practical_significance = "わずかな改善"
print(f"実用的意義: {practical_significance}")
# ビジネス影響の計算
monthly_visitors = 10000 # 月間訪問者数
expected_additional_conversions = monthly_visitors * (cv_rate_b - cv_rate_a)
revenue_per_conversion = 5000 # コンバージョン当たり売上
monthly_revenue_increase = expected_additional_conversions * revenue_per_conversion
print(f"\nビジネス影響予測:")
print(f"月間追加コンバージョン: {expected_additional_conversions:.0f}件")
print(f"月間売上増加: {monthly_revenue_increase:,.0f}円")
else:
print(f"\n結論: 新デザインの改善効果は統計的に有意ではない(p ≥ 0.05)")
print("→ さらなる改善案の検討または追加データの収集が必要")
# 可視化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# CV率の比較
groups = ['A群(現行)', 'B群(新版)']
rates = [cv_rate_a, cv_rate_b]
colors = ['lightblue', 'lightcoral']
bars = ax1.bar(groups, rates, color=colors, alpha=0.7)
ax1.set_ylabel('コンバージョン率')
ax1.set_title('A/Bテスト結果')
ax1.set_ylim(0, max(rates) * 1.2)
# バーの上に数値を表示
for bar, rate in zip(bars, rates):
height = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2., height + 0.001,
f'{rate:.3f}\n({rate*100:.1f}%)',
ha='center', va='bottom')
# 信頼区間の可視化
x_pos = np.arange(len(groups))
ax2.bar(x_pos, rates, color=colors, alpha=0.7,
yerr=[[rates[0]-ci_a[0], rates[1]-ci_b[0]],
[ci_a[1]-rates[0], ci_b[1]-rates[1]]],
capsize=5)
ax2.set_xticks(x_pos)
ax2.set_xticklabels(groups)
ax2.set_ylabel('コンバージョン率')
ax2.set_title('95%信頼区間付き')
plt.tight_layout()
plt.show()
まとめと次のステップ
統計学習得の重要ポイント
この記事で学習した統計学の基礎知識は、データサイエンスの土台となる重要な概念です。完璧に理解する必要はありませんが、**「なぜその手法を使うのか」「結果をどう解釈するのか」**を理解することが最も重要です。
記憶すべき核心概念:
- 記述統計: データの特徴を数値で要約する方法
- 確率分布: データの不確実性を数学的に表現する枠組み
- 推測統計: 限られたデータから全体について推論する手法
- 仮説検定: 統計的な証拠に基づいて意思決定を行う方法
- 相関分析: 変数間の関係を定量化する手法
実務での活用指針
統計学を実務で活用する際は、以下の点に注意してください:
データの性質を理解する 統計手法を選択する前に、データが連続値なのかカテゴリデータなのか、正規分布に従うのかなど、データの性質を必ず確認しましょう。
結果の解釈は慎重に 統計的有意性があっても、実用的意義があるとは限りません。ビジネス上の意味も含めて総合的に判断することが重要です。
前提条件の確認 各統計手法には前提条件があります。これらを満たしているかを確認せずに結果を解釈するのは危険です。
継続学習のロードマップ
初級から中級へ(3-6ヶ月)
- 回帰分析の詳細学習
- 分散分析(ANOVA)
- 実験計画法の基礎
中級から上級へ(6-12ヶ月)
- 多変量解析
- 時系列分析
- ベイズ統計
実践力向上(継続的)
- Kaggleコンペティションでの実践
- 業務データでの分析経験
- 統計ソフトウェア(R、Python)の習熟
参考リソース
書籍
- 「統計学入門」(東京大学出版会)
- 「Pythonで学ぶ統計学入門」
- 「統計思考の世界」
オンライン学習
- Khan Academy Statistics
- Coursera「Introduction to Statistics」
- edX「Probability and Statistics in Data Science」
次に読むべき記事
統計学の基礎を理解したあなたは、以下の記事も参考にしてください:
- 機械学習 入門: 統計学を発展させた機械学習の世界
- Python データ分析 入門: Pythonでの統計分析実装
- データサイエンティスト スキル: 統計学を含む必要スキル全体像
統計学は決して一朝一夕に身につくものではありませんが、基礎概念を理解し、実際のデータで練習を重ねることで、必ずデータ分析の強力な武器となります。まずは今日学んだ内容を、身近なデータで実践してみてください。