からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【Python】2.3:モラン散布図の作成【空間DS入門のノート】

はじめに

 『Pythonで学ぶ空間データサイエンス入門』の独学時のまとめノートです。「定義編・導出編」「作図編・実装編」「可視化編」の三部構成でモデルやアルゴリズムの理解を目指します。
 本の内容から寄り道・回り道しながら進めます。本を読んだ上で補助的に読んでください。

 この記事では、Pythonを使って、モラン散布図を作成します。

【前の内容】

【他の内容】

www.anarchive-beta.com

【今回の内容】

2.3 モラン散布図の作成

 空間ラグ(SL・spatial lag)やグローバル・モランのI(GMI・global Moran's I)を可視化する図であるモラン散布図(Moran scatterplot)を作成します。
 空間ラグについては「2.3:空間ラグの定義式【空間DS入門のノート】 - からっぽのしょこ」、GMIについては「2.3:グローバル・モランのIの定義式【空間DS入門のノート】 - からっぽのしょこ」を参照してください。

 利用するライブラリを読み込みます。

# ライブラリを読込
import geopandas as gpd
from pysal.lib import weights
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap # 配色用
import matplotlib.patches as mpatches # 凡例用
from pandas.api.types import CategoricalDtype # 配色用
import japanize_matplotlib # 日本語の描画用


データの読込

 まずは、地理空間データを読み込んで、前処理を行います。
 詳しくは本の1.1節を参照してください。

国土数値情報ダウンロードサイトのデータの場合

 この例では、国土数値情報ダウンロードサイトから2026年版(基準年)の大阪府の行政区域データ・地価公示データをダウンロードして、大阪市のデータを利用します。

 「国土数値情報ダウンロードサイト」の行政区域データ・地価公示データを読み込みます。

# ファイルパスを指定
DISTRICT_PATH = 'data/N03-20260101_27_GML/N03-20260101_27.shp' # ポリゴンデータ:大阪府(2026年版)
LAND_PATH     = 'data/L01-26_27_GML/L01-26_27.shp'             # 地価公示:大阪府(2026年版)

# データを読込
gdf_district = gpd.read_file(DISTRICT_PATH, encoding='UTF-8') # (2026年版の場合)
gdf_land     = gpd.read_file(LAND_PATH, encoding='shift-jis') # (2026年版の場合)

# 行政区域データを取得
gdf_district         = gdf_district[['N03_004', 'N03_005', 'N03_007', 'geometry']] # (2026年版の場合)
gdf_district.columns = ['city1', 'city2', 'd_code', 'geometry']

# 地価公示データを取得
gdf_land         = gdf_land[['L01_024', 'L01_001', 'L01_008', 'geometry']] # (2026年版の場合)
gdf_land.columns = ['city', 'd_code', 'price', 'geometry']

# データを整形
dst_proj     = 6668
gdf_district = gdf_district.to_crs(epsg=dst_proj) # 空間座標系を再設定
gdf_land     = gdf_land.to_crs(epsg=dst_proj)     # 空間座標系を再設定

# データを加工
gdf_land['log_price'] = np.log(gdf_land['price']) # 対数地価公示価格

 事前に保存しておいた行政区域のポリゴンデータと地価公示データを読み込みます。
 必要に応じて扱いやすいようにデータを整形しておきます。

 行政区域データをまとめます。

# データを統合
gdf_target = gdf_district.dissolve(by='d_code', as_index=False) # 飛び地を統合
gdf_target = gdf_target.merge(
    gdf_land.groupby('d_code')['log_price'].mean(), 
    on='d_code'
) # 平均対数地価公示価格
gdf_target['centroids'] = gdf_target['geometry'].centroid # 重心座標

# データフレームを整形
gdf_target = gdf_target.reindex(
    columns=['city1', 'city2', 'd_code', 'log_price', 'geometry', 'centroids']
) # (目視での確認用)

 市区町村(行政区域コード)ごとに、飛び地などのポリゴンデータをまとめて、重心座標を求めます。(この処理だと警告文が出ますが、ここでの目的には影響しない誤差なので無視します。)
 また、市区町村ごとに対数地価公示価格の平均を計算して、データフレームに結合します。

 大阪市のデータを取り出します。

# 地域を指定
city_name = '大阪市'

# データを抽出 
gdf_target = gdf_target[gdf_target['city1'] == city_name]
gdf_target.head()
city1 city2 d_code log_price geometry centroids
0 大阪市 都島区 27102 13.013004 POLYGON ((135.51481 34.72057, 135.51489 34.720... POINT (135.52721 34.71193)
1 大阪市 福島区 27103 13.507231 POLYGON ((135.46247 34.6874, 135.46253 34.6879... POINT (135.47534 34.6939)
2 大阪市 此花区 27104 12.339599 MULTIPOLYGON (((135.34359 34.62112, 135.34357 ... POINT (135.42011 34.668)
3 大阪市 西区 27106 14.140507 POLYGON ((135.46667 34.67284, 135.4642 34.6747... POINT (135.48375 34.67776)
4 大阪市 港区 27107 12.555794 MULTIPOLYGON (((135.43006 34.64979, 135.43006 ... POINT (135.45084 34.66032)

 大阪府全域だとゴチャゴチャして分かりにくいので、大阪市内の区のデータを利用することにします。

 以上で、統計量の計算や作図に利用する地理空間データを用意できました。

スポンサードリンク

統計量の計算

 次は、各種の統計量を計算する関数を作成して、データから統計量を計算します。

実装

 空間ラグの計算を自作関数として定義します。

# SLの計算を実装
def calc_SL(x, W):
    
    # 偏差を計算
    x_dev = x - np.mean(x)

    # SLを計算
    SL = W @ x_dev
    return SL

 空間ラグの計算については「【Python】2.3:空間ラグの実装【空間DS入門のノート】 - からっぽのしょこ」を参照してください。

 GMIの計算を自作関数として定義します。

# GMIの計算を実装
def calc_GMI(x, W, ddof=0):
    
    # 偏差を計算
    x_dev = x - np.mean(x)

    # 分散を計算
    x_var = np.var(x, ddof=ddof)
    
    # GMIを計算
    GMI  = (x_dev.T @ W @ x_dev).squeeze()
    GMI /= np.sum(W) * x_var
    return GMI

 GMIの計算については「【Python】2.3:空間的自己相関(Global Moran's I)の実装【空間データサイエンス入門のノート】 - からっぽのしょこ」を参照してください。


計算

 入力データを設定します。

# データを取得
x = gdf_target['log_price'].to_numpy()
print(x[:5].round(2))
print(x.shape)
[13.01 13.51 12.34 14.14 12.56]
(24,)

 N 個の地域のデータ \mathbf{x} を作成します。

 \displaystyle
\mathbf{x}
    = \begin{pmatrix}
          x_1 \\ x_2 \\ \vdots \\ x_N
      \end{pmatrix}

 この例では、平均対数地価公示価格を使います。

 空間重み行列を作成します。

# 空間重み行列を作成
adj_obj = weights.Rook.from_dataframe(
    df=gdf_target, geom_col='geometry', use_index=False
)
adj_obj.transform = 'R' # 正規化
W, _ = adj_obj.full()
print(W[:5, :5].round(2))
print(W.shape)
[[0.   0.   0.   0.   0.  ]
 [0.   0.   0.2  0.2  0.  ]
 [0.   0.25 0.   0.25 0.25]
 [0.   0.14 0.14 0.   0.14]
 [0.   0.   0.33 0.33 0.  ]]
(24, 24)

 空間重み行列 \mathbf{W} を作成します。

 \displaystyle
\mathbf{W}
    = \begin{pmatrix}
          w_{11} & w_{12} & \cdots & w_{1N} \\
          w_{21} & w_{22} & \cdots & w_{2N} \\
          \vdots & \vdots & \ddots & \vdots \\
          w_{N1} & w_{N2} & \cdots & w_{NN}
      \end{pmatrix}

 この例では、ルーク型の空間隣接行列を用いて、空間重み行列を作成しています。
 空間重み行列については「【Python】2.2:空間重み行列の作図:境界の共有【空間データサイエンス入門のノート】 - からっぽのしょこ」を参照してください。

 SLを計算をします。

# SLを計算
SL = calc_SL(x, W)
print(SL[:5].round(2))
print(SL.shape)
[ 0.54  0.32  0.21  0.47 -0.01]
(24,)

 自作関数を使って、N 個の地域のSL \mathrm{SL}_1, \cdots, \mathrm{SL}_N を計算します。

 GMIを計算をします。

# GMIを計算
GMI = calc_GMI(x, W)
print(GMI.round(2))
0.3

 自作関数を使って、地域全体のGMI \mathrm{GMI} 計算します。

 以上で、作図に利用する統計量を求まりました。

スポンサードリンク

グラフの作成

 最後に、モラン散布図を作成します。
 pysal や splot ライブラリを利用して作図する方法は本を参照してください。

 作図データを格納します。

# 地域数を取得
N = len(gdf_target)

# 偏差を計算
x_dev = x - np.mean(x)

# 値を計算
gdf_target['deviation']   = x_dev # 偏差
gdf_target['spatial_lag'] = SL    # 空間ラグ

 データフレームに作図に利用する値を格納します。

データの確認

 データをコロプレス図で確認します。

作図コード(クリックで展開)

# 配色の範囲を設定
u = 0.5
x_min = gdf_target['log_price'].min()
x_max = gdf_target['log_price'].max()
x_min = np.floor(x_min /u)*u # u単位で切り下げ
x_max = np.ceil(x_max /u)*u  # u単位で切り上げ
u = 0.1
d_size = gdf_target['deviation'].abs().max()
d_size = np.ceil(d_size /u)*u  # u単位で切り上げ
u = 0.1
sl_size = gdf_target['spatial_lag'].abs().max()
sl_size = np.ceil(sl_size /u)*u  # u単位で切り上げ

# データを作図
fig, axes = plt.subplots(
    nrows=2, ncols=2, 
    figsize=(18, 12), dpi=100, facecolor='white', 
    constrained_layout=True
)
fig.suptitle('choropleth map', fontsize=20)

## 入力データの作図

# ラベルを作成
fml_lbl = '$x_i$'

# 入力データを描画
ax = axes[0, 0]
gdf_target.boundary.plot(
    ax=ax, 
    edgecolor='white', linewidth=0.5
) # 行政区界
gdf_target.plot(
    ax=ax, column='log_price', 
    cmap='viridis', vmin=x_min, vmax=x_max, 
    legend=True, legend_kwds={'label': fml_lbl, 'shrink': 0.9}
) # 入力データ
for n in range(N):
    area_x, area_y = gdf_target.loc[n, 'centroids'].coords[0] # 座標
    area_lbl       = gdf_target.loc[n, 'city2'] # 地域名
    ax.text(
        x=area_x, y=area_y, s=area_lbl, ha='center', va='center', 
        size=10
    ) # 地域名
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')
ax.set_title('input data', fontsize=20)
ax.grid()
ax.set_aspect(aspect='equal', adjustable='box')

## 偏差の作図

# ラベルを作成
fml_lbl = '$x_i - \\bar{x}$'

# 偏差を描画
ax = axes[0, 1]
gdf_target.boundary.plot(
    ax=ax, 
    edgecolor='white', linewidth=0.5
) # 行政区界
gdf_target.plot(
    ax=ax, column='deviation', 
    cmap='coolwarm', vmin=-d_size, vmax=d_size, 
    legend=True, legend_kwds={'label': fml_lbl, 'shrink': 0.9}
) # 偏差
for n in range(N):
    area_x, area_y = gdf_target.loc[n, 'centroids'].coords[0] # 座標
    area_lbl       = gdf_target.loc[n, 'city2'] # 地域名
    ax.text(
        x=area_x, y=area_y, s=area_lbl, ha='center', va='center', 
        size=10
    ) # 地域名
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')
ax.set_title('deviation', fontsize=20)
ax.grid()
ax.set_aspect(aspect='equal', adjustable='box')

## SLの作図

# ラベルを作成
fml_lbl = '$SL_i = \\sum_{i=1}^N w_{ij} (x_j - \\bar{x})$'

# SLを描画
ax = axes[1, 1]
gdf_target.boundary.plot(
    ax=ax, 
    edgecolor='white', linewidth=0.5
) # 行政区界
gdf_target.plot(
    ax=ax, column='spatial_lag', 
    cmap='coolwarm', vmin=-sl_size, vmax=sl_size, 
    legend=True, legend_kwds={'label': fml_lbl, 'shrink': 0.9}
) # 空間ラグ
for n in range(N):
    area_x, area_y = gdf_target.loc[n, 'centroids'].coords[0] # 座標
    area_lbl       = gdf_target.loc[n, 'city2'] # 地域名
    ax.text(
        x=area_x, y=area_y, s=area_lbl, ha='center', va='center', 
        size=10
    ) # 地域名
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')
ax.set_title('spatial lag', fontsize=20)
ax.grid()
ax.set_aspect(aspect='equal', adjustable='box')

# 不要なサブプロットを非表示
axes[1, 0].axis('off')

plt.show()

入力データに関するコロプレス図

 左上図はデータの値(平均対数地価公示価格)、右上図は偏差の値、右下図はSLの値をグラデーションで示します。偏差とSLの図に関しては、0を中心とする範囲でグラデーション(値が0のとき灰色、値が小さいほど濃い青色、値が大きいほど濃い赤色)を設定しています。

モラン散布図

 モラン散布図を作成します。

# 軸の範囲を設定
u = 0.5
axis_size = max(np.abs(x_dev).max(), np.abs(SL).max())
axis_size = np.ceil(axis_size /u)*u # u単位で切り上げ

# ラベルを作成
param_lbl = f'$N = {N}, GMI = {GMI:.2g}$'

# モラン散布図を作成
fig, ax = plt.subplots(
    figsize=(6, 6), dpi=100, facecolor='white', 
    constrained_layout=True
)
fig.suptitle('Moran scatterplot', fontsize=20)

ax.axhline(
    y=0.0, 
    color='black', linewidth=1.0, linestyle='--', 
    zorder=10
) # x軸線
ax.axvline(
    x=0.0, 
    color='black', linewidth=1.0, linestyle='--', 
    zorder=10
) # y軸線
ax.axline(
    xy1=(0, 0), slope=GMI, 
    color='red', linewidth=1.0, linestyle='-', 
    label='Moran regression line', 
    zorder=11
) # 回帰直線
ax.scatter(
    x=x_dev, y=SL, 
    s=50, 
    zorder=20
) # 偏差 - 空間ラグ
ax.set_xlabel('$x_i - \\bar{x}$')
ax.set_ylabel('$SL_i = \\sum_{j=1}^N w_{ij} (x_j - \\bar{x})$')
ax.set_title(param_lbl, loc='left')
ax.legend()
ax.grid()
ax.set_xlim(xmin=-axis_size, xmax=axis_size)
ax.set_ylim(ymin=-axis_size, ymax=axis_size)
ax.set_aspect(aspect='equal', adjustable='box')

plt.show()

モラン散布図

 横軸を偏差、縦軸を空間ラグの散布図で、空間ラグの偏りを示します。原点を通る傾きがGMIの直線(赤色の実線)が回帰直線に対応します。

 ax.scatter() を使って、散布図を描画します。x座標の引数 x に偏差(または標準化した変数)、y座標の引数 y にSLを指定します。
 ax.axline() を使って、回帰直線を描画します。切片の引数 xy1 に原点の座標、傾きの引数 slope にGMIを指定します。

 偏差の平均は \mathbb{E}[x_n - \bar{x}] = \frac{1}{N} \sum_{n=1}^N \{x_n - \bar{x}\} = 0 です。偏差 x_i - \bar{x} が0のとき地域 i は全体平均と一致、正の値(0より大きい)のとき全体平均よりも大きい、負の値(0より小さい)のとき全体平均よりも小さいことを表します。空間ラグ \mathrm{SL}_i が0のとき地域 i の近隣地域は全体平均と一致、正の値のとき全体平均よりも大きい、負の値のとき全体平均よりも小さいことを表します。

 平均値に対する大小関係(各地域の符号)のカテゴリを割り当てます。

# カテゴリを割当
gdf_target['dev_type'] = np.where(
    gdf_target['deviation'] >= 0.0, 'High', 'Low'
) # 横軸の High,Low
gdf_target['sl_type'] = np.where(
    gdf_target['spatial_lag'] >= 0.0, 'High', 'Low'
) # 縦軸の High,Low
gdf_target['moran_type'] = gdf_target['dev_type'] + '-' + gdf_target['sl_type'] # カテゴリラベル 
gdf_target['quadrant'] = np.select(
    [
        (gdf_target['deviation'] >= 0.0) & (gdf_target['spatial_lag'] >= 0.0), # High-High
        (gdf_target['deviation'] < 0.0)  & (gdf_target['spatial_lag'] >= 0.0), # Low-High
        (gdf_target['deviation'] < 0.0)  & (gdf_target['spatial_lag'] < 0.0),  # Low-Low
        (gdf_target['deviation'] >= 0.0) & (gdf_target['spatial_lag'] < 0.0)   # High-Low
    ],
    [1, 2, 3, 4],
    default=0
) # 象限番号

# データを確認
gdf_target[['city2', 'deviation', 'spatial_lag', 'dev_type', 'sl_type', 'moran_type', 'quadrant']].head()
city2 deviation spatial_lag dev_type sl_type moran_type quadrant
0 都島区 0.131964 0.540768 High High High-High 1
1 福島区 0.626191 0.321787 High High High-High 1
2 此花区 -0.541442 0.208086 Low High Low-High 2
3 西区 1.259467 0.466808 High High High-High 1
4 港区 -0.325246 -0.014860 Low Low Low-Low 3

 偏差(横軸)と空間ラグ(縦軸)の符号(0より大きいか小さいか)によりそれぞれ 'High', 'Low' のラベルを割り当て、2つのラベルを文字列結合して象限ごと(4パターン)のラベル(カテゴリ)と象限番号(カテゴリ番号)を作成します。

 カテゴリごとに色付けしたモラン散布図を作成します。

# カラーマップを作成
cmap = ListedColormap([
    'hotpink',   # High-High
    'orange',    # Low-High
    'royalblue', # Low-Low
    'limegreen'  # High-Low
])
# 軸の範囲を設定
u = 0.5
axis_x_size = np.abs(x_dev).max()
axis_y_size = np.abs(SL).max()
axis_x_size = np.ceil(axis_x_size /u)*u # u単位で切り上げ
axis_y_size = np.ceil(axis_y_size /u)*u # u単位で切り上げ

# ラベルを作成
param_lbl = f'$N = {N}, GMI = {GMI:.2g}$'

# モラン散布図を作成
fig, ax = plt.subplots(
    figsize=(8, 6), dpi=100, facecolor='white', 
    constrained_layout=True
)
fig.suptitle('Moran scatterplot', fontsize=20)

ax.plot(
    [[-axis_x_size, 0.0], [axis_x_size, 0.0]], 
    [[0.0, -axis_y_size], [0.0, axis_y_size]], 
    color='black', linewidth=1.0, linestyle='--', 
    zorder=10
) # xy軸線
ax.axline(
    xy1=(0, 0), slope=GMI, 
    color='red', linewidth=1.0, linestyle='-', 
    zorder=11
) # 回帰直線
scatter = ax.scatter(
    x=gdf_target['deviation'], 
    y=gdf_target['spatial_lag'], 
    cmap=cmap, c=gdf_target['quadrant'], 
    s=50, 
    zorder=20
) # 偏差 - 空間ラグ
for i in range(N):
    # ラベルの表示位置を調整
    area_x, area_y, area_lbl = gdf_target.loc[i, ['deviation', 'spatial_lag', 'city2']]
    offset_x = 0.0 # 調整幅を指定
    offset_y = 0.05 # 調整幅を指定
    offset_x *= np.sign(area_x)
    offset_y *= np.sign(area_y)
    offset_x = offset_x if area_x+offset_x <= axis_x_size else axis_x_size-area_x
    offset_x = offset_x if area_x+offset_x >= -axis_x_size else -axis_x_size-area_x
    offset_y = offset_y if area_y+offset_y <= axis_y_size else axis_y_size-area_y
    offset_y = offset_y if area_y+offset_y >= -axis_y_size else -axis_y_size-area_y
    ax.annotate(
        text=area_lbl, xy=(area_x, area_y), 
        xytext=(area_x+offset_x, area_y+offset_y), 
        ha='center', va='center', rotation=0, 
        arrowprops=dict(
            arrowstyle='-', color='black', linewidth=0.5
        ), 
        size=10, 
        zorder=21
    ) # 地域名
ax.set_xlabel('$x_i - \\bar{x}$')
ax.set_ylabel('$SL_i = \\sum_{j=1}^N w_{ij} (x_j - \\bar{x})$')
ax.set_title(param_lbl, loc='left')
ax.grid()
ax.set_xlim(xmin=-axis_x_size, xmax=axis_x_size)
ax.set_ylim(ymin=-axis_y_size, ymax=axis_y_size)

# 凡例を設定
handles, _ = scatter.legend_elements()
labels     = ['High-High', 'Low-High', 'Low-Low', 'High-Low']
ax.legend(
    handles=handles, labels=labels,
    title='quadrant', loc='upper left'
)

plt.show()

モラン散布図

 各地域が属する象限(カテゴリ)ごとに色分けして示します。

 ax.scatter() を使って、散布図を描画します。カラーマップの引数 cmap に離散型のカラーマップ、点の色の引数 c にカテゴリ番号を指定します。
 離散型のカラーマップは、ListedColormap() で作成できます。カテゴリ番号に対応する任意の色名(やカラーコード)を指定して、カラーマップを作成しています。

 軸線(黒色の破線)に区切られた4つのエリアごとにカテゴリが割り当てられる(色分けされる)のを確認できます。

 モラン散布図のカテゴリカルマップを作成します。

# カラーマップを作成
cmap = ListedColormap([
    'hotpink',   # High-High
    'orange',    # Low-High
    'royalblue', # Low-Low
    'limegreen'  # High-Low
])

# カテゴリ順(カテゴリ名と色名の対応)を指定
categories = [
    'High-High',
    'Low-High',
    'Low-Low',
    'High-Low'
]

# カテゴリ順を設定
dtype = CategoricalDtype(
    categories=categories, ordered=True
)
gdf_target['moran_type'] = gdf_target['moran_type'].astype(dtype)
# ラベルを作成
param_lbl = f'$N = {N}$'

# モラン散布図を作成
fig, ax = plt.subplots(
    figsize=(9, 6), dpi=100, facecolor='white', 
    constrained_layout=True
)
fig.suptitle('categorical map', fontsize=20)

gdf_target.boundary.plot(
    ax=ax, 
    edgecolor='white', linewidth=0.5
) # 行政区界
gdf_target.plot(
    ax=ax, column='moran_type', 
    cmap=cmap, categorical=True, 
    legend=True, legend_kwds={
        'title': 'quadrant', 'loc': 'upper left'
    }
) # 象限番号
for n in range(N):
    area_x, area_y = gdf_target.loc[n, 'centroids'].coords[0] # 座標
    area_lbl       = gdf_target.loc[n, 'city2'] # 地域名
    ax.text(
        x=area_x, y=area_y, s=area_lbl, ha='center', va='center', 
        size=10
    ) # 地域名
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')
ax.set_title(param_lbl, loc='left')
ax.grid()
ax.set_aspect(aspect='equal', adjustable='box')

plt.show()

モラン散布図におけるカテゴリ

 各地域のカテゴリ(モラン散布図における象限番号)を塗りつぶしの色で示します。

 GeoDataFrameと gdf.plot() を使って、カテゴリカル・コロプレス図を描画します。categorical 引数に True を指定すると離散型のデータ(カテゴリデータ)として扱われます。データ列の引数 column にカテゴリラベル(やカテゴリ番号)の列、カラーマップの引数 cmap に離散型のカラーマップを指定します。
 この例では、凡例に(カテゴリ番号ではなく)カテゴリラベルを表示するために、カテゴリ番号に対応するようにカテゴリラベルにPandasのCategorical型の順序を指定しています。Categorical型の順序は、CategoricalDtype() で設定できます。
 または、辞書型のオブジェクトを使って配色を設定します。

# カラーマップを作成
colors = {
    'High-High': 'hotpink',   # High-High
    'Low-High':  'orange',    # Low-High
    'Low-Low':   'royalblue', # Low-Low
    'High-Low':  'limegreen'  # High-Low
}
# ラベルを作成
param_lbl = f'$N = {N}$'

# モラン散布図を作成
fig, ax = plt.subplots(
    figsize=(9, 6), dpi=100, facecolor='white', 
    constrained_layout=True
)
fig.suptitle('categorical map', fontsize=20)

gdf_target.boundary.plot(
    ax=ax, 
    edgecolor='white', linewidth=0.5
) # 行政区界
gdf_target.plot(
    ax=ax, 
    color=gdf_target['moran_type'].map(colors), categorical=True
) # 象限番号
for n in range(N):
    area_x, area_y = gdf_target.loc[n, 'centroids'].coords[0] # 座標
    area_lbl       = gdf_target.loc[n, 'city2'] # 地域名
    ax.text(
        x=area_x, y=area_y, s=area_lbl, ha='center', va='center', 
        size=10
    ) # 地域名
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')
ax.set_title(param_lbl, loc='left')
ax.grid()
ax.set_aspect(aspect='equal', adjustable='box')

# 凡例を設定
handles = [
    mpatches.Patch(color=color, label=label) for label, color in colors.items()
]
ax.legend(
    handles=handles,
    title='quadrant', loc='upper left'
)

plt.show()

モラン散布図におけるカテゴリ

 この例では、辞書型のオブジェクトとしてカラーマップを作成しています。カテゴリ名をキー、色名を値として配色を指定します。
 カテゴリラベル列の文字列をキーとして、辞書型のカラーマップから .map() で対応する色名を取り出して、塗りつぶし色の引数 color に色を指定します。

 ホットスポット(High-High・桃色)は、自身が高く近隣も高い地帯なので、密集しています。
 ドーナツ(Low-High・オレンジ色)は、自身が低く近隣は高い地域なので、ホットスポットに隣接しています。
 コールドスポット(Low-low・青色)は、自身が低く近隣も低い地帯なので、密集しています。
 ダイヤモンドインザラフ(High-Low・緑色)は、自身が高く近隣は低い地域なので、コールドスポットに隣接しています。
 以上の4つのカテゴリを確認できます。

 以上で、モラン散布図を作成できました。

 この記事では、モラン散布図を作成しました。次の記事では、空間ラグの計算を図で確認します。

参考文献

おわりに

 モラン散布図だけで1記事を書くつもりはなかったのですが、後々に出てくるLISAクラスターマップなどでもモラン散布図のカテゴリが使われるということで、まとめておくかと筆を取りました。
 凡例にカテゴリラベルを並べるのに苦労したので、備忘録として2つの方法を書いています。

 2026年8月4日は、私立恵比寿中学の結成17周年の日です。

 おめでとうございます!!!!!!!!
 引き続き変革期な事務所の中で、えびちゅうの活動は今後も活発に続いていくようで、一先ずの安心とグループ・メンバー共々活動を続けてくれることへの感謝を胸に日々を過ごしております。今後ともよろしくお願いします。

 そういえば某ステは金曜9時に移動したんだった。

【次の内容】

 空間ラグの計算を図で確認します。

www.anarchive-beta.com

 LISAクラスターマップを作成します。

https://www.anarchive-beta.com/entry/2026/08/08/210000www.anarchive-beta.com