人口統計は最も重要な基幹統計の一つである.総務省の e-Stat は確かに有用であるが,かゆいところに手が届かない.例えば「市区町村ごと,年齢5歳階級ごとの人口構成の国勢調査ごとの推移を知りたい」という要求には全く無力である.
主として技術的な理由によるものと,統計調査の粒度の細かさによる.技術的な理由としては,データベースの画面表示セル数の上限を容易に超えてしまうデータ量になってしまうことである.しかし,根本的な理由は調査の粒度の細かさである.
2005 年以前と 2010 年以降とでは調査の精度が違う.今後は高精度なデータファイルが e-Stat に掲載されていくものと思われるが,2005 年以前に関しては都道府県より細かい粒度は存在しない.そこを求めると手作業になってしまい,現実的ではない.国立社会保障・人口問題研究所ならデータを持っているかもしれない.
2020 年は国勢調査の年にあたる.総務省にはできるだけ細かい粒度でのデータ掲載を望むものである.
国勢調査のデータファイル
大正 9 年(1920 年)から平成 17 年(2005 年)まで
都道府県別の統計はない.全国のみである.男女別の統計がある.下図の 17 番のファイルをダウンロードする.ファイル名は 05017 である.ワークシート 2 枚からなる.JH17017-01 というワークシートが目的である.
今回はこのデータを使う.
平成 7 年(1995 年)から平成 17 年(2005 年)まで
都道府県別の統計のみで,市区町村の粒度はない.男女別の統計がある.
平成 22 年(2010 年)以降
市区町村の粒度で各年齢,男女別のデータが存在する.本来,このレベルで調査・公開してもらいたい.
データクレンジング
よく見る積み上げ棒グラフでは5歳ごとの年齢階級(5歳階級)でデータ系列を分類している.しかし,この表記だとグラフ作成はしやすいが解釈が難しくなる.少子高齢化の正体が見えづらいのは,これが原因ではないか.
本来,年齢は生まれた年と現在との差で計算される相対的な値に過ぎない.だから,生まれた年を5年毎の階級(生年階級とでも言おうか)に区切ってデータ系列にしてみたらどうだろう.
5歳階級を生年に変換
年度から年齢階級を引き算するだけである.例えば,2015 年度の 0-4 歳階級は 2015 となる.DATEDIFF 関数の逆の使い方をやるわけだ.できた列のタイトルを BirthYear としておこう.
年度は横軸となる
これは通常のグラフと変わらない.
データ系列の要素数は同じでなくてはならない
1920 年から始まった国勢調査は 2005 年で 18 回目である.だからデータ系列の要素数も 18 でなくてはならない.
何を当たり前のことを,と思うかも知れない.しかし,意外と盲点である.さもないとグラフの表記が狂う.横軸の位置が定まらないのである.グラフにおいてこれは致命的だ.
その目でもう一度テーブルを見直すと,不足しているデータがあることに気がつく.生年でフィルターをかけた際に抽出されてくるレコードは 18 件あるだろうか?もし不足しているなら追加する必要がある.
直積を作るには SQL が向いている
テーブルに新たな行を追加するには EXCEL ならループの中で ListRows.Add メソッドを使うだろう.だが,EXCEL ではこういう作業はまだ苦手だ.分かっている人はすぐにピンとくると思うが,これは直積を作ることに他ならない.こういう作業は SQL にやらせた方がうまく行く.
そこで一旦テーブルを txt ファイルにエクスポートして SQL Server にインポートする.
一時テーブルでマスターを作成
使い捨てのマスターテーブルなので,一時テーブルに生年と年度を書き込む.この一時テーブルには35 件(1835 年から 2005 年まで 5 年ごと)× 18 件(1920 年から 2005 年まで 5 年ごと)で 630 件のデータが入る.このマスターに対して左外部結合を行う.
下記クエリは生年と年度の直積を作成するものである.
DROP TABLE IF EXISTS #SERIES; CREATE TABLE #SERIES(BIRTHYEAR INT NOT NULL); INSERT INTO #SERIES VALUES (1835), (1840), (1845), (1850), (1855), (1860), (1865), (1870), (1875), (1880), (1885), (1890), (1895), (1900), (1905), (1910), (1915), (1920), (1925), (1930), (1935), (1940), (1945), (1950), (1955), (1960), (1965), (1970), (1975), (1980), (1985), (1990), (1995), (2000), (2005); DROP TABLE IF EXISTS #FISCALYEAR; CREATE TABLE #FISCALYEAR(FISCALYEAR INT NOT NULL); INSERT INTO #FISCALYEAR VALUES(1920), (1925), (1930), (1935), (1940), (1945), (1950), (1955), (1960), (1965), (1970), (1975), (1980), (1985), (1990), (1995), (2000), (2005); WITH CTE(BirhtYear, FiscalYear) AS(SELECT * FROM #SERIES CROSS JOIN #FISCALYEAR) SELECT * FROM CTE ORDER BY BirhtYear, FiscalYear;
(35 行処理されました) (18 行処理されました) (630 行処理されました)
マスターに人口テーブルを全結合
次は生年と年度の直積に人口テーブルを全結合するクエリである.左外部結合ではなく全結合するのは 1945 年の国勢調査のみ,年齢階級の集計区分が異なるためである.
SELECT * FROM CTE FULL JOIN dbo.[T_Population(1920-2005)] ON CTE.BirhtYear = dbo.[T_Population(1920-2005)].BirthYear AND CTE.FiscalYear = dbo.[T_Population(1920-2005)].FiscalYear ORDER BY CTE.BirhtYear, CTE.FiscalYear;
(648 行処理されました)
この 1945 年のデータをどう扱うべきだろうか?ここで一旦,元のデータに戻ろう.
1945 年は終戦の年である.なぜこの時の国勢調査のみ, 1 歳から 5 歳という年齢階級で集計されたのかは,定かでない.他の年はすべて 0 歳から 4 歳という年齢階級で集計されているのにである.推測だが,日本人の年齢の数え方に「数え年」があるためではないだろうか.
境界の年齢にある人口の分だけ集計値がずれるが,グラフの見やすさを優先する.基のデータが存在しない以上,やむを得ない.
戦後 10 年間の年齢の集計には注釈が多い.「数え年」,「沖縄」など戦後の混乱が続いていることを伺わせる.
一旦人口テーブルを削除しよう.
DROP TABLE dbo.[T_Population(1920-2005)];
コマンドは正常に完了しました。
元の EXCEL ファイルの 1945 年の年齢階級を 1 歳ずつ減らして修正する.その後年度から年齢階級を引き算して生年を算出する.
テキストファイルをエクスポートして SQL Server にインポートする.ファイル末尾に余計な空行があるためメモ帳で削除しておく.
以下のクエリを実行して結果を確認する.
DROP TABLE IF EXISTS #SERIES; CREATE TABLE #SERIES(BIRTHYEAR INT NOT NULL); INSERT INTO #SERIES VALUES (1835), (1840), (1845), (1850), (1855), (1860), (1865), (1870), (1875), (1880), (1885), (1890), (1895), (1900), (1905), (1910), (1915), (1920), (1925), (1930), (1935), (1940), (1945), (1950), (1955), (1960), (1965), (1970), (1975), (1980), (1985), (1990), (1995), (2000), (2005); DROP TABLE IF EXISTS #FISCALYEAR; CREATE TABLE #FISCALYEAR(FISCALYEAR INT NOT NULL); INSERT INTO #FISCALYEAR VALUES(1920), (1925), (1930), (1935), (1940), (1945), (1950), (1955), (1960), (1965), (1970), (1975), (1980), (1985), (1990), (1995), (2000), (2005); WITH CTE(BirhtYear, FiscalYear) AS(SELECT * FROM #SERIES CROSS JOIN #FISCALYEAR) SELECT * FROM CTE FULL JOIN dbo.T_Population_1920_2005 ON CTE.BirhtYear = dbo.T_Population_1920_2005.BirthYear AND CTE.FiscalYear = dbo.T_Population_1920_2005.FiscalYear ORDER BY CTE.BirhtYear, CTE.FiscalYear; DROP TABLE IF EXISTS #SERIES; DROP TABLE IF EXISTS #FISCALYEAR;
(35 行処理されました) (18 行処理されました) (630 行処理されました)
結果を「ヘッダー付きでコピー」して EXCEL に貼り付け,テーブルに変換する.大量の NULL が見えるが,「検索と置換」で 0 に一括置換しておこう.
下図にあるように,よく見ると C 列,D 列,E 列は不要だ.削除する.
まず年度を昇順ソート,ついで生年を降順ソート
グラフを作成する前にテーブルをソートするのだが,順番がある.まず「年度」を昇順ソート,ついで「生年」を降順ソートである.
年度は横軸の並ぶ順序を決めるものであり,生年はデータ系列の PlotOrder を決めるものである.EXCEL の仕様に合わせている.
後の行程でフィルターをかけた際に処理が楽になる.必ずやっておこう.これでグラフ作成の準備が完了した.
EXCEL によるグラフ作成
データ系列が多いため VBA でグラフを作成する.その際の基本方針を述べておく.
基本方針
グラフタイプは積み上げ棒グラフ
日本の人口を5歳の生年ごとに塗り分けたい.実数を表現するのであるから積み上げ棒グラフが最適である.
総数なら積み上げ縦棒グラフ,男女別なら積み上げ横棒グラフである.
後で調べて分かったのだが,EXCEL では積み上げグラフと集合グラフの組み合わせは相性が非常に悪く,両者の組み合わせを一つのグラフで表現するにはテーブルの構造を完全に破壊しなければならず,データの可逆性が失われる.EXCEL の限界である.
データ系列の追加
これまでも述べてきたように,テーブルに生年でフィルターをかけ,抽出された行を一つのデータ系列として追加する.それをループする.
ループカウンターは 2005 から開始し,1935 で終わり,5 ずつ減算する.
オブジェクトテーマカラーの指定
オブジェクトテーマカラーは 60 色
EXCEL のオブジェクトテーマカラーは全部で 60 色ある.今回扱うデータ系列数は 35 である.全部の色を使い切ることはないはずだが,既に死亡していなくなった過去世代の階級はグレーのグラデーションとしたい.
2005 年時点でいなくなった階級は 1915 年生まれ以前である.というか,1920 年から調査が始まったのだから当然といえば当然だ.
どこまでグレーとし,どこからカラーにするかはグラフを作りながら考えよう.まずは手を動かすことだ.
上図を見ると,グレーの系統は 4 つ,青の系統は 3 つある.重複として考えると実際に使用できる色は 5 系統 25 色である.意外に少ない.
25 年周期とするのが合理的
隣接する年齢階級は同系統のグラデーションとしたい.4 色で 20 年,5 色で 25 年だから,計算上は 1 系統 4 色でも間に合う.しかし,データの特徴をよく見ると,1 系統 5 色 25 年周期とするのが合理的と思われる.
- 2000 年生まれ以降を青の系統 2 色
- 1975 年生まれ以降を緑の系統 5 色
- 1950 年生まれ以降をゴールドの系統 5 色
- 1925 年生まれ以降をオレンジの系統 5 色
- それ以前は白の系統 5 色の循環
理由は不明だが,EXCEL による色指定と html での色指定が一致しない.この原因について追求するのはまた別の機会にしよう.
コードの実際
Option Explicit Sub PopulationChart() Dim mySht1 As Worksheet Dim mySht2 As Worksheet Dim myLstObj As ListObject Dim myCht As Chart Set mySht1 = Worksheets("Sheet3") Set myLstObj = mySht1.ListObjects(1) Set mySht2 = Worksheets.Add(after:=mySht1) Set myCht = mySht2.Shapes.AddChart2(Style:=-1, _ XlChartType:=xlColumnStacked, _ Left:=0, _ Top:=0, _ Width:=400, _ Height:=400).Chart Dim i As Long Dim j As Long Dim myRng1 As Range Dim FiscalYear() As Long Dim Population() As Long Dim mySeries As Series For i = 2005 To 1835 Step -5 With myLstObj .Range.AutoFilter field:=1, Criteria1:=i Set myRng1 = Intersect(.DataBodyRange, _ .Range.SpecialCells(Type:=xlCellTypeVisible)) If myRng1 Is Nothing Then Else For j = 0 To myRng1.Rows.Count - 1 ReDim Preserve FiscalYear(j) ReDim Preserve Population(j) FiscalYear(j) = myRng1.Cells(j + 1, 2) Population(j) = myRng1.Cells(j + 1, 3) Next j Set mySeries = myCht.SeriesCollection.NewSeries With mySeries .Name = i .XValues = FiscalYear .Values = Population End With End If .Range.AutoFilter field:=1 End With Next i Dim myAxis As Axis Dim myChtGrp As ChartGroup With myCht With .ChartTitle .Caption = "日本人口の年齢階級推移" .Left = 0 End With Set myAxis = .Axes(xlCategory) With myAxis .Format.Line.Visible = msoFalse .MajorGridlines.Format.Line.Visible = msoFalse End With Set myAxis = .Axes(xlValue) With myAxis .DisplayUnit = xlTenThousands .Format.Line.Visible = msoFalse .MaximumScale = 125000000 .MajorUnit = .MaximumScale / 5 .MajorGridlines.Format.Line.Visible = msoFalse With .DisplayUnitLabel .Orientation = xlHorizontal End With End With With .PlotArea .Format.Fill.Visible = msoFalse .Format.Line.Visible = msoFalse .Left = -8 .Width = 400 End With With .ChartArea .Format.Fill.ForeColor.ObjectThemeColor = msoThemeColorBackground1 .Format.TextFrame2.TextRange.Font.Name = "TimesNewRoman" End With Set myChtGrp = .ChartGroups(1) With myChtGrp .GapWidth = 0 End With For Each mySeries In .SeriesCollection With mySeries Debug.Print .Format.Fill.ForeColor.RGB Select Case .Name Case "2005" .Format.Fill.ForeColor.RGB = RGB(47, 85, 151) Case "2000" .Format.Fill.ForeColor.RGB = RGB(32, 56, 100) Case "1995" .Format.Fill.ForeColor.RGB = RGB(226, 240, 217) Case "1990" .Format.Fill.ForeColor.RGB = RGB(197, 224, 180) Case "1985" .Format.Fill.ForeColor.RGB = RGB(169, 209, 142) Case "1980" .Format.Fill.ForeColor.RGB = RGB(84, 130, 53) Case "1975" .Format.Fill.ForeColor.RGB = RGB(56, 87, 35) Case "1970" .Format.Fill.ForeColor.RGB = RGB(255, 242, 204) Case "1965" .Format.Fill.ForeColor.RGB = RGB(255, 230, 153) Case "1960" .Format.Fill.ForeColor.RGB = RGB(255, 217, 102) Case "1955" .Format.Fill.ForeColor.RGB = RGB(191, 144, 0) Case "1950" .Format.Fill.ForeColor.RGB = RGB(127, 96, 0) Case "1945" .Format.Fill.ForeColor.RGB = RGB(251, 229, 214) Case "1940" .Format.Fill.ForeColor.RGB = RGB(248, 203, 173) Case "1935" .Format.Fill.ForeColor.RGB = RGB(244, 177, 131) Case "1930" .Format.Fill.ForeColor.RGB = RGB(197, 90, 17) Case "1925" .Format.Fill.ForeColor.RGB = RGB(132, 60, 12) Case "1920" .Format.Fill.ForeColor.RGB = RGB(242, 242, 242) Case "1915" .Format.Fill.ForeColor.RGB = RGB(217, 217, 217) Case "1910" .Format.Fill.ForeColor.RGB = RGB(191, 191, 191) Case "1905" .Format.Fill.ForeColor.RGB = RGB(166, 166, 166) Case "1900" .Format.Fill.ForeColor.RGB = RGB(127, 127, 127) Case "1895" .Format.Fill.ForeColor.RGB = RGB(242, 242, 242) Case "1890" .Format.Fill.ForeColor.RGB = RGB(217, 217, 217) Case "1885" .Format.Fill.ForeColor.RGB = RGB(191, 191, 191) Case "1880" .Format.Fill.ForeColor.RGB = RGB(166, 166, 166) Case "1875" .Format.Fill.ForeColor.RGB = RGB(127, 127, 127) Case "1870" .Format.Fill.ForeColor.RGB = RGB(242, 242, 242) Case "1865" .Format.Fill.ForeColor.RGB = RGB(217, 217, 217) Case "1860" .Format.Fill.ForeColor.RGB = RGB(191, 191, 191) Case "1855" .Format.Fill.ForeColor.RGB = RGB(166, 166, 166) Case "1850" .Format.Fill.ForeColor.RGB = RGB(127, 127, 127) Case "1845" .Format.Fill.ForeColor.RGB = RGB(242, 242, 242) Case "1840" .Format.Fill.ForeColor.RGB = RGB(217, 217, 217) Case "1835" .Format.Fill.ForeColor.RGB = RGB(191, 191, 191) End Select End With Next mySeries End With End Sub
結果
1925 年生まれが 1945 年調査で急減している.もちろん第2次世界大戦が影響している.
続いて 1950 年生まれが急増している.これが団塊世代である.25 年後の 1975 年生まれ,これが団塊ジュニア世代である.
このサイクルで行けば 2000 年頃に第3次ベビーブームが来るはずだった.しかし実際には少子化が進行しており,この時点で日本の将来はほぼ決まった.
要因は様々なことが言われている.少子化対策はもちろん必要だ.だが,そろそろ「不都合な真実」から目を背けるのではなく,正面から向き合う方向に舵を切るべきだ.
“国勢調査から5歳階級の人口推移を調べる” への1件の返信