この記事は、統計学習ツール StatPlay を開発する中で考えたことをまとめたものです。
StatPlayの新しいコラムで、年齢・性別・都道府県・学歴・企業規模の5つの情報から年収を予測するシミュレーターを公開しました。
裏で動いているのは、こういう式です。
ŷ = f(年齢) + β性別 + β都道府県 + β学歴 + β企業規模
正直に書くと、この式を考えたのも、各係数を決めたのも、ほぼAIの仕事です。私は「年齢・性別・都道府県・学歴・企業規模で年収予測ができたらコラムになりそう」と相談したら、AIが勝手にこの形を組み立てて、コードを書いて、サイトに載せた。で、自分はあとから「これは何をしているんだろう」とコードを必死に読み解いて、AIに何度も質問しながら、ようやく「ああ、こういうことをしているのか」と少し理解できた——いつもの展開です。
この記事は、そうやって追いかけながら理解した範囲を、自分の言葉でなぞり直したものです。
データの出典
厚生労働省「令和5年 賃金構造基本統計調査」(通称:賃金センサス)を使っています。
毎年行われている調査で、年齢・性別・学歴・企業規模・都道府県ごとの「きまって支給する現金給与額」と「年間賞与その他特別給与額」が公表されています。ここから年収(月額給与 × 12 + 年間賞与)を算出しました。
なお、各係数の値はすべてコード内にハードコーディングしています。他のページでは計算にこだわってきましたが、サーバーサイドスクリプトを一切使ってないため、統計そのもののデータをサイトに含めるわけにいかなかったからです。替わりわりこのような記事を書いているという形です。
なぜ「足し算」のモデルにしたか
参考書で重回帰の一般形を見ると、こうなっています。
ŷ = β₀ + β₁x₁ + β₂x₂ + β₃x₃ + …
すべての説明変数を、係数を掛けて足し合わせる。これが基本形です。
ただ、年齢と年収の関係は直線ではありません。20代で急上昇して、50代でピーク、60代で下がる。これを1本の直線(β × 年齢)で近似するのは無理がある。
そこで、年齢だけはルックアップテーブル(折れ線補間)にして、残りの変数は基準カテゴリからの差分として加算する設計に……というのを、AIが勝手にやっていました。
ŷ = f(年齢) + β性別 + β都道府県 + β学歴 + β企業規模
このやり方にも統計学的な呼び名があるそうですが、自分はそこまで詳しくありません。素朴に「年齢の効果は折れ線で、他は足し算で」と表現するくらいの感覚で実装しています。
ちなみにダミー変数を使った重回帰は統計検定2級の出題範囲です。試験勉強で一度通っているはずなのですが、参考書では「カテゴリ変数を0/1で表現する」という形式的な説明にとどまっていて、実装に落とす段階で「ああ、こういうことか」と確認できた感じでした。
各係数の中身
年齢ベースカーブ f(年齢)
賃金センサスの年齢階級別の平均年収から、5歳刻みのアンカーポイントが設定されています。
| 年齢 | 22 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 |
|---|---|---|---|---|---|---|---|---|---|---|
| 年収(万円) | 280 | 340 | 420 | 480 | 540 | 590 | 620 | 640 | 580 | 480 |
中間の年齢は線形補間で埋めています。たとえば27歳なら:
f(27) = 340 + (27 - 25)/(30 - 25) × (420 - 340)
= 340 + 0.4 × 80
= 372 万円
55歳がピークで60歳以降に下がるのは、役職定年・再雇用による賃金カットが反映されている形です。賃金センサスのカーブをそのまま写しているので、実態として概ねこういう形になっています。
性別効果 β性別
| 性別 | 効果(万円) |
|---|---|
| 男性(基準) | ±0 |
| 女性 | −120 |
「基準カテゴリ」という考え方がポイントです。男性を0にして、女性をそこからの差分(−120万円)として扱う。これが重回帰でカテゴリ変数を扱うときの「ダミー変数」です。
参考書で「ダミー変数を導入する」と書かれていたときは抽象的な操作に見えていましたが、実装してみると「全部数値にして足し算するため」だったのだなと、自分なりに納得しました。
都道府県効果 β都道府県
賃金センサスの都道府県別データから、全国平均との差分が算出されています(一部抜粋)。
| 都道府県 | 効果 | 都道府県 | 効果 |
|---|---|---|---|
| 東京都 | +130 | 沖縄県 | −80 |
| 神奈川県 | +80 | 青森県 | −70 |
| 大阪府 | +50 | 宮崎県 | −60 |
| 愛知県 | +40 | 北海道 | −30 |
同じ年齢・性別・学歴・企業規模でも、住む地域による差がこれだけ出るという結果になりました。これは賃金センサスの平均値の差をそのまま反映したもので、特定の個人の収入を決めるものではありません。あくまで「同じ属性の人々の平均値が地域でどう違うか」を表しているにすぎない、という前提で見てください。
学歴効果と企業規模効果
| 学歴 | 効果 | 企業規模 | 効果 | |
|---|---|---|---|---|
| 大学院卒 | +80 | 大手(1000人〜) | +60 | |
| 大卒(基準) | ±0 | 大企業(100〜999人・基準) | ±0 | |
| 専門・短大卒 | −40 | 中企業(30〜99人) | −20 | |
| 高卒 | −80 | 小企業(10〜29人) | −60 |
これも基準カテゴリ(大卒・大企業)からの差分として表現されています。
計算例
35歳・男性・東京都・大卒・大企業の場合:
ŷ = f(35) + β男性 + β東京 + β大卒 + β大企業
= 480 + 0 + 130 + 0 + 0
= 610 万円
55歳・男性・東京都・大卒・大企業の場合:
ŷ = f(55) + β男性 + β東京 + β大卒 + β大企業
= 640 + 0 + 130 + 0 + 0
= 770 万円
年齢が35歳から55歳に変わるだけで、年収カーブのピーク差が反映されて160万円の違いが出ます。35歳から55歳までの平均をとると、計算上は年に8万円ずつ上がる勘定になりますが、実際は40代後半から50代前半が一番伸びています。冒頭で「年齢は直線ではない」と書いた理由が、この計算でも見えてきます。各係数の値は、こうやって入力に応じて足し合わせるだけで予測値になります。
予測区間と信頼区間
ポイント予測だけでは不誠実です。同じ属性でも個人差は大きい。そこで2種類の「幅」を出しています。
const PRED_SD = 100; // 個人のばらつき(標準偏差)
const CONF_SD = 10; // 平均の推定誤差(標準偏差)
- 95%予測区間: ŷ ± 1.96 × 100 = ŷ ± 196万円
- 95%信頼区間: ŷ ± 1.96 × 10 = ŷ ± 19.6万円
35歳・男性・東京の例だと:
予測値: 610 万円
予測区間: 414 万円 〜 806 万円 (あなた個人がいる幅)
信頼区間: 590 万円 〜 630 万円 (同じ属性の人の平均がある幅)
予測区間が圧倒的に広い。これが「平均は推定できても、個人は当てられない」という統計の正直さだと思います。
予測関数の全体像
最終的に、予測関数はこれだけです。
function predict(age, gender, prefEffect, eduEffect, compEffect) {
const mean = interpolateAge(age)
+ GENDER_EFFECT[gender]
+ prefEffect
+ eduEffect
+ compEffect;
return {
mean,
predLower: mean - 1.96 * PRED_SD,
predUpper: mean + 1.96 * PRED_SD,
confLower: mean - 1.96 * CONF_SD,
confUpper: mean + 1.96 * CONF_SD
};
}
外部ライブラリもAPIコールもない。テーブルと足し算だけ。コードを最初に見せられたときは「これでいいのかな」と思いましたが、賃金センサスを開きながら数字を確認すると、確かに概ね近い値が出ているようです。AIの判断に追いついた、というよりは、AIの判断を信じられる材料が手元にひとつ増えた、という感覚です。
このモデルの限界
正直に書いておきます。
交互作用を無視している。「東京の女性」と「沖縄の女性」で性別効果が同じ−120万円とは限らないのに、このモデルでは同一に扱っている。
年齢×性別の交互作用。実際には男女の年収格差は年齢とともに拡大する傾向があるそうですが、このモデルはそれを反映していません。
職種・業種が入っていない。年収に最も強く影響する変数のひとつのはずですが、意図的に省きました。コラムの目的は「信頼区間と予測区間の違いを体感する」ことなので、変数を増やしすぎないほうが良いと判断しました。
このレベルから先、つまり交互作用や複雑な変数関係を本格的に扱おうとすると、線形回帰の枠を超えていくのだろうな、と漠然と思っています。機械学習やニューラルネットワークと呼ばれる世界がそのあたりだと聞いていますが、自分はまだそこに踏み込めていません。線形モデルで「ここまでは説明できそう」「ここから先は別の道具が要りそう」という境目があるらしい、という程度の感覚です。
個人差を一定の散らばりで近似。PRED_SD = 100 は固定値としていますが、本当は年齢や地域によって散らばりの大きさ自体が変わるはずだと思います。そこまではモデルに反映できていません。
標準偏差の決め方そのもの。ここが一番、自分の能力を超えている部分です。PRED_SD = 100、CONF_SD = 10 という値は、賃金センサスの散らばりを参考に置いた数字ですが、本来もっと精緻に計算すべきものなのだろうと思います。そこまでは追いつけていません。
正直、降参です。完璧なモデルではありません。でも、「こういう仮定を置いています」と開示するところまでが、自分にできる誠実さの限界かなと思っています。
「自分が確認できる範囲は確認したうえで、その先は別の道具や別の知見を信頼するしかない」——前回の確率分布表の記事で書いた感覚と、結局は同じところに着地している気がします。
おわりに
回帰式を立てるプロセスは、AIの仕事を後から眺めた範囲で言うと、こんな感じでした。
- 公開データから、各変数ごとの「平均的な効果」を抽出する
- それを足し合わせるモデルを作る
- 予測の不確かさ(区間)を正直に添える
参考書で正規方程式や行列を見ると身構えてしまいますが、実際にやっていることはこのレベルの足し算でした。数式の壁で止まっている人は、こうやって「動くものを通して読み解く」アプローチを試してみると、参考書に戻ったときスッと読めるようになるかもしれません。少なくとも私はそうでした。
「動いて見えたら一発でわかる」というStatPlayの基本姿勢が、今回の実装の中でも自分なりに確認できた気がしています。
実装したコードはすべてGitHubで公開しています。係数も計算方法も全部読めるので、おかしいところを見つけたらぜひ教えてください。
関連
- StatPlay 年収予測コラム — この記事で解説したシミュレーター
- StatPlay — 統計の概念をインタラクティブに体感する学習ツール
- GitHub — コード全文(CC BY-NC 4.0)
StatPlay © 2026 Sasai Lab