米価格は西高東低なのか?(対応がない場合の2標本t検定で検証)
米価格が世間のニュースの主要な話題になって以来、世間でまことしやかにささやかれる噂として、 「西日本は米が高くて東日本は米が安い」 と言われています。
日本農業新聞 「[ニッポンの米]店頭価格は西高東低? 生産量少 在庫少で上昇 本紙9都市調査」
https://www.agrinews.co.jp/news/index/306988
産経ニュース 「コメ価格「西高東低」、岡山は青森・八戸より1600円高い 出荷早い西日本に注文殺到」
https://www.sankei.com/article/20250529-F7VAGPE4UROWLL2PV524E6JTXM/
そこで今回は、対応がない場合の2標本t検定の例としてちょうど最適な題材かと思ったので、総務省の統計データを使って、この 「西高東低」説が統計的に有意なのかをPythonで検証 してみました。
本当はもう少しタイムリーな時期に投稿したかったのですが、これはもともと私が通う社会人大学院で「あなたの好きなデータ分析を行う」という課題で行った分析で
ネットからコピペしたと誤認されると面倒なので、(一応ではありますが)秋まで記事化を控えていました😅
使用するデータ・分析環境
使用するデータ
- 資料源: 政府統計の総合窓口(e-Stat)で公開されている総務省統計局「小売物価統計調査(動向編)」
- 調査年月: 2025年5月分
- 対象品目: 「うるち米(単一原料米,「コシヒカリ」) 5kg 当たり価格」
- 対象地域: 調査対象となっている日本全国の都道府県庁所在市及び人口15万以上の市
総務省統計局は毎月、人口15万以上の市について様々な製品の小売価格を公表していますので今回は米価格の部分をありがたく使わせていただきます。
分析環境
今回はGoogle Colaboratoryを使用します(執筆当時でPython 3.11.13)。
おそらくJupyter Notebook環境であれば問題なく動作すると思います。
対応がない場合の2標本t検定について
対応がない2標本t検定(独立2標本t検定) は、2つの独立した集団の平均値に統計的な差があるか偶然なのかどうかを検定する方法です。たとえば、「東日本」と「西日本」のように、互いに無関係なグループ間で平均値を比較したい場合に用いられます。
主な特徴
・サンプル間に対応(ペア)がない(例:AグループとBグループの別々の人や都市など)
・各サンプルは互いに独立している
検定の流れ
帰無仮説(H₀):「2つの集団の平均値は等しい」
対立仮説(H₁):「2つの集団の平均値は異なる(または一方が高い)」
各グループの平均値・標準偏差・サンプルサイズを計算
t値とp値を算出し、有意水準(例:5%)と比較
p値が有意水準未満なら、平均値に有意な差があると判断
計算式
対応がない2標本t検定(独立2標本t検定)におけるt値計算式(不等分散)
たとえばこんな場面に…
- 地域ごとの価格差の検証
- 男女間のテスト結果比較
- 新旧製品の性能比較 など
分析
前処理
今回は西日本・東日本は以下の条件で分割しています。
(ここの分け方はいろいろ議論あるかと思いますが、いったんこれで通します。今回の分析の結論はあくまで子の分け方においてという前提です。)
東日本
- 北海道
- 東北地方 - 青森県、岩手県、宮城県、秋田県、山形県、福島県
- 関東地方 - 茨城県、栃木県、群馬県、埼玉県、千葉県、東京都、神奈川県
- 甲信越地方 - 新潟県、山梨県、長野県
西日本
- 北陸地方 - 富山県、石川県、福井県
- 東海地方 - 静岡県、岐阜県、愛知県、三重県
- 近畿地方 - 滋賀県、京都府、大阪府、兵庫県、奈良県、和歌山県
- 中国地方 - 鳥取県、島根県、岡山県、広島県、山口県
- 四国地方 - 徳島県、香川県、愛媛県、高知県
- 九州・沖縄地方 - 福岡県、佐賀県、長崎県、熊本県、大分県、宮崎県、鹿児島県、沖縄県
ライブラリインストール
# グラフを日本語化する
!pip install japanize-matplotlib
# 各種ライブラリのインポート
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import japanize_matplotlib
from scipy import stats
表から該当範囲を抽出する
e-statからダウンロードした表から余計な部分を削除します。
# Excelファイルを読み込む
df_excel = pd.read_excel('b001-1.xlsx', header=None)
# 「札幌市」および「うるち米(単一原料米, 「コシヒカリ」)」が含まれる行を抽出
def contains_keywords(row):
return row.astype(str).str.contains('札幌市', regex=False).any() or \
row.astype(str).str.contains('うるち米(単一原料米, 「コシヒカリ」)', regex=False).any()
filtered_rows = df_excel[df_excel.apply(contains_keywords, axis=1)]
# e-statの表は横長に書かれているので、行を転置
df = filtered_rows.T
# 列名を ['index', 'value'] に指定
df.columns = ['city', 'price']
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 札幌市以降の各市の値を取得
sapporo_index = df[df['city'] == '札幌市'].index[0]
df_cities = df.loc[sapporo_index:]
# 表示
display(df_cities.head())
df_cities.head()の表示結果
| city | price | |
|---|---|---|
| 15 | 札幌市 | 4335.0 |
| 16 | 函館市 | 4053.0 |
| 17 | 旭川市 | 4228.0 |
| 18 | 青森市 | 4029.0 |
| 19 | 八戸市 | 3759.0 |
西日本・東日本の分類情報を追加する
あらかじめ西日本・東日本の分類を記載したcities.xlsxを用意しておきます。
1列目には市の名前が、2列目には西日本か東日本かが記載されています。
2つの表を市の名前をキーに結合します。
## 西日本/東日本が記載されたcities.xlsx を読み込む
df_regions = pd.read_excel('cities.xlsx')
# 2表を結合
df_merged = pd.merge(df_cities, df_regions, left_on='city', right_on='市', how='left')
# 不要な列を削除し、列名を整理する
df_merged = df_merged.drop(columns=['市'])
# 東日本・西日本を結合した結果を表示
display(df_merged.head())
df_merged.head()の出力結果
| city | price | region | |
|---|---|---|---|
| 0 | 札幌市 | 4335.0 | 東日本 |
| 1 | 函館市 | 4053.0 | 東日本 |
| 2 | 旭川市 | 4228.0 | 東日本 |
| 3 | 青森市 | 4029.0 | 東日本 |
| 4 | 八戸市 | 3759.0 | 東日本 |
記述統計量・可視化
# 記述統計量
# 西日本・東日本それぞれの記述統計量を取得
desc_west = df_merged[df_merged['region'] == '西日本']['price'].describe()
desc_east = df_merged[df_merged['region'] == '東日本']['price'].describe()
# DataFrameにまとめる
stats_df = pd.DataFrame({
'西日本': desc_west,
'東日本': desc_east
})
print(stats_df)
# 価格差の算出
diff_mean = desc_west['mean'] - desc_east['mean'] # 平均値の差
diff_median = desc_west['50%'] - desc_east['50%'] # 中央値の差
print(f"平均値の差 (西-東): {diff_mean:.1f} 円")
print(f"中央値の差(西-東): {diff_median:.1f} 円")
# ヒストグラム
plt.figure(figsize=(8, 4))
for region in ['東日本', '西日本']:
sns.histplot(df_merged[df_merged['region'] == region]['price'], label=region, kde=False, bins=10, alpha=0.7)
plt.xlabel('米価格(円/5kg)')
plt.ylabel('都市数')
plt.title('地域別米小売価格の分布(2025年5月)')
plt.legend(title='地域')
plt.show()
# 箱ひげ図
plt.figure(figsize=(6, 4))
sns.boxplot(x='region', y='price', data=df_merged)
plt.xlabel('地域')
plt.ylabel('米価格(円/5kg)')
plt.title('東日本・西日本の米小売価格比較(2025年5月)')
plt.show()
記述統計量
| 指標 | 西日本 | 東日本 |
|---|---|---|
| データ数 | 45.00 | 36.00 |
| 平均値 | 4945.71 | 4607.69 |
| 標準偏差 | 335.30 | 354.35 |
| 最小値 | 4218.00 | 3759.00 |
| 第1四分位数 | 4763.00 | 4370.25 |
| 中央値 | 4951.00 | 4640.00 |
| 第3四分位数 | 5135.00 | 4903.25 |
| 最大値 | 5631.00 | 5098.00 |
平均値の差 (西-東): 338.0 円
中央値の差(西-東): 311.0 円


ぱっと見は、西日本のほうが高そうな雰囲気がありますね。
また、分布としては正規分布にある程度近そうなので、2標本t検定を使って問題なさそう。
本番!統計的に有意か確認する(対応がない場合の2標本t検定)
ここからが本番です。
「西日本」都市群と「東日本」都市群の「うるち米(単一原料米,「コシヒカリ」)5kg 当たり価格」の平均値に統計的に有意な差があるか、特に西日本の平均価格が東日本の平均価格よりも高いかどうかを検証するため、2標本t検定を実施します。本検定における帰無仮説と対立仮説は以下の通りです。
仮説の設定
-
帰無仮説 (
)H_0
西日本都市群における米価格の母平均 は、東日本都市群における同価格の母平均μ_W と等しいか、またはそれよりも低い。μ_E
-
対立仮説 (
)H_1
西日本都市群における米価格の母平均 は、東日本都市群における同価格の母平均μ_W よりも高い。μ_E H_1: μ_W > μ_E
今回は 「西日本が高いか=片側の検定」 を行いたいので、この対立仮説に基づき、片側検定を行い帰無仮説を棄却できるか調べます。
# t検定の実施
# NaN を除外して配列化
east = df_merged.loc[df_merged["region"] == "東日本", "price"].dropna().astype(float).to_numpy()
west = df_merged.loc[df_merged["region"] == "西日本", "price"].dropna().astype(float).to_numpy()
# Welchのt検定(eastがwestより小さいか?)
t_stat, p_one = stats.ttest_ind(east, west,equal_var=False,alternative="less")
print(f"t 値 = {t_stat:.2f}, 片側 p 値 = {p_one:.5e}")
# 仮説検定の判定
alpha = 0.05
if (t_stat < 0) and (p_one < alpha):
print("有意差あり(西高東低が統計的に有意)")
else:
print("有意差なし")
t 値 = -4.37, 片側 p 値 = 2.02516e-05
有意差あり(西高東低が統計的に有意)
算出されたt値は-4.37であった。このt値に対応する片側検定のP値(P(T<=t) 片側)は2.03E-05(すなわち0.0000203)である。
このP値は、設定した有意水準 α = 0.05 よりも十分に小さい(0.0000203 < 0.05)。したがって 、帰無仮説 H₀ は棄却され、対立仮説 H₁ が採択される。
この結果から、「西日本」都市群は「東日本」都市群における2025年5月の平均小売価格よりも統計的に有意に高いと結論づけられる。
なぜ西日本のほうが米価格が高いのか?
一つは、この価格差の背景には、生産地からの距離が関連している可能性。
(農林水産省の作協調査によると2023年の米の生産上位10県がすべて東日本に所在)
あとは結構エイヤーで西日本と東日本を分けたので、地域区分の設定によって結果が変わる可能性は結構あります。
今後の展望としては、複数年にわたる時系列分析や、物流コスト、所得水準といった社会経済的要因を組み合わせることで、より詳細な価格差の構造解明が期待される(論文風に締めました)。
Discussion