都道府県別の人口密度を評価 総務省統計局データをPythonで加工 e-Stat with Python(2)

 


e-Stat APIの都道府県別、男女別、総人口・日本人別の人口データ("statsDataId": "0003448232")と国土数値情報の行政区域データを活用して、各都道府県の人口密度について図化・分析してみます。

必要なLibraryは以下のとおりです。
# Import the required libralies
!pip install geopandas
import geopandas as gpd
import pathlib
import requests
import pandas as pd
import matplotlib.pyplot as plt
!pip install mapclassify
import mapclassify

#matplotlibで日本語を表示するためのおまじない
!pip install japanize_matplotlib
import japanize_matplotlib

 様々なデータを都道府県別で分析評価していく上で、日本地図を色分け表示するのがわかりやすいので、国土数値情報から都道府県地図データを読み込んで、かつ、各都道府県の概算面積を求めるために、座標系を日本測地系JGD2011のUTM(ユニバーサル横メルカトル)座標系の中日本(東経132-138)を表すzone53、すなわちEPSG6690に変換する関数を定義します。

# Import the required libralies
#!pip install geopandas
#import geopandas as gpd
#import pathlib

#都道府県別日本地図のGeoDataFrameを作成する関数japan_pref_map()を定義
# Define the function  'japana_pref_map()' which create the Japanese map including each prefecture Geo data
def creat_jp_pref_map():

# Create the data saving directory
#    NOTEBOOK_PATH = pathlib.Path().resolve()
#    DATA_DIRECTORY = NOTEBOOK_PATH / "data"

# Download the Japan Map Data at Zip style
# Sorce: National Land Numerical Information released by MILT
    JapanMap_url = 'https://nlftp.mlit.go.jp/ksj/gml/data/N03/N03-2023/N03-20230101_GML.zip'
    # From Country Lands Numerical Infomation System by Japanese Gavornment
    gdf_jp = gpd.read_file(JapanMap_url, encoding='shift-jis')

#    jp_gdf.head()

# Concentrate each prefecture from many city's geometry
# pref_gdf = jp_gdf.dissolve("N03_001") #ERROR CODE
# japan_gdfをN03_001列(都道府県名)でグループ化して、各都道府県のジオメトリを結合する
    gdf_jp_pref = gdf_jp.dissolve("N03_001", aggfunc='first')

# reset_index()を使って元の "N03_001" 列を新しい列として復元する
    gdf_jp_pref = gdf_jp_pref.reset_index()

#CRSを(EPSG:6690)に変換
    gdf_jp_pref_EPSG6690 = gdf_jp_pref.to_crs("EPSG:6690")

#都道府県ごとに面積を計算し、GeoDataFrameに追加
    gdf_jp_pref_EPSG6690["面積(m2)"] = gdf_jp_pref_EPSG6690.area

#    print(gdf_japan)
#    print(gdf_jp_pref_EPSG6690)

    return gdf_jp_pref_EPSG6690

creat_jp_pref_map()

 次に、e-Stat_APIから口推計の都道府県別人口データ(年次最新版)を取得します。

# Import the required libralies
#import requests
#import pandas as pd

APP_ID = "*****************************" #自分のe-Stat API IDを入力
API_URL = "http://api.e-stat.go.jp/rest/3.0/app/json/getStatsData"

params = {
    "appId": APP_ID,
    "statsDataId": "0003448232", #人口推計-総務省-都道府県、男女別人口(総人口、日本人人口)のstatDataID
    "lang": "J",  # Order Japanese
    "cdTime": "2022000000" # Subjected year Order 2021
}

#人口推計の当該DataをDownLoad
response = requests.get(API_URL, params=params)
# Process the response
data = response.json()

# Exchange JSON Data to Pandas DataFrame
df = pd.DataFrame(data['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])

# Processing Pandas data and change the column name
column_rename = {
    '@tab': 'タブ',
    '@cat01': '性別(000:男女計,001:男性,002:女性)',
    '@cat02': '001:総人口, 002:日本人人口',
    '@area': '都道府県コード',
    '@time': '対象年',
    '@unit': '単位',
    '$': '人口(千人)'
}

df.rename(columns=column_rename, inplace=True)
# 'inplace=True' is mean that the column names of the original DataFrame is changed
# if 'inplace=False' is orded, a new DataFrame would be created

df

 出力は以下の通りです。

タブ性別(000:男女計,001:男性,002:女性)001:総人口, 002:日本人人口都道府県コード対象年単位人口(千人)
0001000001000002022000000千人124947
1001000001010002022000000千人5140
2001000001020002022000000千人1204
3001000001030002022000000千人1181
4001000001040002022000000千人2280
........................
283001002002430002022000000千人894
284001002002440002022000000千人573
285001002002450002022000000千人551
286001002002460002022000000千人817
287001002002470002022000000千人736

 出力を確認すると。287行のデータが存在しており、全国+都道府県=48で割ると6setのデータがあることがわかります。内容をわたしなりに推定した結果、このうち総人口の男女計は分類@cat01=000,@cat02=001が該当していると想像しました(一応、e-StatのDataViewにて複数個の数値の合致を確認してみました)。ちなみに最終版の@cat01=002, @cat02=002は日本人の女性人口を表しているような気がします。

 次に、都道府県コードを都道府県の名称に置き換えます。これは前回のコードを流用しております。

#Dataの構造が格納されているMetaInfoを取得
API_URL = "http://api.e-stat.go.jp/rest/3.0/app/json/getMetaInfo?"

params = {
    "appId": APP_ID,
    "statsDataId": "0003448232",
    "limit": 2,
    "lang": "J"  # 日本語を指定
}

response = requests.get(API_URL, params=params)
data = response.json()
data

#辞書の構造を表示
#import pprint
#pprint.pprint(data)

# META_INFOの中から都道府県コードと都道府県名を退避している箇所を取得
values = data['GET_META_INFO']['METADATA_INF']['CLASS_INF']['CLASS_OBJ']
#pprint.pprint(values)

#type(values) #CLASS_OBJにはlist形式でデータが格納されていることを確認

for dictionary in values:
#    print('dictionary = ', dictionary)
    if dictionary['@id'] == 'area':
        pref_list = dictionary['CLASS']

prefectures_list = {}
for pref_code in pref_list:
#    print(pref_code)
    prefectures_list[pref_code['@code']] = pref_code['@name']

#print('prefecture_list = ', prefectures_list)

# 先ほど作成した辞書を使用して、都道府県コードに基づき、
# 都道府県名の列を追加するための関数を定義
def replace_pref_name(pref_code):
    return prefectures_list.get(pref_code, None)

# 都道府県コード列を都道府県名列に置き換え
df["都道府県"] = df["都道府県コード"].apply(replace_pref_name)

print(df)

#国土数値情報から日本都道府県地図の
gdf_jp_pref = creat_jp_pref_map()

 次は都道府県人口データの中から、今回データ表示しようとしている都道府県別の男女総人口の部分をPickUpします。さらに、都道府県ごとの総面積から各都道府県の人口密度(人/km2)を求めます。

# Select the requirede data from DataFrame'df'
# Ex. 'total of meil and femeil : code=000' and 'total population(Janapese + foreigner) : code=001'
filtered_data = df[(df['性別(000:男女計,001:男性,002:女性)']=='000') & (df['001:総人口, 002:日本人人口']=='001')]
# Tips (...) & (...)の括弧を忘れるとエラー発生

df_total_pop = filtered_data.copy()

#print(df_total_pop)

# Give the population data to gdf_jp_pref
for index, row in df_total_pop.iterrows():
#    print(row["都道府県"], row["人口(千人)"])
    gdf_jp_pref.loc[gdf_jp_pref['N03_001'] == row["都道府県"], "人口(千人)"] = row["人口(千人)"]

gdf_jp_pref["人口(千人)"] = gdf_jp_pref["人口(千人)"].astype(float)
gdf_jp_pref["面積(m2)"] = gdf_jp_pref["面積(m2)"].astype(float)

gdf_jp_pref["人口密度(人/km2)"] = gdf_jp_pref["人口(千人)"]*1000/(gdf_jp_pref["面積(m2)"]/1000_000)

gdf_jp_pref.head()

出力は省略して、以下のコードで図化します。

# Show the plot of results

# Import the required librarise
#import matplotlib.pyplot as plt
#!pip install mapclassify
#import mapclassify
#matplotlibで日本語を表示するためのおまじない
#!pip install japanize_matplotlib
#import japanize_matplotlib

ax = gdf_jp_pref.plot(
    figsize=(8,8),
    column="人口密度(人/km2)",
    cmap="rainbow",
    markersize=15,
    legend=False
#    legend=True,
)

# データ型を数値に変換
gdf_jp_pref["人口密度(人/km2)"] = gdf_jp_pref["人口密度(人/km2)"].astype(float)

# カラーバーの作成
#sm = plt.cm.ScalarMappable(cmap="plasma")
sm = plt.cm.ScalarMappable(cmap="rainbow")
sm.set_array(gdf_jp_pref["人口密度(人/km2)"])
cbar = plt.colorbar(sm, ax=ax, shrink=0.7, location='right', pad=-0.1, fraction=0.1)
# Below cbar parameters
# 'shrink' is color bar size
# 'locatioin' is color bar location(ex. 'upper right', 'horizontal etc.)
# 'pad' is color bar relative position for graph
# 'fraction' is the division rate of colorbar(ex. fraction=0.1 is 10% division)

# Graph TitleをSet
ax.set_title("都道府県別 人口密度(人/km2)")

 出力はこのようになります。

 一般的に知られている大都市圏以外は日本国中1000人/km2未満の紫色になっています。東京都は6000人/km2と飛び抜けて人口密度が高く、おそらく、大阪、埼玉、神奈川が続き、あとは千葉や愛知、福岡、兵庫が続いているように見えます。

 これは森林も含めた各都道府県の総面積での比較です。もし、生活に不便な森林の面積を控除した各都道府県の面積で比較してみたらどうなるか、知的好奇心がワキワキになったので、データを探して加工してみます。

 森林面積をGoogleしてみたところ、林野庁HPに都道府県別森林率・人口林率(平成29年3月31日現在)(URL:https://www.rinya.maff.go.jp/j/keikaku/genkyou/h29/1.html)というのがあったので、統計データの時期は異なることを覚悟で、Webスクレ-ピング技術を使って、データを取得します。

 ここでChatGPT先生が大活躍してくれました。ChatGPT先生とのやりとりは以下のとおりです。

***ChatGPT先生への質問と回答***

質問:

林野庁が公表している都道府県別森林率・人口林率(URL:https://www.rinya.maff.go.jp/j/keikaku/genkyou/h29/1.html)から各都道府県の森林面積、人口林面積、国土面積を取得して、PandasのDataFrameを作成するコードを教えてください。

回答:

残念ながら、私(ChatGPT)は直接ウェブサイトにアクセスしてデータを取得することはできません。また、URL先のページの内容も私が閲覧できる情報には含まれていません(2021年9月までの情報しか持っていません)。


ただし、Pythonを使ってウェブサイトから情報をスクレイピングしてPandasのDataFrameを作成する方法をお伝えすることは可能です。


以下は、RequestsとBeautifulSoupを使用して林野庁のウェブサイトからデータをスクレイピングし、PandasのDataFrameを作成するサンプルコードです。ただし、実際にウェブサイトの内容が変わっている可能性もあるため、適宜調整してください。

import requests

from bs4 import BeautifulSoup

import pandas as pd


url = "https://www.rinya.maff.go.jp/j/keikaku/genkyou/h29/1.html"


# ウェブページからHTMLを取得

response = requests.get(url)

html_content = response.text


# BeautifulSoupを使ってHTMLを解析

soup = BeautifulSoup(html_content, "html.parser")


# テーブルを取得

table = soup.find("table")


# テーブルのヘッダーを取得

headers = [header.text.strip() for header in table.find_all("th")]


# テーブルのデータを取得

data_rows = []

for row in table.find_all("tr"):

    data_row = [cell.text.strip() for cell in row.find_all("td")]

    if data_row:

        data_rows.append(data_row)


# PandasのDataFrameに変換

df = pd.DataFrame(data_rows, columns=headers)


# 不要な行を削除

df = df.dropna()


# 列の型を数値に変換

df["森林面積(千ha)"] = df["森林面積(千ha)"].str.replace(",", "").astype(float)

df["人口林面積(千ha)"] = df["人口林面積(千ha)"].str.replace(",", "").astype(float)

df["国土面積(千ha)"] = df["国土面積(千ha)"].str.replace(",", "").astype(float)


# データフレームを表示

print(df)

このコードを実行すると、ウェブサイトから取得したデータがPandasのDataFrameとして表示されるはずです。ただし、サイトの構造が変わっている可能性があるため、動作しない場合はサイトの変更に対応する必要があるかもしれません。

なお、ウェブスクレイピングにはサイトの利用規約を遵守することが重要です。サイトの利用規約を確認し、スクレイピングが許可されている場合にのみ実行してください。また、頻繁なスクレイピングはサイトに負荷をかけることになりますので、適切な頻度で行うようにしてください。

*****ChatGPT先生 Finish*****

 以下、Pythonのコードです。

#Webスクレイピングに必要なlibraries
!pip install requests
import requests
!pip install beautifulsoup4
from bs4 import BeautifulSoup


url = "https://www.rinya.maff.go.jp/j/keikaku/genkyou/h29/1.html"

# ウェブページからHTMLを取得
response = requests.get(url)
html_content = response.text

# BeautifulSoupを使ってHTMLを解析
soup = BeautifulSoup(html_content, "html.parser")

# テーブルを取得
table = soup.find("table")

# テーブルのヘッダーを取得
# This parts don't work!
#headers = [header.text.strip() for header in table.find_all("th")]
# But We can use data_rows[0] to DataFrame's column name!

# テーブルのデータを取得
data_rows = []
for row in table.find_all("tr"):
    data_row = [cell.text.strip() for cell in row.find_all("td")]
    if data_row:
        data_rows.append(data_row)

# Create the column name of the PandasDataFrame
headers = ['Num'] + data_rows[0]

#print('headers', headers)

# PandasのDataFrameに変換
df_forest = pd.DataFrame(data_rows, columns=headers)

# 不要な行を削除
df_forest = df_forest.dropna()

# 列の型を数値に変換
df_forest['森林面積(ha)'] = df_forest['森林面積(ha)'].str.replace(",", "").astype(float)
df_forest['人工林面積(ha)'] = df_forest['人工林面積(ha)'].str.replace(",", "").astype(float)
df_forest['国土面積(ha)'] = df_forest['国土面積(ha)'].str.replace(",", "").astype(float)

# データフレームを表示
#print(df)

# Give the population data to gdf_jp_pref
for index, row in df_forest.iterrows():
#    print(row["都道府県"], row["人口(千人)"])
    gdf_jp_pref.loc[gdf_jp_pref['N03_001'] == row["都道府県"], "森林面積(ha)"] = row["森林面積(ha)"]

gdf_jp_pref['人口密度(森林面積除き)(人/km2)'] = gdf_jp_pref['人口(千人)']*1000 / (gdf_jp_pref['面積(m2)']/1000_000 - gdf_jp_pref['森林面積(ha)']/100)
gdf_jp_pref.tail()

 OutputはOmitしますが、almost No ErrorでSuccess。Teacher ChatGPT様様でございます。このCodeの中で「人口密度(森林面積除き)(人/km2)」までCalculateして、gdf_jp_prefにColumnをAddしています。

 We should show the resultでございます。CodeはLike belowです。


ax = gdf_jp_pref.plot(
    figsize=(8,8),
    column="人口密度(森林面積除き)(人/km2)",
    cmap="rainbow",
    markersize=15,
    legend=False
#    legend=True,
)

# データ型を数値に変換
#gdf_jp_pref["人口密度(森林面積除き)(人/km2)"] = gdf_jp_pref["人口密度(人/km2)"].astype(float)

# カラーバーの作成
sm = plt.cm.ScalarMappable(cmap="rainbow")
sm.set_array(gdf_jp_pref["人口密度(森林面積除き)(人/km2)"])
cbar = plt.colorbar(sm, ax=ax, shrink=0.7, location='right', pad=-0.1, fraction=0.1)
# Below cbar parameters
# 'shrink' is color bar size
# 'locatioin' is color bar location(ex. 'upper right', 'horizontal etc.)
# 'pad' is color bar relative position for graph
# 'fraction' is the division rate of colorbar(ex. fraction=0.1 is 10% division)

# Graph TitleをSet
ax.set_title("都道府県別 人口密度(森林面積除き)(人/km2)")

 Plot is like belowです。

  参考までに、SimpleなPopulationDensityとWithoutForestのPopulationDensityを都道府県別に棒グラフにして比べてみましたが、都道府県によってばらつきはあるものの、当然ある程度WithoutForestのPopulationDensityが大きくなっていることがわかります。傾向としては大都市圏では変化率が小さく、田舎ほど変化率が大きくなる、すなわち、森林面積が大きいということだと思われます。
 では、都道府県別の森林率について図化してみます。今後のことを考えて、都道府県別カラー地図の描画を関数かしておきました。
# 都道府県別のColorBarMapを図化するfn_create_color_map()関数を作成
def fn_create_color_map(gdf_jp_pref, style, data_column, size):

    ax = gdf_jp_pref.plot(
        figsize=size,
        column=data_column,
        cmap=map_style,
        markersize=15,
        legend=False
)

# カラーバーの作成
    sm = plt.cm.ScalarMappable(cmap=map_style)
    sm.set_array(gdf_jp_pref[data_column])
    cbar = plt.colorbar(sm, ax=ax, shrink=0.7, location='right', pad=-0.2, fraction=0.1)
# Below cbar parameters
# 'shrink' is color bar size
# 'locatioin' is color bar location(ex. 'upper right', 'horizontal etc.)
# 'pad' is color bar relative position for graph
# 'fraction' is the division rate of colorbar(ex. fraction=0.1 is 10% division)

# Graph TitleをSet
    ax.set_title("都道府県別 " + data_column)

    plt.show()

    return
####################

map_style = 'cool'
data_column = "人口密度(森林面積除き)(人/km2)"
size = (10,10)

fn_create_color_map(gdf_jp_pref, map_style, data_column, size)


全体としては想像どおりの地図になります。参考に棒グラフもつけておきます。
細かい部分を見ていくと、ちょっと興味深い点がいくつかあります。
・森林率のトップは高知県のようです。
・思っていたよりも北海道や青森県は少し森林率が低めです。
・長野県、岐阜県、岩手県、秋田県、山形県、宮崎県あたりの森林率が高いようです。
・首都圏をはじめ大都市圏ははやはり低いですね。
・滋賀県の森林率が低いです。琵琶湖のせいだと思われます。
・香川県の森林率を低いです。これは理由がわかりません。






コメント