0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全国の年収データを地図で見てみる(MapLibreで色塗りと円グラフ表示もしてみる)

Last updated at Posted at 2024-04-04

全国の年収データを地図で見てみます

日本全国の年収のデータを地図で見てみようと思います。
MapLibreを使用して、行政界ポリゴンのベクトルタイルを平均年収で色分けして表示しました。また、市区町村ごとの年収データを円グラフ化して地図上に表示したコードサンプルもあります。

年収データについて

この記事で取り扱う年収は世帯年収を指します。
収入に関する公的なデータとしては

  • 国民生活基礎調査
  • 住宅・土地統計調査
  • 賃金構造基本統計調査
  • 就業構造基本調査
    などがあります。

書籍「ビジュアルでわかる日本」でも国民生活基礎調査や住宅・土地統計調査が紹介されています。
また、こちらのページにもありますように、国民生活基礎調査では全体の平均年収、住宅・土地統計調査は市区町村ごとの年収階層の分布が公表されています。
ただ、全市区町村のデータはないため地図で表示しようとした場合、歯抜けのような状態になります。

全国の年収のデータ

公的データではないですが、調査対象ではない市区町村の世帯年収を推計して歯抜けにならないように全国の市区町村の世帯年収を見ることができるデータもあります。
この記事では、全国の市区町村、町丁目レベルの世帯年収を推計したデータを使って地図で見ていきます。
以下に登場する行政界のポリゴンや年収別世帯数のデータはマップマーケティングの行政界ポリゴンと年収別世帯数推計データを使用しました。

都道府県の平均年収を見る

都道府県レベルであれば一覧性という意味では地図ではなく表の方が適している場合が多いと思います。
しかし、地図で見ることで、関東地方と中部地方の世帯年収が比較的高いことが一目で分かります。
各都道府県の平均世帯年収(357万円~539万円)を6段階に分けて色付けしました。青いほど低く、赤いほど高くなります。
都道府県の平均年収

市区町村の平均年収を見る

市区町村ごとの平均年収です。(沖縄県まで含めると画像が小さく見づらくなるため、沖縄県を除いて表示しました)
こちらも段階分けして色付けしました。使用した色は同じですが、都道府県の場合と段階分けに使用した値が異なりますので、同じ色でも都道府県の平均年収とは違う点にご注意ください。
小さくて見づらいですが東京の千代田区、港区あたりの世帯年収が特に高いことが分かります。
市区町村レベルで見ることで、都道府県レベルではわからなかった傾向が見えてきます。
市区町村の平均年収

北海道にクローズアップして見てみます。
北海道の東部にある別海町の世帯年収が他の市区町村より高いことが分かります。
北海道の地区町村の平均年収

年収別世帯数は平均年収だけではなく、年収を100万円刻みにして細かく年収分布をみることができます。今回はある程度グループ化して年収400万円未満、400から800万円、800万円以上の割合を円グラフで見てみました。
白が年収400万円未満、水色が400から800万円、青が800万円以上になります。
別海町の周辺の市区町村は北海道の他の市区町村に比べて世帯年収400~800万円の割合が高いようです。別海町は特に年収800万円を超える世帯が多いようです。
別海町周辺の平均年収と年収階層グラフ

ホタテ御殿の猿払村は?

猿払村は2022年度の市町村税課税状況等の調の結果によって、ホタテ御殿として話題になりました。
今回使用したデータは住宅土地統計をベースデータの一つとして採用しています。
猿払村は住宅土地統計の集計対象外となっていて収入階級が公表されてないため、猿払村の世帯年収は北海道平均に近い値になっています。

MapLibreで円グラフを表示するコード

以下は市区町村ポリゴンごとに色分けと円グラフ表示を行ったJavaScriptのコードです。
公式のexampleのコードをベースにして、

  • ドーナツグラフを単純な円グラフに変更
  • グラフのテキスト部分を削除
  • データ値に応じたグラフサイズの変更を削除
    などを行いました。

公式のコードから今回の記事では不要な部分を削ったため、削除漏れなどあるかもしれませんがご容赦ください。
MapLibreのMarkerにSVG要素をセットする方法なので、試してはいないですがSVGで円グラフを描く際に使われる他の方法(stroke-dasharrayなど)やSVGでグラフを出力するようなライブラリでも可能だと思います。

import './style.css'
// MapLibreの読み込み
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const map = new maplibregl.Map({
    container: 'map',
    style: {
        version: 8,
        sources: {
            osm: {
                type: 'raster',
                tiles: [
                    'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
                ],
                tileSize: 256,
                attribution:
                    '<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>',
            },
            cityTile: {
                type: 'vector',
                tiles: [
                    'http://localhost:5173/nenshu2020_layer2023_city/tiles/{z}/{x}/{y}.pbf'
                ],
            },
            prefTile: {
                type: 'vector',
                tiles: [
                    'http://localhost:5173/nenshu2020_layer2023_pref/tiles/{z}/{x}/{y}.pbf'
                ],
            }
        },
        layers: [
            // 背景地図レイヤー
            {
                id: 'osm-layer',
                type: 'raster',
                source: 'osm',
                minzoom: 0,
                maxzoom: 22,
            },
            // 市区町村塗りつぶしレイヤー
            {
                id: 'cityPolygonLayer',
                type: 'fill',
                source: 'cityTile',
                'source-layer': 'nenshu2020_layer2023_city',
                paint: {
                    'fill-color': [
                        'interpolate',
                        ['linear'],
                        [
                            'case', 
                            ['==', ['get', 'household'], 0], 
                            0, // 世帯数が0の場合は除算せず、0をセット
                            ['/', ['get', 'nenshu_total_amount'], ['get', 'household']] // 平均年収
                        ],
                        0,
                        '#0855c4',
                        160,
                        '#5b97ee',
                        321,
                        '#92c7ff',
                        481,
                        '#ffd976',
                        642,
                        '#ffa746',
                        802,
                        '#d7352b',
                    ],
                    'fill-opacity': 0.6,
                },
            },
            //市区町村ラインレイヤー
            {
                id: 'cityLineLayer',
                type: 'line',
                source: 'cityTile',
                'source-layer': 'nenshu2020_layer2023_city',
                paint: {
                    'line-color': '#666',
                    'line-opacity': 0.6,
                    'line-width': 0.1
                },
            },
            //都道府県ラインレイヤー
            {
                id: 'prefLineLayer',
                type: 'line',
                source: 'prefTile',
                'source-layer': 'nenshu2020_layer2023_pref',
                paint: {
                    'line-color': '#666',
                    'line-opacity': 0.8,
                    'line-width': 1
                },
            }
        ],
    },
    center: [144.95707, 43.41482],
    zoom: 9
});

map.on('click', 'cityPolygonLayer', (e) => {
    console.log(e.features[0].properties);
});
map.on('data', (e) => {
    if (e.sourceId !== 'cityTile' || !e.isSourceLoaded) return;
    const markers = {};
    let markersOnScreen = {};

    function updateMarkers() {
        // 描画されているfeatureを取得
        const features = map.queryRenderedFeatures({
            layers: ['cityPolygonLayer']
        });
        const newMarkers = {};
        for (let i = 0; i < features.length; i++) {
            const props = features[i].properties;
            const id = props.geocode;

            // 世帯数が0の市区町村はスキップ
            if (props['household'] === 0) continue;

            // 年収階層(400万円未満、400~800万円、800万円以上)でグループ化
            const nenshuUnder400M = props['nenshu_under_200M'] + props['nenshu200-300M'] + props['nenshu300-400M'];
            const nenshuBetween400And800M = props['nenshu400-500M'] + props['nenshu500-600M'] + props['nenshu600-700M'] + props['nenshu700-800M'];
            const nenshuOver800M = props['nenshu800-900M'] + props['nenshu900-1000M'] + props['nenshu1000-1250M'] + props['nenshu1250-1500M'] + props['nenshu1500-2000M'] + props['nenshu_over_2000M'];

            let marker = markers[id];
            if (!marker) {
                // 円グラフのSVGを作成してマーカーの要素にセット
                const el = createPieChart([nenshuUnder400M , nenshuBetween400And800M, nenshuOver800M]);
                marker = markers[id] = new maplibregl.Marker({
                    element: el
                }).setLngLat([props.lng, props.lat]);
            }
            newMarkers[id] = marker;

            // 現在描画されているマーカー以外をマップに追加
            if (!markersOnScreen[id]) marker.addTo(map);
        }

        //表示外のマーカーを削除
        for (const id in markersOnScreen) {
            if (!newMarkers[id]) markersOnScreen[id].remove();
        }
        markersOnScreen = newMarkers;
    }

    map.on('moveend', updateMarkers);
    updateMarkers();
});

// 円グラフのSVG要素のHTMLを生成
function createPieChart(items) {
    const offsets = [];
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        offsets.push(total);
        total += items[i];
    }
    const r = 28;
    const w = r * 2;
    const colors = ['#ede8e4', '#7ec8ef', '#006bbe'];

    let html =
        `<div><svg width="${
            w
        }" height="${
            w
        }" viewbox="0 0 ${
            w
        } ${
            w
        }" style="display: block">`;

    for (let i = 0; i < items.length; i++) {
        html += segment(
            offsets[i] / total,
            (offsets[i] + items[i]) / total,
            r,
            colors[i]
        );
    }
    html += '</svg></div>';

    const el = document.createElement('div');
    el.innerHTML = html;
    return el.firstChild;
}

// 円グラフのpath要素のHTMLを生成
function segment(start, end, r, color) {
    if (end - start === 1) end -= 0.00001;
    const a0 = 2 * Math.PI * (start - 0.25);
    const a1 = 2 * Math.PI * (end - 0.25);
    const x0 = Math.cos(a0), y0 = Math.sin(a0);
    const x1 = Math.cos(a1), y1 = Math.sin(a1);
    const largeArc = end - start > 0.5 ? 1 : 0;
    return [
        '<path d="M',
        r,
        r,
        'L',
        r + r * x0,
        r + r * y0,
        'A',
        r,
        r,
        0,
        largeArc,
        1,
        r + r * x1,
        r + r * y1,
        `" fill="${color}" />`
    ].join(' ');
}

参考サイト

町丁目レベルで平均年収を見る

町丁目の平均年収
別海町の町丁目のみ表示するようにfilterをセットして色分けしてみました。
別海町は酪農が盛んであることや海沿いより内陸の方が世帯年収が高いことから、酪農農家世帯の影響で他の北海道の市区町村より世帯年収が高くなっていることが推測されます。

年収データの活用方法

これまで地図で年収データを見てきました。今回紹介しませんでしたが、行政界レベルの他に郵便番号エリアや500mメッシュなどでも見ることができます。
年収データは店舗の出店計画やチラシなどの販売促進の計画で活用されます。
世帯年収が高いところを重視するだけではなく、ターゲットとなる顧客層に適した世帯年収を軸に分析することができます。

東京23区 年収マップ

最後に、Tableau Publicで公開している東京23区の年収マップをご紹介します。
東京23区 年収マップ

こちらのリンクから地図を操作しながらインタラクティブに東京23区の年収を見ることができます。
東京23区 年収マップ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?