library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.1.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(sf)
## Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
library(nanoparquet)
library(patchwork)
library(sdmTMB)
# データ配置ディレクトリ
<- "data"
data_dir
# クマ出没データのパス
<- file.path(data_dir, "kuma_data.parquet")
data_file
# 土地利用データのパス
<-
landuse_files c("L03-a-21_5436.geojson",
"L03-a-21_5536.geojson",
"L03-a-21_5537.geojson",
"L03-a-21_5636.geojson",
"L03-a-21_5637.geojson") |>
::map_chr(\(f) file.path(data_dir, f))
purrr
# 石川県行政区域データのパス
<- file.path(data_dir,
ishikawa_pref_file "N03-20250101_17.geojson")
# 擬似乱数のシードの設定
set.seed(1234)
「クマ共生ハッカソン」の記事です。これまでに、小松市のクマ出没予測マップと金沢市のクマ出没予測マップを作成しました。今回は範囲を広げて、石川県全体の予測マップを作成することとします。
種分布モデル(SDM)を利用していますが、クマ(ツキノワグマ)そのものの分布予測ではなく、クマ出没の事案が発生する確率の予測になります。予測には、RのsdmTMBパッケージを使用しました。
予測能力の高いモデルが加賀・能登で異なりましたので、別々にマップをつくり、最後にあわせるという処理をおこないました。
データ
いつものとおり、クマ出没情報は、いしかわオープンデータカタログの「石川県クマ出没データ」からダウンロードしたものに前処理を加えたものを使用します。国土数値情報の土地利用3次メッシュデータ、行政区域データも同じく使用します。
まず、Rパッケージの読み込みとデータファイルのパスの設定、擬似乱数のシードの設定をおこないます。
データ読み込み
データを読み込みます。
# クマ出没データの読み込み
<- read_parquet(data_file) |>
kuma_data # 500mメッシュコードを3次(1km)メッシュコードに変換
::mutate(mesh_code = str_sub(`500mメッシュコード`, 1, 8))
dplyr
# 土地利用データの読み込み
<- landuse_files %>%
landuse_data ::map(\(f) {
purrr::st_read(f, quiet = TRUE)
sf|>
}) ::bind_rows() |>
dplyr# m^2単位をkm^2単位にする
::mutate_at(2:14, `/`, 10^6) |>
dplyr# 3次メッシュコードを文字型にする
::mutate(mesh_code = as.character(`メッシュ`))
dplyr
# 地域名の定義
<- c("Kaga", "Noto")
region
# 石川県の行政区域データの読み込み
<- sf::st_read(ishikawa_pref_file, quiet = TRUE)
ishikawa
# 各地域の市町名を格納するリスト
<- vector("list", 2)
city
# 加賀地方(かほく市以南)の市町を抽出
"Kaga"]] <- ishikawa |>
city[[::filter(N03_004 %in% c("金沢市", "小松市", "加賀市", "かほく市",
dplyr"白山市", "能美市", "野々市市", "川北町",
"津幡町", "内灘町")) |>
::st_union()
sf
# 能登地方(宝達志水町以北)の市町を抽出
"Noto"]] <- ishikawa |>
city[[::filter(N03_004 %in% c("輪島市", "珠洲市", "穴水町",
dplyr"能登町", "七尾市", "羽咋市",
"志賀町", "宝達志水町",
"中能登町")) |>
::st_union() sf
データの集計・整形
クマ出没データについて、3次メッシュコードと年ごとにクマ出没情報の有無と件数を集計します。
<- kuma_data |>
kuma_sum ::rename(year = `出没年`) |>
dplyr::group_by(mesh_code, year) |>
dplyr::summarise(present = as.numeric(n() > 0),
dplyrn = n(),
.groups = "drop")
予測モデル作成のため、データを整形します。
まず、土地利用データのうち加賀地方・能登地方に重なる部分をそれぞれ抽出します。
<- purrr::map(
landuse
region, \(r) {<- sf::st_intersects(landuse_data, city[[r]], sparse = FALSE)
intersects
landuse_data[intersects, ]
})names(landuse) <- region
土地利用データから、3次メッシュコードと地理情報だけを抽出したデータを作成します。
<- purrr::map(
geometry
region, \(r) {|>
landuse[[r]] ::select(mesh_code, geometry)
dplyr
})names(geometry) <- region
3次メッシュコードごとに、6年間の出没ありの年の割合を計算します。
<- kuma_sum |>
prop_app ::group_by(mesh_code) |>
dplyr::summarise(prop = n() / 6, .groups = "drop") dplyr
データ可視化
出没年の割合の地図化
3次メッシュコードごとの、6年間の出没ありの年の割合を地図として可視化します。
そのための関数を定義します。
<- function(region, title) {
plot_prop |>
geometry[[region]] ::left_join(prop_app, by = "mesh_code") |>
dplyr::mutate(prop = replace_na(prop, 0)) |>
dplyrggplot(aes(fill = prop)) +
geom_sf() +
scale_fill_viridis_c(name = "割合", limits = c(0, 1)) +
labs(title = title) +
theme_minimal(base_family = "Noto Sans JP")
}
加賀地方と能登地方の出現割合の地図を並べて描画します。
<- plot_prop("Kaga", "加賀地方")
p1 <- plot_prop("Noto", "能登地方")
p2 / p1 + plot_layout(guides = "collect") p2

土地利用との関係
土地利用データと出没割合データを結合して、両者の関係を図示します。
そのための関数を定義します。
<- function(region, type,
plot_landuse_prop title = NULL, xlab = NULL,
ylab = "割合") {
|>
landuse[[region]] ::left_join(prop_app, by = "mesh_code") |>
dplyr::replace_na(list(n = 0, prop = 0)) |>
tidyrggplot(aes(x = .data[[type]], y = prop)) +
geom_point(color = "red", size = 2.5) +
geom_smooth(method = "gam") +
labs(title = title, x = xlab, y = ylab) +
theme_bw(base_family = "Noto Sans JP")
}
加賀地方: 森林面積と出没割合
加賀地方における、3次メッシュ中の森林面積とクマ出没のあった年の割合との関係です。青い線はGAMによる平滑化線、灰色の領域はその95%信頼区間です(以下同じ)。
plot_landuse_prop("Kaga", "森林",
x = expression(paste("森林面積(", km^2, ")")))

森林面積が中くらいのところで出現割合が高くなっているようです。
加賀地方: 河川地及び湖沼面積と出没割合
つづいて、加賀地方における、3次メッシュ中の河川地及び湖沼面積とクマ出没頻度との関係です。
plot_landuse_prop("Kaga", "河川地及び湖沼",
x = expression(paste("河川地及び湖沼面積(", km^2, ")")))

能登地方: 森林面積と出没割合
能登地方における、3次メッシュ中の森林面積とクマ出没のあった年の割合との関係です。
plot_landuse_prop("Noto", "森林",
x = expression(paste("森林面積(", km^2, ")")))

森林面積が0.38km2くらいのところで出現割合がやや高くなっているようです。
能登地方: 河川地及び湖沼面積と出没割合
能登地方における、3次メッシュ中の河川地及び湖沼面積とクマ出没頻度との関係です。
plot_landuse_prop("Noto", "河川地及び湖沼",
x = expression(paste("河川地及び湖沼面積(", km^2, ")")))

どこもあまり高くないようです。
ブナ豊凶との関係
ブナ豊凶とクマ出没件数との関係を見てみます。
そのための関数を定義します。
<- function(region, title = NULL,
plot_buna_freq xlab = "年", ylab = "出没件数") {
::expand_grid(
tidyrmesh_code = unique(landuse[[region]]$mesh_code),
year = 2019:2024
|>
) ::left_join(kuma_sum, by = c("mesh_code", "year")) |>
dplyr::replace_na(list(present = 0, n = 0)) |>
tidyr::mutate(buna = if_else(year == 2020, "大凶作", "大凶作以外")) |>
dplyrggplot(aes(x = as.factor(year), y = n, color = buna)) +
geom_jitter(size = 2.5, alpha = 0.5, height = 0, width = 0.05) +
labs(title = title, x = xlab, y = ylab) +
scale_color_discrete(name = "ブナの豊凶") +
theme_bw(base_family = "Noto Sans JP")
}
加賀地方における加賀地方における、各年の3次メッシュごとのクマ出没件数です。大凶作の年(2020年)は赤色でしめしてあります。また見やすくするため、横軸方向にはランダムノイズを加えています。
plot_buna_freq("Kaga")

大凶作の2020年は出没件数がかなり多くなっています。
plot_buna_freq("Noto")

大凶作の2020年はやや多いようです。
クマ出没予測モデル
環境データの作成
モデルの説明変数として使用する可能性のある環境データを作成します。具体的には以下のような変数を作成します。
- forest: 3次メッシュ中の森林面積(km2)
- forest2: 3次メッシュ中の森林面積(km2)の2乗
- water: 3次メッシュ中の河川地及び湖沼面積(km2)
- water2: 3次メッシュ中の河川地及び湖沼面積(km2)の2乗
<- purrr::map(
env_data
region, \(r) {|>
landuse[[r]] ::mutate(forest = `森林`,
dplyrforest2 = `森林`^2,
water = `河川地及び湖沼`,
water2 = `河川地及び湖沼`^2) |>
as.data.frame() |> # drop geometry
::select(mesh_code, forest, forest2, water, water2)
dplyr
})names(env_data) <- region
3次メッシュコードと年のすべての組み合わせをつくっておき、これに出没のありなしデータと環境データを結合します。
<- purrr::map(
kuma_env_data
region, \(r) {::expand_grid(
tidyrmesh_code = unique(landuse[[r]]$mesh_code),
year = 2019:2024
|>
) ::left_join(kuma_sum, by = c("mesh_code", "year")) |>
dplyr::mutate(present = replace_na(present, 0),
dplyrn = replace_na(n, 0)) |>
::left_join(env_data[[r]], by = "mesh_code") |>
dplyr# ブナ豊凶をしめすダミー変数`buna_poor`を追加(大凶作=1)
::mutate(buna_poor = if_else(year == 2020, 1, 0))
dplyr
})names(kuma_env_data) <- region
sdmTMBで使用するメッシュ作成のため、土地利用データにUTM座標系の座標を追加します。
<- purrr::map(
coord
region, \(r) {|>
landuse[[r]] ::st_transform(4326) |> # WGS84
sf::st_make_valid() |>
sf::st_centroid() |>
sf::st_coordinates() |>
sfas.data.frame() |>
::rename(longitude = X, latitude = Y) |>
dplyradd_utm_columns(ll_names = c("longitude", "latitude"))
})names(coord) <- region
UTM座標に3次メッシュコードを結合させます。
<- purrr::map(
coord2
region, \(r) {|>
coord[[r]] bind_cols(
|>
landuse[[r]] ::select(mesh_code)
dplyr
)
})names(coord2) <- region
出没あり・なしデータにUTM座標を結合させます。
<- purrr::map(
kuma_env_coord
region, \(r) {|>
kuma_env_data[[r]] ::left_join(coord2[[r]], by = "mesh_code")
dplyr
})names(kuma_env_coord) <- region
sdmTMBの空間モデルのため、メッシュを作成します。
<- purrr::map(
mesh
region, \(r) {make_mesh(kuma_env_coord[[r]],
xy_cols = c("X", "Y"), cutoff = 2)
})names(mesh) <- region
作成されたメッシュの確認します。加賀地方です。
plot(mesh[["Kaga"]])
能登地方です。
plot(mesh[["Noto"]])
モデルの作成
説明変数を変えて以下の5つのモデルを作成しました。土地利用に関する説明変数は、背景知識から選択しました。
- モデル1: ブナの豊凶(
buna_poor
)を説明変数とするモデル - モデル2: 森林面積(
forest
)とその2乗を説明変数とするモデル - モデル3: 森林面積(
forest
)とその2乗、ブナの豊凶(buna_poor
)を説明変数とするモデル - モデル4: 森林面積(
forest
)とその2乗、河川・湖沼面積(water
)とその2乗を説明変数とするモデル - モデル5: 森林面積(
forest
)とその2乗、河川・湖沼面積(water
)とその2乗、ブナの豊凶(buna_poor
)を説明変数とするモデル
<- vector("list", 5)
formula 1]] <- as.formula("present ~ buna_poor")
formula[[2]] <- as.formula("present ~ forest + forest2")
formula[[3]] <- as.formula("present ~ forest + forest2 + buna_poor")
formula[[4]] <- as.formula("present ~ forest + forest2 + water + water2")
formula[[5]] <- as.formula("present ~ forest + forest2 + water + water2 + buna_poor") formula[[
あてはめ
結果を格納するリストを作成します。
<- vector("list", 2)
fit names(fit) <- region
加賀地方
まずは加賀地方からあてはめを実行します。
目的変数をクマ出没情報のありなし、説明変数はそれぞれ用意したもの、誤差構造は二項分布で、リンク関数はロジットとしています。また、時空間の自己相関をモデルに組み込むようにしています。
"Kaga"]] <- purrr::map(
fit[[1:5, \(i) {
sdmTMB(formula[[i]],
data = kuma_env_coord[["Kaga"]],
mesh = mesh[["Kaga"]],
family = binomial(link = "logit"),
spatial = "on",
time = "year",
time_varying = ~ 1,
time_varying_type = "ar1",
extra_time = 2025)
})
モデルのあてはめ結果をチェックします。
::walk(1:5, \(i) {
purrrcat(paste("model:", i, "\n"))
::sanity(fit[["Kaga"]][[i]])
sdmTMB
})## model: 1
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 2
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 3
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 4
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 5
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
いずれも問題は発見されませんでした。
AICを比較します。
::walk(1:5, \(i) {
purrrprint(paste0("モデル", i, "のAIC: ", AIC(fit[["Kaga"]][[i]])))
})## [1] "モデル1のAIC: 5302.83109360736"
## [1] "モデル2のAIC: 4948.74500061589"
## [1] "モデル3のAIC: 4946.29361973717"
## [1] "モデル4のAIC: 4938.24338079213"
## [1] "モデル5のAIC: 4935.79658029803"
モデル5のAICがもっともちいさいという結果がえられました。予測能力が高いモデルとしてモデル5を採用します。
モデル5のあてはめ結果の要約を表示します。
summary(fit[["Kaga"]][[5]])
## Spatiotemporal model fit by ML ['sdmTMB']
## Formula: formula[[i]]
## Mesh: mesh[["Kaga"]] (isotropic covariance)
## Time column: year
## Data: kuma_env_coord[["Kaga"]]
## Family: binomial(link = 'logit')
##
## Conditional model:
## coef.est coef.se
## (Intercept) -5.85 0.80
## forest 9.49 0.72
## forest2 -9.83 0.60
## water 6.27 1.83
## water2 -15.68 4.50
## buna_poor 1.06 0.39
##
## Time-varying parameters:
## coef.est coef.se
## (Intercept)-2019 -0.0500000 0.2600000
## (Intercept)-2020 -0.0900000 0.2900000
## (Intercept)-2021 -0.1100000 0.2900000
## (Intercept)-2022 -0.0700000 0.3000000
## (Intercept)-2023 0.0900000 0.3000000
## (Intercept)-2024 0.2900000 0.3800000
## (Intercept)-2025 0.2000000 0.4000000
## rho-(Intercept) 0.6853522 0.6276213
##
## Matérn range: 13.67
## Spatial SD: 2.49
## Spatiotemporal IID SD: 0.83
## ML criterion at convergence: 2456.898
##
## See ?tidy.sdmTMB to extract these values as a data frame.
能登地方
能登地方についても同じようにあてはめを実行します。
"Noto"]] <- purrr::map(
fit[[1:5, \(i) {
sdmTMB(formula[[i]],
data = kuma_env_coord[["Noto"]],
mesh = mesh[["Noto"]],
family = binomial(link = "logit"),
spatial = "on",
time = "year",
time_varying = ~ 1,
time_varying_type = "ar1",
extra_time = 2025)
})
モデルのあてはめ結果をチェックします。
::walk(1:5, \(i) {
purrrcat(paste("model:", i, "\n"))
::sanity(fit[["Noto"]][[i]])
sdmTMB
})## model: 1
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 2
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 3
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 4
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
## model: 5
## ✔ Non-linear minimizer suggests successful convergence
## ✔ Hessian matrix is positive definite
## ✔ No extreme or very small eigenvalues detected
## ✔ No gradients with respect to fixed effects are >= 0.001
## ✔ No fixed-effect standard errors are NA
## ✔ No standard errors look unreasonably large
## ✔ No sigma parameters are < 0.01
## ✔ No sigma parameters are > 100
## ✔ Range parameter doesn't look unreasonably large
いずれも問題は発見されませんでした。
AICを比較します。
::walk(1:5, \(i) {
purrrprint(paste0("モデル", i, "のAIC: ", AIC(fit[["Noto"]][[i]])))
})## [1] "モデル1のAIC: 1849.24235738959"
## [1] "モデル2のAIC: 1798.91126843042"
## [1] "モデル3のAIC: 1800.63098454961"
## [1] "モデル4のAIC: 1802.59181656214"
## [1] "モデル5のAIC: 1804.30948840935"
モデル2のAICがもっともちいさいという結果がえられました。予測能力が高いモデルとしてモデル2を採用します。
能登地方については、森林面積のみのモデルが選択されました。ブナ豊凶データも含まれませんでしたが、これは能登にはブナの分布が少ないことを反映したものかもしれません。
モデル2のあてはめ結果の要約を表示します。
summary(fit[["Noto"]][[2]])
## Spatiotemporal model fit by ML ['sdmTMB']
## Formula: formula[[i]]
## Mesh: mesh[["Noto"]] (isotropic covariance)
## Time column: year
## Data: kuma_env_coord[["Noto"]]
## Family: binomial(link = 'logit')
##
## Conditional model:
## coef.est coef.se
## (Intercept) -6.74 0.59
## forest 7.19 1.03
## forest2 -6.38 0.92
##
## Time-varying parameters:
## coef.est coef.se
## (Intercept)-2019 -0.3800000 0.4400000
## (Intercept)-2020 0.3500000 0.4500000
## (Intercept)-2021 0.8400000 0.5000000
## (Intercept)-2022 0.1700000 0.4600000
## (Intercept)-2023 -0.1500000 0.4200000
## (Intercept)-2024 0.0300000 0.4000000
## (Intercept)-2025 0.0000000 0.5400000
## rho-(Intercept) 0.1204386 0.5280517
##
## Matérn range: 16.11
## Spatial SD: 1.35
## Spatiotemporal IID SD: 1.05
## ML criterion at convergence: 891.456
##
## See ?tidy.sdmTMB to extract these values as a data frame.
2025年の出没確率の予測
最後に、3次メッシュごとに、2025年のクマ出没確率を予測します。
加賀地方
まずは、加賀地方です。
2025年はブナが大凶作と予測されているので、それを反映させて2025年のデータを作成します。
<- env_data[["Kaga"]] |>
newdata ::left_join(coord2[["Kaga"]], by = "mesh_code") |>
dplyr::mutate(buna_poor = 1,
dplyryear = 2025)
モデル5を使用して2025年の出没確率を予測します。
<- vector("list", 2)
pred names(pred) <- region
"Kaga"]] <- predict(fit[["Kaga"]][[5]], newdata, type = "response") pred[[
出没確率を地図化します。pred[["Kaga"]]
は通常のデータフレームになっていますので、sf::st_as_sf()
でsf型に変換します。
"Kaga"]] |>
pred[[::st_as_sf() |>
sfggplot(aes(fill = est)) +
geom_sf() +
scale_fill_viridis_c(name = "出没確率", limits = c(0, 1)) +
theme_minimal(base_family = "Noto Sans JP")

能登地方
つづいて能登地方です。
能登地方については、ブナ豊凶は予測モデルには含まれませんが、いちおう大凶作として2025年のデータを作成します。
<- env_data[["Noto"]] |>
newdata ::left_join(coord2[["Noto"]], by = "mesh_code") |>
dplyr::mutate(buna_poor = 1,
dplyryear = 2025)
モデル2を使用して2025年の出没確率を予測します。
"Noto"]] <- predict(fit[["Noto"]][[2]], newdata, type = "response") pred[[
出没確率を地図化します。
"Noto"]] |>
pred[[::st_as_sf() |>
sfggplot(aes(fill = est)) +
geom_sf() +
scale_fill_viridis_c(name = "出没確率", limits = c(0, 1)) +
theme_minimal(base_family = "Noto Sans JP")

予測データの統合
加賀地方と能登地方の予測データを結合させて、両者で重複しているメッシュを抽出します。
<- bind_rows(pred)
pred_all <- pred_all |>
dup ::count(mesh_code) |>
dplyr::filter(n > 1) dplyr
重複しているメッシュでは予測値は両者の平均とします。
<- pred_all |>
est_dup ::filter(mesh_code %in% dup$mesh_code) |>
dplyr::group_by(mesh_code) |>
dplyr::summarise(est = mean(est), .groups = "drop") dplyr
重複メッシュの予測値を更新し、重複を除いて、必要な変数だけを抽出します。
<- pred_all |>
combinded_pred ::left_join(est_dup, by = "mesh_code") |>
dplyr::mutate(est = if_else(is.na(est.y), est.x, est.y)) |>
dplyr::distinct(mesh_code, .keep_all = TRUE) |>
dplyr::select(mesh_code, est, geometry) |>
dplyr::st_as_sf() sf
ggplot(combinded_pred, aes(fill = est)) +
geom_sf() +
scale_fill_viridis_c(name = "出没確率", limits = c(0, 1)) +
theme_minimal(base_family = "Noto Sans JP")

ファイルに出力しておきます。
<- file.path("outputs", "kuma_prediction.geojson")
output_file if (file.exists(output_file))
file.remove(output_file)
## [1] TRUE
::st_write(combinded_pred, output_file)
sf## Writing layer `kuma_prediction' to data source
## `outputs/kuma_prediction.geojson' using driver `GeoJSON'
## Writing 4512 features with 2 fields and geometry type Polygon.
2025年の出没予測確率と実際の出没データの比較
2025年の出没状況を読み込みます。
<- file.path(data_dir, "bear_sightings_r7.csv") |>
occ_2025 ::read_csv() |>
readr::mutate(mesh_code = jpmesh::coords_to_mesh(`経度`, `緯度`, 1) |>
dplyras.character()) |>
::group_by(mesh_code) |>
dplyr::summarise(n = n(), .groups = "drop")
dplyr## Rows: 103 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (8): WKT, 出没日, 時間, 市町名, 場所, 備考, 頭数, 月
## dbl (2): 緯度, 経度
## lgl (1): 番号
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
予測と、実際の2025年の出没データを結合します。
<- combinded_pred |>
bind_2025 ::left_join(occ_2025, by = "mesh_code") |>
dplyr::mutate(occ_2025 = if_else(is.na(n), 0, 1)) dplyr
予測確率を区分して、各区分ごとの出没ありの割合を計算し、グラフにします。
<- seq(0, 1, 0.2)
breaks <- length(breaks)
n_breaks
<- bind_2025 |>
prop_2025 ::mutate(est_cat = cut(est, breaks,
dplyrinclude.lowest = TRUE)) |>
::group_by(est_cat) |>
dplyr::summarise(n = n(),
dplyrn_present = sum(occ_2025),
prop_present = n_present / n,
.groups = "drop") |>
::mutate(mid = (breaks[2:n_breaks] +
dplyr1:(n_breaks - 1)]) / 2)
breaks[
ggplot(prop_2025, aes(x = mid, y = prop_present)) +
geom_col(fill = "red", alpha = 0.7) +
scale_x_continuous(limits = c(min(breaks), max(breaks)),
breaks = breaks, minor_breaks = NULL) +
scale_y_continuous(limits = c(0, 1),
breaks = breaks) +
labs(x = "2025年の出没予測確率区分",
y = "出没ありの割合") +
theme_bw(base_family = "Noto Sans JP")

ことしの出現情報はまだこれから増えるところですので、縦軸はこれから高くなっていくと考えられます。右に行くほど割合が高くなっているのでは、とりあえずはうまく推定できているようです。
おわりに
石川県全体の2025年のクマ出没確率予測マップが完成しました。これをGISで使用したり、ウェブアプリで表示したりといった利用が考えられます。
なお、出没確率が低く予測されていても、クマが出没しないとは言い切れません。クマが生息していそうな場所に立ち入るときは鈴を持参するとか、誘引物を放置しないとかいった基本的な対策が必要です。