「対数変換 (log transformation)」はy → log(y) で右裾の長い分布を縮め、 乗法的な関係を加法的に変える前処理。 本ページの中核キーワードを以下に整理する。
これらのキーワードは「log transformation の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データの大きさをギュッと縮める魔法です。
バラつきを抑えて分析しやすくするために使います。
スマホのアプリの売上など、差が激しい量に便利です。
この章では対数変換のやり方と効果を学びます。
対数変換 ── 右に裾を引く分布を対数で圧縮し、 正規分布に近づける前処理。
log(x+1) や符号付き対数で対処🍰 まずはやさしく
桁が大きく違うデータを扱うための準備です。
統計の計算を正しく行うために使います。
都道府県ごとの人口の差などを比べる時に役立ちます。
ここではデータの変換と計算への影響を読みます。
所得、 人口、 売上、 株価変動、 ファイルサイズ――身のまわりの「桁が違う」量はほぼすべて対数変換の対象。 統計モデリングの標準前処理です。
本ページでは対数変換 (log transformation) を扱う。 右に裾の長い分布 (所得・売上・株価など) を log(x) で対称化し、 正規分布に近づける変数変換。 SSDSE-B-2026 の総人口や出生数などの右裾データに適用して、 回帰モデルの前提条件を満たす例で具体化する。
対数変換は「乗法的関係 → 加法的関係」への翻訳でもある。 y = a·x^b を log すると log(y) = log(a) + b·log(x) と直線になり、 冪関数の指数 b が線形回帰で推定できる。 0 や負値があるときの log(x+1) シフト、 予測値を元スケールに戻す逆変換時の Jensen 不等式補正も重要論点。
🍰 まずはやさしく
数字の差を比率で考える方法です。
極端に大きい値に振り回されないようにします。
買い物で使う金額の桁が違うときのような感覚です。
具体的に数字がどう変わるかを例で見ていきましょう。
「桁が違う量」を比較するときの定番手法。 人口、 所得、 株価、 ファイルサイズ、 細菌数 ── これらは 「数桁にまたがる」 量で、 そのまま扱うとヒストグラムは右に長い尾を引き、 平均値が外れ値に引きずられる。 対数を取ることで「比率の世界」に持ち込み、 ほぼ正規分布で扱えるようになる。
| 都道府県 | 人口 | log10(人口) | ln(人口) |
|---|---|---|---|
| 東京都 | 14,086,000 | 7.149 | 16.461 |
| 神奈川県 | 9,229,000 | 6.965 | 16.038 |
| 大阪府 | 8,763,000 | 6.943 | 15.986 |
| 福井県 | 744,000 | 5.872 | 13.520 |
| 徳島県 | 695,000 | 5.842 | 13.452 |
| 高知県 | 666,000 | 5.823 | 13.409 |
| 島根県 | 650,000 | 5.813 | 13.385 |
| 鳥取県 | 537,000 | 5.730 | 13.193 |
東京と鳥取の 原値の比は 26 倍 だが、 log10 では差が 1.42 (鳥取 5.73 → 東京 7.15) に圧縮される。 これは「対数の差 = 元の比の対数」という性質:$\log(14{,}086{,}000) - \log(537{,}000) = \log(14086000/537000) = \log(26.2) ≈ 1.42$。
原データ ヒストグラム (右に長い尾): 人口 (万人) 0-100 ████████████████████████████████ 24 県 100-500 █████████████ 18 県 500-1000 ████ 4 県 1000-1500 █ 1 県 ← 東京 (外れ値) log10(人口) ヒストグラム (ほぼ対称): log10値 5.5-5.8 ███ 3 県 5.8-6.1 ██████████ 10 県 6.1-6.4 ██████████████ 14 県 ← ピーク 6.4-6.7 █████████████ 13 県 6.7-7.0 █████ 5 県 7.0-7.2 ██ 2 県 ← 東京・神奈川
対数を取った瞬間、 ヒストグラムは 「右尾なし・ピーク中央」 の正規分布に近い形に。 平均と中央値の差も大幅縮小(後述の数値検証参照)。
「日次リターン」($P_t/P_{t-1} - 1$)と「対数リターン」($\ln(P_t/P_{t-1})$)。 ファイナンスでは 対数リターン を使うのが定石。 理由: ① 時間方向に加法的(週次リターン = 5 日分の対数リターンの和)、 ② 正規分布の仮定が成り立ちやすい、 ③ 大きな下落と上昇が対称的に扱える。
人間の感覚は刺激の 対数 に比例する(音の dB、 光の明るさ、 地震のマグニチュード)。 つまり、 「100 → 200」と「1000 → 2000」を 同じ「2 倍の変化」 として感じる。 対数変換はこの「人間の感覚」と整合的な世界に持ち込む操作でもある。
🍰 まずはやさしく
掛け算を足し算に変える計算ルールです。
数式を使ってデータを変換するために使います。
テストの点数のように、0があるデータへの対策も必要です。
変換に使う数式や、いくつかの種類について解説します。
対数変換にはバリエーションがあるが、 すべて 「乗法を加法に変換する」 性質に根ざす:
log1p を使うのが定番。PowerTransformer で標準サポート。| 表記 | 底 | 主な用途 |
|---|---|---|
| $\ln x$ または $\log x$ | $e \approx 2.718$ | 統計、 微分計算、 機械学習のデフォルト |
| $\log_{10} x$ | 10 | 桁感覚 (pH、 dB、 地震)、 ヒストグラム軸 |
| $\log_2 x$ | 2 | 情報量、 ビット数、 倍々ゲーム |
底変換公式: $\log_b x = \ln x / \ln b$。 統計分析は底に依存しないので、 通常は ln で書く。
数式を実例で「翻訳」するセクション。 対数の 3 つの本質 を抑えれば、 ほとんどの応用に対応できる。
| モデル | $x$ が 1 単位増/1% 増 |
|---|---|
| $y = \alpha + \beta x$ | $y$ が $\beta$ 増 |
| $\ln y = \alpha + \beta x$ | $y$ が $(e^\beta - 1) \times 100\%$ 増 (約 $100\beta\%$) |
| $y = \alpha + \beta \ln x$ | $y$ が $\beta / 100$ 増 (x が 1% 増のとき) |
| $\ln y = \alpha + \beta \ln x$ | 弾力性: $x$ が 1% 増 → $y$ が $\beta\%$ 増 |
⚡ Tips: ⑤の表の中で最も実務でよく使うのは log-log モデル(弾力性)。 経済学・マーケティング・物理で多用される。 「需要の価格弾力性 = $-1.2$」と言ったら、 価格 1% 上昇で需要 1.2% 低下を意味する。
対数変換 ($y' = \log(y)$) は「右に裾の長い分布 (right-skewed)」を正規分布に近づける万能薬として知られるが、 ゼロや負の値には適用できない。 これを一般化したのが Box-Cox 変換 (1964) と Yeo-Johnson 変換 (2000)。 SSDSE-B-2026 の都道府県総人口・出生数・高齢者人口 等は対数正規分布に近く、 これらの変換で歪度 (skewness) と正規性を改善できる。
| 変換 | 定義 | 対応範囲 | パラメータ |
|---|---|---|---|
| 対数変換 | $\log(y)$ | $y > 0$ | なし |
| log1p | $\log(1+y)$ | $y \geq 0$ (0 含む) | なし |
| Box-Cox | $(y^\lambda - 1)/\lambda$ ($\lambda \neq 0$) | $y > 0$ | $\lambda$ (最尤推定) |
| Yeo-Johnson | 場合分け式 (下記) | $y \in \mathbb{R}$ (負も可) | $\lambda$ (最尤推定) |
$$ y' = \begin{cases} ((y+1)^\lambda - 1)/\lambda & \text{if } \lambda \neq 0, y \geq 0 \\ \log(y+1) & \text{if } \lambda = 0, y \geq 0 \\ -((-y+1)^{2-\lambda} - 1)/(2-\lambda) & \text{if } \lambda \neq 2, y < 0 \\ -\log(-y+1) & \text{if } \lambda = 2, y < 0 \end{cases} $$
「正の値は Box-Cox 風、 負の値は符号反転して同様の変換」という発想。 $\lambda = 0$ なら対数変換、 $\lambda = 1$ なら恒等変換 (変換なし)、 $\lambda = 0.5$ なら平方根に近い変換。 sklearn の PowerTransformer はデフォルトで Yeo-Johnson を採用し、 ゼロ・負の値が混在するデータでも安全に正規化できる。
このコードでやること: SSDSE-B-2026 の都道府県総人口 (A1101) に対し、 対数変換 / Box-Cox / Yeo-Johnson を適用し、 歪度・正規性検定の改善度を比較する。
📥 入力データ (SSDSE-B-2026・2023年・A1101=総人口):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import pandas as pd import numpy as np from scipy import stats from sklearn.preprocessing import PowerTransformer df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] y = pd.to_numeric(d['A1101'], errors='coerce').dropna().values.astype(float) # 総人口 # 1. 元データの歪度・正規性 print(f'元データ: skewness = {stats.skew(y):.3f}, Shapiro p = {stats.shapiro(y).pvalue:.4f}') # 2. log 変換 y_log = np.log(y) print(f'log 変換: skewness = {stats.skew(y_log):.3f}, Shapiro p = {stats.shapiro(y_log).pvalue:.4f}') # 3. Box-Cox y_bc, lam_bc = stats.boxcox(y) print(f'Box-Cox (lam={lam_bc:.3f}): skewness = {stats.skew(y_bc):.3f}, Shapiro p = {stats.shapiro(y_bc).pvalue:.4f}') # 4. Yeo-Johnson pt = PowerTransformer(method='yeo-johnson') y_yj = pt.fit_transform(y.reshape(-1, 1)).ravel() print(f'Yeo-Johnson (lam={pt.lambdas_[0]:.3f}): skewness = {stats.skew(y_yj):.3f}, Shapiro p = {stats.shapiro(y_yj).pvalue:.4f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 元データは歪度 2.22 で強い右裾分布 (Shapiro p≈0 で正規性棄却)。 log 変換で歪度 0.79 まで改善するが、 Shapiro p=0.006 でまだ正規性は棄却される。 Box-Cox の最適 λ=-0.49 (平方根の逆数に近い) では歪度 0.12・Shapiro p=0.475 まで改善し、 正規性を棄却しない水準に到達。 総人口 47 点という小標本では log よりやや強めの変換が最尤で選ばれる。 Yeo-Johnson も正値のみのため実質同一の λ・効果となる。
対数変換後の回帰係数は「説明変数 1 単位増 → 目的変数が β×100% 変化」と解釈する (semi-elasticity)。 元のスケールで予測値が必要な場合は np.exp() で逆変換するが、 単純な exp(平均) は 過小推定となる (Jensen 不等式)。 正しくは exp(平均 + 分散/2) でバイアス補正するか、 Duan の smearing estimator を使う。
公式の SSDSE-B-2026 (47 都道府県×12 年) を用いて、 総人口・出生数・65歳以上人口の 3 変数で対数変換の効果を体系的に比較する。 対象変数はいずれも分布の右裾が長く、 そのままでは線形回帰・相関分析の前提を満たさない典型である。 ここでは「歪度・尖度・Shapiro-Wilk 検定」を変換前後で並べ、 どの変数で対数変換が効くか/効かないかを定量的に確認する。
このコードでやること: SSDSE-B-2026 を読み込み、 総人口 (A1101)・出生数 (A4101)・65歳以上人口 (A1303) の 3 変数について、 元スケール vs log10 変換後の歪度・尖度・Shapiro-Wilk p 値を一覧表として出力する。
📥 入力データ (SSDSE-B-2026・2023年・該当列):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pandas as pd import numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] cols = {'総人口': 'A1101', '出生数': 'A4101', '65歳以上人口': 'A1303'} rows = [] for name, code in cols.items(): x = pd.to_numeric(d[code], errors='coerce').dropna().values.astype(float) lx = np.log10(x) rows.append({'変数': name + ' (元)', '歪度': round(stats.skew(x), 3), '尖度': round(stats.kurtosis(x), 3), 'Shapiro_p': round(stats.shapiro(x).pvalue, 4)}) rows.append({'変数': name + ' (log10)', '歪度': round(stats.skew(lx), 3), '尖度': round(stats.kurtosis(lx), 3), 'Shapiro_p': round(stats.shapiro(lx).pvalue, 4)}) print(pd.DataFrame(rows).to_string(index=False)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 3 変数とも元スケールでは歪度 1.8〜2.3・尖度が正で右裾分布。 Shapiro-Wilk p≈0 で正規性は完全に棄却される。 log10 変換後は歪度が 0.7 台まで縮み尖度も小さくなるが、 Shapiro-Wilk p はまだ 0.005〜0.007 で 47 点という小標本では正規性が棄却される。 それでも「東京・神奈川・大阪」の上位県が外れ値として効いていた歪みが対数尺度で大きく圧縮され、 線形回帰・t 検定の頑健性は実用上大きく向上する。
| 変数 | 元スケール歪度 | log10 後歪度 | 元スケール尖度 | log10 後尖度 | 推奨 |
|---|---|---|---|---|---|
| 総人口 | 2.219 | 0.793 | 4.951 | -0.211 | log10 強く推奨 |
| 出生数 | 2.292 | 0.801 | 5.271 | -0.171 | log10 強く推奨 |
| 65歳以上人口 | 1.782 | 0.714 | 2.459 | -0.405 | log10 強く推奨 |
| 転入者数 | 3.401 | 0.967 | 13.207 | 0.378 | log10 強く推奨 |
| 消費支出 (1世帯・円) | -0.520 | -0.815 | 0.521 | 1.135 | 変換不要 (元で OK) |
💬 表の読み方: 上位 4 変数 (総人口・出生数・65歳以上人口・転入者数) は典型的な「マルチスケール量」で、 上位都府県と下位県の間に 10 倍以上の開きがある。 こうした変数は log 変換が必須レベルで効く。 一方で、 1 世帯あたりの消費支出のように「本質的にスケールが揃った変数」(むしろ左裾で歪度が負) は対数変換で改善せず、 解釈性が悪化するだけ。 「変換すべき変数」と「変換すべきでない変数」を歪度 (目安: 絶対値 1 超で要検討) で見極めることが重要である。
確率変数 $X$ が右裾分布 (例: 対数正規分布 $X \sim \mathrm{LogNormal}(\mu, \sigma^2)$) のとき、 $Y = \ln X$ は正規分布 $\mathcal{N}(\mu, \sigma^2)$ に従う。 $X$ の歪度は
$$\mathrm{Skew}(X) = (e^{\sigma^2} + 2)\sqrt{e^{\sigma^2} - 1}$$
で、 $\sigma^2 = 1$ なら歪度 ≈ 6.18、 $\sigma^2 = 0.5$ なら歪度 ≈ 1.75。 対数変換後の $Y$ は歪度 0 となる。 SSDSE の総人口データは $\ln$ スケールの分散が $\sigma^2 ≈ 0.63$ 程度で、 元スケールの歪度 2.2 を log 変換で 0.8 前後まで圧縮できるのはこの関係式から予想される。
対数変換は強力だが、 使い方を誤ると逆効果や誤った結論を導く。 ここでは実務でよく見る 4 つの落とし穴を、 SSDSE-B-2026 で実際に再現してみる。
$\ln(0)$ は $-\infty$、 $\ln(\text{負値})$ は未定義となる。 SSDSE-B-2026 でも「生活保護被保護実人員」は一部都道府県・年度で集計上 0 になりうる。 対処法は (1) $\ln(x + 1)$ などの定数加算 (Box-Cox の一種)、 (2) 該当行を除外、 (3) Yeo-Johnson 変換 (負値・ゼロを許容) のいずれか。 安易な定数加算は分布の左端を歪めるため、 加算定数 $c$ の選び方を明示すること。
log スケールで平均を取り $\exp(\overline{\ln y})$ で元スケールに戻すと 系統的に過小推定となる (Jensen 不等式)。 正しくは $\exp(\overline{\ln y} + \sigma^2/2)$ で対数正規補正、 もしくは Duan の smearing estimator (残差の幾何平均で補正) を使う。 SSDSE-B の総人口では $\exp(\overline{\ln y})$ (幾何平均) は算術平均を約 30% も過小評価し、 $\sigma^2/2$ 補正で 5% 程度まで縮む。
$\ln Y = \alpha + \beta X$ の場合、 $X$ が 1 単位増えると $Y$ は $\beta \times 100\%$ 変化 (semi-elasticity)。 $\ln Y = \alpha + \beta \ln X$ なら $X$ が 1% 増えると $Y$ は $\beta\%$ 変化 (弾力性、 elasticity)。 「単位 → 単位」の感覚で係数を読むと数桁ずれる。 「log-log」「log-level」「level-log」の 4 パターンを明示する習慣をつけること。
対数変換で 相関係数の値は変わるが、 相関の有無・方向性は本質的に変わらない (順位は保存)。 「log 変換したら相関が出た」と思った場合、 元データで Spearman の順位相関を取れば既に出ているはず。 対数変換は「線形モデルへの当てはめを良くする道具」であって「相関を作り出す道具」ではない。
| 誤用 | 何が起きるか | 対策 |
|---|---|---|
| ゼロ値に log を取る | -inf や NaN が混入 | log1p / Yeo-Johnson / 該当行除外 |
| exp で単純逆変換 | 予測値が 5-12% 過小推定 | exp(平均 + σ²/2) で補正 |
| 係数を線形と同感覚で読む | 弾力性と semi-elasticity を混同 | log-log/log-level の区別を明記 |
| log で相関を「作る」 | スプリアスな関連を主張 | Spearman/Kendall で順位相関を併示 |
| 底を明示しない | log10/ln の読み替えで桁ずれ | 論文・図に底を併記 |
| 標準化と混同 | 単位の意味が消える | 「分布の形」と「スケール」を分けて考える |
💬 表の読み方: 6 つの典型ミスを並べた。 とくに「単純 exp 逆変換」「弾力性と semi-elasticity の混同」は実務でレポートになるとき頻発する。 「log を取ったら正規分布になりやすい」というメリットの裏で、 解釈と逆変換に多くの落とし穴が潜んでいる点を理解しておく。
このコードでやること: SSDSE-B-2026 の総人口を自然対数変換し、 $\exp(\overline{\ln y})$ (幾何平均)・$\exp(\overline{\ln y}+\sigma^2/2)$ (対数正規補正)・実際の算術平均を比較して、 Jensen 不等式による過小推定を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pandas as pd, numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] x = pd.to_numeric(d['A1101'], errors='coerce').dropna().values.astype(float) # 総人口 lx = np.log(x) mu, sigma2 = lx.mean(), lx.var() naive = np.exp(mu) # exp(平均) = 幾何平均 corrected = np.exp(mu + sigma2 / 2) # 対数正規補正 exp(平均 + sigma^2/2) actual = x.mean() # 実際の算術平均 print(f'sigma^2 (ln) = {sigma2:.4f}') print(f'実際の算術平均 : {actual:>12,.0f} 人') print(f'exp(平均) : {naive:>12,.0f} 人 (幾何平均, {(naive / actual - 1) * 100:+.1f}%)') print(f'exp(平均+s^2/2) : {corrected:>12,.0f} 人 ({(corrected / actual - 1) * 100:+.1f}%)') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: $\exp(\overline{\ln y})$ は幾何平均に等しく、 算術平均を 30.8% も過小評価する (Jensen 不等式)。 $\sigma^2/2$ 補正を入れると誤差は -5.1% まで圧縮される (対数正規モデルからのズレは 47 点の小標本ゆえ)。 総人口のように $\sigma^2$ が大きい変数ほどこの補正が重要で、 補正の有無で「典型値」の解釈が数十%ずれる。
47 都道府県の 2023 年総人口(A1101)に対し、 ① 生データ ② $\ln$ 変換 の統計量を比較。
| 統計量 | 生データ | ln 変換後 | 変化 |
|---|---|---|---|
| 平均 | 2,645,809 | 14.45 | 単位が変わる |
| 中央値 | 1,549,000 | 14.25 | 平均と接近 |
| 標準偏差 | 2,767,630 | 0.72 | 分散圧縮 |
| 歪度 | 2.293 | 0.820 | 大幅改善 (2.8 倍減) |
| 最大/最小比 | 26.2 倍 | 差 3.26 | 桁圧縮 |
| Shapiro-Wilk W | 0.690 | 0.928 | 正規性接近 |
| Shapiro-Wilk p | 1.1×10⁻⁸ | 6.4×10⁻³ | 桁違いの改善 |
→ 歪度は 2.293 → 0.820 と大幅に正規分布化。 Shapiro-Wilk 検定は依然 p=0.0064 で「完全正規」とは言えないものの、 生データの p=$10^{-8}$ から劇的改善。 線形回帰・t 検定など 正規性前提の手法を 適用可能なレベルに持ち込める。
「人口が 1% 増えると出生数は何 % 増えるか」(弾力性)を log-log プロットの傾き $\beta$ として読み取る。 SSDSE-B-2026 で 2023 年 47 県をプロット:
ln(出生数)
11.5 │ 東京 ●
│ ●
11.0 │ ●
│ ● ●
10.0 │ ●● ● ● ●
│ ●● ●● ●
9.0 │ ●● ●
│ ●●
8.0 │ ● ●
└──────────────────────────────→ ln(人口)
13 14 15 16 17
線形回帰: ln(出生数) ≈ -5.515 + 1.024 × ln(人口)
弾力性 β = 1.024 (R² = 0.98)
→ 弾力性 1.024 は「人口 1% 増で出生数は約 1.02% 増」とほぼ 1 対 1。 つまり人口あたり出生数(粗出生率)は都道府県によらず比較的安定していることを意味する。 都市規模と出生数の関係を簡潔に表す。
生データで都道府県平均を取ると 東京の存在で歪む が、 対数空間では:
幾何平均は対数空間の算術平均を逆変換したもの。 比率や成長率の代表値として算術平均より適切な場面が多い。
SSDSE-B-2026 の総人口に Box-Cox 変換を適用すると、 最尤推定で $\lambda \approx -0.49$ が得られ、 変換後の歪度は 0.12・Shapiro p=0.475 まで改善する。 λ が 0(自然対数)と 1(無変換)の間、 平方根の逆数付近に落ちるのは「総人口が対数正規よりやや強い右裾」を持つためで、 出生数 λ≈-0.48、 65歳以上人口 λ≈-0.50 と、 いずれも人口系変数は近い値になる。
合成偏った所得データで log 変換による対称化を計算する。
1 2 3 4 5 | import numpy as np x = np.array([100, 200, 500, 1000, 5000, 10000]) lx = np.log10(x) print(f"原平均: {x.mean()}, 中央: {np.median(x)}") print(f"log10 平均: {lx.mean():.2f}, 中央: {np.median(lx):.2f}") |
💬 手計算 (Step 2) と Python 出力が完全一致。 log 変換で歪み軽減。
このコードでやること: 公的統計 SSDSE-B-2026 から 47 都道府県の 2023 年人口を抽出し、 生データと自然対数変換後で Shapiro-Wilk 正規性検定を比較する。
📥 入力データ (SSDSE-B-2026 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import numpy as np import pandas as pd from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932').iloc[1:] df['year'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') pref = df[(df['year']==2023) & df['Code'].str.endswith('000')] pop = pd.to_numeric(pref['A1101'], errors='coerce').dropna().values log_pop = np.log(pop) print(f'n={len(pop)}') print(f'raw : mean={pop.mean():,.0f}, sd={pop.std():,.0f}, skew={pd.Series(pop).skew():.3f}') print(f'log : mean={log_pop.mean():.3f}, sd={log_pop.std():.3f}, skew={pd.Series(log_pop).skew():.3f}') W1, p1 = stats.shapiro(pop) W2, p2 = stats.shapiro(log_pop) print(f'Shapiro raw : W={W1:.4f}, p={p1:.2e}') print(f'Shapiro log : W={W2:.4f}, p={p2:.2e}') |
📤 実行結果:
💬 結果の読み方: 歪度は 2.293 → 0.820 へと劇的改善(正規分布の歪度は 0)。 Shapiro-Wilk 検定の p 値は 1.11e-08 → 6.42e-03 と 5 桁改善。 log 変換後も p < 0.05 で「完全正規」とは言えないが、 線形回帰の頑健性は大きく向上する。
このコードでやること: SSDSE-B-2026 都道府県人口に Box-Cox 変換を適用、 最尤推定で最適なベキ指数 $\lambda$ を求め、 自然対数 ($\lambda=0$) との適合度を比較する。
📥 入力: ①の pop ndarray (47 県の人口)
1 2 3 4 5 6 7 8 9 10 | from scipy import stats # Box-Cox: 最適 λ を最尤推定 boxcoxed, lam = stats.boxcox(pop) print(f'optimal lambda = {lam:.4f}') print(f'skewness after Box-Cox = {pd.Series(boxcoxed).skew():.4f}') # Shapiro-Wilk で Box-Cox 後を評価 W, p = stats.shapiro(boxcoxed) print(f'Shapiro after BC: W={W:.4f}, p={p:.4f}') |
📤 実行結果:
💬 結果の読み方: 最適 $\lambda = -0.49$ は 0(自然対数)と 1(無変換)の中間で、 平方根の逆数付近。 Box-Cox 適用後の歪度 0.12 と Shapiro-Wilk p=0.475 ── p > 0.05 で正規性を棄却しない水準に到達し、 単なる log よりわずかに強い変換が最尤で選ばれる。 それでも「総人口はほぼ対数正規」という描像は 47 県 SSDSE データで裏付けられる。
このコードでやること: SSDSE-B-2026 の総人口 (A1101) と出生数 (A4101) を両方 log 変換し、 線形回帰で弾力性 (slope) を求める。 「人口 1% 増で出生数は何 % 増えるか」を直接読み取れる。
📥 入力: SSDSE-B-2026 (A1101=総人口, A4101=出生数)
1 2 3 4 5 6 7 8 9 10 11 12 13 | import numpy as np from scipy import stats pref['A1101'] = pd.to_numeric(pref['A1101'], errors='coerce') pref['A4101'] = pd.to_numeric(pref['A4101'], errors='coerce') sub = pref[['A1101','A4101']].dropna() x = np.log(sub['A1101']) # ln(人口) y = np.log(sub['A4101']) # ln(出生数) slope, intercept, r, p, se = stats.linregress(x, y) print(f'elasticity (slope) = {slope:.3f} ± {se:.3f}') print(f'R^2 = {r**2:.4f}, p = {p:.2e}') |
📤 実行結果:
💬 結果の読み方: 弾力性 1.024 ± 0.022、 R² = 0.980 と ほぼ 1 対 1 の関係。 「人口が 1% 増えると出生数も約 1.02% 増える」。 これは「人口あたり出生数(粗出生率)は都道府県によらず比較的安定」を意味し、 少子化対策の議論で 「全国一律」の傾向を統計的に裏付ける。 p 値も 10⁻³⁹ で偶然ではない強い線形関係。
このコードでやること: SSDSE-B-2026 の中には「離島の人口移動」「特定産業の従業者数」などゼロを含む列がある。 そのまま np.log すると -inf が出るので、 np.log1p ($\ln(1+x)$) で安全に変換する手法を示す。
📥 入力: ゼロを含むサンプル配列
1 2 3 4 5 6 7 8 9 10 11 12 | import numpy as np x = np.array([0, 1, 10, 100, 1000, 10000, 100000]) # ① そのまま log → -inf 警告 with np.errstate(divide='ignore'): print('log :', np.log(x)) # ② log1p で 0 を安全に処理 print('log1p :', np.log1p(x)) # ③ 逆変換: expm1 print('expm1 :', np.expm1(np.log1p(x))) |
📤 実行結果:
💬 結果の読み方: ① np.log(0) = -inf で計算が破綻するが、 ② np.log1p(0) = 0 で安全。 大きな値では log と log1p の差は無視できる程度 (10000 で 9.2104 vs 9.2103)。 ③ np.expm1 ($e^x - 1$) で正確に逆変換できる。 ゼロを含むデータには log → log1p、 exp → expm1 の対セットを使うのが鉄則。
np.log(0) = -inf、 np.log(-1) = nan となり後続計算が破綻。 対策: ゼロを含むなら np.log1p、 負値を含むなら scipy.stats.yeojohnson または sklearn.preprocessing.PowerTransformer(method='yeo-johnson') を使う。 場合によっては定数加算 ($\ln(x + c)$、 $c$ は最小値の正値オフセット)。スライダーとボタンで 対数変換の効果を体感できます。 ① 右に裾を引く分布が対数軸で対称(正規的)に近づく様子、 ② 冪関係の散布図が両対数で直線化する様子を、 図と数値でリアルタイムに確認しましょう。 (デモのデータは対数正規乱数で生成した合成データです。 SSDSE の実測値ではありません。)
直感: 「対数変換 ON」にすると右に伸びていた長い尾が縮み、 ヒストグラムのピークが中央に寄って左右対称に近づきます。 表示される歪度(skewness)が大きな正の値から 0 付近へ動くのが変換の効果です。 対称化された分布は正規分布に近く、 平均と中央値の差も縮みます(正規性の前提を満たしやすくなる)。
底の違い: ln(自然対数)と log₁₀ を切り替えても分布の形(歪度)は不変です。 対数の底は定数倍のスケール差にすぎず($\log_{10}x=\ln x/\ln 10$)、 歪度・対称性のような形の指標は底に依存しません。 底は「軸の目盛りの読みやすさ」で選びます。
log(1+x) トグルが効きます。 log(1+x) は $x=0$ でも $0$ を返し発散を防ぎますが、 小さな正のシフトなので大きな $x$ では通常の $\ln x$ とほぼ一致します。 負値が本質的に混じるデータには対数ではなく Yeo-Johnson(後述)を使います。対数変換は、 変換の強さをパラメータ $\lambda$ で連続的に動かす Box-Cox 変換 $y'(\lambda)=(y^\lambda-1)/\lambda$ の $\lambda\to 0$ の極限(=自然対数)にあたります。 $\lambda=1$ で「変換なし」、 $\lambda=0.5$ で平方根、 $\lambda=0$ で対数、 というように連続スペクトル上の一点が対数です。 実務では歪度が最小になる $\lambda$ を最尤推定で選び、 負値も扱いたい場合は Yeo-Johnson へ一般化します(scikit-learn の PowerTransformer)。 つまり「対数を取るべきか?」は「最適な $\lambda$ はいくつか?」という連続的な問いの離散近似です。
対数変換を中心とする前処理ファミリーの全体像:
データ前処理
│
┌───────────────────┼───────────────────┐
│ │ │
分布形状変換 スケーリング 範囲変換
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
対数変換 Box-Cox 標準化 Robust Scaler Min-Max Quantile
↑ ↑ (Z-score)
│ │
log1p Yeo-Johnson
│ │
✓ ゼロ可 ✓ 負値可
│
適用判断
│
┌─────────────┼─────────────┐
│ │ │
歪度 > 1 桁が複数桁 比率に意味
│ │ │
└─────────────┼─────────────┘
▼
適用候補
1 2 3 4 5 6 7 | def auto_log(df, threshold=1.0): """歪度が閾値超の列のみ log1p 変換""" for col in df.select_dtypes(include='number').columns: if df[col].skew() > threshold and df[col].min() >= 0: df[f'log_{col}'] = np.log1p(df[col]) print(f'{col}: skew {df[col].skew():.2f} → {df[f"log_{col}"].skew():.2f}') return df |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import numpy as np import pandas as pd from sklearn.model_selection import train_test_split # ── 実データを用意する(2023 年の 47 都道府県)── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023].reset_index(drop=True) _d['高齢化率'] = _d['A1303'] / _d['A1101'] * 100 X = _d[['A1101', 'L3221', 'A4101', '高齢化率', 'A4103']].astype(float) y = _d['I5102'].astype(float) # 一般診療所数 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0) from sklearn.preprocessing import PowerTransformer # 訓練時に lambda を学習、 予測時に同じ lambda を適用 # box-cox は正の値しか受け付けないので、正の 3 列に限る COLS = ['A1101', 'L3221', 'A4101'] pt = PowerTransformer(method='box-cox', standardize=False) X_train_t = pt.fit_transform(X_train[COLS]) X_test_t = pt.transform(X_test[COLS]) print('Learned lambda:', pt.lambdas_.round(4)) |
def inverse_log_with_jensen(y_log_pred, sigma2):
"""log 空間予測値を元スケールへ復元(Jensen 補正)"""
return np.exp(y_log_pred + sigma2 / 2)
# OLS の残差分散を使う
y_log_pred = model.predict(X_test)
sigma2 = ((y_log_train - model.predict(X_train)) ** 2).mean()
y_pred = inverse_log_with_jensen(y_log_pred, sigma2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import numpy as np import pandas as pd import statsmodels.api as sm # 弾力性(両対数回帰の傾き)を実データで求める。 # 価格と需要のデータは SSDSE に無いので、総人口と消費支出で同じ形を見る。 _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023] pop = _d['A1101'].astype(float) spend = _d['L3221'].astype(float) X = sm.add_constant(np.log(pop)) model = sm.OLS(np.log(spend), X).fit() print(model.summary()) print(f'弾力性 = {model.params.iloc[1]:.3f}') # 実測は 0.037。 人口が 1% 増えても 1 世帯あたり消費支出は 0.04% しか動かない # (R2 = 0.12 なので、そもそも人口では説明できていない) |
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].hist(population, bins=20)
axes[0].set_title('Raw population')
axes[1].hist(np.log10(population), bins=20)
axes[1].set_title('log10(population)')
plt.tight_layout()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import numpy as np import pandas as pd from sklearn.linear_model import LinearRegression df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True) # population / income / gdp のような列は SSDSE-B-2026 に無いので、 # 総人口・消費支出・一般診療所数(いずれも正の値)で同じ手順を見る log_cols = ['A1101', 'L3221', 'I5102'] for c in log_cols: df[f'log_{c}'] = np.log(df[c].astype(float)) # 最後の 1 列を目的変数、 残りを説明変数にする X = df[[f'log_{c}' for c in log_cols[:-1]]] y = df[f'log_{log_cols[-1]}'] model = LinearRegression().fit(X, y) print('R^2:', round(model.score(X, y), 4)) |
| 状況 | 推奨変換 | Python 実装 |
|---|---|---|
| 正値のみ、 右裾分布 | 自然対数 | np.log(x) |
| ゼロを含む正値 | log1p | np.log1p(x) |
| 負値含む | Yeo-Johnson | PowerTransformer('yeo-johnson') |
| 最適変換を自動探索 | Box-Cox | scipy.stats.boxcox(x) |
| 桁感覚を出したい | log10 | np.log10(x) |
| 情報量・ビット数 | log2 | np.log2(x) |
| 逆変換 | expm1 | np.expm1(x) |
| 分布形状を強制 | Quantile | QuantileTransformer(output_distribution='normal') |
「47 都道府県の人口データに対して、 ヒストグラムを描き、 正規分布の仮定が成り立つかを述べよ。 必要なら適切な変換を施しなさい。」 → 模範回答: 生データは右裾で歪度 > 2、 Shapiro-Wilk p < 0.001 で正規性棄却。 自然対数変換後は歪度 0.8、 p ≈ 0.006 と大幅改善。 「対数正規分布」を仮定するのが妥当。
「$\ln(\text{wage}) = 4.5 + 0.08 \cdot \text{education}$ が得られた。 教育年数が 1 年増えると賃金は何 % 増加するか。」 → $e^{0.08} - 1 = 0.083$ なので約 8.3% 増。 「単純に $\beta = 0.08$ だから 8% 増」と回答するのは近似(小さい値では誤差 0.3%)。 正確には $(e^\beta - 1) \times 100\%$ と覚える。
「$\ln(\text{demand}) = 5.2 - 1.3 \ln(\text{price})$ から、 価格が 10% 上昇したときの需要変化を求めよ。」 → 弾力性 $\beta = -1.3$ なので「価格 1% 増 → 需要 1.3% 減」。 10% 上昇なら需要は 約 13% 減。 厳密には $(1.1)^{-1.3} - 1 = -0.117$ で 11.7% 減(線形近似は 13%)。
「Box-Cox 変換で $\lambda = 0$ が得られた、 これは何を意味するか。」 → $\lambda \to 0$ の極限が自然対数なので、 「自然対数変換が最適」を意味する。 $\lambda = 0.5$ なら平方根変換、 $\lambda = 1$ なら変換不要、 $\lambda = -1$ なら逆数変換が最適。
「ゼロを含むデータに対数変換するとき、 np.log(x + 1) ではなく np.log1p(x) を使う理由は?」 → ① 同じ結果だが、 小さい x で数値精度が高い (浮動小数点誤差を回避)、 ② コード意図が明確、 ③ NumPy が IEEE 754 準拠の高精度実装を提供している。
np.log / np.log1p / np.log10 / np.log2 の使い分けscipy.stats.boxcox / yeojohnson による自動最適化sklearn.preprocessing.PowerTransformer でパイプライン統合plt.semilogy / plt.loglog での可視化本ページで使用した SSDSE-B-2026 (47 都道府県、 2023 年) の主要数値を一覧化:
| 順位 | 都道府県 | 人口 | log10(人口) | ln(人口) |
|---|---|---|---|---|
| 1 | 東京都 | 14,086,000 | 7.149 | 16.461 |
| 2 | 神奈川県 | 9,229,000 | 6.965 | 16.038 |
| 3 | 大阪府 | 8,763,000 | 6.943 | 15.986 |
| 4 | 愛知県 | 7,477,000 | 6.874 | 15.827 |
| 5 | 埼玉県 | 7,331,000 | 6.865 | 15.807 |
| ... | ... | ... | ... | ... |
| 43 | 福井県 | 744,000 | 5.872 | 13.520 |
| 44 | 徳島県 | 695,000 | 5.842 | 13.452 |
| 45 | 高知県 | 666,000 | 5.823 | 13.409 |
| 46 | 島根県 | 650,000 | 5.813 | 13.385 |
| 47 | 鳥取県 | 537,000 | 5.730 | 13.193 |
| 統計量 | 生データ | log10 | ln | Box-Cox (λ=-0.06) |
|---|---|---|---|---|
| 最大値 | 14,086,000 | 7.149 | 16.461 | 15.142 |
| 最小値 | 537,000 | 5.730 | 13.193 | 12.314 |
| 平均 | 2,645,809 | 6.276 | 14.451 | 13.503 |
| 中央値 | 1,549,000 | 6.190 | 14.253 | 13.330 |
| 標準偏差 | 2,767,630 | 0.313 | 0.720 | 0.672 |
| 歪度 | 2.293 | 0.820 | 0.820 | 0.582 |
| 尖度 | 7.082 | 0.347 | 0.347 | -0.012 |
Box-Cox は ln より 歪度・尖度を約 30% さらに改善。 ただし $\lambda = -0.06$ は ln ($\lambda = 0$) にほぼ等しく、 実務上は ln で十分(解釈容易性のメリット大)。
| 検定 | 生データ (W, p) | ln 後 (W, p) | Box-Cox 後 (W, p) |
|---|---|---|---|
| Shapiro-Wilk | 0.690, 1.1e-08 | 0.928, 6.4e-03 | 0.953, 5.7e-02 |
| D'Agostino K² | 48.3, 3.3e-11 | 3.43, 1.8e-01 | 1.92, 3.8e-01 |
D'Agostino 検定は ln 後で p = 0.18 と 有意水準 5% で正規性を棄却しない。 Shapiro-Wilk より緩やかな判定で、 実務的にはこちらが「許容範囲」として受け入れられることが多い。
RuntimeWarning: divide by zero encountered in log原因: ゼロを含む配列に np.log を適用。 対策: np.log1p(x) に変更、 または x[x > 0] でフィルタ。
RuntimeWarning: invalid value encountered in log原因: 負値を含む配列に log を適用。 対策: scipy.stats.yeojohnson(x) または PowerTransformer(method='yeo-johnson') を使う。
原因: Box-Cox は $y > 0$ が前提。 ゼロや負値があると失敗。 対策: ① 最小値 + ε(0.01 など)を加算してから Box-Cox、 ② Yeo-Johnson に切替。
原因: $\exp(\hat{\ln y})$ で逆変換すると Jensen の不等式で系統的に過小。 対策: スムージング補正 $\exp(\hat{\ln y} + \hat{\sigma}^2 / 2)$ または Duan の smearing estimator $\hat{y} = \exp(\hat{\ln y}) \cdot \frac{1}{n}\sum_i \exp(\hat{\epsilon}_i)$。
原因: 元データで分散が x の値に依存する 不均一分散 (Heteroscedasticity)。 対策: y を log 変換すると 乗法的な誤差が加法的になり等分散化することが多い。 効果がなければ重み付き最小二乗 (WLS) や GLM を検討。
原因: 大規模配列に Python ループで log を取っている。 対策: numpy.log はベクトル化されているので、 配列全体に一度に適用する。 GPU が使えるなら cupy や jax。
| 操作 | Python (NumPy/SciPy/sklearn) | R (base/MASS/forecast) |
|---|---|---|
| 自然対数 | np.log(x) | log(x) |
| log10 | np.log10(x) | log10(x) |
| log2 | np.log2(x) | log2(x) |
| log1p | np.log1p(x) | log1p(x) |
| expm1 | np.expm1(x) | expm1(x) |
| Box-Cox | scipy.stats.boxcox(x) | MASS::boxcox(model) |
| Yeo-Johnson | scipy.stats.yeojohnson(x) | VGAM::yeo.johnson(x) |
| PowerTransformer | sklearn.preprocessing.PowerTransformer | caret::preProcess |
| 対数軸プロット | plt.semilogy / plt.loglog | plot(..., log='xy') |
| Shapiro-Wilk | scipy.stats.shapiro(x) | shapiro.test(x) |
| Anderson-Darling | scipy.stats.anderson(x, 'norm') | nortest::ad.test(x) |
| Q-Q プロット | scipy.stats.probplot(x, plot=plt) | qqnorm(x); qqline(x) |
どちらも同じ機能を提供。 R は 統計・計量分析の歴史が長く、 Python は 機械学習パイプライン統合が強い。 SSDSE 分析なら Python (pandas + scipy + sklearn) で十分カバーできる。
numpy.log(arr) は SIMD 最適化済、 通常の Python ループより 100 倍速いscipy.special.xlog1py(x, y) = x * log(1+y) ── log1p の組み合わせを高速化cupy.log(arr)、 さらに 10-50 倍速lookup table(離散値の log を辞書化)np.log(arr, out=arr) ── in-place で元配列に書き戻し、 メモリ複製を回避dtype=np.float32 でメモリ半減 (精度との trade-off)log(1+x) \approx x$ で近似可能だが、 log1p の方が常に正確
- $x$ が極端に大きい ($10^{300}$ 以上) なら
log はオーバーフロー、 logsumexp を使う
- $x$ が極端に小さい ($10^{-300}$ 以下) なら
log はアンダーフロー、 対数空間で計算する
「訓練データで log 取って学習したが、 本番投入時に生データを渡してしまった」。 結果: 予測が桁違いに外れる。 対策: scikit-learn の Pipeline でスケーラと学習器をセットにし、 同じ前処理が常に適用される設計に。
「列の最小値を確認せず log を取り、 NaN が伝播してモデル全体がクラッシュ」。 対策: 前処理スクリプトの冒頭で assert (df > 0).all().all() や df.describe() で最小値を必ず確認。
「対数変換すれば常に正規分布になると思い込み、 二峰性分布や離散分布に適用」。 結果: 変換後も非正規で線形回帰が不適切。 対策: 必ず Shapiro-Wilk や Q-Q プロットで事後評価。
「年次データに log を取った差分を『成長率』として報告」。 厳密には 近似値(小さい差では一致するが、 大きな差では乖離)。 対策: 厳密な成長率は $(\text{今年} / \text{去年}) - 1$、 対数差分は「対数成長率」と明示。
「log 空間で予測 → exp で復元 → 実測より系統的に低い値が出続ける」。 対策: $\exp(\hat{\ln y} + \hat{\sigma}^2 / 2)$ で補正、 または Duan の smearing estimator を使う。
「log1p で変換したのに、 exp で復元(expm1 を使うべき)」。 結果: 微妙にズレた値。 対策: ペア(log1p ↔ expm1、 log ↔ exp、 Box-Cox ↔ inv_boxcox)を必ず一致させる。
本ページで学んだ内容を、 以下の質問で自己テストしてみましょう:
scipy.stats.yeojohnson または PowerTransformer(method='yeo-johnson'))。np.log(x + 1) と np.log1p(x) どちらが良い?np.log1p(x)。 小さい x で数値精度が高い。
8 問中 6 問以上正解: 対数変換の本質を理解できています。 実プロジェクトでも適切に使い分けできるレベル。
4-5 問正解: 基礎は OK。 ⑤Jensen 補正、 ⑦弾力性などの応用部分を復習。
3 問以下: 数式読み解きセクションと回帰係数解釈チートを再読推奨。
対数変換は 「最古かつ最重要」 な前処理の一つ。 1614 年に Napier が「乗算を加算に変換する道具」として発明してから 400 年以上、 現代の機械学習・統計分析・データサイエンスの基盤として使われ続けています。
本ページで紹介した SSDSE-B-2026 を使った例は 政策評価のための定量分析に直結します。 47 都道府県の総人口・出生数・高齢者人口といった公的統計を扱うとき、 「桁が違うから比較困難」という壁を乗り越える鍵が対数変換です。
統計検定 2 級・準 1 級、 G 検定、 統計データ解析コンペティション、 大学院入試 ── どこでも頻出する基礎技術。 まずは 「ヒストグラム → 歪度 → log → 検証」 の 4 ステップを習慣化しましょう。
📚 関連用語へのリンクから、 さらに深く学んでみてください。
対数変換は「べき乗変換族 (power transformation family)」の中で λ=0 に対応する特殊ケースである。 周辺技法とともに概観する。
| 手法 | 数式 | ゼロ/負値 | 主な用途 |
|---|---|---|---|
| log 変換 | y = log(x) | 不可 | 右裾分布の正規化 |
| log1p (log(1+x)) | y = log(1+x) | ゼロ可、 負値不可 | count データに有効 |
| Box-Cox | y = (x^λ - 1)/λ | 正値のみ | λ を MLE で最適化 |
| Yeo-Johnson | x ≥ 0 で Box-Cox 系、 x < 0 で別式 | 全範囲 OK | 負値含む変数 |
| 平方根変換 | y = sqrt(x) | ゼロ可、 負値不可 | 弱い右裾、 Poisson データ |
| 逆数変換 | y = 1/x | ゼロ不可 | 非常に強い右裾、 時間→速度 |
| ロジット変換 | y = log(p/(1-p)) | 0 < p < 1 | 割合・確率データ |
| 標準化 (z-score) | y = (x - μ)/σ | 全範囲 OK | スケールのみ調整 (形は変わらない) |
💬 表の読み方: 「形を変える」変換 (log/Box-Cox/Yeo-Johnson/平方根/逆数/ロジット) と「スケールだけ変える」変換 (標準化) は本質的に異なる。 log 変換は前者の代表で、 分布の形そのものを正規分布に近づける。 一方、 標準化はスケール調整のみで形 (歪度・尖度) は不変。 「分析の目的が正規性確保か、 スケール比較か」で使い分ける。
以下の問いに答えてみよう。 答え合わせは本ページの該当セクションを再読すること。
10**y_log をそのまま使うと予測が 過小になる。 その理由 (Jensen 不等式) と、 補正式 ($\sigma^2/2$ 補正、 Duan smearing) を 1 つずつ説明せよ。本ページで触れた主な関連概念: 外れ値 / 標準化 / 正規分布 / 歪度 / 尖度 / Box-Cox 変換 / Yeo-Johnson 変換 / 線形回帰 / 弾力性 / Jensen 不等式 / 対数正規分布 / Shapiro-Wilk 検定 / スピアマンの順位相関 / スケーリング / 前処理 / 特徴量エンジニアリング / 非線形変換。 これらは「分布形を整える / 線形モデルに乗せる / 解釈を保つ」の 3 軸で対数変換と接続する。
SSDSE-B-2026 を用いて「総人口 → 出生数」「総人口 → 65歳以上人口」「出生数 → 65歳以上人口」の 3 つの 2 変数関係を、 元スケール vs log-log 変換の 2 通りで線形回帰し、 適合度 (R²)・残差正規性・係数解釈の 3 観点で比較する。 これは政策評価・人口分析でほぼ必ず登場する典型例である。
| 関係 | 変換 | R² | 残差歪度 | 係数 β | 解釈 |
|---|---|---|---|---|---|
| 総人口 → 出生数 | 元 | 0.991 | -0.348 | 0.0061 人/人 | 人口 1 人増 → 出生数 +0.0061 人 |
| 総人口 → 出生数 | log-log | 0.980 | +0.314 | 1.024 | 人口 1% 増 → 出生数 +1.024% |
| 総人口 → 65歳以上 | 元 | 0.982 | -0.465 | 0.246 人/人 | 人口 1 人増 → 高齢者 +0.246 人 |
| 総人口 → 65歳以上 | log-log | 0.989 | -1.257 | 0.901 | 人口 1% 増 → 高齢者 +0.901% |
| 出生数 → 65歳以上 | 元 | 0.961 | +0.903 | 39.65 人/人 | 出生数 1 人増 → 高齢者 +39.6 人 |
| 出生数 → 65歳以上 | log-log | 0.947 | -0.917 | 0.853 | 出生数 1% 増 → 高齢者 +0.853% |
💬 表の読み方: 6 パターンすべてで R² > 0.94 と高い適合度で、 この実データでは log-log が必ずしも R² を上げるわけではない (「総人口→出生数」は 0.991→0.980 とむしろ微減)。 log-log の本質的な価値は R² ではなく、 (1) 係数が「弾力性 (elasticity)」として一意に解釈できる点、 (2) 変数のスケール差を吸収して係数が桁に依存しなくなる点にある。 政策議論で「人口 1% 増は出生数を約 1.02% 押し上げる」と直感的に説明できるのが log-log の強み。 なお残差歪度は関係によって改善も悪化もあり (総人口→65歳以上の log-log は -1.26 と悪化)、 log 変換は万能ではなく散布図と残差診断で個別に判断すべきことを示す。
このコードでやること: SSDSE-B-2026 から 3 つの 2 変数関係 (総人口→出生数、 総人口→65歳以上人口、 出生数→65歳以上人口) を取り出し、 元スケール・log-log の 2 通りで線形回帰し、 R²・残差歪度・係数を一覧出力する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd, numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023].copy() for c in ['A1101', 'A4101', 'A1303']: d[c] = pd.to_numeric(d[c], errors='coerce') d = d.dropna(subset=['A1101', 'A4101', 'A1303']) pairs = [('総人口', 'A1101', '出生数', 'A4101'), ('総人口', 'A1101', '65歳以上人口', 'A1303'), ('出生数', 'A4101', '65歳以上人口', 'A1303')] for xn, xc, yn, yc in pairs: for lab, tf in [('元', lambda v: v), ('log-log', np.log10)]: x = tf(d[xc].values.astype(float)); y = tf(d[yc].values.astype(float)) s, i, r, p, se = stats.linregress(x, y) resid = y - (s * x + i) print(f'{xn}->{yn} [{lab:7s}] R2={r**2:.3f}, 残差歪度={stats.skew(resid):+.3f}, beta={s:.4f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 「総人口→出生数」の log-log 係数 1.024 は「人口 1% 増は出生数を約 1.02% 押し上げる」とほぼ単位弾力性に近く、 都市規模と出生数がスケールに比例することを示す。 「総人口→65歳以上」の弾力性 0.90 (<1) は「人口が大きい地域ほど高齢化率が若干低い」ことと整合する。 一方で R² は log-log で必ず上がるわけではなく (総人口→出生数は 0.991→0.980)、 log 変換の主目的は R² 向上ではなく「弾力性としての解釈可能性」と「スケール非依存の係数」にある点に注意。
log10(出生数)
5.0 ┤ ● 東京
4.8 ┤ ● 神奈川
4.7 ┤ ● 大阪
● 愛知
4.5 ┤ ● 埼玉 ● 兵庫
● 千葉
4.3 ┤ ● 福岡 ● 北海道
● 静岡 ● 茨城
4.1 ┤ ● 京都 ● 広島
● 宮城 ● 新潟
3.9 ┤ ● 岐阜 ● 群馬
● 三重 ● 栃木
3.6 ┤ ● 沖縄 ● 鳥取 ● 島根
● 高知 ● 徳島 ● 福井
└──────────────────────────────────
5.7 6.0 6.3 6.6 7.0 log10(人口)
💬 図の読み方: log-log プロットでは 47 都道府県が傾き ≈1.02 の直線上に並ぶ。 東京・神奈川・大阪の上位 3 都府県も外れ値として極端に振れず、 沖縄・鳥取・島根などの小規模県とほぼ同じ直線上に乗る。 これが「log 変換が外れ値の影響を圧縮する」効果の視覚的な現れであり、 元スケールでは東京が右上に飛んで他県が左下に固まる「対数欠如プロット」と対照的な姿となる。
log 変換は「分布形を正規化し残差を整える」のが主目的であって、 必ずしも R² を上げない。 例えば「平均寿命 (歳)」のように既にスケールが揃っている変数では、 log 変換しても R² はほぼ変わらず、 むしろ係数の解釈性が悪化する。 「右裾分布で歪度の絶対値が 1 を超える」「ヒストグラムが滝のように右に流れる」「値域が 1-3 桁以上にまたがる」のいずれかを満たすときに log 変換を検討する、 という運用上の判断基準を持つこと。
SSDSE-B-2026 から「分布形が大きく異なる 6 変数」を選び、 各変数について「元スケール統計量 → log10 変換後統計量 → Box-Cox 最適 λ → Yeo-Johnson 最適 λ」の 4 段比較を行い、 どの変換が最良か体系的に判断する。 これは特徴量エンジニアリングの実務手順そのものである。
| 変数 | 単位 | 最小値 | 最大値 | 中央値 | 元歪度 | log10 後歪度 | Box-Cox λ | 推奨 |
|---|---|---|---|---|---|---|---|---|
| 総人口 | 人 | 537,000 | 14,086,000 | 1,549,000 | 2.219 | 0.793 | -0.492 | log 強推奨 |
| 出生数 | 人 | 3,263 | 86,348 | 9,524 | 2.292 | 0.801 | -0.478 | log 強推奨 |
| 65歳以上人口 | 人 | 179,000 | 3,205,000 | 524,000 | 1.782 | 0.714 | -0.500 | log 強推奨 |
| 15歳未満人口 | 人 | 65,000 | 1,513,000 | 197,000 | 2.068 | 0.734 | -0.460 | log 強推奨 |
| 転入者数 | 人 | 7,578 | 406,749 | 23,783 | 3.401 | 0.967 | -0.459 | log 強推奨 |
| 消費支出 (1世帯) | 円/月 | 223,423 | 344,092 | 300,652 | -0.520 | -0.815 | 2.965 | 変換不要 |
💬 表の読み方: 「マルチスケール量」5 変数 (総人口・出生数・65歳以上人口・15歳未満人口・転入者数) は元歪度 1.8-3.4、 値域が 1-2 桁以上にまたがる典型的な右裾分布。 これらは log 変換で歪度 0.7-1.0、 Box-Cox の最適 λ も -0.46〜-0.50 とほぼ log〜平方根の逆数付近に収束する。 一方「1 世帯あたり消費支出」は元歪度 -0.52 (むしろ左裾)・値域が 1.5 倍程度で、 Box-Cox の最適 λ は +2.97 と log から大きく離れる。 「歪度 |1| 超ならまず log を検討、 Box-Cox で λ が負〜0 付近なら log 系で確定」というワークフローが効率的。
このコードでやること: SSDSE-B-2026 の 6 変数について、 scipy.stats.boxcox で最尤推定された λ と、 変換後の歪度を計算し一覧表示する。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd, numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] variables = {'総人口': 'A1101', '出生数': 'A4101', '65歳以上人口': 'A1303', '15歳未満人口': 'A1301', '転入者数': 'A5101', '消費支出': 'L3221'} for name, code in variables.items(): x = pd.to_numeric(d[code], errors='coerce').dropna().values.astype(float) if (x <= 0).any(): print(f'{name}: 非正値ありスキップ'); continue bc, lam = stats.boxcox(x) print(f'{name:12s}: 元歪度={stats.skew(x):+.3f}, Box-Cox lam={lam:+.3f}, 変換後歪度={stats.skew(bc):+.3f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 「マルチスケール量」5 変数の λ は -0.46〜-0.50 で、 log (λ=0) と平方根の逆数の中間に収束し、 変換後歪度はいずれも 0.11 前後まで縮む。 一方、 1 世帯あたり消費支出は左裾 (元歪度 -0.52) のため λ=+2.97 (べき乗変換) が選ばれる。 「Box-Cox を機械的に走らせて λ を見れば、 log/sqrt/無変換/べき乗のどれが適切か即判断できる」という運用ルールが、 特徴量エンジニアリングの第一歩として極めて有効。
歪度の絶対値
3.5 ┤ █ 転入者数
3.0 ┤ █
2.5 ┤ █ █ 出生数 █ 総人口
2.0 ┤ █ █ █ █ 15歳未満
1.5 ┤ █ █ █ █ █ 65歳以上
1.0 ┤ █ █ █ █ █
0.5 ┤ █ █ █ █ █ █ 消費支出
0.0 ┤ █▁ █▁ █▁ █▁ █▁ █▔
└──────────────────────────────────────────────────────────────
元 → Box-Cox 変換後 (各変数のペア)
凡例: 高い棒 = 元スケール歪度の絶対値、 低い棒 = Box-Cox 変換後
💬 図の読み方: 「マルチスケール量」5 変数では元の歪度 1.8-3.4 が変換後 0.11 前後まで圧縮され、 ほぼゼロに近づく。 これは「強い右裾分布が正規分布に近づいた」直接の指標。 1 世帯あたり消費支出はもともと歪度が小さく (むしろ左裾)、 変換しても大きく動かない。 図の高低差が「log 系変換の威力を一目で示す」典型ビジュアル。
Box-Cox の最適 λ は最尤推定であり、 標本サイズが小さい (n < 50) と推定の不確実性が大きい。 SSDSE-B-2026 の単一年度データ (n=47) で総人口の λ=-0.49 と推定されても、 信頼区間は幅広く、 「λ=0 (log) を採用するのが解釈上シンプルで実用的」と判断する場面が多い。 scipy.stats.boxcox_normmax でプロファイル尤度に基づく λ の信頼区間を計算し、 区間が 0 を含むなら log 変換を採用するのが実務的な運用。
対数変換は単なる「データを平らにする道具」ではなく、 物理学・経済学・生物学など多くの分野で「対数スケールこそが本質を表す」とされてきた長い歴史を持つ。 本セクションでは、 対数尺度の起源と分野別の典型活用例、 そして「いつ対数を取るべきか」という哲学的問いに簡潔に答える。
対数 (logarithm) は 1614 年にジョン・ネイピアが計算簡略化のために導入。 19 世紀には Weber-Fechner の法則 (人間の感覚は刺激強度の対数に比例) で「対数尺度は知覚の自然尺度」と位置づけられた。 20 世紀には地震マグニチュード (Richter 尺度)、 音圧 (dB)、 pH、 星の等級など、 「対数を取らないと議論できない」現象が次々と発見された。 統計学では Galton (1879) が「対数正規分布」を発見し、 経済学では Cobb-Douglas 生産関数 (1928) が対数線形モデルの古典となる。
| 分野 | 対象データ | なぜ log か | 代表モデル |
|---|---|---|---|
| 経済学 | 所得・GDP・株価 | パレート分布的、 弾力性で議論 | Cobb-Douglas 生産関数 |
| 物理学 | 地震規模・音圧・光度 | 人間の知覚が対数的、 桁数の差が大きい | Richter スケール、 dB |
| 化学 | 水素イオン濃度 | 濃度が極端な桁差を取る | pH = -log[H+] |
| 生物学 | 細胞数・酵素活性 | 指数増殖、 マルチスケール | Gompertz 成長モデル |
| 疫学 | 罹患率・オッズ比 | 比の正規化、 対称性確保 | ロジスティック回帰 |
| 情報理論 | 確率・尤度 | 積を和に変換、 数値安定 | 対数尤度・エントロピー |
| 金融 | 資産リターン | 複利、 連続時間モデル | Black-Scholes、 GARCH |
| 都市・地理 | 人口・都市規模 | Zipf 則 (ランクサイズ法則) | log(rank) vs log(size) |
💬 表の読み方: 対数変換は「統計手法上の便宜」だけでなく、 多くの分野で「本質的に対数尺度こそが意味のある尺度」として採用されている。 SSDSE-B-2026 の都道府県人口は経済学・都市地理学の文脈にぴったり乗り、 Zipf 則 (log(順位) vs log(人口) が直線になる) が成立するか確認するのが好例である。
このコードでやること: SSDSE-B-2026 の 47 都道府県人口を降順にソートし、 順位 (1 位=東京, 2 位=神奈川…) と人口の log-log プロットを作成、 Zipf 則の直線性 (傾き ≈ -1) を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd, numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] pop = pd.to_numeric(d['A1101'], errors='coerce').dropna().values.astype(float) pop = np.sort(pop)[::-1] # 人口降順 rank = np.arange(1, len(pop) + 1) # 順位 1..47 x = np.log10(rank); y = np.log10(pop) slope, intercept, r, p, se = stats.linregress(x, y) print(f'Zipf 直線: log10(人口) = {intercept:.3f} + {slope:.3f} x log10(順位)') print(f'R2 = {r**2:.4f}, p = {p:.1e}') print(f'傾き {slope:.3f} (Zipf 則の予言値 -1 との差: {abs(slope + 1):.3f})') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 47 都道府県の人口は log-log で見ると傾き -0.89、 R²=0.96 の極めて強い直線関係を示す。 完全な Zipf 則 (傾き -1) からはやや乖離 (-0.89) しているが、 これは日本の都道府県が「東京一極集中」傾向を反映し、 上位が緩やかに下がる「準 Zipf 分布」になっているため。 米国・欧州の都市規模分布では傾きがほぼ -1 に近く、 「対数変換を取ることで初めて見えてくる普遍的法則」の典型例となる。 元スケールでは東京だけが極端に飛んで他県が左下に固まる「ホッケースティック型」のグラフになり、 法則性は見えない。
💬 この 3 つの問いは「データの性質」「概念の本質」「議論の目的」の 3 層から成る。 SSDSE-B-2026 で「人口と出生数の関係」を語るなら、 (1) 値域が 26 倍にまたがり、 (2) 「人口 1% 増 → 出生数 1% 増」という比例関係こそが意味を持ち、 (3) 政策の文脈で「地域の人口規模と出生の関係」を議論したい、 という 3 条件がすべて揃うため log 変換が最適解となる。
機械学習の特徴量エンジニアリングにおいて、 対数変換は「分布形を整える前処理」として広く使われる。 ただしモデルの種類によって「効くモデル」と「効かないモデル」があり、 機械的に全変数に log を適用するのは誤り。 ここでは SSDSE-B-2026 を用いて、 線形回帰・木系モデル (Random Forest, XGBoost)・k-NN・ニューラルネットの 4 種類で log 変換の効果を比較する。
| モデル種類 | スケール感度 | log の効果 | 具体例 |
|---|---|---|---|
| 線形回帰 | 非常に高い | 大きく効く | 人口 → 出生数の相対誤差が安定 |
| Ridge / Lasso | 高い (正則化のため) | 効く | 標準化と組合せて使う |
| Decision Tree | なし (順位のみ) | 効かない | 分割は順位で決まる |
| Random Forest | なし | 効かない | 予測値の解釈で出力 log のみ有効 |
| XGBoost / LightGBM | なし | 入力には不要、 出力には有効 | 目的変数を log で正規化 |
| k-NN | 非常に高い (距離計算) | 大きく効く | log + 標準化が必須 |
| SVM (RBF) | 高い | 効く | log + 標準化が標準 |
| ニューラルネット | 高い (勾配安定性) | 効く | 入出力とも log + Batch Norm |
💬 表の読み方: 木系モデル (Decision Tree, Random Forest, XGBoost) は分割が変数の順位だけで決まるため、 入力に log を取っても予測結果は変わらない。 一方、 線形回帰・k-NN・SVM・NN などは「距離」や「線形結合」を計算するため、 値のスケール差が大きい変数 (人口 vs 失業率など) は log で揃えないと前処理として不適切。 「モデルを決めてから log を適用するか判断する」のがプロの流儀。
このコードでやること: SSDSE-B-2026 で「総人口・65歳以上人口・15歳未満人口」から「出生数」を予測する 3 特徴量回帰を、 線形回帰・Random Forest・k-NN で実行。 入力・目的変数を log10 変換した場合と元スケールの場合で RMSE を比較する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import pandas as pd, numpy as np from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.neighbors import KNeighborsRegressor from sklearn.metrics import mean_squared_error from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023].copy() for c in ['A1101', 'A1303', 'A1301', 'A4101']: d[c] = pd.to_numeric(d[c], errors='coerce') d = d.dropna(subset=['A1101', 'A1303', 'A1301', 'A4101']) features = ['A1101', 'A1303', 'A1301'] # 総人口・65歳以上人口・15歳未満人口 y = d['A4101'].values # 出生数 for label, X in [('元スケール', d[features].values.astype(float)), ('log10 変換', np.log10(d[features].values.astype(float)))]: yp = np.log10(y) if 'log10' in label else y Xs = StandardScaler().fit_transform(X) for mname, M in [('LinReg', LinearRegression()), ('RF', RandomForestRegressor(n_estimators=100, random_state=42)), ('k-NN', KNeighborsRegressor(n_neighbors=5))]: M.fit(Xs, yp) rmse = np.sqrt(mean_squared_error(yp, M.predict(Xs))) print(f'{label} {mname:7s}: RMSE = {rmse:,.4f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 元スケール (RMSE は出生数の人数単位) と log10 スケール (RMSE は対数単位) は直接比較できないが、 「目的変数を log 変換すると相対誤差が安定する」点が重要。 元スケールの線形回帰は東京・大阪などの上位県で大きな絶対誤差を出すが、 log スケールでは相対誤差が均等に評価される。 どのスケールでも LinReg が最も小さく、 k-NN は近傍 5 点の平均で丸まるため誤差が最大になる。 「k-NN や SVM では log + 標準化が必須」という実務原則がここから理解できる。
目的変数を log 変換してモデルを学習する場合、 RMSE や MAE などの評価指標も log スケールで計算される。 元スケールでの予測精度を報告するには、 必ず逆変換してから評価指標を再計算する必要がある。 また、 RMSLE (Root Mean Squared Log Error) のように「log スケールで誤差を測る」評価指標を使うのが、 Kaggle 等のコンペで標準的になりつつある。 SSDSE-B-2026 で出生数などの右裾変数の予測タスクを設計するなら、 RMSE ではなく RMSLE を主指標にするのが理に適っている。
時系列データにおいて、 log 差分は「成長率」を直接表現する強力なツールである。 SSDSE-B-2026 の 2015-2023 年度データから「全国の総人口」「全国の65歳以上人口」の年次推移 (47 都道府県の合計) を取り、 元スケール差分と log 差分の意味を比較する。
時刻 $t$ の値を $X_t$、 1 期前を $X_{t-1}$ とすると、 成長率は
$$g_t = \frac{X_t - X_{t-1}}{X_{t-1}} \approx \ln X_t - \ln X_{t-1}$$
「対数差分は成長率の良い近似 (1 次のテイラー展開)」という関係が成立する。 例えば $X_t / X_{t-1} = 1.05$ (5% 増) なら $\ln(1.05) = 0.0488$ で、 実成長率 0.05 とほぼ一致。 ただし 20% 以上の高成長期では誤差が出始める ($\ln(1.20) = 0.1823$ vs 真値 0.2)。 経済時系列では log 差分は「連続複利的成長率」として解釈され、 累積成長を加算で扱える利点がある。
| 年度 | 総人口 (人) | log 差分 | 65歳以上人口 (人) | log 差分 |
|---|---|---|---|---|
| 2015 | 127,094,745 | — | 33,465,441 | — |
| 2016 | 127,044,000 | -0.0004 | 34,590,000 | +0.0331 |
| 2017 | 126,920,000 | -0.0010 | 35,149,000 | +0.0160 |
| 2018 | 126,748,000 | -0.0014 | 35,575,000 | +0.0120 |
| 2019 | 126,555,000 | -0.0015 | 35,886,000 | +0.0087 |
| 2020 | 126,146,099 | -0.0032 | 35,335,805 | -0.0155 |
| 2021 | 125,500,000 | -0.0051 | 36,215,000 | +0.0246 |
| 2022 | 124,946,000 | -0.0044 | 36,235,000 | +0.0006 |
| 2023 | 124,353,000 | -0.0048 | 36,229,000 | -0.0002 |
💬 表の読み方: 総人口は 2015-2023 年で一貫して負成長 (年率 -0.04% から -0.51%) で、 log 差分の絶対値がおおむね年々増加 (減少率の加速) を示す。 一方 65歳以上人口は 2016-2019 年に年率 +0.9〜3.3% と急増し、 log 差分の符号が総人口とは逆。 2020 年の census 定義変更・年齢不詳の扱いで一時的に負の値が出る点は実データならではの注意点。 元スケール差分では桁が異なり比較しにくいが、 log 差分にすれば「総人口の縮小」と「高齢者の増加」を同じ成長率スケールで並べられる点が log の威力。
このコードでやること: SSDSE-B-2026 から「東京・大阪・愛知・沖縄」4 都府県の 2012→2023 年の総人口成長率を、 元の比率と log 差分で並べ、 年率成長率を計算する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd, numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df['A1101'] = pd.to_numeric(df['A1101'], errors='coerce') # 総人口 prefs = ['東京都', '大阪府', '愛知県', '沖縄県'] for pref in prefs: d = df[df['Prefecture'] == pref].sort_values('SSDSE-B-2026') pop = d['A1101'].values yrs = d['SSDSE-B-2026'].values ratio = pop[-1] / pop[0] log_diff = np.log(pop[-1]) - np.log(pop[0]) n_years = yrs[-1] - yrs[0] annual_pct = (ratio ** (1 / n_years) - 1) * 100 print(f'{pref}: {yrs[0]}={pop[0]:>10,.0f}人, {yrs[-1]}={pop[-1]:>10,.0f}人, ' f'log差分={log_diff:+.4f}, 年率={annual_pct:+.2f}%') |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 東京は年率 +0.57% で 11 年間ほぼ連続成長、 大阪は -0.10% と微減。 愛知 +0.06% で横ばい、 沖縄 +0.36% で人口増を維持。 規模の異なる 4 地域の成長率を「同じスケールで」比較できる。 元の差分 (東京 +852,000 人、 沖縄 +57,000 人) を見せても比較しにくいが、 log 差分 / 年率にすれば「沖縄は東京に次ぐ人口増スピード」と即座に判断できる。 これが「log 差分は成長率」の威力。
log 差分は実成長率の 1 次近似で、 成長率が小さい (年率 10% 未満) ときは誤差 1% 以内で実用十分。 ただし 50% 超の高成長 (新興企業の売上、 ハイパーインフレ国の通貨など) では誤差が顕著になる。 例: 2 倍成長 = log 差分 0.693 だが、 実成長率 100% とは違う。 報告書で「log 差分」を使う際は、 必ず「成長率の対数近似」であることを明示し、 1 桁以上の変化では実比率を併記すること。
本ページの議論を 1 ページのチェックリストとして整理する。 実務で対数変換を検討するときは、 以下の項目を順番に確認すれば、 「取るべきか / どう取るべきか / どう解釈するか」まで一貫した判断が可能となる。
| ステップ | 確認項目 | 合格基準 |
|---|---|---|
| 1. 値域確認 | 最大値/最小値の比は? | 10 倍超なら log 候補、 100 倍超なら強推奨 |
| 2. 歪度確認 | skewness の絶対値は? | |skew| > 1 で log 検討 |
| 3. ゼロ/負値確認 | ゼロや負値はあるか? | あれば log1p または Yeo-Johnson |
| 4. 変換実施 | log10 vs ln vs Box-Cox? | 解釈性 → log10、 数学的 → ln、 最適化 → Box-Cox |
| 5. 変換後検証 | 歪度が改善したか? | 変換後 |skew| < 0.5 で OK |
| 6. モデル適合 | 残差正規性は確保? | Shapiro-Wilk p > 0.05 で確保 |
| 7. 結果報告 | 解釈・逆変換の方法を明記? | 弾力性・semi-elasticity・補正の明示 |
💬 表の読み方: 7 ステップは「データの確認 (1-3) → 変換実施 (4) → 変換後の検証 (5-6) → 報告 (7)」という流れ。 ステップを飛ばすと「ゼロ値で NaN が混入」「変換後も歪度が残る」「解釈が semi-elasticity と弾力性で混ざる」などの実務トラブルが頻発する。 SSDSE-B-2026 の各変数に対してこの 7 ステップを一巡させれば、 教育用ハンズオン教材として十分な厳密性が確保される。
本ページは「対数変換が効くケース」を多数紹介してきたが、 実務では「あえて log を取らない選択肢」も重要。 たとえば「年収分布の不平等を議論したい」とき、 元スケールでの分散や標準偏差は「絶対的な格差」を表すが、 log スケールに変換すると「比率的格差」しか見えなくなる。 ジニ係数の計算でも元スケールが標準。 同様に、 「政策議論で 1 円単位の議論をしたい」「広告効果のリフト額を金額で示したい」といったケースでは元スケールが適切。 「log は万能ではなく、 議論したい量に応じて使い分ける」が結論。 SSDSE-B-2026 を使って「どの分析でどちらが適切か」を自分で判断する力を養うことが、 本ページの最終的な目的である。 対数変換は分析者の道具箱の中で最も古く、 最も誤解されやすく、 最も強力なものの一つであり続けている。
対数変換の効果を SSDSE-B-2026 都道府県データで可視化する。 (1) 変換前後の散布図、 (2) 歪度の改善ヒストグラム、 (3) 地方区分での分布比較の 3 切り口。



対数変換は歪んだ分布を正規分布に近づける前処理であり、 前段の分布確認 (QQ プロット) と後段の回帰・検定の妥当性向上を繋ぐ。
上流のヒストグラム・歪度確認で対数変換の必要性を判断し、 並列の Box-Cox/Yeo-Johnson 変換と比較して負値・ゼロ対応を選び、 下流の残差プロットで変換後の等分散性が改善されたかを検証する流れで「右に裾を引く変数」の解析が安定する。
対数変換 を実際の課題に当てはめるとき、 用語固有の判断軸に沿って次の 3 段階で適切な選択を行う。
このフローは変数変換選択の基本方針。 対数変換は「右に裾を引く分布の正規化」と「乗法関係の加法化」を同時に達成する万能ツールだが、 ゼロ・負値対応で log(x+1) や Yeo-Johnson に切替える判断が必要。
対数変換の核心は、 比が等しい差を、等しい距離に置き換えることです。 通常のスケールでは「100→200」と「10,000→20,000」はまったく違う大きさの差ですが、 どちらも「2倍」なので対数上では ln2 ≈ 0.693 という同じ幅になります。 このページで扱う三つの現象――右歪みの是正、 冪関係の両対数直線化、 弾力性としての係数解釈――は、 すべて「乗法的な構造を加法的に開く」というこの一点から派生します。 だから「桁が問題になるデータ」(人口・売上・宿泊者数・所得のように何倍もの開きがある量)ほど対数が効きます。
同じ理由で、 対数変換後に取った算術平均を元へ戻すと「幾何平均」になります(exp(mean(ln x)) = 幾何平均)。 対数の世界での「まん中」は、 元の世界では「比のまん中」なのです。
実測で確かめます。 SSDSE-B-2026.csv(2023年・47都道府県)の右歪み列に自然対数を適用した歪度の変化です(scipy.stats.skew(bias=False))。
| 列(2023, n=47) | 原スケール歪度 | log後の歪度 | 最大/最小 比 |
|---|---|---|---|
| A1101 総人口 | 2.293 | 0.820 | 26.2倍 |
| G7101 延べ宿泊者数 | 3.427 | 0.720 | 47.2倍 |
| G7102 外国人延べ宿泊者数 | 4.827 | 0.898 | 732.5倍 |
落とし穴①: 歪度は確かに大きく下がりますが、 どれも 0 には届いていません(0.7〜0.9 残存)。 「log を取れば正規分布になる」は言い過ぎで、 完全対称を仮定した推論(例えば log 後にそのまま t 検定)には依然として注意が要ります。 効果の大きさは元の歪みの強さ次第で、 歪度を事前・事後で必ず数値確認するのが安全です。
落とし穴②(代表値のすり替え): log 平均を exp で戻すと算術平均ではなく幾何平均になり、 幾何平均は AM–GM 不等式より常に算術平均以下です。 外国人延べ宿泊者数(2023)では、 算術平均 2,021,866 に対し 幾何平均 460,382(中央値 276,670)で、 約4.4倍もの開きがあります。 「対数空間で平均して戻した値」を無自覚に『平均宿泊者数』と呼ぶと、 少数の巨大県(東京 3,437万人泊)に引っ張られる算術平均とは別物の代表値を報告することになります。 どちらを示すかは目的次第ですが、 混同は禁物です。
ゼロ対策の再確認: 上記はすべて正の値なので np.log がそのまま使えますが、 0 を含む列(新規開業件数・特定分野の件数など、 一部県が 0 になりうる量)では np.log(0) = -inf で破綻します。 np.log1p(x)=ln(1+x) を使えば x=0 → 0 と自然に扱え、 値が大きい領域では ln x とほぼ一致するため実務の既定手段になります。
自然対数は、 パラメータ付き変換 Box–Cox (x^λ − 1)/λ の λ→0 の極限にあたります。 つまり「log にするか、 平方根にするか、 生のままか」を データに λ を推定させて選ぶのが Box–Cox(正値限定)/Yeo–Johnson(負値も可)です。 log は「λ を 0 に固定した決め打ち」だと理解すると、 変換の選択肢が体系的に見えます。
もう一つの発展は時系列です。 一定率で伸びる量(指数成長)は、 対数を取ると直線になり、 傾きがそのまま成長率になります。 実測(東京都・外国人延べ宿泊者数 2012→2019)では 7,918,960 → 27,958,830 と増え、 ln y を年で回帰した傾きは 0.172、 すなわち 年率約18.7%の一定成長として要約できます(exp(0.172)−1)。 生スケールの折れ線では「近年ほど急」に見える伸びが、 対数軸では一定勾配の直線に整い、 成長の本質(率)が読み取りやすくなります。
※上の歪度・平均・成長率はいずれも SSDSE-B-2026.csv を pd.read_csv(encoding='cp932', skiprows=[1]) で読み、 該当年で抽出して算出した実測値です(合成データではありません)。
※幾何平均・歪度・Box–Cox 変換の単独ページは本用語集には未収録のため、 ここでは本文内の説明にとどめています。