今回はベンフォードの法則と
カイ二乗検定による不正の検出方法についてです。
解説動画はこちら
ベンフォードの法則
今回はベンフォードの法則のお話です
この法則は、自然界に出てくる多くの数値の最初の桁の分布が
この法則によると
ここから先のコードをGoogle Colabで試す場合は
以下をインストールしてください

ベンフォードの法則は何に使えるのか
次のような不正の調査などで利用されているらしいです
ここから先はどのように不正を発見するのかを
見てみましょう。
・カイ二乗検定を用いる方法
カイ二乗検定は観察されたデータと期待される
データの間の差を評価するための統計的手法のことです
データの分布が特定の比率に従っているかどうかを
検定するために使用されます
P値が一定の水準以下の場合(確率が低い)
あり得ないことが起きているとし
ここでは理論値と
観測値として、操作した比率のデータで検証していきます。
以下のコードで描画させる事ができます。

数字の3が出てくる確率を多くしました。
これが理論上の比率と合うのかを検定します。
この操作された比率の場合は
ベンフォードの法則に従わないと判断されるようです。
理論上の比率と観測された比率が正しいのか
異なるのかを仮説に置き
一般的には帰無仮説 には差がない(比率と等しい)とします。
観測結果と理論値の差が大きいと
大きくなる値になります。
自由度と有意水準から
カイ二乗分布を用いて、カイ二乗検定統計量の
閾値を求めていきます。
この閾値と先ほど求めたカイ二乗値を比較します。
その結果
閾値より小さい : よく有ること
となり、閾値より大きくなった場合は
P値(確率)が低くなり
あり得ない事が起きているとして帰無仮説を棄却し
対立仮説 : 観察された比率は期待される比率と異なる
を採択することになります。
カイ二乗分布の検定統計量の表を作ってみましょう

こんな感じで、有意水準と自由度で
閾値を求められます。
紙で行う場合は、この表を見て行いますが
Pythonプログラミングでは、これを使わず
直接確率を求められます。

この場合は求めたカイ二乗値は
閾値を超える値になるため
P値は低くなり、対立仮説の採択、となります。
データのリンク先
こちらをColabで行う場合は
ファイル置き場においてください。
データを読み込むコードはこれです。
これを集計して最初の数値のカウントと
比率を求めます。

カイ二乗検定を行うと
このデータの場合は
ベンフォードの法則に従っていないとは言えないようですね
正しいデータである可能性が高いです。
まとめ
ただし、必ずしも当てはまる訳ではないので
見極めは重要です。
もしお手持ちに会計データなどがある場合は
これで比率が正しいかを試してみると
面白いかもしれませんね。
今日はこれまでです
それでは
カイ二乗検定による不正の検出方法についてです。
解説動画はこちら
ベンフォードの法則
今回はベンフォードの法則のお話です
この法則は、自然界に出てくる多くの数値の最初の桁の分布が
「一様ではなくある特定の分布になっている」という法則のことです。
電気料金の請求書、住所の番地、株価、人口の数値、死亡率、川の長さなど...
特定の範囲に限定されたものは当てはまらないですが
これに当てはまるデータが世の中にはたくさんあります。
これに当てはまるデータが世の中にはたくさんあります。
この法則によると
大きな数値ほど最初の桁に現れる確率は小さくなり
ベンフォードの法則に従った最初の桁の理論的確率は
Pythonコードでは以下の式で表せます
Pythonコードでは以下の式で表せます
math.log10((d+1)/d)
ここから先のコードをGoogle Colabで試す場合は
以下をインストールしてください
pip install japanize_matplotlib
import math import numpy as np import scipy.stats as stats import japanize_matplotlib import matplotlib.pyplot as plt # ベンフォードの理論的な確率分布 def benford_distribution(): return [math.log10(1 + 1 / d) for d in range(1, 10)] benford_probs = benford_distribution() # 棒グラフの描画 labels = [1, 2, 3, 4, 5, 6, 7, 8, 9] bar_width = 0.35 x = np.arange(len(labels)) plt.bar(x - bar_width/2, benford_probs, width=bar_width, label='理論的確率', color='blue') plt.xlabel('最初の桁') plt.ylabel('確率') plt.title('ベンフォードの法則の比較') plt.xticks(x, labels) plt.legend() plt.tight_layout() plt.show()

ベンフォードの法則は何に使えるのか
次のような不正の調査などで利用されているらしいです
会計データ
選挙データ
化学データ
経済データ
ここから先はどのように不正を発見するのかを
見てみましょう。
ベンフォードの法則に従うかを調査する方法
・カイ二乗検定を用いる方法
カイ二乗検定は観察されたデータと期待される
データの間の差を評価するための統計的手法のことです
データの分布が特定の比率に従っているかどうかを
検定するために使用されます
検定統計量(カイ二乗統計量)を求め、カイ二乗分布を用いて
統計量と自由度からp値(確率)を求めます
統計量と自由度からp値(確率)を求めます
P値が一定の水準以下の場合(確率が低い)
あり得ないことが起きているとし
観察された比率が期待される比率と異なると
結論づける事ができます。
カイ二乗検定を用いたベンフォードの法則比率との比較結論づける事ができます。
ここでは理論値と
観測値として、操作した比率のデータで検証していきます。
以下のコードで描画させる事ができます。
import numpy as np import scipy.stats as stats import japanize_matplotlib import matplotlib.pyplot as plt # サンプルサイズを設定 n_samples = 1000 # 適宜変更 # ベンフォードの法則に従った最初の桁の理論的確率 benford_probs = [0.301, 0.176, 0.125, 0.097, 0.079, 0.067, 0.058, 0.051, 0.046] # 観測された出現確率を設定 observed_probs = [0.295, 0.172, 0.166, 0.092, 0.074, 0.062, 0.053, 0.046, 0.040] # 最初の桁のラベル labels = [1, 2, 3, 4, 5, 6, 7, 8, 9] # 棒グラフの幅 bar_width = 0.35 x = np.arange(len(labels)) # 棒グラフの描画 plt.bar(x - bar_width/2, benford_probs, width=bar_width, label='理論的確率', color='blue') plt.bar(x + bar_width/2, observed_probs, width=bar_width, label='観測された確率', color='orange') # グラフの設定 plt.xlabel('最初の桁') plt.ylabel('確率') plt.title('ベンフォードの法則の比較') plt.xticks(x, labels) plt.legend() plt.tight_layout() plt.show()

数字の3が出てくる確率を多くしました。
これが理論上の比率と合うのかを検定します。
# 出現数を計算 observed_counts = [int(p * n_samples) for p in observed_probs] expected_counts = [int(p * n_samples) for p in benford_probs] print("観測数 : ",observed_counts) print("期待値 : ",expected_counts) # カイ二乗検定を実施 chi2_stat, p_value = stats.chisquare(f_obs=observed_counts, f_exp=expected_counts) # 結果を表示 print("カイ二乗統計量:", chi2_stat) print("p値:", p_value) # 結論 alpha = 0.05 # 有意水準 if p_value < alpha: print("ベンフォードの法則に従わないと判断される") else: print("ベンフォードの法則に従うと判断される")
観測数 : [295, 172, 166, 92, 74, 62, 53, 46, 40]
期待値 : [301, 176, 125, 97, 79, 67, 58, 51, 46]
カイ二乗統計量: 16.30967165997854
p値: 0.03815623468852384
ベンフォードの法則に従わないと判断される
この操作された比率の場合は
ベンフォードの法則に従わないと判断されるようです。
カイ二乗検定の仕組み
ここからはカイ二乗検定の仕組みを
説明していきます。
説明していきます。
まずは前提として何を検定するのかを定めます。
通常は仮説を置く、ということをしています。
通常は仮説を置く、ということをしています。
仮説の設定:
帰無仮説 : 観察された比率は期待される比率と等しい
対立仮説 : 観察された比率は期待される比率と異なる
理論上の比率と観測された比率が正しいのか
異なるのかを仮説に置き
一般的には帰無仮説 には差がない(比率と等しい)とします。
1.カイ二乗値を求める
仮説を置いたら
まず初めにカイ二乗値を求めていきます。
カイ二乗値は下記の式で求められます。
(観測結果 - 理論値)**2 / 理論値 の合計値
まず初めにカイ二乗値を求めていきます。
カイ二乗値は下記の式で求められます。
(観測結果 - 理論値)**2 / 理論値 の合計値
観測結果と理論値の差が大きいと
大きくなる値になります。
2.自由度を求める
次に自由度ですが、この場合は
カテゴリ数 - 1
次に自由度ですが、この場合は
カテゴリ数 - 1
という値になります。
この場合、1-9までの数値の比率の個数だとすると
9-1 = 8になります。
3.カイ二乗の検定統計量と比べて大きいか小さいかを見る
この場合、1-9までの数値の比率の個数だとすると
9-1 = 8になります。
3.カイ二乗の検定統計量と比べて大きいか小さいかを見る
自由度と有意水準から
カイ二乗分布を用いて、カイ二乗検定統計量の
閾値を求めていきます。
この閾値と先ほど求めたカイ二乗値を比較します。
その結果
閾値より小さい : よく有ること
閾値より大きい : よく有ることではない
となり、閾値より大きくなった場合は
P値(確率)が低くなり
あり得ない事が起きているとして帰無仮説を棄却し
対立仮説 : 観察された比率は期待される比率と異なる
を採択することになります。
カイ二乗分布の検定統計量の表を作ってみましょう
import numpy as np import pandas as pd from scipy.stats import chi2 # 有意水準 alpha_levels = [0.99, 0.975, 0.95, 0.9, 0.1, 0.05, 0.025, 0.01] # 自由度 degrees_of_freedom = range(1, 10) # カイ二乗分布の臨界値を計算 chi_square_table = {'自由度/有意水準': degrees_of_freedom} for alpha in alpha_levels: chi_square_table[f'α = {alpha}'] = [chi2.ppf(1 - alpha, df) for df in degrees_of_freedom] # カイ二乗値のDataFrameを作成 chi_square_df = pd.DataFrame(chi_square_table) chi_square_df

こんな感じで、有意水準と自由度で
閾値を求められます。
紙で行う場合は、この表を見て行いますが
Pythonプログラミングでは、これを使わず
直接確率を求められます。
import numpy as np import matplotlib.pyplot as plt from scipy.stats import chi2 # 自由度 df = 8 # 有意水準 alpha = 0.05 # カイ二乗の臨界値を計算 critical_value = chi2.ppf(1 - alpha, df) # 観測したカイ二乗値 observed_chi_squared = sum([(o*1000-b*1000)**2/(b*1000) for b,o in zip(benford_probs,observed_probs)]) # xの範囲を設定 x = np.linspace(0, 30, 1000) # カイ二乗分布の確率密度関数を計算 y = chi2.pdf(x, df) # グラフを描画 plt.figure(figsize=(10, 6)) plt.plot(x, y, label=f'Chi-squared Distribution (df={df})', color='blue') plt.fill_between(x, y, where=(x >= critical_value), color='red', alpha=0.5, label='Rejection Region (p < 0.05)') plt.axvline(critical_value, color='red', linestyle='--', label=f'Critical Value = {critical_value:.2f}') plt.axvline(observed_chi_squared, color='green', linestyle='--', label=f'Observed Chi-squared = {observed_chi_squared:.2f}') plt.title('Chi-squared Distribution with 5% Significance Level') plt.xlabel('Chi-squared Value') plt.ylabel('Probability Density') plt.legend() plt.grid() plt.show()

この場合は求めたカイ二乗値は
閾値を超える値になるため
P値は低くなり、対立仮説の採択、となります。
実際のデータで比較するとどうなるか?
統計データを読み込みしてやってみましょう
e-statのデータを読み込みしてみます
(令和2年 都道府県・市区町村別の主な結果)
(令和2年 都道府県・市区町村別の主な結果)
データのリンク先
こちらをColabで行う場合は
ファイル置き場においてください。
データを読み込むコードはこれです。
import pandas as pd import numpy as np file_path = "major_results_2020.xlsx" # 8行目をヘッダーとして指定(0-indexed) df = pd.read_excel(file_path, header=8) df.iloc[0:2, 35:]
これを集計して最初の数値のカウントと
比率を求めます。
data = df.iloc[:, 35:] # 先頭の数字をカウントするための関数 def count_leading_digits(series): leading_digits = series.astype(str).str[0] return leading_digits.value_counts() # 全列のデータを結合して先頭の数字をカウント all_leading_digits = data.astype(str).stack().str[0] digit_counts = all_leading_digits.value_counts().sort_index() digit_counts = digit_counts[~digit_counts.index.str.contains('-')] # 結果を表示 print(digit_counts) # 比率を計算 total_count = digit_counts.sum() leading_digit_ratios = digit_counts / total_count print(leading_digit_ratios)
1 8324
2 4726
3 3465
4 2592
5 2240
6 1891
7 1594
8 1302
9 1330
Name: count, dtype: int64
1 0.303088
2 0.172080
3 0.126165
4 0.094378
5 0.081561
6 0.068854
7 0.058040
8 0.047408
9 0.048427
Name: count, dtype: float64
import numpy as np import math import matplotlib.pyplot as plt from scipy.stats import norm from collections import Counter # ベンフォードの法則による理論的な確率分布 def benford_distribution(): return [math.log10(1 + 1 / d) for d in range(1, 10)] # ベンフォードの理論的分布 benford_dist = benford_distribution() # サンプルデータの分布 sample_distribution = list(leading_digit_ratios.values) # グラフ描画 x = range(1, 10) fig, ax = plt.subplots(figsize=(10, 6)) ax.bar(x, sample_distribution, width=0.4, label="Sample Distribution", align="center", alpha=0.7) ax.bar([xi + 0.4 for xi in x], benford_dist, width=0.4, label="Benford Distribution", color="orange", alpha=0.7) # グラフの設定 ax.set_xticks(x) ax.set_xticklabels([str(d) for d in x]) ax.set_xlabel("Leading Digit") ax.set_ylabel("Frequency") ax.set_title("Comparison of Sample Distribution and Benford's Law") ax.legend() plt.show()

カイ二乗検定を行うと
# カイ二乗検定の実施 import scipy.stats as stats # サンプルの頻度を期待値に基づいて計算 expected_frequencies = [sum(sample_distribution) * p for p in benford_dist] observed_frequencies = sample_distribution # カイ二乗検定を実行 chi2_stat, p_value = stats.chisquare(observed_frequencies, f_exp=expected_frequencies) # 結果を表示 print("カイ二乗統計量:", chi2_stat) print("p値:", p_value) # 有意水準を設定 alpha = 0.05 if p_value < alpha: print("帰無仮説を棄却します。サンプルデータはベンフォードの法則に従っていない可能性があります。") else: print("帰無仮説を棄却できません。サンプルデータはベンフォードの法則に従っている可能性があります。")
カイ二乗統計量: 0.000739463432140538
p値: 0.9999999999999992
帰無仮説を棄却できません。
サンプルデータはベンフォードの法則に従っている可能性があります。
サンプルデータはベンフォードの法則に従っている可能性があります。
このデータの場合は
ベンフォードの法則に従っていないとは言えないようですね
正しいデータである可能性が高いです。
まとめ
ベンフォードの法則を用いると無作為に抽出したデータの場合
操作された数値を発見できる可能性はあります。
ただし、必ずしも当てはまる訳ではないので
見極めは重要です。
もしお手持ちに
これで比率が正しいかを試してみると
面白いかもしれませんね。
今日はこれまでです
それでは
コメントする