Skip to content

JRF-2018/simple_synthetic_population

Repository files navigation

仮想合成人口個票の簡易な実装

このプロジェクトについて

ちょっとシミュレーションをするために調べていると「仮想合成人口個票」または「合成人口データ」と呼ばれる便利なものがあることを知りました。しかし、《関西大学ソシオネットワーク戦略研究機構》で配布されているものには申請が必要なようで、そこまでキバったことをするつもりのない私は、他の方の論文などを参考に、その簡易版を作ってみることにしたのでした。

なお、まだこのデータを使ったシミュレーション実験を私はしていません。エージェントを使った感染モデルの実験をしたいと思っているのですが、それは当分先になりそうです。そうやって実用すればこのデータにもアラが見えてくるはずですから、そういった実験後に公開すべきか迷ったのですが、そうなったらそうなったで修正すればいいという気軽な気持ちで、公開しました。気分一新して次の実験にかかれるようにするためです。ですから、このデータはあくまでも「開発中」で将来大きく変更する可能性のあることをご了承の上、お使いください。

データの使い方

人口 1000 人のデータの population-1000.csv と人口 10000 人の population-10000.csv はすぐに利用していただけます。データの形式は CSV で、1行に個人1人のデータが入っています。次のような感じです。

F00000,1,4,M,P00999,M,35.053615863678885
F00000,1,4,S,P00206,F,36.326073829151696
F00000,1,4,C,P00376,M,6.445093742801383
F00000,1,4,C,P00365,M,3.3376282080545137
F00001,1,3,M,P00997,M,42.52366613414124
F00001,1,3,S,P00695,F,37.119412249125304
F00001,1,3,C,P00830,M,8.726501657691637
F00002,3,2,M,P00996,F,50.807942551793374
F00002,3,2,C,P00173,M,21.727633350412795
F00003,15,1,M,P00998,M,53.2177379476259
.
.
.

各項目は次のよう並んでます。

世帯ID,世帯種コード,世帯人数,個人種コード,個人ID,性別,年齢

世帯ID と 個人ID の番号は 00000 からはじまり全部は切れ目がないため、先頭の 'F' や 'P' を取ってから配列に配置していただくのが便利です。

世帯種コードの意味は次のようになります。B11_CODE は世帯コードが国勢調査(令和二年)の表11-1および表11-2で使われている世帯種コードにどう対応しているかを示しています。ただし、13, 14, 16 のコードは今回使われません。無視しています。

## 家族類型
B11_CODE = [None] * 17
##  核家族世帯
B11_CODE[ 0] = '111_夫婦のみの世帯'
B11_CODE[ 1] = '112_夫婦と子供から成る世帯'
B11_CODE[ 2] = '113_男親と子供から成る世帯'
B11_CODE[ 3] = '114_女親と子供から成る世帯'
##  核家族以外の世帯
B11_CODE[ 4] = '1201_夫婦と両親から成る世帯'
B11_CODE[ 5] = '1202_夫婦とひとり親から成る世帯'
B11_CODE[ 6] = '1203_夫婦,子供と両親から成る世帯'
B11_CODE[ 7] = '1204_夫婦,子供とひとり親から成る世帯'
B11_CODE[ 8] = '1205_夫婦と他の親族(親,子供を含まない)から成る世帯'
B11_CODE[ 9] = '1206_夫婦,子供と他の親族(親を含まない)から成る世帯'
B11_CODE[10] = '1207_夫婦,親と他の親族(子供を含まない)から成る世帯'
B11_CODE[11] = '1208_夫婦,子供,親と他の親族から成る世帯'
B11_CODE[12] = '1209_兄弟姉妹のみから成る世帯'
B11_CODE[13] = '1210_他に分類されない世帯'
##  その他
B11_CODE[14] = '2_非親族を含む世帯'
B11_CODE[15] = '3_単独世帯'
B11_CODE[16] = '4_世帯の家族類型「不詳」'

個人種コードは以下のようになります。

M: 世帯主 (master)
S: 配偶者 (spouse)
C: 子供   (children)
P: 親(世帯主の) (parents)
O: その他 (others)

性別は以下のようになります。

M: 男 (male)
F: 女 (female)

データの生成

synthesize_population.py でデータが生成できます。

$ python synthesize_population.py --population=1000 \
         --output=population-1000.csv
46 100
93 200
226 500
311 700
447 1000
done initial step.
enforced family cond.
100 11.2958025624846 14.615401603987305
200 11.2958025624846 16.919701208682675
300 11.2958025624846 18.127250091602583
400 11.2958025624846 19.29572395349618
500 11.2958025624846 20.73907272489024
.
.
.

オプションについては synthesize_populationi.py の先頭のほうを見てください。

ARGS.data_dir = './data'
ARGS.output = 'population.csv'
ARGS.population = 1000
ARGS.t0 = 1000 		# 初期温度
ARGS.alpha = 0.95	# 冷却率
ARGS.beta = 1           # 定数
ARGS.m = 100            # 次のパラメータ更新までの時間
ARGS.max_time = 100000  # アニーリング過程に与えられた総処理時間
ARGS.wasserstein = False # True: スコアにワッサーシュタイン距離を使う。
                         # False: スコアに KL ダイバージェンスを使う。
ARGS.swap_hack = True   # True: 近傍を求めるとき親と子の年齢をチェックする。
                        # False: 近傍を求めるとき乱数に頼る。
ARGS.reproduct_hack = False # True: 初期生成時に子や親の年齢書き換えを許す。
                            # False: 許さない。
ARGS.family_cond_hack = True # True: 初期に家族条件を強制。
                             # False: 強制しない。
ARGS.check_12 = True    # True: 子のいる世帯主は12歳以上にする。
                        # False: しない。
ARGS.check_95 = True    # True: 親のいる世帯主は95歳未満にする。
                        # False: しない。

## test_age_diff.py の結果
ARGS.fa_c_mean = 32.757869148885014 # 父子年齢差
ARGS.fa_c_std = 4.829387554625909
ARGS.mo_c_mean = 31.512137597064076 # 母子年齢差
ARGS.mo_c_std = 4.349459715779061
ARGS.m_f_mean = 2.308170609587958   # 夫婦年齢差
ARGS.m_f_std = 4.110029017891528

この ARGS.〜 というのの 〜 という部分を --〜=〜 とオプションで指定できます。たとえば ARGS.max_time を指定したければ --max-time=100000 などとオプションを付けるわけです。max_time のアンダーバーはマイナス記号 --max-time に付け変えてください。Bool 値のオプションは後ろに = を付けず --reproduct-hack とだけすると True を指定し、--no-swap-hack などとすると False を指定することになります。

なお、父子年齢差・母子年齢差・夫婦年齢差は国勢調査の表を元に独自に求めました。それを試すのは test_age_diff.py です。こちらはデータの生成にはもう必要ないですので、ここでは紹介だけに留めます。

実際の生成時のオプションはそれぞれ次の通りです。

$ python synthesize_population.py --population=1000 \
         --max-time=300000 \
         --output=population-1000.csv
$ python synthesize_population.py --population=10000 \
         --max-time=1000000 \
         --output=population-10000.csv

以下は本実験における手法について説明していきます。

手法の説明

基本的には以下の論文の手法を参考にしました。

『日本全国の位置情報付き仮想の個票合成手法の精緻化』(原田 拓弥 & 村田忠彦 著, サイバーメディア HPCジャーナル No. 9 (pp. 53-56), 2020年1月)
https://www.hpc.cmc.osaka-u.ac.jp/wp-content/uploads/2019/12/2018_harada.pdf

令和二年の国勢調査のデータを使い、初期データの仮想個票(世帯データ)を生成したあとは、焼きなまし法(SA法)でデータを最適化していくのが基本的な流れです。焼きなまし法のアルゴリズムはだいたい次のようになります。

  • Step 1: 仮想個票内の二世帯をランダムに選択。世帯主以外の人員をランダムに一人ずつ選ぶ。

  • Step 2: 性別が等しくなかったり、最低限の家族条件に矛盾がある場合は Step 1 に戻り、矛盾がなければ、両者を入れ換える。

  • Step 3: 仮想個票と統計表との差からスコアを作る。

  • Step 4: メトロポリス法により解を遷移判定する。

  • Step 5: 探索回数に達するまで Step1 に戻る。

これは、上の論文をマネして書きましたが、最初、論文を読んだときは「メトロポリス法」という別分野で有名な言葉が出てきたのに驚きました。その意味は↓で知ることができました。↓を元に焼きなまし法のプログラムを作っています。

《焼き鈍し法(SA法)メモ - Negative/Positive Thinking》
https://jetbead.hatenablog.com/entry/20111014/1318598381

焼きなまし法の近傍を求めるアルゴリズムについては Step 1 と Step 2 で述べましたが、スコアについては、父子年齢差・母子年齢差・夫婦年齢差の平均と標準偏差による正規分布と、仮想個票のデータとのマッチングにより行いました。

マッチングにはデフォルトでは KL ダイバージェンスを使っていますが、Gemini さんのススメにより、オプションでワッサーシュタイン距離も使えるようにしています。

なお、父子年齢差・母子年齢差・夫婦年齢差の平均と標準偏差は、国勢調査の表15-1, 表15-5, 表17-1 からそれぞれ求めました。test_age_diff.py で求めています。そこでは父子年齢差・母子年齢差を求めるとき子が18才以上のデータが「18才以上」とまとめられてしまう影響を抑えるために、父母の年齢が54歳以下のデータのみを用いました。

(ところで、国勢調査の表番号は、調査年ごとに違うのでご注意ください。最初、これで私は引っかかって悩んでいたのでした。orz)

上の Step 2 の工夫がないとまともなデータ生成ができませんでした。Step 2 が本実験のキモとなる工夫になるかと思います。Gemini さんに言わせると「このアプローチは、ドメイン知識をアルゴリズムに組み込むことで、より効率的かつ効果的な探索を実現した好例と言える」とのこと。(ちなみにプログラム作成時にはネットの情報の他、Gemini さんに大変お世話になりました。)

なお、最低限の家族条件とは、詳しく書くと、親の場合は世帯主より年齢が上、子の場合は世帯主より年齢が下、世帯主の配偶者は 16歳以上…となっています。

初期データの生成もおそらく上の論文とは違うところです。私の手法では、まず性別別年齢別の人口を先に生成してしまい、その後、世帯主の性別と年齢が合うように世帯をランダムに生成していきます。

  • Step 1: 特定の性別・年齢の個人を国勢調査の表2-1に従ってランダムに生成する。

  • Step 2: 国勢調査の表11-1と表11-2に従って、世帯をランダムに生成し、世帯主の年齢・性別に合うよう、個人を割り当てる。

  • Step 3: 配偶者と高齢者を年齢に矛盾が生じにくいように割り当てる。残り人数などが足りない場合、Step 2 に戻る。

  • Step 4: 子供とその他の人員を割り当てる。子供はできるだけ年齢に矛盾がないように割り当てる。

  • Step 5: 最低限の家族条件を強制するため、条件に合わない家族は、世帯主以外の家族の人員を他の家族の人員と共に条件に合うよう入れ換える。ただし、Step 5 は運悪く失敗することがあり、失敗すると Step 1 からやり直す。

なお、Step 3 と Step 4 で割り当てに失敗したとき Step 1 の個人のデータの年齢を無理矢理に書き換えてツジツマをあわせるための --reproduct-hack というオプションも一時作ったのですが、Step 5 があるため、使わなくても大丈夫です。Step 5 があるため焼きなまし法をしなくても、それなりのデータができあがります。シミュレーションによってはそれで十分でしょう。

Step 2 では、親のいる世帯主は95歳未満とし、子のいる世帯主は12歳以上にする…という処理もしています。子のいる世帯主の制限はいいとして、親のいる世帯主の制限が95歳というのはキツ過ぎると感じる方もいるかもしれません。世帯主100歳親116歳とかもありそうだからです。しかし、使っている統計の性質上世帯主の85歳以上にグラデーションがなく、その年齢層はシミュレーションにおける「限られた資源」としての性格を持っていて、いくぶんキツめに制限したほうが、最適化で良い結果が得られやすいだろうと考えました。

おわりに

実は焼きなまし法のパラメータをどうすればよいのか、今でもよくわからず、その結果に自信がありません。改善策とかあればぜひ著者(JRF)に Twitter (X) などででもそっと知らせていただければうれしいです。

Author

JRF ( http://jrf.cocolog-nifty.com/statuses , Twitter (X): @jion_rockford )

License

MIT License.


(This document is mainly written in Japanese/UTF8.)

About

仮想合成人口個票の簡易な実装

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages