🌟

ヘルシンキ大学のGeoPandas課題を日本版で解いてみた。【ショッピングセンター編】

2024/11/05に公開

概要

ヘルシンキ大学はGeoPandasに関する講義を無料公開しています。サンプルコードや解説、講義動画(英語)も用意され、内容は非常に充実しています。GeoPandasを学ぶ絶好の機会ですので、ぜひ活用してみてください。

https://autogis-site.readthedocs.io/en/2023/lessons/lesson-3/exercise-3.html

今回は、レッスン3の「ショッピングセンターのジオコード化」課題を日本のデータで解いていきます。なお、日本のデータ形式に合わせるため、一部内容を改変しています。

課題概要

ショッピングセンターから徒歩圏内に何人の人が住んでいるかを調べる。

改変内容

  • ヘルシンキのショッピングセンター → 名古屋市のショッピングセンター
  • ショッピングセンターから1.5km以内 → ショッピングセンターから500m以内
  • 空間結合 → 按分(あんぶん)計算

名古屋市はショッピングセンターの数が多く、住んでいる人がわざわざ遠い場所には行かないだろうと考え、範囲を500mに縮小しました。また、範囲が複数の区にまたがる場合があり、人口の精度を上げるため、按分計算を使用しています。

大まかな手順

  1. ショッピングセンター一覧から位置情報を割り出し、GeoPackage(gpkg)ファイルに保存
  2. ショッピングセンターのジオメトリに500mのバッファを追加
  3. ショッピングセンター周辺の人口を可視化

課題3-1. ショッピングセンターの住所を含むGPKGファイルを用意する

ショッピングセンターとは?

日本ショッピングセンター協会によると、以下の定義があります。

ショッピングセンターとは、一つの単位として計画、開発、所有、管理運営される商業・サービス施設の集合体で、駐車場を備えるものをいう。

代表格はイオンモールやららぽーとですね。

スプレットシートとGASを使って住所と緯度経度を取得

愛知県は車社会でショッピングセンターが多いらしいので、愛知県名古屋市のデータを使用することにしました。

まず、日本ショッピングセンター協会の一覧データから、店舗名を取得し、GASを使用して住所、緯度経度を取得します。

GASのスクリプトは以下のブログを参考にさせていただきました。
【GAS】複数の施設名から住所と緯度経度を一括取得してGoogleスプレッドシートに反映する

function geocoder() {
  // 開始行の設定
  const START_ROW = 2;         // データが開始される行
  const FACILITY_COL = 3;      // 施設名が入力されている列
  const ADDRESS_COL = 4;       // 住所情報を格納する列
  const LAT_COL = 5;           // 緯度を格納する列
  const LNG_COL = 6;           // 経度を格納する列
  
  // シート「名古屋市SC」を取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('名古屋市SC');
  var lastrow = spreadsheet.getLastRow();   // シートの最終行を取得
  
  // 開始行から最終行までループ
  for (var i = START_ROW; i <= lastrow; i++) {
    
    // 各行の施設名を取得
    var facility = spreadsheet.getRange(i, FACILITY_COL).getValue();
    
    // ジオコーダーを作成し、日本語に設定
    var geocoder = Maps.newGeocoder();
    geocoder.setLanguage('ja');
    
    // 施設名でジオコーディングを実行
    var response = geocoder.geocode(facility);
    
    // ジオコーディング結果が存在する場合
    if (response['results'][0] != null) {
      // 緯度、経度、住所情報を対応する列に書き込み
      spreadsheet.getRange(i, LAT_COL).setValue(response['results'][0]['geometry']['location']['lat']);
      spreadsheet.getRange(i, LNG_COL).setValue(response['results'][0]['geometry']['location']['lng']);
      spreadsheet.getRange(i, ADDRESS_COL).setValue(response['results'][0]['formatted_address']);
    }
  }
}

住所と緯度経度を取得することができました!
しかし、「千種区 ILYA(イリア)」のように住所を取得できていないデータや「北区 ダイエー上飯田店」のように東京北区の住所を取得しているデータが存在するため、これらは手動でGoogleマップから住所と緯度経度を入力しました。

最終的に、データはCSVファイルとして保存します。

ショッピングセンターCSVファイルをDataFrameに格納

Jupyter Notebookを起動し、CSVファイルを読み込みます。その後、pandasを使ってCSVをDataFrameに格納します。

import pandas
import geopandas as gpd
import pathlib
NOTEBOOK_PATH = pathlib.Path().resolve()

shopping_centres = pandas.read_csv(NOTEBOOK_PATH / 'shopping_center.csv')
# id列を追加
shopping_centres = shopping_centres.assign(
    id=range(1, len(shopping_centres.index) + 1)
).set_index('id')

shopping_centres

DataFrameを作成することができました!

DataFrameをGeoDataFrameに変換する

地理的な操作を行うため、geometry列追加するために、GeoDataFrameへ変換します。

# 緯度経度からPointジオメトリへ変換
geometry = gpd.points_from_xy(shopping_centres.longitude,shopping_centres.latitude)
shopping_centres = gpd.GeoDataFrame(shopping_centres, geometry=geometry,crs="EPSG:4326")
shopping_centres

geometry列が追加されていますね。
CRS(座標参照系)に地理座標系EPSG:4326を指定したのは、ユニバーサル横メルカトル座標系(UTM)EPSG:6690に変換する際にエラーにならないようにするためです。

https://joshi-engineer.com/valueerror-cannot-transform-naive-geometries-please-set-a-crs-on-the-object-first-が発生した件

CRS(座標参照系)をユニバーサル横メルカトル座標系(UTM)へ変換します。

shopping_centres_2D = shopping_centres.to_crs('EPSG:6690')
shopping_centres_2D

これにより、位置情報をm単位で扱うことができます。

GeoPackageファイル(gpkg)として保存する

to_file()を使用し、GeoPackageファイルを保存します。

shopping_centres_2D.to_file(NOTEBOOK_PATH / 'shopping_center.gpkg')

GeoPackage(gpkg)ファイルはシェープファイルの後継として作られたファイル形式で、軽量かつ空間インデックスを保持できることから、支持されている形式です。同じファイル内でレイヤーを作成することができ、便利です。

課題3-2.各ショッピングセンターを中心とし半径500mのバッファデータを追加

現在、geometryはショッピングセンターの位置情報つまり、点情報を保持しています。型(ジオメトリタイプ)はPointです。
最終的にショッピングセンターの周りにどれだけの人が住んでいるか調べたいため、位置情報を中心とした、半径500mのバッファをgeometryに上書きします。バッファを追加すると、型はPolygonになります。

バッファってなに?

ジオメトリのバッファは、空間に対するミンコフスキー和で、周囲の範囲を可視化することができます。

geometryにバッファを持たせて上書きする

shopping_center.gpkgにbuffersレイヤーを追加し、上書き保存します。

shopping_centres = gpd.read_file(NOTEBOOK_PATH / 'shopping_center.gpkg')
shopping_centres['geometry'] = shopping_centres.buffer(500)
# buffersレイヤーに保存
shopping_centres.to_file(NOTEBOOK_PATH / 'shopping_center.gpkg',layer="buffers")

課題3-3.ショッピングセンターの近くに住んでいる人の数を可視化

名古屋市の行政区域データと人口密度データをもとに、ショッピングセンター周辺の人口を取得します。

名古屋市の人口と行政区域を紐づけたGeoDataFrameを作成する

まず、国土交通省の行政区域データから愛知県の行政区域を取得します。
国土数値情報ダウンロードサイト

そのままデータを読み込むと、愛知県のすべての市町村ポリゴンデータが表示されます。

名古屋市の区のデータのみが欲しいので、市町村名カラムで絞り込みを行います。

# 名古屋市の区データを取得する
aichi = gpd.read_file(NOTEBOOK_PATH / 'aichi/N03-20240101_23.geojson')
aichi = aichi.dropna(subset=['N03_005'])
aichi = aichi.loc[aichi['N03_005'].str.contains('区')]
aichi

続いて愛知県人口動向調査結果より、各区の人口を手動で抽出し、CSV形式で保存します。

令和5年 愛知県人口動向調査結果(名古屋市分)

CSVファイルを読み込み、名古屋市の区データのカラムに追加します。

# 名古屋市の人口データを紐づける
population = pd.read_csv(NOTEBOOK_PATH / 'Helsinki/Exercise-3/population.csv')
# 人口密度を人/m2に変換(1 km2 = 1,000,000 m2)
population["population"] = population['population'].map(lambda x: x / 1_000_000)
population_grid = aichi.merge(
    population,
    left_on='N03_005',
    right_on='district',
    how='left'
)
population_grid = population_grid.to_crs('EPSG:6690')

名古屋市の人口比率とショッピングセンターの分布を見てみる

# 人口比重マップ
ax = population_grid.plot(figsize=(8,8),column="population",cmap="Reds",scheme="quantiles",markersize=1,legend=True)
# バッファを水色で表示
shopping_centres.plot(ax=ax,color='lightblue', alpha=0.5, edgecolor='blue')

赤色が濃いほど人口密度が高い区を表しています。水色の円は角ショッピングセンターを中心とした半径500mを表しています。

図をみると、一部のショッピングセンターは周囲500m以内で複数の区にまたがっていることがわかります。そのため、ショッピングセンター周辺の円に重なる面積を比重に応じて割り振って計算する必要があります。

また、名古屋駅がある中村区に多く立ち並んでいますが、どの区にもそれなりにショッピングセンターがあることが伺えます。名古屋市にお住まいの方は買い物に困らなさそうですね!

ショッピングセンター周辺にまたがる区を按分計算

ショッピングセンター周辺と交差する区の面積を按分計算するメソッドを作成します。

すべての区をfor文で回し、ショッピングセンターと交差した区に対し、交差した面積分(intersection_area)を抽出します。区域全体の面積(district_area)に対し、交差した面積(intersection_area)の割合からその区の人口を算出します。

# 各区とショッピングセンターバッファーの交差面積を按分計算
def calculate_intersection_ratio(shopping_center, population_grid):
    intersections = []
    
    for _, district in population_grid.iterrows():
        # ショッピングセンターと区域が交差するか
        if shopping_center.geometry.intersects(district.geometry):
            intersection = shopping_center.geometry.intersection(district.geometry)
            intersection_area = intersection.area
            district_area = district.geometry.area
            # 区域の人口密度から交差エリアの人口を按分計算
            population_in_intersection = district['population'] * district_area * (intersection_area / district_area)
            
            intersections.append({
                'district': district['N03_005'],
                'population': population_in_intersection,
                'area': intersection_area
            })
    
    return intersections

ショッピングセンターの近くに住んでいる人は何人いるかを求める

ショッピングセンターをfor文で1行づつ取り出し、calculate_intersection_ratio()の戻り値をresultに格納します。

最後に人口の降順で並び替えて表示して終了です!


# 各ショッピングセンターに対して人口を計算
results = []
for _, shopping_center in shopping_centres.iterrows():
    intersections = calculate_intersection_ratio(shopping_center, population_grid)
    # 交差エリアの人口を合計
    total_population = sum(
        intersection['population']
        for intersection in intersections
    )
    
    results.append({
        'name': shopping_center['name'],
        'intersecting_districts': len(intersections),
        'ショッピングセンター周辺の人口(人)': round(total_population)
    })
population_summary = pd.DataFrame(results)
population_summary.sort_values('ショッピングセンター周辺の人口(人)', ascending=False)

算出した値が合っているか数字チェック

計算した値がもっともらしいかチェックします。
ショッピングセンター周辺は1の区に収まる店舗、つまりintersecting_districts=1の店舗を選んで計算してみます。

代表して「港区 イオン南陽店」で考えてみます。
南区の人口密度は3,084[人/km2]のため

面積 = πr²
面積 = π(3.14) × (0.5km)² = 0.785km²

人口 = 面積 × 人口密度
人口 = 0.785km² × 3,084人/km²
人口 = 2421人(四捨五入)

港区 イオン南陽店周辺の人口:2418人

「港区 イオン南陽店」の計算結果は2418人だったので、ほぼ一致しているということで大丈夫そうですね。

終わりに

今回、名古屋市のショッピングセンター周辺にどれだけ人が住んでいるのかを可視化しました。まだまだ大まかな指標ですが、このような地域分析を行うことで、次どこにショッピングセンターを立てるかの目安にすることができそうですね!

参考リンク

愛知県ショッピングセンター一覧
https://www.jcsc.or.jp/data/popup/sclist2013_23.html

【GAS】複数の施設名から住所と緯度経度を一括取得してGoogleスプレッドシートに反映する
https://blog.nowhere.co.jp/archives/20200410-28473.html

令和5年 愛知県人口動向調査結果(名古屋市分)
https://www.city.nagoya.jp/somu/page/0000169711.html

国土数値情報ダウンロードサイト(https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-2024.html)

Discussion