研究方式、研究成果、資料來源我都有在書面詳細交代,這邊基本上所有的圖表和地圖以及統計方式都有在書面中使用,所以也都有解釋到。
#rm(list = ls())
#Sys.getlocale()
library(sf) # 讀取 shapefile
## Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
library(readr) # 讀取 csv
# 讀取 shp 檔案
school <- st_read("D:/200學業/270地理系/空間分析/期末考試/SCHOOL/SCHOOL.shp")
## Reading layer `SCHOOL' from data source
## `D:\200學業\270地理系\空間分析\期末考試\SCHOOL\SCHOOL.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 148 features and 4 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: 297078.6 ymin: 2763290 xmax: 312516.7 ymax: 2784542
## Projected CRS: TWD97 / TM2 zone 121
taipei_village <- st_read("D:/200學業/270地理系/空間分析/期末考試/Taipei_village (2)/Taipei_village.shp")
## Reading layer `Taipei_village' from data source
## `D:\200學業\270地理系\空間分析\期末考試\Taipei_village (2)\Taipei_village.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 456 features and 32 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: 296103.7 ymin: 2761535 xmax: 317204.1 ymax: 2789175
## Projected CRS: TWD97 / TM2 zone 121
# 讀取 csv 檔案
sports_venues <- read_csv("D:/200學業/270地理系/空間分析/期末考試/體育局列管本市各類運動場館一覽表.csv")
## Rows: 270 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (6): 公民營, 廠商名稱_市招, 行政區, 經營主體, 聯絡電話, 地址
## dbl (4): 編號, 分機, 經度, 緯度
##
## ℹ 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.
bubble_tea <- read_csv("D:/200學業/270地理系/空間分析/期末考試/手搖杯點位資料(可用).csv")
## Rows: 628 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Address, Response_Address, Response_X, Response_Y
## dbl (1): id
##
## ℹ 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.
fast_food <- read_csv("D:/200學業/270地理系/空間分析/期末考試/大速食店點位資料(可用).csv")
## Rows: 316 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Address, Response_Address, Response_X, Response_Y
## dbl (1): id
##
## ℹ 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.
#str(bubble_tea)
#str(fast_food)
# 這段是 R 的程式碼,假設你已經讀取了 bubble_tea
# 生成店家名稱的列表
store_names <- c(
rep("龜記", 25),
rep("萬波島嶼", 12),
rep("可不可", 38),
rep("清園芋圓", 19),
rep("五銅號", 24),
rep("珍煮丹", 17),
rep("老賴", 17),
rep("迷克夏", 31),
rep("砌茶趣", 67),
rep("comebuy", 57),
rep("台灣第一味", 11),
rep("大苑子", 20),
rep("水巷茶弄", 13),
rep("日出良太", 19),
rep("coco", 57),
rep("清心", 109),
rep("50嵐", 92)
)
# 確認長度是否為 628
length(store_names) # 應該是 628
## [1] 628
# 將這個列表加到 bubble_tea 中成為新欄位 "store"
bubble_tea$store <- store_names
#速食店
# 這段是 R 的程式碼,假設你已經讀取了 fast_food
# 生成速食店名稱的列表
store_names_fastfood <- c(
rep("麥當勞", 63),
rep("肯德基", 31),
rep("漢堡王", 24),
rep("21世紀風味館", 34),
rep("頂呱呱", 25),
rep("達美樂", 24),
rep("必勝客", 40),
rep("胖老爹", 38),
rep("炸機大獅", 18),
rep("派克雞排", 14),
rep("3Q雞排", 5)
)
# 確認長度是否為 316
length(store_names_fastfood) # 應該是 316
## [1] 316
# 將這個列表加到 fast_food 中成為新欄位 "store"
fast_food$store <- store_names_fastfood
# 在刪除前,先記錄被刪掉的資料
bubble_tea_removed <- bubble_tea[is.na(bubble_tea$Response_X) | is.na(bubble_tea$Response_Y), ]
fast_food_removed <- fast_food[is.na(fast_food$Response_X) | is.na(fast_food$Response_Y), ]
# 總筆數
cat("bubble_tea 總共被刪掉的筆數:", nrow(bubble_tea_removed), "\n")
## bubble_tea 總共被刪掉的筆數: 19
cat("以下是被刪掉的店家筆數統計:\n")
## 以下是被刪掉的店家筆數統計:
print(table(bubble_tea_removed$store))
##
## 50嵐 coco comebuy 日出良太 老賴 砌茶趣 清心 清園芋圓
## 3 1 2 4 1 6 1 1
cat("\nfast_food 總共被刪掉的筆數:", nrow(fast_food_removed), "\n")
##
## fast_food 總共被刪掉的筆數: 6
cat("以下是被刪掉的店家筆數統計:\n")
## 以下是被刪掉的店家筆數統計:
print(table(fast_food_removed$store))
##
## 必勝客 炸機大獅 胖老爹
## 2 3 1
# 真正執行刪除
bubble_tea <- bubble_tea[!(is.na(bubble_tea$Response_X) | is.na(bubble_tea$Response_Y)), ]
fast_food <- fast_food[!(is.na(fast_food$Response_X) | is.na(fast_food$Response_Y)), ]
# 刪除後的剩餘資料量
cat("\n刪除後 bubble_tea 剩餘筆數:", nrow(bubble_tea), "\n")
##
## 刪除後 bubble_tea 剩餘筆數: 609
cat("刪除後 fast_food 剩餘筆數:", nrow(fast_food), "\n")
## 刪除後 fast_food 剩餘筆數: 310
library(sf)
library(dplyr)
##
## 載入套件:'dplyr'
## 下列物件被遮斷自 'package:stats':
##
## filter, lag
## 下列物件被遮斷自 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
# 速食店與手搖飲資料:假設座標欄位為 Response_X / Response_Y (且已轉為數值型)
# 將原始 Response_X / Response_Y 轉成數字
bubble_tea$Response_X <- as.numeric(bubble_tea$Response_X)
## Warning: 強制變更過程中產生了 NA
bubble_tea$Response_Y <- as.numeric(bubble_tea$Response_Y)
## Warning: 強制變更過程中產生了 NA
# 找到 X 或 Y 是 NA 的行
na_rows <- which(is.na(bubble_tea$Response_X) | is.na(bubble_tea$Response_Y))
# 總筆數
cat("總共被刪掉的筆數:", length(na_rows), "\n")
## 總共被刪掉的筆數: 10
# 列出被刪掉的店家名稱與筆數
cat("以下是被刪掉的店家筆數統計:\n")
## 以下是被刪掉的店家筆數統計:
print(table(bubble_tea$store[na_rows]))
##
## 50嵐 coco 大苑子 日出良太 砌茶趣 清心
## 3 1 1 2 1 2
fast_food$Response_X <- as.numeric(fast_food$Response_X)
## Warning: 強制變更過程中產生了 NA
fast_food$Response_Y <- as.numeric(fast_food$Response_Y)
## Warning: 強制變更過程中產生了 NA
na_rows_fastfood <- which(is.na(fast_food$Response_X) | is.na(fast_food$Response_Y))
cat("\n【fast_food】\n")
##
## 【fast_food】
cat("總共被刪掉的筆數:", length(na_rows_fastfood), "\n")
## 總共被刪掉的筆數: 9
cat("以下是被刪掉的店家筆數統計:\n")
## 以下是被刪掉的店家筆數統計:
print(table(fast_food$store[na_rows_fastfood]))
##
## 21世紀風味館 肯德基 派克雞排 胖老爹 頂呱呱 麥當勞
## 1 1 1 1 1 2
## 達美樂 漢堡王
## 1 1
# --- 刪掉沒有座標的資料 ---
bubble_tea <- bubble_tea[!(is.na(bubble_tea$Response_X) | is.na(bubble_tea$Response_Y)), ]
fast_food <- fast_food[!(is.na(fast_food$Response_X) | is.na(fast_food$Response_Y)), ]
# 顯示刪除後剩下的資料筆數
cat("\n刪除後 bubble_tea 剩餘筆數:", nrow(bubble_tea), "\n")
##
## 刪除後 bubble_tea 剩餘筆數: 599
cat("刪除後 fast_food 剩餘筆數:", nrow(fast_food), "\n")
## 刪除後 fast_food 剩餘筆數: 301
# 轉換 bubble_tea 與 fast_food
bubble_tea_sf <- st_as_sf(bubble_tea, coords = c("Response_X", "Response_Y"), crs = 3826)
fast_food_sf <- st_as_sf(fast_food, coords = c("Response_X", "Response_Y"), crs = 3826)
# 處理運動場館:經度與緯度欄位名稱是中文
# 確保運動場館資料是資料框
colnames(sports_venues)[colnames(sports_venues) == "經度"] <- "Longitude"
colnames(sports_venues)[colnames(sports_venues) == "緯度"] <- "Latitude"
sports_venues <- as.data.frame(sports_venues)
sports_venues$Longitude <- as.numeric(sports_venues$Longitude)
sports_venues$Latitude <- as.numeric(sports_venues$Latitude)
#str(sports_venues)
# 轉換 sports_venues
sports_venues_sf <- st_as_sf(sports_venues, coords = c("Longitude", "Latitude"), crs = 4326)
#st_crs(taipei_village) # 台北市底圖
#st_crs(sports_venues_sf) # 運動場館
#st_crs(fast_food_sf) # 速食店
#st_crs(bubble_tea_sf) # 飲料店
#發現有速食店廠商資料灌水
# 找出含有「新竹」或「新北」的行
rows_to_remove <- grepl("新竹", fast_food_sf$Address) | grepl("新北", fast_food_sf$Address)
# 被刪掉的資料
removed_data <- fast_food_sf[rows_to_remove, ]
# 統計被刪掉的品牌筆數
cat("以下是被刪掉的品牌筆數統計:\n")
## 以下是被刪掉的品牌筆數統計:
print(table(removed_data$store))
##
## 21世紀風味館
## 10
# 刪掉後的資料(保持變數名稱不變)
fast_food_sf <- fast_food_sf[!rows_to_remove, ]
library(tmap)
# 使用互動模式
# 把運動場館座標轉成台北市底圖的 CRS (TWD97)
sports_venues_sf <- st_transform(sports_venues_sf, st_crs(taipei_village))
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") +
tm_shape(sports_venues_sf) +
tm_dots(col = "公民營", palette = "Set1", size = 0.3, title = "公/民營") +
tm_layout(title = "台北市運動場館分布", legend.outside = TRUE)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').
## [tm_dots()] Argument `title` unknown.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Set1" is named
## "brewer.set1"
## This message is displayed once every 8 hours.
library(tmap)
library(dplyr)
# 🔴 過濾掉 3q雞排 和 21世紀風味館
fast_food_filtered <- fast_food_sf %>%
filter(!store %in% c("3Q雞排", "21世紀風味館"))
# 🔵 切換靜態模式
tmap_mode("plot")
## ℹ tmap mode set to "plot".
# --- 2️⃣ 台北市速食店分布地圖 ---
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") +
tm_borders(col = "white", lwd = 0.5) + # 加上行政邊界
tm_shape(fast_food_filtered) +
tm_dots(col = "store",
palette = "Set3",
size = 0.3,
title = "速食品牌",
alpha = 0.8) +
tm_layout(
title = "台北市速食店分布",
title.size = 1.0,
legend.outside = TRUE,
bg.color = "#F0F0F0",
panel.background = "#F0F0F0",
outer.bg.color = "white", # 地圖外部留白
legend.text.size = 0.6,
legend.title.size = 0.8,
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.[tm_dots()] Argument `title` unknown.[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`[cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Set3" is named
## "brewer.set3"Multiple palettes called "set3" found: "brewer.set3", "hcl.set3". The first one, "brewer.set3", is returned.
# --- 3️⃣ 手搖飲料店圖 ---
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") +
tm_borders(col = "white", lwd = 0.5) + # 🔴 加上邊界
tm_shape(bubble_tea_sf) +
tm_dots(col = "store", palette = "Set3", size = 0.3, title = "手搖飲品牌", alpha = 0.8) +
tm_layout(
title = "台北市手搖飲店分布",
title.size = 1.0,
legend.outside = TRUE,
bg.color = "#F0F0F0", # 🔴 統一背景顏色
panel.background = "#F0F0F0",
outer.bg.color = "white", # 地圖外部留白
legend.text.size = 0.6,
legend.title.size = 0.8,
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.[tm_dots()] Argument `title` unknown.[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`[cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Set3" is named
## "brewer.set3"Multiple palettes called "set3" found: "brewer.set3", "hcl.set3". The first one, "brewer.set3", is returned.
# --- 1️⃣ 建立 500m buffer ---
school_buffer <- st_buffer(school, dist = 500)
# 先篩選出公立和私立運動場館
public_sports <- sports_venues_sf %>%
filter(公民營 == "公營") %>%
st_transform(crs = st_crs(school)) # 轉換成跟 school 一樣的投影座標系統
private_sports <- sports_venues_sf %>%
filter(公民營 == "民營") %>%
st_transform(crs = st_crs(school))
# --- 2️⃣ 計算各 buffer 內的點位數 ---
# ⚠️ 改用 st_contains(),只算 buffer 內包含的點(不算邊界模糊重疊)
#str(sports_venues_sf)
bubbletea_counts <- sapply(st_contains(school_buffer, bubble_tea_sf), length)
fastfood_counts <- sapply(st_contains(school_buffer, fast_food_sf), length)
public_counts <- sapply(st_contains(school_buffer, public_sports), length)
private_counts <- sapply(st_contains(school_buffer, private_sports), length)
# --- 3️⃣ 組成 final_counts,補 0(長度相同,對應每個學校)
final_counts <- school %>%
mutate(
bubbletea_count = bubbletea_counts,
fastfood_count = fastfood_counts,
public_sports_count = public_counts,
private_sports_count = private_counts,
unhealthy_index = ifelse(public_sports_count == 0, NA,
(bubbletea_count + fastfood_count) / public_sports_count),
unhealthy_index_combined = ifelse((public_sports_count + private_sports_count) == 0, NA,
(bubbletea_count + fastfood_count) / (public_sports_count + private_sports_count))
)
# --- 查看結果 ---
print(final_counts)
## Simple feature collection with 148 features and 10 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: 297078.6 ymin: 2763290 xmax: 312516.7 ymax: 2784542
## Projected CRS: TWD97 / TM2 zone 121
## First 10 features:
## Type Name SID index geometry bubbletea_count
## 1 私立 松山國小 1 0.407779 POINT (308286.4 2771496) 5
## 2 公立 西松國小 2 0.500235 POINT (306854.9 2771689) 9
## 3 公立 敦化國小 3 0.440644 POINT (305347.2 2771329) 4
## 4 公立 民生國小 4 0.422485 POINT (305712 2772151) 3
## 5 私立 民權國小 5 0.414474 POINT (306729.6 2772821) 2
## 6 公立 民族國小 6 0.435226 POINT (305664.1 2772492) 2
## 7 私立 三民國小 7 0.405999 POINT (306930.4 2772894) 0
## 8 公立 健康國小 8 0.421402 POINT (306782 2772172) 6
## 9 公立 興雅國小 9 0.420354 POINT (307516.8 2771077) 6
## 10 公立 永春國小 10 0.435220 POINT (308431.8 2770848) 10
## fastfood_count public_sports_count private_sports_count unhealthy_index
## 1 2 2 3 3.5
## 2 6 0 4 NA
## 3 2 0 11 NA
## 4 2 0 3 NA
## 5 2 0 2 NA
## 6 1 0 3 NA
## 7 1 0 0 NA
## 8 4 0 3 NA
## 9 3 0 2 NA
## 10 5 0 5 NA
## unhealthy_index_combined
## 1 1.4000000
## 2 3.7500000
## 3 0.5454545
## 4 1.6666667
## 5 2.0000000
## 6 1.0000000
## 7 NA
## 8 3.3333333
## 9 4.5000000
## 10 3.0000000
library(spatstat)
## 載入需要的套件:spatstat.data
## 載入需要的套件:spatstat.univar
## spatstat.univar 3.1-3
## 載入需要的套件:spatstat.geom
## spatstat.geom 3.4-1
## 載入需要的套件:spatstat.random
## spatstat.random 3.3-3
## 載入需要的套件:spatstat.explore
## 載入需要的套件:nlme
##
## 載入套件:'nlme'
## 下列物件被遮斷自 'package:dplyr':
##
## collapse
## spatstat.explore 3.4-2
## 載入需要的套件:spatstat.model
## 載入需要的套件:rpart
## spatstat.model 3.3-5
## 載入需要的套件:spatstat.linnet
## spatstat.linnet 3.2-5
##
## spatstat 3.3-2
## For an introduction to spatstat, type 'beginner'
library(sf)
#str(sports_venues)
test_NNA <- function(sf_points, layer_name) {
if (nrow(sf_points) == 0) {
cat("\n---", layer_name, "---\n")
cat("⚠️ 這個圖層沒有點,無法進行 NNA 檢定。\n")
return(NULL)
}
# 轉成點座標
coords <- st_coordinates(sf_points)
# 取得邊界
bbox <- st_bbox(sf_points)
# 檢查 bbox 是否有 NA
if (any(is.na(bbox))) {
cat("\n---", layer_name, "---\n")
cat("⚠️ 這個圖層的邊界有 NA,無法建立窗口。\n")
return(NULL)
}
# 建立 owin 窗口
win <- owin(xrange = as.numeric(c(bbox["xmin"], bbox["xmax"])),
yrange = as.numeric(c(bbox["ymin"], bbox["ymax"])))
# 建立 ppp 物件
ppp_obj <- ppp(x = coords[, 1],
y = coords[, 2],
window = win)
# 計算平均最近鄰距離
mean_NND <- mean(nndist(ppp_obj))
# 理論隨機平均最近鄰距離 (CSR)
lambda <- intensity(ppp_obj) # 點密度
expected_NND <- 0.5 / sqrt(lambda)
# Z 分數(統計檢定)
se_NND <- 0.26136 / sqrt(lambda * npoints(ppp_obj))
z_score <- (mean_NND - expected_NND) / se_NND
cat("\n---", layer_name, "---\n")
cat("平均最近鄰距離:", mean_NND, "\n")
cat("理論隨機平均最近鄰距離:", expected_NND, "\n")
cat("Z分數:", z_score, "\n")
if (z_score < -1.96) {
cat("結果: 顯著聚集\n")
} else if (z_score > 1.96) {
cat("結果: 顯著均勻分布\n")
} else {
cat("結果: 沒有顯著差異 (隨機分布)\n")
}
}
# ⚠️ 最後,請一定檢查 4 個 sf 點圖層是否存在:
print(nrow(public_sports))
## [1] 64
print(nrow(private_sports))
## [1] 206
print(nrow(bubble_tea_sf))
## [1] 599
print(nrow(fast_food_sf))
## [1] 291
# 如果都不是 0,直接執行
test_NNA(public_sports, "公立運動場館")
## Warning: data contain duplicated points
##
## --- 公立運動場館 ---
## 平均最近鄰距離: 897.3532
## 理論隨機平均最近鄰距離: 1065.036
## Z分數: -2.409605
## 結果: 顯著聚集
test_NNA(private_sports, "私立運動場館")
## Warning: data contain duplicated points
##
## --- 私立運動場館 ---
## 平均最近鄰距離: 257.1123
## 理論隨機平均最近鄰距離: 491.4866
## Z分數: -13.09371
## 結果: 顯著聚集
test_NNA(bubble_tea_sf, "手搖飲料店")
## Warning: data contain duplicated points
##
## --- 手搖飲料店 ---
## 平均最近鄰距離: 67.85662
## 理論隨機平均最近鄰距離: 333.9653
## Z分數: -37.308
## 結果: 顯著聚集
test_NNA(fast_food_sf, "速食店")
## Warning: data contain duplicated points
##
## --- 速食店 ---
## 平均最近鄰距離: 575.2384
## 理論隨機平均最近鄰距離: 5898.669
## Z分數: -29.45201
## 結果: 顯著聚集
###都是顯著聚集,但公家體育館分散一些
# 假設 public_sports 是 sf 格式
# --- 1️⃣ 轉成 spatstat 的 ppp 物件 ---
coords <- st_coordinates(public_sports)
win <- owin(xrange = range(coords[,1]), yrange = range(coords[,2]))
ppp_obj <- ppp(x = coords[,1], y = coords[,2], window = win)
## Warning: data contain duplicated points
# --- 2️⃣ 真實資料最近鄰距離 ---
real_nnd <- nndist(ppp_obj)
# --- 3️⃣ 產生隨機 CSR 模擬(強度與窗口一致) ---
csr_ppp <- rpoispp(lambda = intensity(ppp_obj), win = win)
csr_nnd <- nndist(csr_ppp)
# --- 4️⃣ 合併成長格式資料框 ---
ecdf_df <- data.frame(
distance = c(real_nnd, csr_nnd),
type = rep(c("真實", "CSR (隨機)"), times = c(length(real_nnd), length(csr_nnd)))
)
# --- 5️⃣ ECDF 疊加圖 ---
ggplot(ecdf_df, aes(x = distance, color = type)) +
stat_ecdf(geom = "step", size = 1) +
scale_color_manual(values = c("真實" = "#6FB7B7", "CSR (隨機)" = "#9999CC")) +
labs(
title = "公立場館真實vs.隨機CSR累積分布",
x = "距離 (m)",
y = "累積機率",
color = "類別"
) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
plot.title = element_text(size = 14, hjust = 0.5, face = "bold"),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10),
legend.position = "bottom",
legend.text = element_text(size = 10),
#legend.title = element_blank() # 不顯示圖例標題
)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
剩下三張用function
library(sf)
library(spatstat)
library(ggplot2)
library(dplyr)
# 🔧 定義繪製 ECDF 疊加圖的函數
plot_nna_ecdf <- function(sf_points, layer_name) {
# 轉成 ppp
coords <- st_coordinates(sf_points)
win <- owin(xrange = range(coords[,1]), yrange = range(coords[,2]))
ppp_obj <- ppp(x = coords[,1], y = coords[,2], window = win)
# 真實資料最近鄰距離
real_nnd <- nndist(ppp_obj)
# CSR 隨機模擬
csr_ppp <- rpoispp(lambda = intensity(ppp_obj), win = win)
csr_nnd <- nndist(csr_ppp)
# 合併資料框
ecdf_df <- data.frame(
distance = c(real_nnd, csr_nnd),
type = rep(c("真實", "CSR (隨機)"), times = c(length(real_nnd), length(csr_nnd)))
)
# 繪圖
p <- ggplot(ecdf_df, aes(x = distance, color = type)) +
stat_ecdf(geom = "step", size = 1) +
scale_color_manual(values = c("真實" = "#6FB7B7", "CSR (隨機)" = "#9999CC")) +
labs(
title = paste0(layer_name, "真實vs.隨機CSR累積分布"),
x = "距離 (m)",
y = "累積機率",
color = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
plot.title = element_text(size = 14, hjust = 0.5, face = "bold"),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10),
legend.position = "bottom",
legend.text = element_text(size = 10)
)
return(p)
}
# ⚡ 分別執行並顯示三張圖
p_private <- plot_nna_ecdf(private_sports, "私立運動場館")
## Warning: data contain duplicated points
p_bubbletea <- plot_nna_ecdf(bubble_tea_sf, "手搖飲料店")
## Warning: data contain duplicated points
p_fastfood <- plot_nna_ecdf(fast_food_sf, "速食店")
## Warning: data contain duplicated points
# 顯示圖
p_private
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
p_bubbletea
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
p_fastfood
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
# 建立多元線性回歸模型 #跟公立運動場不顯著 #速食店邊緣顯著但也很低
model_multi <- lm(private_sports_count ~ fastfood_count + bubbletea_count, data = final_counts)
# 查看模型摘要
summary(model_multi)
##
## Call:
## lm(formula = private_sports_count ~ fastfood_count + bubbletea_count,
## data = final_counts)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.1095 -0.8214 -0.4994 0.4435 9.5175
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.49939 0.20190 2.473 0.014538 *
## fastfood_count 0.16964 0.09036 1.877 0.062469 .
## bubbletea_count 0.16096 0.04589 3.507 0.000603 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.603 on 145 degrees of freedom
## Multiple R-squared: 0.2436, Adjusted R-squared: 0.2331
## F-statistic: 23.35 on 2 and 145 DF, p-value: 1.623e-09
# 產生 HTML 格式表格(在 RStudio Viewer / 瀏覽器看)
library(stargazer)
##
## Please cite as:
## Hlavac, Marek (2022). stargazer: Well-Formatted Regression and Summary Statistics Tables.
## R package version 5.2.3. https://CRAN.R-project.org/package=stargazer
# 我們可以把它存在變數內(例如 output_html),再貼到 Word/PPT
# 用 stargazer 產生 HTML 格式
stargazer(model_multi,
type = "html",
title = "私立運動場館數量回歸結果",
dep.var.labels = "私立運動場館數量",
covariate.labels = c("速食店數量", "手搖飲料店數量"),
single.row = FALSE,
digits = 3,
out = "private_sports_model.html") # 直接輸出成 HTML 檔
##
## <table style="text-align:center"><caption><strong>私立運動場館數量回歸結果</strong></caption>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left"></td><td><em>Dependent variable:</em></td></tr>
## <tr><td></td><td colspan="1" style="border-bottom: 1px solid black"></td></tr>
## <tr><td style="text-align:left"></td><td>私立運動場館數量</td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left">速食店數量</td><td>0.170<sup>*</sup></td></tr>
## <tr><td style="text-align:left"></td><td>(0.090)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td style="text-align:left">手搖飲料店數量</td><td>0.161<sup>***</sup></td></tr>
## <tr><td style="text-align:left"></td><td>(0.046)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td style="text-align:left">Constant</td><td>0.499<sup>**</sup></td></tr>
## <tr><td style="text-align:left"></td><td>(0.202)</td></tr>
## <tr><td style="text-align:left"></td><td></td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left">Observations</td><td>148</td></tr>
## <tr><td style="text-align:left">R<sup>2</sup></td><td>0.244</td></tr>
## <tr><td style="text-align:left">Adjusted R<sup>2</sup></td><td>0.233</td></tr>
## <tr><td style="text-align:left">Residual Std. Error</td><td>1.603 (df = 145)</td></tr>
## <tr><td style="text-align:left">F Statistic</td><td>23.346<sup>***</sup> (df = 2; 145)</td></tr>
## <tr><td colspan="2" style="border-bottom: 1px solid black"></td></tr><tr><td style="text-align:left"><em>Note:</em></td><td style="text-align:right"><sup>*</sup>p<0.1; <sup>**</sup>p<0.05; <sup>***</sup>p<0.01</td></tr>
## </table>
#可能會分析關係,所以先把st_join做出來
#str(taipei_village)
# 計算有無公立運動場館
final_counts <- final_counts %>%
mutate(has_public_sports = ifelse(public_sports_count == 0, "無公立運動場館", "有公立運動場館"))
# 先把標籤整理成「數字(百分比%)」格式
pie_data <- final_counts %>%
mutate(has_public_sports = ifelse(public_sports_count == 0, "無公立運動場館", "有公立運動場館")) %>%
count(has_public_sports) %>%
mutate(
prop = n / sum(n) * 100,
label = paste0(n, " (", round(prop, 1), "%)")
)
# 畫圓餅圖
p <- ggplot(pie_data, aes(x = "", y = n, fill = has_public_sports)) + # 🔴 y 改成 n
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(
aes(label = label),
position = position_stack(vjust = 0.5),
color = "#3C3C3C",
size = 6
) +
scale_fill_manual(values = c("#6FB7B7", "#9999CC"), name = NULL) +
labs(title = "國小周邊 500m 內公立運動場館分布比例") + # 移除 fill 的圖例標題
theme_void(base_family = "STHeiti") +
theme(
legend.position = "bottom",
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
legend.background = element_rect(fill = "#F0F0F0", color = NA),
legend.box.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
legend.text = element_text(size = 16),
legend.title = element_blank(),
plot.title = element_text(hjust = 0.5, size = 14)
)
print(p)
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
ggsave("myplot.png", p, width = 8, height = 6, dpi = 300, bg = "#F0F0F0")
### 各個區也看一下
library(ggplot2)
# 確認 final_counts 中的欄位是數值型態
final_counts <- final_counts %>%
mutate(has_public_sports = as.numeric(public_sports_count > 0))
# 空間交集:把學校 final_counts 接上行政區名稱
final_counts <- st_join(final_counts, taipei_village["TOWNNAME"], join = st_within)
#str(final_counts)
#str(taipei_village)
# 2️⃣ 按行政區分組,計算各區「有公立運動場館的學校數量」以及「總學校數量」
public_sports_by_area <- final_counts %>%
group_by(TOWNNAME) %>%
summarise(
total_schools = n(),
schools_with_public_sports = sum(has_public_sports),
proportion_with_public_sports = schools_with_public_sports / total_schools * 100
) %>%
arrange(desc(proportion_with_public_sports)) # 依照比例排序
# 3️⃣ 畫長條圖
ggplot(public_sports_by_area, aes(x = reorder(TOWNNAME, -proportion_with_public_sports), y = proportion_with_public_sports)) +
geom_col(fill = "#6FB7B7") +
geom_text(aes(label = paste0(round(proportion_with_public_sports, 1), "%")),
vjust = -0.5, size = 3, color = "#3C3C3C") + # 文字顏色
labs(
title = "各行政區內國小500m內公立運動場館涵蓋比例",
x = NULL, # 🔴 直接隱藏 X 軸標題
y = NULL # 🔴 直接隱藏 Y 軸標題
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) + # 🔴 上方多留 10% 空間
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
plot.title = element_text(size = 14, hjust = 0.5, face = "bold"),
#axis.title = element_text(size = 8),
axis.text.x = element_text(size = 7, angle = 45, hjust = 1), # 調小 X 軸文字
axis.text.y = element_text(size = 8)
# 調小 Y 軸文字
)
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): Windows
## 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
library(dplyr)
library(tidyr)
# 計算每個行政區的總學校數 & 有公立運動場館的學校數
counts_by_area <- final_counts %>%
group_by(TOWNNAME) %>%
summarise(
total_schools = n(),
schools_with_public_sports = sum(has_public_sports, na.rm = TRUE))
# 資料轉長格式,方便 ggplot 畫分堆長條圖
counts_long <- counts_by_area %>%
pivot_longer(cols = c("total_schools", "schools_with_public_sports"),
names_to = "category",
values_to = "count")
# 畫分堆長條圖
ggplot(counts_long, aes(x = TOWNNAME, y = count, fill = category)) +
geom_col(position = "dodge") +
labs(
title = "各行政區國小總數vs.有公立運動場館的國小總數",
x = NULL, # 🔴 隱藏 X 軸標題
y = NULL, # 🔴 隱藏 Y 軸標題
fill = "類別"
) +
scale_fill_manual(values = c("#6FB7B7", "#9999CC"),
labels = c("有公立運動場館的學校數", "總學校數")) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
plot.title = element_text(size = 14, hjust = 0.5, face = "bold"),
axis.text.x = element_text(size = 7, angle = 45, hjust = 1),
axis.text.y = element_text(size = 8),
legend.position = "bottom",
legend.text = element_text(size = 12),
legend.title = element_blank() # 🔴 隱藏圖例標題
)
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## Windows 字型資料庫裡不明的字型系列
library(dplyr)
library(ggplot2)
library(tidyr)
library(patchwork) # 用來一次合併多個圖表
##
## 載入套件:'patchwork'
## 下列物件被遮斷自 'package:spatstat.geom':
##
## area
# 設定不顯示警告
options(warn = -1)
# 計算每個學校的型態
final_counts <- final_counts %>%
mutate(
has_public = public_sports_count > 0,
has_private = private_sports_count > 0,
has_any = has_public | has_private,
type = case_when(
has_public & !has_private ~ "僅公立",
!has_public & has_private ~ "僅私立",
TRUE ~ "無運動場館"
)
)
# 產生四種統計資料
total_counts <- final_counts %>%
group_by(TOWNNAME) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(type = "總學校數")
any_counts <- final_counts %>%
filter(has_any) %>%
group_by(TOWNNAME) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(type = "至少有一家")
only_public <- final_counts %>%
filter(type == "僅公立") %>%
group_by(TOWNNAME) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(type = "僅公立")
only_private <- final_counts %>%
filter(type == "僅私立") %>%
group_by(TOWNNAME) %>%
summarise(count = n(), .groups = "drop") %>%
mutate(type = "僅私立")
# 合併四種資料
plot_data <- bind_rows(total_counts, any_counts, only_private, only_public) %>%
mutate(type = factor(type, levels = c("總學校數", "至少有一家", "僅私立", "僅公立")))
# 把 NA 值補 0
plot_data <- plot_data %>%
complete(TOWNNAME, type, fill = list(count = 0))
# 繪製每個行政區的單一圖表
# 繪製每個行政區的單一圖表
# 繪製每個行政區的單一圖表
p <- ggplot(plot_data, aes(x = type, y = count, fill = type)) +
geom_col(position = "dodge") +
facet_wrap(~ TOWNNAME, ncol = 4) +
scale_fill_manual(
values = c(
"總學校數" = "#6FB7B7",
"至少有一家" = "#9999CC",
"僅私立" = "#B766AD",
"僅公立" = "#AFAF61"
),
name = "類別"
) +
labs(
x = NULL,
y = "學校數",
title = "各行政區國小周邊運動場館分布"
) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA), # 🔴 背景顏色
panel.background = element_rect(fill = "#F0F0F0", color = NA), # 🔴 面板背景
legend.background = element_rect(fill = "#F0F0F0", color = NA), # 🔴 圖例背景
legend.box.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_blank(), # 🔴 x軸文字移除
axis.text.y = element_text(size = 8, color = "#3C3C3C"), # y軸文字小一點
axis.title.y = element_text(size = 10, color = "#3C3C3C"), # y軸標題縮小 & 顏色統一
strip.text = element_text(size = 10, color = "#3C3C3C"), # facet 標題
legend.position = "right", # 圖例放右側
legend.title = element_text(size = 10, color = "#3C3C3C"), # 圖例標題
legend.text = element_text(size = 8, color = "#3C3C3C"), # 圖例文字
plot.title = element_text(size = 12, hjust = 0.5, color = "#3C3C3C") # 標題置中 & 顏色
)
# 顯示圖
print(p)
library(dplyr)
library(ggplot2)
# 1️⃣ 篩選沒有任何運動場館的學校
no_sports_schools <- final_counts %>%
filter(public_sports_count == 0 & private_sports_count == 0)
# 2️⃣ 計算加權總分
no_sports_schools <- no_sports_schools %>%
mutate(total_score = bubbletea_count * 1 + fastfood_count * 2) %>%
filter(total_score > 0) # 🔴 只保留總分 > 0
# 3️⃣ 整理標籤(行政區+學校名稱)
no_sports_schools <- no_sports_schools %>%
mutate(label = paste0(TOWNNAME, " ", Name))
# 4️⃣ 繪製長條圖
ggplot(no_sports_schools, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 統一顏色
coord_flip() + # 橫向長條圖
labs(title = "公私立運動場館都沒有的國小被誘惑排名",
x = NULL, y = NULL) + # 🔴 刪掉 x、y 軸標題
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA), # 背景顏色
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"), # x 軸文字
axis.text.y = element_text(size = 9, color = "#3C3C3C"), # y 軸文字
plot.title = element_text(size = 14, hjust = 0.5, face = "bold", color = "#3C3C3C"), # 標題
legend.position = "none" # 不顯示圖例
)
library(sf)
library(dplyr)
library(tmap)
# 🟩 假設以下資料已存在:
# final_counts:含有學校屬性與外部誘惑分數
# school_buffer:國小 500m buffer
# taipei_village:台北市行政區多邊形
# 1️⃣ 篩選無運動場館的學校 + 計算外部誘惑指數
no_sports_schools <- final_counts %>%
filter(public_sports_count == 0 & private_sports_count == 0) %>%
mutate(total_score = bubbletea_count * 1 + fastfood_count * 2,
tempt_group = ifelse(total_score > 0, "有外部誘惑", "無外部誘惑")) %>%
select(SID, tempt_group)
# 🟩 直接把這 33 筆 buffer 抓出來
buffer_with_tempt <- school_buffer %>%
filter(SID %in% no_sports_schools$SID) %>%
st_join(no_sports_schools, by = "SID")
#buffer 中心的點(final_counts 也用 33 筆顯示)
school_points <- final_counts %>%
filter(SID %in% no_sports_schools$SID)
# 🟩 畫圖:33 筆 buffer 分 2 顏色
tmap_mode("plot") # 靜態地圖模式
## ℹ tmap mode set to "plot".
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") + # 灰色行政區邊界
tm_shape(buffer_with_tempt) +
tm_fill(col = "tempt_group",
palette = c("無外部誘惑" = "#9999CC", "有外部誘惑" = "#6FB7B7"),
title = "外部誘惑指數",
alpha = 0.8) +
tm_borders(col = "white", lwd = 0.5) + # buffer 外框
tm_shape(school_points) +
tm_dots(col = "black", size = 0.1) + # 中心點黑點
tm_layout(
title = "台北市國小 500m Buffer:\n無運動場館學校依外部誘惑分組",
title.size = 1.2,
legend.outside = TRUE,
bg.color = "#F0F0F0",
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# 1️⃣ 篩選「只有私立,沒有公立」的學校
only_private_schools <- final_counts %>%
filter(public_sports_count == 0 & private_sports_count != 0)
# 2️⃣ 計算加權分數
only_private_schools <- only_private_schools %>%
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1)
# 3️⃣ 先印出:總共有幾所學校
cat("總共有", nrow(only_private_schools), "所學校\n")
## 總共有 64 所學校
# 4️⃣ 再篩選:只留總分 >0 的
only_private_schools_positive <- only_private_schools %>%
filter(total_score > 0)
cat("其中有", nrow(only_private_schools_positive), "所學校分數>0\n")
## 其中有 64 所學校分數>0
# 🔴 先篩選:total_score >= 15
only_private_schools_filtered <- only_private_schools %>%
filter(total_score >= 16) %>%
mutate(label = paste0(TOWNNAME, " - ", Name))
# 🔵 畫圖
ggplot(only_private_schools_filtered, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 🔴 顏色一致
geom_text(aes(label = total_score), hjust = -0.1, size = 3, color = "black") + # 🔴 分數標籤
coord_flip() +
labs(
title = "僅私立運動場館(無公立)學校外部誘惑指數 ≥16",
x = NULL,
y = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
axis.text.x = element_text(size = 8, color = "#3C3C3C"),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
plot.title = element_text(size = 12, hjust = 0.5, face = "bold", color = "#3C3C3C")
)
# 🟩 篩選:僅有私立運動場館
private_only_schools <- final_counts %>%
filter(public_sports_count == 0 & private_sports_count > 0) %>%
mutate(total_score = bubbletea_count * 1 + fastfood_count * 2,
tempt_group = ifelse(total_score > 0, "有外部誘惑", "無外部誘惑")) %>%
select(SID, tempt_group)
# 🟩 取得 buffer
buffer_private_only <- school_buffer %>%
filter(SID %in% private_only_schools$SID) %>%
st_join(private_only_schools, by = "SID")
# 🟩 中心學校點
school_points_private_only <- final_counts %>%
filter(SID %in% private_only_schools$SID)
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") + # 行政區灰色
tm_shape(buffer_private_only) +
tm_fill(col = "tempt_group",
palette = c("無外部誘惑" = "#9999CC", "有外部誘惑" = "#6FB7B7"),
title = "外部誘惑指數",
alpha = 0.8) +
tm_borders(col = "white", lwd = 0.5) + # buffer 外框
tm_shape(school_points_private_only) +
tm_dots(col = "black", size = 0.1) + # 中心點黑點
tm_layout(
title = "台北市國小 500m Buffer:\n僅有私立運動場館學校依外部誘惑分組",
title.size = 1.2,
legend.outside = TRUE,
bg.color = "#F0F0F0",
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`The visual variable "fill" of the layer "polygons" contains a unique value. Therefore a categorical scale is applied (tm_scale_categorical).
library(sf)
library(dplyr)
# --- 速食店:判斷每間店家是否至少有被一個 buffer 擊中 ---
fastfood_in_buffer_flag <- st_join(fast_food_sf, school_buffer, join = st_intersects) %>%
st_drop_geometry() %>%
filter(!is.na(SID)) %>%
distinct(id, store)
# 移除 3Q雞排 和 21世紀風味館
fastfood_in_buffer_flag <- fastfood_in_buffer_flag %>%
filter(!store %in% c("3q雞排", "21世紀風味館"))
# --- 速食店:總家數 ---
fastfood_total_counts <- fast_food_sf %>%
st_drop_geometry() %>%
filter(!is.na(store)) %>%
filter(!store %in% c("3Q雞排", "21世紀風味館")) %>% # 🔴 移除
count(store, name = "total_count")
# --- 速食店:在 buffer 內的家數 ---
fastfood_in_buffer_counts <- fastfood_in_buffer_flag %>%
count(store, name = "in_buffer_count")
# --- 合併計算百分比 ---
fastfood_coverage_unique <- left_join(fastfood_total_counts, fastfood_in_buffer_counts, by = "store") %>%
mutate(in_buffer_count = ifelse(is.na(in_buffer_count), 0, in_buffer_count),
percentage_in_buffer = (in_buffer_count / total_count) * 100) %>%
arrange(desc(percentage_in_buffer))
print(fastfood_coverage_unique)
## # A tibble: 9 × 4
## store total_count in_buffer_count percentage_in_buffer
## <chr> <int> <int> <dbl>
## 1 炸機大獅 15 14 93.3
## 2 達美樂 23 20 87.0
## 3 必勝客 38 33 86.8
## 4 派克雞排 13 11 84.6
## 5 胖老爹 36 30 83.3
## 6 漢堡王 23 18 78.3
## 7 麥當勞 61 47 77.0
## 8 肯德基 30 22 73.3
## 9 頂呱呱 24 13 54.2
# --- 手搖飲 ---
bubbletea_in_buffer_flag <- st_join(bubble_tea_sf, school_buffer, join = st_intersects) %>%
st_drop_geometry() %>%
filter(!is.na(SID)) %>%
distinct(id, store)
bubbletea_total_counts <- bubble_tea_sf %>%
st_drop_geometry() %>%
filter(!is.na(store)) %>%
count(store, name = "total_count")
bubbletea_in_buffer_counts <- bubbletea_in_buffer_flag %>%
count(store, name = "in_buffer_count")
bubbletea_coverage_unique <- left_join(bubbletea_total_counts, bubbletea_in_buffer_counts, by = "store") %>%
mutate(in_buffer_count = ifelse(is.na(in_buffer_count), 0, in_buffer_count),
percentage_in_buffer = (in_buffer_count / total_count) * 100) %>%
arrange(desc(percentage_in_buffer))
ggplot(fastfood_coverage_unique, aes(x = percentage_in_buffer, y = reorder(store, percentage_in_buffer))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) + # 🔵 改成你的顏色,微透明
geom_text(aes(label = paste0(round(percentage_in_buffer, 1), "%")),
hjust = -0.1, size = 3, color = "#3C3C3C") + # 🔵 加上黑灰字
labs(title = "台北市速食店品牌:國小500m內覆蓋率",
x = NULL, # 🔵 拿掉 x 軸標題
y = NULL) + # 🔵 拿掉 y 軸標題
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA), # 🔵 灰背景
panel.background = element_rect(fill = "#F0F0F0", color = NA), # 🔵 灰背景
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"), # 🔵 格線顏色淡化
panel.grid.minor = element_blank()
) +
xlim(0, max(fastfood_coverage_unique$percentage_in_buffer, na.rm = TRUE) + 5) # 🔵 保留空間
# --- 手搖飲長條圖 ---
ggplot(bubbletea_coverage_unique, aes(x = percentage_in_buffer, y = reorder(store, percentage_in_buffer))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) + # 🔵 改成你的顏色,微透明
geom_text(aes(label = paste0(round(percentage_in_buffer, 1), "%")),
hjust = -0.1, size = 3, color = "#3C3C3C") + # 🔵 加上黑灰字
labs(title = "台北市手搖飲品牌:國小500m內覆蓋率",
x = NULL, # 🔵 拿掉 x 軸標題
y = NULL) + # 🔵 拿掉 y 軸標題
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA), # 🔵 灰背景
panel.background = element_rect(fill = "#F0F0F0", color = NA), # 🔵 灰背景
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"), # 🔵 格線顏色淡化
panel.grid.minor = element_blank()
) +
xlim(0, max(bubbletea_coverage_unique$percentage_in_buffer, na.rm = TRUE) + 5) # 🔵 保留空間
library(sf)
library(dplyr)
library(ggplot2)
# 🟡 台北市國小總數
total_schools <- nrow(final_counts) # 148間
# 🟡 速食店被覆蓋的學校數 (distinct store + SID)
fastfood_school_coverage <- st_join(fast_food_sf, school_buffer, join = st_intersects) %>%
st_drop_geometry() %>%
filter(!is.na(SID), !store %in% c("3Q雞排", "21世紀風味館")) %>%
distinct(store, SID) %>%
count(store, name = "covered_school_count") %>%
mutate(percentage_schools = (covered_school_count / total_schools) * 100) %>%
arrange(desc(percentage_schools))
# 🟡 手搖飲店被覆蓋的學校數
bubbletea_school_coverage <- st_join(bubble_tea_sf, school_buffer, join = st_intersects) %>%
st_drop_geometry() %>%
filter(!is.na(SID)) %>%
distinct(store, SID) %>%
count(store, name = "covered_school_count") %>%
mutate(percentage_schools = (covered_school_count / total_schools) * 100) %>%
arrange(desc(percentage_schools))
# 🟡 覆蓋學校比例長條圖 - 速食店
ggplot(fastfood_school_coverage, aes(x = percentage_schools, y = reorder(store, percentage_schools))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = paste0(covered_school_count, "校 (", round(percentage_schools, 1), "%)")),
hjust = 0, size = 3, color = "#3C3C3C") +
labs(title = "台北市速食店品牌:國小 500m 內覆蓋學校比例",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(fastfood_school_coverage$percentage_schools, na.rm = TRUE) + 5)
# 🟡 覆蓋學校比例長條圖 - 手搖飲店
ggplot(bubbletea_school_coverage, aes(x = percentage_schools, y = reorder(store, percentage_schools))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = paste0(covered_school_count, "校 (", round(percentage_schools, 1), "%)")),
hjust = 0, size = 3, color = "#3C3C3C") +
labs(title = "台北市手搖飲品牌:國小 500m 內覆蓋學校比例",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(bubbletea_school_coverage$percentage_schools, na.rm = TRUE) + 5)
# 🟩 篩選:同時有公立與私立運動場館的學校
both_sports_schools <- final_counts %>%
filter(public_sports_count > 0 & private_sports_count > 0) %>%
mutate(total_score = bubbletea_count * 1 + fastfood_count * 2) %>%
select(SID, total_score)
# 🟩 合併到 buffer,並手動指定 factor 順序
buffer_both <- school_buffer %>%
filter(SID %in% both_sports_schools$SID) %>%
st_join(both_sports_schools, by = "SID") %>%
mutate(score_group = case_when(
total_score <= 8 ~ "8分以下",
total_score <= 16 ~ "16分以下",
total_score <= 24 ~ "24分以下",
total_score > 24 ~ "24分以上",
),
# 🔴 這裡手動設定 factor 順序
score_group = factor(score_group,
levels = c("8分以下", "16分以下", "24分以下", "24分以上")))
# 🟩 中心點
school_points_both <- final_counts %>%
filter(SID %in% both_sports_schools$SID)
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") +
tm_shape(buffer_both) +
tm_fill(col = "score_group",
palette = c("8分以下" = "#D1E9E9",
"16分以下" = "#95CACA",
"24分以下" = "#4F9D9D",
"24分以上" = "#336666"),
title = "外部誘惑指數",
alpha = 0.8) +
tm_borders(col = "white", lwd = 0.5) +
tm_shape(school_points_both) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小 500m Buffer:\n同時有公私立運動場館學校外部誘惑指數分組",
title.size = 1.2,
legend.outside = TRUE,
bg.color = "#F0F0F0",
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`[plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
# 假設你已經有 final_counts、school_buffer 這兩個資料
# 1️⃣ 篩選出至少有公立運動場館的學校(不管私立)
public_schools <- final_counts %>%
filter(public_sports_count > 0) %>%
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1) %>%
select(SID, total_score)
# 2️⃣ 把總分分成四個等級(注意:這邊名稱要對應下面的圖層色票!)
buffer_public <- school_buffer %>%
filter(SID %in% public_schools$SID) %>%
st_join(public_schools, by = "SID") %>%
mutate(score_group = case_when(
total_score <= 8 ~ "0~8分",
total_score <= 16 ~ "9~16分",
total_score <= 24 ~ "17~24分",
total_score > 24 ~ "24分以上"
),
# 🔴 設定 factor 順序
score_group = factor(score_group,
levels = c("0~8分", "9~16分", "17~24分", "24分以上")))
# 3️⃣ 中心點層:直接拿 final_counts 中有公立的
school_points_public <- final_counts %>%
filter(public_sports_count > 0)
# 4️⃣ 繪圖
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white") + # 灰色底 + 邊框
tm_shape(buffer_public) +
tm_fill(col = "score_group",
palette = c("0~8分" = "#D1E9E9",
"9~16分" = "#95CACA",
"17~24分" = "#4F9D9D",
"24分以上" = "#336666"),
title = "外部誘惑指數",
alpha = 0.8) +
tm_borders(col = "white", lwd = 0.5) + # 邊框
tm_shape(school_points_public) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小500m Buffer有公立運動場館國小外部誘惑",
title.size = 1.2, # 🔴 標題放大(tmap title.size 是比例數字)
legend.outside = TRUE,
bg.color = "#F0F0F0", # 🔴 背景灰色底
panel.background = "#F0F0F0",
legend.text.size = 0.6,
legend.title.size = 0.8,
frame = TRUE
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`[plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
# 1️⃣ 篩選有公立運動場館的學校
public_schools <- final_counts %>%
filter(public_sports_count > 0) %>%
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1) %>%
filter(total_score <= 4) %>% # 🔴 只留最高 4 分以內
mutate(label = paste0(TOWNNAME, " - ", Name)) %>% # 建立標籤
arrange(total_score)
# 2️⃣ 繪製橫向長條圖
ggplot(public_schools, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 統一顏色
geom_text(aes(label = total_score),
hjust = -0.1, size = 3, color = "black") +
coord_flip() +
labs(
title = "台北市有公立運動場館國小500m",
x = NULL,
y = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
axis.text.x = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(size = 12, hjust = 0.5, face = "bold", color = "#3C3C3C")
)
# 🟩 總共有公立運動場館的學校
n_public_schools <- final_counts %>%
filter(public_sports_count > 0) %>%
nrow()
# 🟩 只有公立(沒有私立)的學校
n_only_public_schools <- final_counts %>%
filter(public_sports_count > 0 & private_sports_count == 0) %>%
nrow()
# 輸出
cat("總共有公立運動場館的學校數量:", n_public_schools, "\n")
## 總共有公立運動場館的學校數量: 51
cat("其中只有公立運動場館的學校數量:", n_only_public_schools, "\n")
## 其中只有公立運動場館的學校數量: 22
library(ggplot2)
library(dplyr)
tempt_scores_16up <- final_counts %>%
filter(public_sports_count > 0 & private_sports_count > 0) %>%
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1) %>%
filter(total_score > 16) %>%
mutate(label = paste0(TOWNNAME, " - ", Name)) %>%
select(label, total_score)
library(ggplot2)
ggplot(tempt_scores_16up, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 用同樣顏色
geom_text(aes(label = total_score), hjust = -0.1, size = 3, color = "black") +
coord_flip() +
labs(
title = "同時有公私立運動場館學校外部誘惑指數 ≥16 分",
x = NULL,
y = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
axis.text.x = element_text(size = 8, color = "#3C3C3C"),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
plot.title = element_text(size = 12, hjust = 0.5, face = "bold", color = "#3C3C3C")
)
# 🔵 僅有公立運動場館的學校(無私立)
tempt_scores_16up <- final_counts %>%
filter(public_sports_count > 0 & private_sports_count == 0) %>%
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1) %>%
filter(total_score > 0) %>%
mutate(label = paste0(TOWNNAME, " - ", Name)) %>%
select(label, total_score)
library(ggplot2)
ggplot(tempt_scores_16up, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 用統一顏色
geom_text(aes(label = total_score), hjust = -0.1, size = 3, color = "black") +
coord_flip() +
labs(
title = "僅有公立運動場館學校外部誘惑指數 ",
x = NULL,
y = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
axis.text.x = element_text(size = 8, color = "#3C3C3C"),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
plot.title = element_text(size = 12, hjust = 0.5, face = "bold", color = "#3C3C3C")
)
# 🔵 只要有公立運動場館的學校(有無私立皆可),且誘惑指數 >= 16
tempt_scores_16up <- final_counts %>%
filter(public_sports_count > 0) %>% # 🟢 只要有公立
mutate(total_score = fastfood_count * 2 + bubbletea_count * 1) %>%
filter(total_score >= 16) %>% # 🟢 誘惑指數 >= 16
mutate(label = paste0(TOWNNAME, " - ", Name)) %>%
select(label, total_score)
library(ggplot2)
ggplot(tempt_scores_16up, aes(x = reorder(label, total_score), y = total_score)) +
geom_col(fill = "#6FB7B7") + # 用統一顏色
geom_text(aes(label = total_score), hjust = -0.1, size = 3, color = "black") +
coord_flip() +
labs(
title = "有公立運動場館學校外部誘惑指數 ≥16 分",
x = NULL,
y = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
axis.text.x = element_text(size = 8, color = "#3C3C3C"),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
plot.title = element_text(size = 12, hjust = 0.5, face = "bold", color = "#3C3C3C")
)
# 🟠 速食店:有/沒有 的比例
fastfood_pie <- final_counts %>%
mutate(has_fastfood = ifelse(fastfood_count > 0, "有速食店", "無速食店")) %>%
count(has_fastfood, name = "n") %>%
mutate(prop = n / sum(n) * 100)
# 🟠 飲料店:有/沒有 的比例
bubbletea_pie <- final_counts %>%
mutate(has_bubbletea = ifelse(bubbletea_count > 0, "有飲料店", "無飲料店")) %>%
count(has_bubbletea, name = "n") %>%
mutate(prop = n / sum(n) * 100)
# 🔵 速食店圓餅圖
p1 <- ggplot(fastfood_pie, aes(x = "", y = prop, fill = has_fastfood)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(aes(label = paste0(n, " (", round(prop, 1), "%)")),
position = position_stack(vjust = 0.5),
color = "#3C3C3C", size = 5) +
scale_fill_manual(values = c("有速食店" = "#6FB7B7", "無速食店" = "#9999CC"), name = NULL) +
labs(title = "國小周邊 500m 內速食店有無比例") +
theme_void() +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
legend.position = "bottom",
legend.text = element_text(size = 12, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C")
)
# 🔵 飲料店圓餅圖
p2 <- ggplot(bubbletea_pie, aes(x = "", y = prop, fill = has_bubbletea)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(aes(label = paste0(n, " (", round(prop, 1), "%)")),
position = position_stack(vjust = 0.5),
color = "#3C3C3C", size = 5) +
scale_fill_manual(values = c("有飲料店" = "#6FB7B7", "無飲料店" = "#9999CC"), name = NULL) +
labs(title = "國小周邊 500m 內飲料店有無比例") +
theme_void() +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
legend.position = "bottom",
legend.text = element_text(size = 12, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C")
)
# ➜ 並排顯示
p1
p2
ggsave("fastfood_pie.png", plot = p1, width = 6, height = 6, dpi = 300)
ggsave("bubbletea_pie.png", plot = p2, width = 6, height = 6, dpi = 300)
# 🟠 至少有一間速食店或手搖飲料店
tempt_pie <- final_counts %>%
mutate(has_anytempt = ifelse(fastfood_count > 0 | bubbletea_count > 0, "有外部誘惑", "無外部誘惑")) %>%
count(has_anytempt, name = "n") %>%
mutate(prop = n / sum(n) * 100)
# 🔵 外部誘惑圓餅圖
p3 <- ggplot(tempt_pie, aes(x = "", y = prop, fill = has_anytempt)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(aes(label = paste0(n, " (", round(prop, 1), "%)")),
position = position_stack(vjust = 0.5),
color = "#3C3C3C", size = 5) +
scale_fill_manual(values = c("有外部誘惑" = "#6FB7B7", "無外部誘惑" = "#9999CC"), name = NULL) +
labs(title = "國小周邊 500m 內外部誘惑(速食或手搖)有無比例") +
theme_void() +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
legend.position = "bottom",
legend.text = element_text(size = 12, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C")
)
ggsave("p3_tempt_pie_chart.png", p3, width = 8, height = 6, dpi = 300)
sports_count <- data.frame(
category = c("公立運動場館", "私立運動場館"),
count = c(nrow(public_sports), nrow(private_sports))
)
# 計算百分比,並組合數字+百分比的標籤
sports_count <- sports_count %>%
mutate(
prop = count / sum(count) * 100,
label = paste0(count, " (", round(prop, 1), "%)")
)
# 畫圓餅圖
p2 <- ggplot(sports_count, aes(x = "", y = count, fill = category)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(
aes(label = label),
position = position_stack(vjust = 0.5),
color = "#3C3C3C",
size = 5
) +
scale_fill_manual(values = c("#6FB7B7", "#9999CC")) +
labs(
title = "台北市公私立運動場館數量分布",
fill = NULL # 隱藏圖例標題
) +
theme_void(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
text = element_text(color = "#3C3C3C"),
plot.title = element_text(size = 18, hjust = 0.5, face = "bold"),
legend.text = element_text(size = 12),
legend.position = "bottom"
)
print(p2)
ggsave("public_private_sports_pie.png", plot = p2, width = 8, height = 6, dpi = 300, bg = "#F0F0F0")
library(sf)
library(dplyr)
library(tmap)
# 🔴 確認公民營欄位:公立 / 私立
public_sports <- sports_venues_sf %>% filter(公民營 == "公營")
private_sports <- sports_venues_sf %>% filter(公民營 == "民營")
# 1️⃣ 私立運動場館交集
private_joined <- st_join(school_buffer, private_sports, join = st_intersects, left = TRUE) %>%
mutate(has_private = ifelse(is.na(編號), "無私立運動場館", "有私立運動場館")) %>%
select(SID, has_private, geometry) %>%
group_by(SID, has_private) %>%
summarise(geometry = st_union(geometry), .groups = "drop")
# 2️⃣ 公立運動場館交集
public_joined <- st_join(school_buffer, public_sports, join = st_intersects, left = TRUE) %>%
mutate(has_public = ifelse(is.na(編號), "無公立運動場館", "有公立運動場館")) %>%
select(SID, has_public, geometry) %>%
group_by(SID, has_public) %>%
summarise(geometry = st_union(geometry), .groups = "drop")
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
# 🔵 私立運動場館圖
p1 <- tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white", alpha = 1) +
tm_shape(private_joined) +
tm_fill(col = "has_private",
palette = c("有私立運動場館" = "#6FB7B7", "無私立運動場館" = "#9999CC"),
title = "私立運動場館") +
tm_shape(final_counts) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小 500m Buffer 是否有私立運動場館",
title.size = 4,
legend.outside = TRUE,
bg.color = "#F0F0F0" # 🔴 這邊加上背景顏色
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# 🔵 公立運動場館圖
p2 <- tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white", alpha = 1) +
tm_shape(public_joined) +
tm_fill(col = "has_public",
palette = c("有公立運動場館" = "#6FB7B7", "無公立運動場館" = "#9999CC"),
title = "公立運動場館") +
tm_shape(final_counts) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小 500m Buffer 是否有公立運動場館",
main.title.size = 8, # 🔴 這裡調整標題大小,數字愈大,字愈大(預設 1)
legend.outside = TRUE,
bg.color = "#F0F0F0" # 🔴 這邊加上背景顏色
)
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# 顯示地圖
p1
## [plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
p2
## [plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
# 🔴 移除 sf geometry(轉成一般 data.frame)
final_counts_df <- st_drop_geometry(final_counts)
boxplot_data <- final_counts_df %>%
select(fastfood_count, bubbletea_count) %>%
pivot_longer(cols = everything(), names_to = "type", values_to = "count") %>%
mutate(type = case_when(
type == "fastfood_count" ~ "速食店",
type == "bubbletea_count" ~ "手搖飲料店",
TRUE ~ type # 預防萬一有其他值
))
# 2️⃣ 計算統計量(最小值、Q1、中位數、Q3、最大值)
box_stats <- boxplot_data %>%
group_by(type) %>%
summarise(
ymin = min(count),
lower = quantile(count, 0.25),
middle = median(count),
upper = quantile(count, 0.75),
ymax = max(count)
)
# 3️⃣ 畫盒鬚圖,並加上數值標註
ggplot(boxplot_data, aes(x = type, y = count, fill = type)) +
geom_boxplot(width = 0.5, alpha = 0.6, color = "black") +
# "手搖飲料店" Q1
geom_text(data = filter(box_stats, type == "手搖飲料店"),
aes(y = lower, label = paste("Q1:", lower)),
vjust = -0.3, hjust = -0.3, size = 3, color = "black") +
# "速食店" Q1
geom_text(data = filter(box_stats, type == "速食店"),
aes(y = lower, label = paste("Q1:", lower)),
vjust = -0.5, hjust = -1.2, size = 3, color = "black") +
# "手搖飲料店" Q3
geom_text(data = filter(box_stats, type == "手搖飲料店"),
aes(y = upper, label = paste("Q3:", upper)),
vjust = -0.3, hjust = -0.3, size = 3, color = "black") +
# "速食店" Q3
geom_text(data = filter(box_stats, type == "速食店"),
aes(y = upper, label = paste("Q3:", upper)),
vjust = -0.7, hjust = -2.2, size = 3, color = "black") +
# "手搖飲料店" Median
geom_text(data = filter(box_stats, type == "手搖飲料店"),
aes(y = middle, label = paste("Med:", middle)),
vjust = -0.3, hjust = 0, size = 3, color = "black") +
# "速食店" Median
geom_text(data = filter(box_stats, type == "速食店"),
aes(y = middle, label = paste("Med:", middle)),
vjust = -0.6, hjust = -1.7, size = 3, color = "black") +
# "手搖飲料店" Min
geom_text(data = filter(box_stats, type == "手搖飲料店"),
aes(y = ymin, label = paste("Min:", ymin)),
vjust = 1.2, hjust = -0.2, size = 3, color = "black") +
# "速食店" Min
geom_text(data = filter(box_stats, type == "速食店"),
aes(y = ymin, label = paste("Min:", ymin)),
vjust = 1.2, hjust = -2, size = 3, color = "black") +
# "手搖飲料店" Max
geom_text(data = filter(box_stats, type == "手搖飲料店"),
aes(y = ymax, label = paste("Max:", ymax)),
vjust = 1, hjust = -0.1, size = 3, color = "black") +
# "速食店" Max
geom_text(data = filter(box_stats, type == "速食店"),
aes(y = ymax, label = paste("Max:", ymax)),
vjust = -0.5, hjust = -0.1, size = 3, color = "black") +
labs(title = "台灣國小周邊速食店與手搖飲料店分布比較", y = "店家數量", x = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10),
plot.title = element_text(size = 14, hjust = 0.5, face = "bold"),
legend.position = "none"
)
library(sf)
library(dplyr)
library(tmap)
# 假設你已經有 taipei_village、school_buffer、fast_food_sf、bubble_tea_sf、final_counts
# 🔴 速食店交集
fastfood_joined <- st_join(school_buffer, fast_food_sf, join = st_intersects, left = TRUE) %>%
mutate(has_fastfood = ifelse(is.na(id), "無速食店", "有速食店")) %>%
select(SID, has_fastfood, geometry) %>%
group_by(SID, has_fastfood) %>%
summarise(geometry = st_union(geometry), .groups = "drop")
# 🔴 飲料店交集
bubbletea_joined <- st_join(school_buffer, bubble_tea_sf, join = st_intersects, left = TRUE) %>%
mutate(has_bubbletea = ifelse(is.na(id), "無飲料店", "有飲料店")) %>%
select(SID, has_bubbletea, geometry) %>%
group_by(SID, has_bubbletea) %>%
summarise(geometry = st_union(geometry), .groups = "drop")
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
# 🔵 速食店圖
p1 <- tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white", alpha = 1) +
tm_shape(fastfood_joined) +
tm_fill(col = "has_fastfood",
palette = c("有速食店" = "#AFAF61", "無速食店" = "#B87070"),
title = "速食店") +
tm_shape(final_counts) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小 500m Buffer 是否有速食店",
title.size = 4,
legend.outside = TRUE,
bg.color = "#F0F0F0"
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# 🔵 飲料店圖
p2 <- tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white", alpha = 1) +
tm_shape(bubbletea_joined) +
tm_fill(col = "has_bubbletea",
palette = c("有飲料店" = "#AFAF61", "無飲料店" = "#B87070"),
title = "飲料店") +
tm_shape(final_counts) +
tm_dots(col = "black", size = 0.1) +
tm_layout(
title = "台北市國小 500m Buffer 是否有飲料店",
main.title.size = 8,
legend.outside = TRUE,
bg.color = "#F0F0F0"
)
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
# 顯示地圖
p1
## [plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.
p2
# 如果需要儲存
# tmap_save(p1, "Taipei_FastFood_Buffer_Map.png", width = 8, height = 10, dpi = 300)
# tmap_save(p2, "Taipei_BubbleTea_Buffer_Map.png", width = 8, height = 10, dpi = 300)
library(dplyr)
library(ggplot2)
# --- 速食店品牌分布 ---
fastfood_in_buffer <- st_join(school_buffer, fast_food_sf, join = st_intersects)
fastfood_brand_counts <- fastfood_in_buffer %>%
st_drop_geometry() %>%
filter(!is.na(store)) %>% # 🔴 去掉 NA 品牌
count(store) %>%
mutate(percentage = n / sum(n) * 100) %>%
arrange(desc(percentage))
# 移除指定品牌
fastfood_brand_counts <- fastfood_brand_counts %>%
filter(!store %in% c("3Q雞排", "21世紀風味館"))
str(fastfood_brand_counts)
## 'data.frame': 9 obs. of 3 variables:
## $ store : chr "麥當勞" "必勝客" "胖老爹" "達美樂" ...
## $ n : int 67 50 46 37 33 23 19 18 17
## $ percentage: num 21.1 15.8 14.5 11.7 10.4 ...
ggplot(fastfood_brand_counts, aes(x = percentage, y = reorder(store, percentage))) +
geom_col(fill = "steelblue") +
geom_text(aes(label = paste0(round(percentage, 1), "%")),
hjust = -0.1, size = 3) + # 🔴 在柱狀圖外顯示比例
labs(title = "國小500m內速食店品牌分布",
x = "百分比(%)",
y = "品牌") +
theme_minimal(base_family = "STHeiti") +
theme(axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 8),
plot.title = element_text(hjust = 0.5)) +
xlim(0, max(fastfood_brand_counts$percentage) + 5) # 🔴 給文字標籤留空間
# --- 手搖杯品牌分布 ---
bubbletea_in_buffer <- st_join(school_buffer, bubble_tea_sf, join = st_intersects)
bubbletea_brand_counts <- bubbletea_in_buffer %>%
st_drop_geometry() %>%
filter(!is.na(store)) %>% # 🔴 去掉 NA 品牌
count(store) %>%
mutate(percentage = n / sum(n) * 100) %>%
arrange(desc(percentage))
ggplot(fastfood_brand_counts, aes(x = percentage, y = reorder(store, percentage))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = paste0(round(percentage, 1), "%")),
hjust = -0.1, size = 3, color = "#3C3C3C") +
labs(title = "國小500m內速食店品牌分布",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(fastfood_brand_counts$percentage) + 5)
ggplot(bubbletea_brand_counts, aes(x = percentage, y = reorder(store, percentage))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = paste0(round(percentage, 1), "%")),
hjust = -0.1, size = 3, color = "#3C3C3C") +
labs(title = "國小500m內手搖飲品牌分布",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(bubbletea_brand_counts$percentage) + 5)
ggplot(fastfood_brand_counts, aes(x = n, y = reorder(store, n))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = n),
hjust = -0.1, size = 3, color = "#3C3C3C") +
labs(title = "國小500m內速食店品牌數量分布",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(fastfood_brand_counts$n, na.rm = TRUE) + 5)
ggplot(bubbletea_brand_counts, aes(x = n, y = reorder(store, n))) +
geom_col(fill = "#6FB7B7", alpha = 0.8) +
geom_text(aes(label = n),
hjust = -0.1, size = 3, color = "#3C3C3C") +
labs(title = "國小500m內手搖飲品牌數量分布",
x = NULL, y = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
axis.text.x = element_text(size = 10, color = "#3C3C3C"),
axis.text.y = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold", color = "#3C3C3C"),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
) +
xlim(0, max(bubbletea_brand_counts$n, na.rm = TRUE) + 5)
library(ggplot2)
library(dplyr)
library(tidyr)
# 假設兩個資料框:
# 1️⃣ bubbletea_brand_counts (store, n)
# 2️⃣ bubbletea_in_buffer_counts (store, n)
# 計算每家店的 500m 內比例(%)
# 1️⃣ 先合併資料
bubbletea_brand_merged <- bubbletea_brand_counts %>%
rename(total_count = n) %>% # ✅ 把 n 改成 total_count
left_join(
bubbletea_in_buffer_counts %>% rename(buffer_count = in_buffer_count), # ✅ 這裡正確用 in_buffer_count
by = "store"
) %>%
mutate(buffer_count = ifelse(is.na(buffer_count), 0, buffer_count)) %>%
filter(!store %in% c("日出良太")) # ✅ 移除不要的品牌
# 2️⃣ 長格式(為了畫雙色柱狀圖)
bubbletea_long <- bubbletea_brand_merged %>%
pivot_longer(cols = c(total_count, buffer_count),
names_to = "type",
values_to = "count") %>%
mutate(type = factor(type,
levels = c("total_count", "buffer_count"),
labels = c("總店面數", "500m內店面數")))
bubbletea_labels <- bubbletea_brand_merged %>%
mutate(percent = round(buffer_count / total_count * 100, 1)) %>%
select(store, percent)
# 加入百分比到長格式資料中
bubbletea_long <- bubbletea_long %>%
left_join(bubbletea_labels, by = "store")
max_val <- max(bubbletea_brand_merged$total_count, bubbletea_brand_merged$buffer_count, na.rm = TRUE)
# 3️⃣ 繪製 4x4 Facet Grid
ggplot(bubbletea_long, aes(x = type, y = count, fill = type)) +
geom_col(width = 0.6) +
# 每個長條圖的數字
geom_text(aes(label = count), vjust = -0.5, size = 2.5, color = "#3C3C3C") +
# 顯示 500m 內比例
geom_text(
data = bubbletea_labels,
aes(x = 1.5, y = max_val + 20, label = paste0(percent, "%")),
inherit.aes = FALSE, size = 3, color = "#3C3C3C", fontface = "bold"
) +
facet_wrap(~ store, ncol = 4) +
scale_y_continuous(limits = c(0, max_val + 50)) +
scale_fill_manual(values = c("總店面數" = "#9999CC", "500m內店面數" = "#6FB7B7")) +
labs(title = "台北市手搖飲品牌店面總數 vs 學校500m內店面數比例",
x = NULL, y = NULL, fill = NULL) +
theme_minimal(base_family = "STHeiti") +
theme(
strip.background = element_blank(),
strip.text = element_text(size = 8, face = "bold", color = "#3C3C3C"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
legend.position = "bottom",
legend.text = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(hjust = 0.5, size = 12, face = "bold", color = "#3C3C3C"),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
)
# 4️⃣ 如果需要另存圖檔
# ggsave("bubbletea_4x4_brand_comparison.png", width = 10, height = 8, dpi = 300)
# 計算每家店的 500m 內店面比例
friedchicken_summary <- fastfood_brand_counts %>%
rename(total_count = n) %>%
left_join(
fastfood_in_buffer_counts %>% rename(buffer_count = in_buffer_count),
by = "store"
) %>%
mutate(
buffer_count = ifelse(is.na(buffer_count), 0, buffer_count),
percent = round(buffer_count / total_count * 100, 1) # 百分比
)
friedchicken_long <- friedchicken_summary %>%
pivot_longer(
cols = c(total_count, buffer_count),
names_to = "type",
values_to = "count"
) %>%
mutate(
type = factor(type,
levels = c("total_count", "buffer_count"),
labels = c("總店面數", "500m內店面數"))
)
store_labels <- friedchicken_long %>%
group_by(store) %>%
summarise(
total_count = max(count[type == "總店面數"], na.rm = TRUE),
buffer_count = max(count[type == "500m內店面數"], na.rm = TRUE),
percent = round(buffer_count / total_count * 100, 1)
) %>%
mutate(
store_label = paste0(percent, "%")
)
# 把 store_labels 合併回主資料框
friedchicken_long <- friedchicken_long %>%
left_join(store_labels %>% select(store, store_label), by = "store")
friedchicken_long <- friedchicken_long %>%
mutate(type = factor(type, levels = c("總店面數", "500m內店面數")))
# 畫圖
ggplot(friedchicken_long, aes(x = type, y = count, fill = type)) +
geom_col(width = 0.6) +
# 每個 bar 上顯示數字
geom_text(aes(label = count), vjust = -0.5, size = 2.5, color = "#3C3C3C") +
# 在最上面顯示 500m 內店面比例
geom_text(
data = store_labels,
aes(x = 1.5, y = max(friedchicken_long$count, na.rm = TRUE) + 5, label = store_label),
inherit.aes = FALSE, size = 3, color = "#3C3C3C", fontface = "bold"
) +
facet_wrap(~ store, ncol = 3) +
scale_y_continuous(limits = c(0, max(friedchicken_long$count, na.rm = TRUE) + 10)) +
scale_fill_manual(values = c( "500m內店面數" = "#6FB7B7","總店面數" = "#9999CC")) +
labs(
title = "台北市炸雞速食店面總數 vs 學校500m內店面數比例",
x = NULL, y = NULL, fill = NULL
) +
theme_minimal(base_family = "STHeiti") +
theme(
strip.background = element_blank(),
strip.text = element_text(size = 8, face = "bold", color = "#3C3C3C"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
legend.position = "bottom",
legend.text = element_text(size = 8, color = "#3C3C3C"),
plot.title = element_text(
hjust = 0.5, size = 12, face = "bold", color = "#3C3C3C"
),
plot.background = element_rect(fill = "#F0F0F0", color = NA),
panel.background = element_rect(fill = "#F0F0F0", color = NA),
panel.grid.major = element_line(color = "grey80"),
panel.grid.minor = element_blank()
)
# 1️⃣ 合併 buffer 與 final_counts,產生分組
school_buffer_with_type <- school_buffer %>%
st_join(final_counts %>% select(SID, has_public, has_private), by = "SID") %>%
mutate(
sports_group = case_when(
has_public & has_private ~ "同時有公立與私立",
has_public & !has_private ~ "只有公立",
!has_public & has_private ~ "只有私立",
TRUE ~ "無運動場館"
)
)
# 2️⃣ 繪圖
tmap_mode("plot") # 靜態模式
## ℹ tmap mode set to "plot".
p <- tm_shape(taipei_village) +
tm_polygons(col = "grey90", border.col = "white", alpha = 1) +
tm_shape(school_buffer_with_type) +
tm_fill(
col = "sports_group",
palette = c(
"同時有公立與私立" = "#6FB7B7",
"只有私立" = "#9999CC",
"只有公立" = "#B766AD",
"無運動場館" = "#AFAF61"
),
title = "運動場館分組"
) +
tm_shape(final_counts) +
tm_dots(col = "black", size = 0.1, alpha = 0.8) +
tm_layout(
title = "台北市國小 500m Buffer:運動場館資源分布",
title.size = 1,
legend.outside = TRUE,
bg.color = "#F0F0F0",
panel.background = "#F0F0F0",
legend.text.size = 0.6,
legend.title.size = 0.8,
frame = TRUE # 🔴 保留邊框
)
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
p