| 項目 | 內容 |
|---|---|
| 組別名稱(報告題目) | 晴天騎、雨天停?天氣條件對首爾共享單車租借量的影響 |
| 小組成員 | 胡嘉成、蔡昀展、蔡浚庭、方世燊 |
| 學生/學號 | 方世燊/113612037 |
| 研究主題 | 共享單車的使用情況與天氣條件的關聯 |
| 標題 | 是否已涵蓋 |
|---|---|
| 研究主題 | ✅ |
| 研究動機 | ✅ |
| 研究依據 | ✅ |
| 研究問題 | ✅ |
| 研究假設 | ✅ |
| 研究方法 | ✅ |
| 研究結果 | ✅ |
| 結論與討論 | ✅ |
| 參考資料 | ✅ |
| 程式碼 | ✅ |
| 相關截圖/連結 | ✅ |
本專題探討共享單車的使用情況與天氣條件的關聯。我們使用首爾市共享單車(Ttareungyi)一整年的每小時租借資料,檢驗溫度、濕度、降雨與季節是否和租借量有關,並比較哪一個天氣因素的影響最大。
共享單車已經是許多城市通勤和短程移動的重要工具,台灣的 YouBike、首爾的 Ttareungyi 都是例子。我們自己也常騎單車,發現天氣很會影響騎不騎:天氣太熱、太冷或下雨時,就會改搭其他交通工具。
這個問題也有實際用途。單車營運單位需要知道哪些時候需求高、哪些時候需求低,才能安排車輛調度和維修。如果能用統計方法確認天氣和時間對租借量的影響,就能提供具體的數據依據。
我們選擇首爾共享單車資料,是因為它公開可得、沒有遺漏值,且同時有數值變數(溫度、濕度、降雨量)和類別變數(季節、假日),可以完整練習本學期學到的統計方法。
過去研究一致發現,天氣是影響共享單車使用量的重要因素。
研究缺口:首爾資料的既有研究多以機器學習追求預測準確度,較少用基礎統計檢定個別回答「每個天氣因素是否顯著影響租借量、影響有多大」。我們用初等統計方法,逐一比較溫度、濕度、降雨量和租借量的關係,讓結果更容易解釋。
| # | 研究問題 | 研究假設 |
|---|---|---|
| Q1 | 溫度和每小時租借量是否有關? | 溫度與租借量呈正相關:溫度越高,租借量越多 |
| Q2 | 濕度和每小時租借量是否有關? | 濕度與租借量呈負相關 |
| Q3 | 有下雨和沒下雨的時段,平均租借量是否不同? | 下雨時段的平均租借量顯著低於沒下雨時段 |
| Q4 | 四個季節的平均租借量是否不同? | 至少有一個季節不同,預期冬季最低 |
| Q5 | 同時考慮溫度、濕度、降雨量時,哪個因素對租借量的影響最大? | 三者都有顯著影響,其中溫度的影響最大 |
UCI Machine Learning Repository 的 Seoul Bike Sharing Demand 資料集(CC BY 4.0 授權)。資料涵蓋 2017 年 12 月 1 日至 2018 年 11 月 30 日,共 8,760 筆,每筆是一個小時的首爾共享單車租借量,遺漏值 0 筆。
| 變項 | 角色 | 類型 | 單位/類別 |
|---|---|---|---|
| 每小時租借量(Rented Bike Count) | 應變數 | 數值 | 台次 |
| 溫度(Temperature) | 自變數 | 數值 | °C |
| 濕度(Humidity) | 自變數 | 數值 | % |
| 降雨量(Rainfall) | 自變數 | 數值 | mm |
| 是否下雨 | 自變數 | 類別 | 有雨/沒雨(由降雨量轉換) |
| 季節(Seasons) | 自變數 | 類別 | 春、夏、秋、冬 |
顯著水準設為 α = .05,所有分析以 R(4.3.1)完成。
| 研究問題 | 統計方法 | 虛無假設 H₀ |
|---|---|---|
| 全部 | 敘述統計(平均數、中位數、標準差、偏態、峰度)、直方圖、盒鬚圖、散佈圖 | — |
| Q1 溫度 | Pearson 相關係數、簡單線性迴歸;補充:加入溫度平方項檢查曲線關係 | ρ = 0 |
| Q2 濕度 | Pearson 相關係數、簡單線性迴歸 | ρ = 0 |
| Q3 下雨 | Levene 變異數同質性檢定 → 兩獨立樣本 t 檢定(變異數不同質時用 Welch t);效果量 Cohen’s d | μ有雨 = μ沒雨 |
| Q4 季節 | Levene 檢定 → 單因子 ANOVA(變異數不同質時以 Welch ANOVA 為主);事後比較 Tukey HSD 與 Games-Howell;效果量 η² | 四季平均數相等 |
| Q5 綜合 | 複迴歸(溫度、濕度、降雨量 → 租借量)、標準化係數 β、VIF、殘差診斷;穩健性:以 log(租借量 + 1) 重跑 | 各迴歸係數 = 0 |
前提假設的處理方式:樣本數超過八千,依中央極限定理,平均數與迴歸係數的抽樣分布會接近常態,所以租借量本身右偏不會讓 t 檢定和迴歸失效。不過我們仍以 log(租借量 + 1) 重跑主要分析,確認結論不受右偏影響。變異數同質性則用 Levene 檢定判斷,再決定要用哪個版本的檢定。
| 變項 | N | 平均數 | 中位數 | 標準差 | 最小值 | 最大值 | 偏態 | 峰度 |
|---|---|---|---|---|---|---|---|---|
| 每小時租借量(台) | 8,465 | 729.16 | 542.0 | 642.35 | 2.0 | 3,556.0 | 1.14 | 0.82 |
| 溫度(°C) | 8,465 | 12.77 | 13.5 | 12.10 | -17.8 | 39.4 | -0.17 | -0.89 |
| 濕度(%) | 8,465 | 58.15 | 57.0 | 20.48 | 0.0 | 98.0 | 0.07 | -0.81 |
| 降雨量(mm) | 8,465 | 0.15 | 0.0 | 1.13 | 0.0 | 35.0 | 14.61 | 289.72 |
清理後共 8,465 個營運時段。每小時租借量平均 729.2 台,中位數 542 台,標準差 642.4 台。平均數大於中位數,偏態 1.14,表示租借量右偏:大部分時段只有幾百台,少數尖峰時段超過三千台(圖 1 左)。取對數後分布變得較對稱(圖 1 右)。
溫度從 -17.8°C 到 39.4°C,平均 12.8°C,分布接近對稱。濕度平均 58.1%。降雨量極度右偏(偏態 14.6),只有 516 個時段(6.1%)有下雨,所以額外用「是否下雨」來分析(圖 2)。四季的時段數分別為春 2160、夏 2208、秋 1937、冬 2160。
溫度與每小時租借量呈中度正相關,r(8463) = .56,95% CI [.55, .58],p < .001。簡單線性迴歸為:
租借量 = 347.77 + 29.86 × 溫度,R² = .32
也就是溫度每升高 1°C,每小時租借量平均增加約 30 台;溫度單獨可以解釋租借量 31.7% 的變異。拒絕 H₀,支持研究假設 Q1。
不過圖 3 的 LOESS 曲線顯示關係不是一直線:約 25–30°C 時租借量最高,再熱就開始下降。圖 4 把溫度每 5°C 分一組,平均租借量在 25–30°C 達到 1,235 台,到 35°C 以上降為 900 台。加入溫度平方項後,平方項係數為負且顯著(p = .018),但 R² 幾乎沒有增加(.3167 → .3171),表示曲線趨勢存在、但幅度不大,主要只發生在少數高溫時段(35°C 以上只有 89 個時段)。
濕度與每小時租借量呈弱負相關,r(8463) = -.20,95% CI [-.22, -.18],p < .001。迴歸斜率為 -6.33,即濕度每增加 1%,租借量平均減少約 6.3 台,R² = .04(只解釋 4.1% 的變異)。拒絕 H₀,支持研究假設 Q2,但關聯強度明顯弱於溫度。排除濕度 = 0% 的 17 筆疑似異常資料後,r = -.21,結論不變。
| 是否下雨 | N | 平均數 | 標準差 | 中位數 |
|---|---|---|---|---|
| 沒雨 | 7,949 | 765.6 | 642.7 | 591 |
| 有雨 | 516 | 167.3 | 263.9 | 71 |
Levene 檢定顯示兩組變異數不同質,F(1, 8463) = 358.60,p < .001(有雨時段的標準差只有沒雨時段的一半不到),因此採用 Welch t 檢定:
t(978.5) = 43.77,p < .001,平均差 = 598.4 台,95% CI [571.5, 625.2]
沒雨時段平均每小時租借 765.6 台,有雨時段只有 167.3 台,約為沒雨時的 22%。效果量 Cohen’s d = 0.96,屬於大效果。改用 log(租借量 + 1) 重做 Welch t 檢定,結果同樣顯著(t(561.6) = 32.82,p < .001)。拒絕 H₀,支持研究假設 Q3。
| 季節 | N | 平均數 | 標準差 | 95% CI 下限 | 95% CI 上限 |
|---|---|---|---|---|---|
| 春 | 2,160 | 746.3 | 618.7 | 720.1 | 772.4 |
| 夏 | 2,208 | 1,034.1 | 690.2 | 1,005.3 | 1,062.9 |
| 秋 | 1,937 | 924.1 | 617.5 | 896.6 | 951.6 |
| 冬 | 2,160 | 225.5 | 150.4 | 219.2 | 231.9 |
Levene 檢定顯示四季變異數不同質,F(3, 8461) = 663.52,p < .001(冬季標準差遠小於其他季節),所以以 Welch ANOVA 為主要結果:
Welch F(3, 3,809.7) = 2,030.6,p < .001
傳統單因子 ANOVA 也得到相同結論,F(3, 8461) = 875.6,p < .001,效果量 η² = .24,表示季節可以解釋租借量約 24% 的變異,屬於大效果。拒絕 H₀,支持研究假設 Q4。
| 比較 | 平均差 | Games-Howell p | Tukey HSD p |
|---|---|---|---|
| 夏 − 春 | 287.8 | < .001 | < .001 |
| 秋 − 春 | 177.9 | < .001 | < .001 |
| 冬 − 春 | -520.7 | < .001 | < .001 |
| 秋 − 夏 | -110.0 | < .001 | < .001 |
| 冬 − 夏 | -808.5 | < .001 | < .001 |
| 冬 − 秋 | -698.6 | < .001 | < .001 |
事後比較中,Games-Howell(不假設變異數相等)和 Tukey HSD 的六組比較全部顯著。平均租借量由高到低為:夏(1,034 台)> 秋(924 台)> 春(746 台)> 冬(226 台)。冬季最低,只有夏季的 22%,與假設相符。
| 預測變項 | B | 標準誤 | 95% CI | β | t | p |
|---|---|---|---|---|---|---|
| 截距 | 834.84 | 16.66 | [802.19, 867.50] | — | 50.12 | < .001 |
| 溫度 | 32.61 | 0.45 | [31.74, 33.49] | 0.61 | 72.87 | < .001 |
| 濕度 | -8.84 | 0.27 | [-9.38, -8.31] | -0.28 | -32.53 | < .001 |
| 降雨量 | -53.57 | 4.89 | [-63.14, -43.99] | -0.09 | -10.96 | < .001 |
以溫度、濕度、降雨量同時預測租借量,整體模型顯著,F(3, 8461) = 1,999.7,p < .001,R² = .41(調整後 R² = .41),三個天氣變數共同解釋了 41.5% 的變異。
在控制其他兩個變數之下:
三個係數都顯著,標準化係數絕對值的大小順序為 溫度(.61)> 濕度(.28)> 降雨量(.09)(圖 8)。溫度的影響約為濕度的 2.2 倍。拒絕 H₀,支持研究假設 Q5。值得注意的是,濕度在複迴歸中的 β(-.28)比它和租借量的單純相關(-.20)更強,表示在溫度相同的條件下,濕度的負面影響更明顯。
前提假設檢查:
穩健性檢查:由於殘差不完全符合常態與等變異,我們改用 log(租借量 + 1) 當依變數重跑複迴歸(表 6)。模型 R² 提高到 .50,三個係數的方向與顯著性都不變,β 的大小順序也仍是溫度(.63)> 濕度(-.33)> 降雨量(-.21),表示結論不受右偏影響。以對數模型來解釋:溫度每升高 1°C,租借量約增加 6.2%;降雨量每增加 1 mm,租借量約減少 19.7%。
| 預測變項 | B | 標準誤 | 95% CI | β | t | p |
|---|---|---|---|---|---|---|
| 截距 | 6.453 | 0.028 | [6.399, 6.507] | — | 233.766 | < .001 |
| 溫度 | 0.060 | 0.001 | [0.058, 0.061] | 0.628 | 80.762 | < .001 |
| 濕度 | -0.019 | 0.000 | [-0.020, -0.018] | -0.334 | -41.712 | < .001 |
| 降雨量 | -0.219 | 0.008 | [-0.235, -0.203] | -0.214 | -27.038 | < .001 |
| # | 研究假設 | 主要統計結果 | 是否支持 |
|---|---|---|---|
| Q1 | 溫度與租借量呈正相關 | r = .56,p < .001;高溫(> 30°C)時略為下降 | ✅ 支持 |
| Q2 | 濕度與租借量呈負相關 | r = -.20,p < .001 | ✅ 支持(弱相關) |
| Q3 | 下雨時段平均租借量較低 | Welch t = 43.77,p < .001,d = 0.96 | ✅ 支持 |
| Q4 | 四季不同,冬季最低 | Welch F = 2,030.6,p < .001,η² = .24;夏 > 秋 > 春 > 冬 | ✅ 支持 |
| Q5 | 三者皆顯著,溫度影響最大 | R² = .41;β:溫度 .61、濕度 -.28、降雨量 -.09 | ✅ 支持 |
Q3 中下雨的效果很大(d = 0.96),但在 Q5 複迴歸裡降雨量的 β 只有 -.09,是三個變數中最小的。我們認為原因是降雨量有 93.9% 的時段都是 0,分布極度右偏:真正影響騎車意願的是「有沒有下雨」,而不是「下了幾毫米」。從 0 mm 變成 1 mm 時,租借量就已經大幅下降;之後雨再變大,可以再減少的空間就不多了。用「每增加 1 mm 減少多少台」的線性模型,無法描述這種「有或無」的效果,所以標準化係數被低估。這也是我們在計劃書中另外建立「是否下雨」變數的原因。
我們的結果與 Gebhart 與 Noland(2014)在華盛頓特區的發現方向一致:低溫、降雨和高濕度都會降低共享單車的使用。Sathishkumar 與 Cho(2020)及 Sathishkumar、Park 與 Cho(2020)以機器學習模型預測首爾的租借需求,天氣變數是模型中的重要預測變項。本研究用初等統計方法回到「每個因素各自有多大影響」這個問題,得到容易解釋的結論:溫度最重要、下雨的效果最大但以「有無」的形式出現、濕度較弱。
對營運單位來說,可以依照天氣預報調整車輛調度:溫暖(約 20–30°C)的晴天要準備更多車;下雨或冬季時段需求只剩一般時段的二到三成,可以趁這些時段進行車輛維修與重新分配。
未來可以加入小時、假日等時間變數,或改用日資料降低自我相關,以更嚴謹地估計天氣的影響。
以下為完整的 R
程式碼(Final_analysis.R)。本報告的所有數字、表格與圖都由這份程式碼產生,資料檔需放在
data/SeoulBikeData.csv。
# =====================================================================
# 115-1 初等統計 期末專題
# 晴天騎、雨天停?天氣條件對首爾共享單車租借量的影響
# 資料:UCI Seoul Bike Sharing Demand(SeoulBikeData.csv)
# 顯著水準 α = .05
# =====================================================================
# ---- 0. 套件 --------------------------------------------------------
# 若尚未安裝,先執行 install.packages(c("dplyr", "ggplot2", "car", "psych"))
library(dplyr) # 資料整理
library(ggplot2) # 畫圖
library(car) # leveneTest()、vif()
library(psych) # describe():偏態、峰度
# 中文字型(Windows 用微軟正黑體,其他系統用預設字型)
if (.Platform$OS.type == "windows") {
windowsFonts(JH = windowsFont("Microsoft JhengHei"))
base_font <- "JH"
} else {
base_font <- ""
}
theme_set(
theme_minimal(base_size = 12, base_family = base_font) +
theme(panel.grid.minor = element_blank(),
panel.grid.major = element_line(colour = "grey90", linewidth = 0.3),
plot.title = element_text(face = "bold"),
legend.position = "top")
)
# 固定配色:同一個類別在每張圖都用同一個顏色
col_season <- c("春" = "#2a78d6", "夏" = "#eb6834", "秋" = "#1baf7a", "冬" = "#eda100")
col_rain <- c("沒雨" = "#2a78d6", "有雨" = "#eb6834")
col_point <- "#2a78d6"
col_ink <- "#0b0b0b"
# ---- 1. 讀取資料 ----------------------------------------------------
# 原始檔的欄位名稱有 Latin-1 的度數符號,所以用 latin1 讀進來,再依位置改成英文短名
raw <- read.csv("data/SeoulBikeData.csv", fileEncoding = "latin1",
check.names = FALSE)
names(raw) <- c("Date", "Count", "Hour", "Temp", "Humidity", "Wind",
"Visibility", "DewPoint", "Solar", "Rainfall", "Snowfall",
"Seasons", "Holiday", "Functioning")
n_raw <- nrow(raw)
n_missing <- sum(is.na(raw))
n_nonfunc <- sum(raw$Functioning == "No")
sum_nonfunc <- sum(raw$Count[raw$Functioning == "No"]) # 未營運時段的租借量總和(應為 0)
# ---- 2. 資料整理 ----------------------------------------------------
# (1) 刪除系統未營運(Functioning Day = No)的時段
# (2) 建立「是否下雨」:降雨量 > 0 為有雨
# (3) 季節轉成有順序的因子,並改成中文標籤
bike <- raw %>%
filter(Functioning == "Yes") %>%
mutate(
Rain = factor(ifelse(Rainfall > 0, "有雨", "沒雨"),
levels = c("沒雨", "有雨")),
Season = factor(Seasons,
levels = c("Spring", "Summer", "Autumn", "Winter"),
labels = c("春", "夏", "秋", "冬")),
LogCount = log(Count + 1) # 右偏時使用的對數轉換
)
n_clean <- nrow(bike)
# ---- 3. 敘述統計 ----------------------------------------------------
num_vars <- c("Count", "Temp", "Humidity", "Rainfall")
desc_tab <- psych::describe(bike[, num_vars])
desc_tab <- data.frame(
變項 = c("每小時租借量(台)", "溫度(°C)", "濕度(%)", "降雨量(mm)"),
N = desc_tab$n,
平均數 = desc_tab$mean,
中位數 = desc_tab$median,
標準差 = desc_tab$sd,
最小值 = desc_tab$min,
最大值 = desc_tab$max,
偏態 = desc_tab$skew,
峰度 = desc_tab$kurtosis
)
# 類別變項次數
rain_freq <- table(bike$Rain)
season_freq <- table(bike$Season)
# 圖 1:租借量直方圖(原始 vs. 取對數)
hist_df <- rbind(
data.frame(值 = bike$Count, 尺度 = "原始租借量(台)"),
data.frame(值 = bike$LogCount, 尺度 = "log(租借量 + 1)")
)
hist_df$尺度 <- factor(hist_df$尺度, levels = c("原始租借量(台)", "log(租借量 + 1)"))
fig_hist <- ggplot(hist_df, aes(x = 值)) +
geom_histogram(bins = 40, fill = col_point, colour = "white", linewidth = 0.2) +
facet_wrap(~ 尺度, scales = "free") +
labs(title = "圖 1 每小時租借量的分布", x = NULL, y = "時段數")
# 圖 2:四個數值變項的盒鬚圖(各自尺度)
box_df <- rbind(
data.frame(變項 = "租借量(台)", 值 = bike$Count),
data.frame(變項 = "溫度(°C)", 值 = bike$Temp),
data.frame(變項 = "濕度(%)", 值 = bike$Humidity),
data.frame(變項 = "降雨量(mm)", 值 = bike$Rainfall)
)
box_df$變項 <- factor(box_df$變項, levels = unique(box_df$變項))
fig_box <- ggplot(box_df, aes(x = "", y = 值)) +
geom_boxplot(fill = "#dbe8f8", colour = col_point, width = 0.5,
outlier.size = 0.6, outlier.alpha = 0.4) +
facet_wrap(~ 變項, scales = "free_y", nrow = 1) +
labs(title = "圖 2 各數值變項的盒鬚圖", x = NULL, y = NULL)
# ---- 4. Q1 溫度與租借量:Pearson 相關 + 簡單線性迴歸 --------------
cor_temp <- cor.test(bike$Count, bike$Temp, method = "pearson")
lm_temp <- lm(Count ~ Temp, data = bike)
sum_temp <- summary(lm_temp)
# 補充:檢查「太熱反而下降」的曲線關係(加入溫度平方項)
lm_temp2 <- lm(Count ~ Temp + I(Temp^2), data = bike)
cmp_temp2 <- anova(lm_temp, lm_temp2) # 加入平方項是否顯著改善模型
# 依溫度每 5°C 分組,看平均租借量
temp_bin <- bike %>%
mutate(溫度區間 = cut(Temp, breaks = seq(-20, 40, 5))) %>%
group_by(溫度區間) %>%
summarise(時段數 = n(), 平均租借量 = mean(Count), .groups = "drop") %>%
mutate(組中點 = seq(-17.5, 37.5, 5)[as.integer(溫度區間)])
# 圖 3:溫度 vs. 租借量散佈圖
fig_temp <- ggplot(bike, aes(x = Temp, y = Count)) +
geom_point(colour = col_point, alpha = 0.12, size = 0.8) +
geom_smooth(aes(colour = "線性迴歸"), method = "lm", se = FALSE,
linewidth = 0.9) +
geom_smooth(aes(colour = "LOESS 平滑曲線"), method = "loess", se = FALSE,
linewidth = 0.9) +
scale_colour_manual(name = NULL,
values = c("線性迴歸" = col_ink, "LOESS 平滑曲線" = "#eb6834"),
breaks = c("線性迴歸", "LOESS 平滑曲線")) +
labs(title = "圖 3 溫度與每小時租借量",
x = "溫度(°C)", y = "每小時租借量(台)")
# 圖 4:依溫度分組的平均租借量
fig_tempbin <- ggplot(temp_bin, aes(x = 組中點, y = 平均租借量)) +
geom_line(colour = col_point, linewidth = 0.8) +
geom_point(colour = col_point, size = 2.5) +
geom_text(aes(label = round(平均租借量)), vjust = -1, size = 3,
family = base_font, colour = "grey30") +
scale_x_continuous(breaks = seq(-20, 40, 5)) +
scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) +
labs(title = "圖 4 依溫度分組(每 5°C)的平均租借量",
x = "溫度區間組中點(°C)", y = "平均租借量(台)")
# ---- 5. Q2 濕度與租借量:Pearson 相關 + 簡單線性迴歸 --------------
cor_hum <- cor.test(bike$Count, bike$Humidity, method = "pearson")
lm_hum <- lm(Count ~ Humidity, data = bike)
sum_hum <- summary(lm_hum)
# 濕度 = 0% 的時段(集中在 2018 年 5 月少數幾天,疑似感測器異常)
n_hum0 <- sum(bike$Humidity == 0)
dates_hum0 <- unique(bike$Date[bike$Humidity == 0])
cor_hum_nz <- cor.test(bike$Count[bike$Humidity > 0],
bike$Humidity[bike$Humidity > 0]) # 排除後再算一次
# 圖 5:濕度 vs. 租借量散佈圖
fig_hum <- ggplot(bike, aes(x = Humidity, y = Count)) +
geom_point(colour = col_point, alpha = 0.12, size = 0.8) +
geom_smooth(method = "lm", se = FALSE, colour = col_ink, linewidth = 0.9) +
labs(title = "圖 5 濕度與每小時租借量(黑線為線性迴歸)",
x = "濕度(%)", y = "每小時租借量(台)")
# ---- 6. Q3 有雨 vs. 沒雨:兩獨立樣本 t 檢定 --------------------------
rain_desc <- bike %>%
group_by(Rain) %>%
summarise(N = n(), 平均數 = mean(Count), 標準差 = sd(Count),
中位數 = median(Count), .groups = "drop")
lev_rain <- car::leveneTest(Count ~ Rain, data = bike, center = median)
lev_rain_p <- lev_rain$`Pr(>F)`[1]
t_student <- t.test(Count ~ Rain, data = bike, var.equal = TRUE)
t_welch <- t.test(Count ~ Rain, data = bike, var.equal = FALSE)
t_rain <- if (lev_rain_p < .05) t_welch else t_student # 依 Levene 結果選版本
# 效果量 Cohen's d(合併標準差)
n1 <- rain_desc$N[1]; n2 <- rain_desc$N[2]
s1 <- rain_desc$標準差[1]; s2 <- rain_desc$標準差[2]
sd_pooled <- sqrt(((n1 - 1) * s1^2 + (n2 - 1) * s2^2) / (n1 + n2 - 2))
d_rain <- (rain_desc$平均數[1] - rain_desc$平均數[2]) / sd_pooled
# 補充:取對數後再做一次 Welch t 檢定(處理右偏)
t_rain_log <- t.test(LogCount ~ Rain, data = bike, var.equal = FALSE)
# 圖 6:有雨 vs. 沒雨盒鬚圖
fig_rain <- ggplot(bike, aes(x = Rain, y = Count, fill = Rain)) +
geom_boxplot(width = 0.5, colour = "grey25", outlier.size = 0.6,
outlier.alpha = 0.3, show.legend = FALSE) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 3,
fill = "white", colour = col_ink) +
scale_fill_manual(values = col_rain) +
labs(title = "圖 6 有雨與沒雨時段的租借量(◇為平均數)",
x = NULL, y = "每小時租借量(台)")
# ---- 7. Q4 四季:單因子 ANOVA + 事後比較 ----------------------------
season_desc <- bike %>%
group_by(Season) %>%
summarise(N = n(), 平均數 = mean(Count), 標準差 = sd(Count),
se = sd(Count) / sqrt(n()), .groups = "drop") %>%
mutate(CI下限 = 平均數 - qt(.975, N - 1) * se,
CI上限 = 平均數 + qt(.975, N - 1) * se)
lev_season <- car::leveneTest(Count ~ Season, data = bike, center = median)
lev_season_p <- lev_season$`Pr(>F)`[1]
aov_season <- aov(Count ~ Season, data = bike)
aov_tab <- summary(aov_season)[[1]]
eta2_season <- aov_tab$`Sum Sq`[1] / sum(aov_tab$`Sum Sq`) # η²
welch_season <- oneway.test(Count ~ Season, data = bike, var.equal = FALSE)
tukey_season <- TukeyHSD(aov_season)
# Games-Howell 事後比較(不假設變異數相等)
games_howell <- function(y, g) {
g <- droplevels(g)
lev <- levels(g); k <- length(lev)
m <- tapply(y, g, mean); v <- tapply(y, g, var); n <- tapply(y, g, length)
out <- NULL
for (i in 1:(k - 1)) for (j in (i + 1):k) {
diff <- m[j] - m[i]
se <- sqrt(v[i] / n[i] + v[j] / n[j])
df <- (v[i] / n[i] + v[j] / n[j])^2 /
((v[i] / n[i])^2 / (n[i] - 1) + (v[j] / n[j])^2 / (n[j] - 1))
q <- abs(diff) / (se / sqrt(2))
p <- ptukey(q, nmeans = k, df = df, lower.tail = FALSE)
out <- rbind(out, data.frame(比較 = paste0(lev[j], " − ", lev[i]),
平均差 = diff, 標準誤 = se, 自由度 = df,
p值 = p))
}
rownames(out) <- NULL
out
}
gh_season <- games_howell(bike$Count, bike$Season)
# 圖 7:四季平均租借量與 95% 信賴區間
fig_season <- ggplot(season_desc, aes(x = Season, y = 平均數, fill = Season)) +
geom_col(width = 0.6, show.legend = FALSE) +
geom_errorbar(aes(ymin = CI下限, ymax = CI上限), width = 0.15,
colour = col_ink, linewidth = 0.5) +
geom_text(aes(label = round(平均數), y = CI上限), vjust = -0.6, size = 3.5,
family = base_font, colour = "grey20") +
scale_fill_manual(values = col_season) +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
labs(title = "圖 7 四季平均每小時租借量(誤差線為 95% 信賴區間)",
x = "季節", y = "平均租借量(台)")
# ---- 8. Q5 複迴歸:溫度、濕度、降雨量 → 租借量 ----------------------
lm_multi <- lm(Count ~ Temp + Humidity + Rainfall, data = bike)
sum_multi <- summary(lm_multi)
vif_multi <- car::vif(lm_multi)
# 標準化迴歸係數 β:把所有變數轉成 z 分數再跑迴歸
bike_z <- bike %>%
mutate(across(c(Count, LogCount, Temp, Humidity, Rainfall),
~ as.numeric(scale(.x))))
lm_multi_z <- lm(Count ~ Temp + Humidity + Rainfall, data = bike_z)
beta_multi <- coef(lm_multi_z)[-1]
# 穩健性檢查:依變數改用 log(租借量 + 1)
lm_multi_log <- lm(LogCount ~ Temp + Humidity + Rainfall, data = bike)
sum_multi_log <- summary(lm_multi_log)
beta_multi_log <- coef(lm(LogCount ~ Temp + Humidity + Rainfall, data = bike_z))[-1]
# 整理係數表
make_coef_tab <- function(fit, beta, ci_digits = 2) {
s <- summary(fit)$coefficients
ci <- confint(fit)
fmt <- paste0("[%.", ci_digits, "f, %.", ci_digits, "f]")
data.frame(
預測變項 = c("截距", "溫度", "濕度", "降雨量"),
B = s[, 1],
標準誤 = s[, 2],
`95% CI` = sprintf(fmt, ci[, 1], ci[, 2]),
β = c(NA, beta),
t = s[, 3],
p = s[, 4],
check.names = FALSE, row.names = NULL
)
}
coef_multi <- make_coef_tab(lm_multi, beta_multi)
coef_multi_log <- make_coef_tab(lm_multi_log, beta_multi_log, ci_digits = 3)
# 圖 8:標準化迴歸係數比較
beta_df <- data.frame(
變項 = factor(c("溫度", "濕度", "降雨量"), levels = c("溫度", "濕度", "降雨量")),
β = beta_multi
)
fig_beta <- ggplot(beta_df, aes(x = 變項, y = β)) +
geom_col(width = 0.5, fill = col_point) +
geom_hline(yintercept = 0, colour = "grey40", linewidth = 0.4) +
geom_text(aes(label = sprintf("%.2f", β), vjust = ifelse(β > 0, -0.5, 1.4)),
size = 3.8, family = base_font, colour = "grey20") +
scale_y_continuous(limits = c(-0.45, 0.75)) +
labs(title = "圖 8 複迴歸的標準化係數 β", x = NULL, y = "標準化係數 β")
# 圖 9:殘差診斷(殘差 vs. 預測值、殘差 Q-Q 圖)
diag_df <- data.frame(預測值 = fitted(lm_multi), 殘差 = resid(lm_multi))
fig_resid <- ggplot(diag_df, aes(x = 預測值, y = 殘差)) +
geom_point(colour = col_point, alpha = 0.12, size = 0.8) +
geom_hline(yintercept = 0, colour = col_ink, linewidth = 0.5) +
labs(title = "圖 9a 殘差 vs. 預測值", x = "預測租借量", y = "殘差")
fig_qq <- ggplot(diag_df, aes(sample = 殘差)) +
stat_qq(colour = col_point, alpha = 0.3, size = 0.8) +
stat_qq_line(colour = col_ink, linewidth = 0.5) +
labs(title = "圖 9b 殘差常態 Q-Q 圖", x = "理論分位數", y = "樣本分位數")
# ---- 9. 研究限制:相鄰時段的自我相關 --------------------------------
acf_lag1 <- acf(bike$Count, plot = FALSE)$acf[2] # 落後 1 小時的自我相關
# ---- 10. 單獨執行本檔時,把圖存到 figures/ ---------------------------
if (!isTRUE(getOption("knitr.in.progress"))) {
dir.create("figures", showWarnings = FALSE)
figs <- list(fig1_hist = fig_hist, fig2_box = fig_box, fig3_temp = fig_temp,
fig4_tempbin = fig_tempbin, fig5_hum = fig_hum,
fig6_rain = fig_rain, fig7_season = fig_season,
fig8_beta = fig_beta, fig9a_resid = fig_resid, fig9b_qq = fig_qq)
for (nm in names(figs)) {
ggsave(file.path("figures", paste0(nm, ".png")), figs[[nm]],
width = 7, height = 4.5, dpi = 200, bg = "white")
}
print(sum_multi); print(vif_multi); print(beta_multi)
}
這堂課鼓勵大家可以與 AI 進行合作,但同時希望大家以負責任的方式揭露使用過程。
你是否有使用 AI 工具協助完成作業? ■是 □否
使用的 AI 工具:Claude(Anthropic)
使用 AI 的部分與幫助:
dplyr
刪除未營運時段並建立「是否下雨」變數,以及撰寫 Games-Howell
事後比較的函數。所有分析結果都由小組成員實際執行 R 程式碼確認,報告內容也經過小組討論與修改。
AI 對話連結:______________(或附上截圖)