茨城県の小地域の平均年齢マップ

こんにちは。つくばに住む研究者です。
今回は茨城県の小地域の平均年齢を地図上で可視化していこうと思います。
すなわち、↓こんな感じのものを作りたいということです。

平均年齢で色分けしたプロット

小地域というのは行政単位の市区町村よりも更に細かいレベルでの区分けを指し、例えば「⚪︎⚪︎町」や「××一丁目」のような単位です(詳細は末尾の補足参照)。

小地域統計で得られた数字を、地図上にプロットする際には住所(文字)から緯度と経度に変換する必要があります。住所から緯度経度に変換するために、国土交通省の整備する位置参照情報などを利用します。

今回使う小地域統計はお馴染みのe-statから、位置参照情報は国土数値情報タウンロードサイトから取得できます。小地域の平均年齢と緯度経度を結びつけ、地図上にプロットします。

小地域の人口データの整形

まずは、いつものようにGoogle Colabに必要なライブラリをインポートしていきます。

from google.colab import drive
import os
import pandas as pd
import numpy as  np
import matplotlib.pyplot as plt

drive.mount('/content/drive')
dir_path = '/content/drive/MyDrive/Colab Notebooks/Data/2023_07' #自分のデータの保管場所
os.chdir(dir_path)

小値域データのファイル名は`h03_08.csv`です。CSVのオリジナルのファイルには複数行のヘッダがありますが、4行目をデータフレームのヘッダとして読み込みます。ファイルを読み込むと平均年齢のカラム名が"-.1"となっているのでrenameします。

df_p = pd.read_csv('h03_08.csv',encoding='SHIFT-JIS',header=[4])
df_p = df_p.rename(columns={"-.1": "平均年齢"})

ところで、国勢調査の小地域特性では人口が少ない地域については個人が特定される恐れのあることから公開時には秘匿処理がされており、数字のが”X"という文字に置き換わっています。数値が入っていない行を消し、地域階層レベルが2または4の住所だけを取り出します。また、データには男女別の数字にも分かれているため、男女を含む’総数’のレコードだけを取り出します。

df_p = df_p[df_p['総数']!='-']
df_p = df_p[df_p['平均年齢']!='-']
df_p = df_p[df_p['総数']!='X'] #秘匿処理住所は利用しない
df_p = df_p[df_p['平均年齢']!='X']
df_p = df_p[(df_p['地域階層レベル'] == 2)|(df_p['地域階層レベル'] == 4)] #諸説あり
df_p = df_p[df_p['男女']=='総数']

住所が「都道府県名」「市区町村名」「大字・町名」「字・丁目名」と分かれているので、全て結合します。ついでに今回注目している列だけを取り出し、総数や平均年齢をfloat型に変換します。

df_p.insert(4, '住所', df_p['都道府県名'].str.cat(df_p['市区町村名']).str.cat(df_p['大字・町名']).str.cat(df_p['字・丁目名']))
df_p = df_p[['住所','総数','平均年齢']]
df_p['総数'] = df_p['総数'].astype('float')
df_p['平均年齢'] = df_p['平均年齢'].astype('float')

df_p.head(10)

整形したデータを見てみましょう。

ところで、先にも書いた通り、大きな町では複数地点に分けて統計がとられています。例えば、茨城県水戸市元吉田町は以下の3つのレコードがあります。

大きく平均年齢などに違いがないことから、今回は中央値のレコードを使用することにします。ある要素で重複する複数行から中央値を取り出すにはgroupbyを使います。groupbyで作ったデータフレームはindexがgroupbyで指定した列になるので、これをrenameしておきます。

df_p2 = df_p.groupby('住所').median()#住所が同じレベルの数字は中央値を採用する
df_p2 = df_p2.sort_values('平均年齢',ascending=True)

df_p2.reset_index(inplace=True)
df_p2 = df_p2.rename(columns = {'index':'住所'})
df_p2.head(10)

平均年齢の昇順で並び替えると以下のようになります。

補足ですが、総数が小さい住所は不確実性(分散)が高いという点には注意する必要があります。が、とりあえずは総数が小さい地点についてもそのまま利用します。やはりつくば市には平均年齢が低い地域が多くあることがわかります。(おそらく、筑波大学の近隣地域と思われます。)

街区レベル住所の経度緯度情報の付与

次に、先ほど整形した住所を緯度と経度に変換します。住所から緯度経度に変換するには大きく次の2つの方法が考えられます。

  1. pythonのライブラリを使う方法

  2. 国土交通省の位置情報を使う方法

今回は1の方法でやってみます。

jageocoderというライブラリを使います。jukyo_all_v20.zipという辞書ファイルをGoogle Drive上において、以下のようにしてインストールします。

!pip install jageocoder
!jageocoder install-dictionary jukyo_all_v20.zip

ライブラリをインポートしてテストしてみます。

import jageocoder
jageocoder.init()
address = jageocoder.search('東京都千代田区霞が関一丁目')
print(address)
print(address['candidates'][0]['id'])

以下のような出力が得られれば正常に動作しています。'x'と'y'に緯度経度がそれぞれ入っています。

{'matched': '東京都千代田区霞が関一丁目', 'candidates': [{'id': 12140014, 'name': '一丁目', 'x': 139.7525634765625, 'y': 35.67394256591797, 'level': 6, 'priority': 2, 'note': 'aza_id:0002001/postcode:1000013', 'fullname': ['東京都', '千代田区', '霞が関', '一丁目']}]}
12140014

それでは、先の人口データに緯度と経度を追加していきます。

df_p2.insert(1, 'x', 0)
df_p2.insert(2, 'y', 0)

for i in range(0, df_p2.shape[0]):
  try:
    df_p2.loc[i, 'x'] = jageocoder.search(df_p2['住所'].iloc[i])['candidates'][0]['x']
    df_p2.loc[i, 'y'] = jageocoder.search(df_p2['住所'].iloc[i])['candidates'][0]['y']
  except TypeError:
    pass

df_p2.head(10)

データを見てみます。

いくつかのx,yに正しい値が入っていないようです。国土交通省のデータなどを参照して修正もできるはずですが、今回はこうした不正と思われる値はそのまま除去してしまいます。

df_p2 = df_p2[df_p2['x']<=150]
df_p2 = df_p2[df_p2['y']<=40]

さて、それではプロットしてみます。数値に応じて色を変えてプロットする部分についてはこれまでの記事を参照してください。

import shapely
import folium
import matplotlib.cm as cm
def rgb_to_hex(rgb):
  return '%02x%02x%02x' % rgb

def color_creator(age,v,c):
  degree = sum(age >= v)
  color = c[degree]
  color = rgb_to_hex(tuple((color[0:3]*255).astype(int)))
  return color
map = folium.Map(location=[df_p2.iloc[0]['y'], df_p2.iloc[0]['x']], zoom_start=9)
v= np.arange(25, 55, 5)
c = cm.jet(range(0,250,int(250/len(v))))

for index, row in df_p2.iterrows():
  try:
    folium.CircleMarker(location=[row['y'],row['x']],
                        radius=5,
                        alpha = 0,
                        fill=True,
                        fill_opacity = 1,
                        fill_color='#'+str(color_creator(row['平均年齢'],v,c)),
                        color=None

                        ).add_to(map)
  except ValueError:
    pass

map


茨城全域での街区レベルでの平均年齢の様子


相変わらず左上部分がスカスカですが、うまくプロットできました。なお、今回は配色には'jet'と呼ばれるカラーマップを使っています。平均年齢が低い地区は青で、高くなるに連れて水色ー緑ー黄色ーオレンジー赤ー茶色と変化していきます。今回は地域を25歳〜55歳に設定しているため、茶色の地点は「地区の平均年齢が55歳以上」ということになります。


高縮尺でみると圧倒的に茶色が多くみえます。大都市の近隣では黄色や緑、青の点も少し見えるようです。幾つか気になる都市の近くをクローズアップして見てみましょう。

茨城県南部の平均年齢プロット

つくば市と神栖市には黄色や緑の地点が広く存在しているようです。つくばみらい市の中心部もかなり青い点が密集してます。都市部から離れた地点では殆どが橙〜茶色の点です。つくばエクスプレスの通る「守谷市、つくばみらい市、つくば市」は比較的平均年齢が低い町が多く、JR常磐線の通る「取手市、牛久市、土浦市」のラインはそれと比較して平均年齢が高いことがわかります。

茨城県北部の平均年齢プロット

ついでに、北部の様子です。

次回(こそは)産業や経済の指標についても可視化してみたいと思います。
それでは。


*小地域の単位についての補足
小地域には幾つかのレベルがあり、例えば総務省統計局の発表している国勢調査の小地域統計では、茨城県水戸市のような行政市は「地域階層レベル1」、水戸市に含まれる青柳町は「地域階層レベル2」になります。青柳町より小さい住所単位はありませんが、国勢調査の小地域統計では大きな町についてはその中でも幾つかの地域に分けて小計を出しています。市の中心部には「丁目」を持つ住所もあり、水戸市赤塚には「赤塚一丁目」と「赤塚二丁目」が含まれます。この場合、水戸市赤塚は「地域階層レベル3」、水戸市赤塚一丁目は「地域階層レベル4」となります。
以上をまとめると、国勢調査の小地域統計で「地域階層レベル2」または「地域階層レベル4」が住所としては最小単位となります。


この記事が気に入ったらサポートをしてみませんか?