R: R 描述统计
最后更新:2026-08-26
前面 22 课我们学了 R 的"工具"——向量、数据框、ggplot2。现在开始 R 真正的"价值"——统计分析。这一课先学最基础的"描述统计":用 R 算均值、中位数、标准差等关键指标,把数据"总结"成几个数字。
读完这一课你就能用 R 3 行代码给任何数据集生成"统计报告"。
1. 你将学到
- 描述统计 3 大类:集中趋势、离散程度、分布形态
- 集中趋势:mean/median/mode
- 离散程度:sd/var/IQR/range/mad
- 分位数:quantile/median/Q1/Q3
- summary() / skimr 快速统计
- dplyr + group_by 分组描述
- 实战:1000 行学生成绩报告
2. 一个学生成绩报告的故事
(1) 痛点:1000 个学生怎么"总结"?
Bob老师期末要给 1000 个学生出成绩报告,每个学生 5 门课,要给每门课算:
- 平均分、中位数(集中趋势)
- 标准差、四分位数(离散程度)
- 最高分、最低分(极值)
- 偏度、峰度(分布形态)
手算?Excel 公式?要 2 小时;用 R summary()——
(2) R 的解法
# 1. 一行 summary 看 5 个核心指标
summary(scores$math)
# Min. 1st Qu. Median Mean 3rd Qu. Max.
# 45.0 72.0 82.0 81.5 92.0 99.0
# 2. 一行 skimr 看完整报告
library(skimr)
skim(scores$math)
# ── Variable type: numeric ──
# min max mean sd p25 p50 p75 hist
# 45 99 81.5 12 72 82 92 ▇▇▇▇▇
# 3. 一行 dplyr 分组统计
scores |> group_by(class) |> summarise(
平均分 = mean(math),
中位数 = median(math),
标准差 = sd(math),
最高 = max(math),
最低 = min(math),
人数 = n()
)
3 行代码 → 完整成绩报告。
3. 描述统计 3 大类
(1) 三大类概述
graph TB
A[描述统计] --> B[集中趋势<br/>Central Tendency]
A --> C[离散程度<br/>Dispersion]
A --> D[分布形态<br/>Shape]
B --> E[mean 中位数 mode]
C --> F[sd var range IQR]
D --> G[偏度 skewness<br/>峰度 kurtosis]
style A fill:#fff3cd
style B fill:#d4edda
style C fill:#cce5ff
style D fill:#f8d7da
(2) 速查表
| 类别 | 函数 | 含义 |
|---|---|---|
| 集中 | mean() |
算术平均 |
| 集中 | median() |
中位数(50% 分位) |
| 集中 | mode() |
众数(需 DescTools 包) |
| 离散 | sd() |
标准差 |
| 离散 | var() |
方差 |
| 离散 | range() |
极值(min/max) |
| 离散 | IQR() |
四分位距(Q3-Q1) |
| 离散 | mad() |
中位数绝对偏差 |
| 分布 | skewness() |
偏度(需 e1071) |
| 分布 | kurtosis() |
峰度(需 e1071) |
4. 集中趋势
(1) mean() 平均值
x <- c(85, 90, 78, 92, 88)
mean(x) # [1] 86.6
mean(x, na.rm = TRUE) # 跳过 NA
mean(x, trim = 0.1) # 截尾平均(去掉最大最小 10%)
(2) median() 中位数
median(c(85, 90, 78, 92, 88)) # [1] 88
median(c(85, 90, 78, 92, 10000)) # [1] 90 ← 不受 10000 影响
(3) 众数(特殊函数)
# R 没有内置 mode(),用 DescTools 包
install.packages("DescTools")
library(DescTools)
Mode(c(1, 2, 2, 3, 3, 3, 4)) # [1] 3
5. 离散程度
(1) sd() / var() 标准差 / 方差
x <- c(85, 90, 78, 92, 88)
sd(x) # [1] 5.32 ← 标准差
var(x) # [1] 28.3 ← 方差(sd^2)
标准差 = 数据点距离平均值的"平均距离"。值越大数据越分散。
(2) range() 极值
x <- c(85, 90, 78, 92, 88)
range(x) # [1] 78 92 ← 最小最大
diff(range(x)) # [1] 14 ← 极差
(3) IQR() 四分位距
x <- c(85, 90, 78, 92, 88, 70, 95, 65, 88, 92)
IQR(x) # [1] 11.5 ← Q3 - Q1
quantile(x, c(0.25, 0.5, 0.75))
# 25% 50% 75%
# 78.25 88.0 89.75
Q1 - 1.5*IQR ~ Q3 + 1.5*IQR)。
(4) mad() 中位数绝对偏差
mad(c(85, 90, 78, 92, 10000)) # 极度稳健的离散指标
# [1] 4.45
6. 分位数
(1) quantile() 分位数
x <- 1:100
# 默认 0%, 25%, 50%, 75%, 100%
quantile(x)
# 0% 25% 50% 75% 100%
# 1.0 25.75 50.5 75.25 100.0
# 自定义分位
quantile(x, probs = c(0.1, 0.5, 0.9))
# 10% 50% 90%
# 10.9 50.5 90.1
(2) 常用分位数
| 分位 | 函数 | 含义 |
|---|---|---|
| Q0 (0%) | min(x) |
最小值 |
| Q1 (25%) | quantile(x, 0.25) |
下四分位 |
| Q2 (50%) | median(x) |
中位数 |
| Q3 (75%) | quantile(x, 0.75) |
上四分位 |
| Q4 (100%) | max(x) |
最大值 |
7. summary() 一行综合统计
x <- c(85, 90, 78, 92, 88, NA, 75, 95)
summary(x)
# Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
# 75.00 81.50 88.00 87.62 90.50 95.00 1
6 个核心指标 + NA 计数——1 行搞定!
# 数据框
df <- data.frame(
age = c(20, 25, 30, 35),
score = c(85, 90, 78, 92)
)
summary(df)
# age score
# Min. :20.00 Min. :78.00
# 1st Qu.:23.75 1st Qu.:83.25
# Median :27.50 Median :87.50
# Mean :27.50 Mean :86.25
# 3rd Qu.:31.25 3rd Qu.:90.50
# Max. :35.00 Max. :92.00
8. skimr 高级综合统计
install.packages("skimr")
library(skimr)
x <- c(85, 90, 78, 92, 88, 70, 95, 65, 88, 92)
skim(x)
# ── Data Summary ────────────────────────
# Values x
# Number of rows 10
# Number of distinct 8
# Mean 84.2
# Standard deviation 10.13
# Min 65
# Max 95
# Median 87.5
# ...(更多指标)
skim() 给出 20+ 指标——比 summary() 详细 10 倍。
9. 分组描述统计
(1) dplyr + group_by + summarise
library(dplyr)
# 模拟 3 班 × 5 学生成绩
scores <- tibble(
class = rep(c("1班", "2班", "3班"), each = 5),
student = paste0("S", 1:15),
math = c(85, 78, 92, 65, 88, # 1 班
90, 75, 80, 95, 70, # 2 班
82, 88, 76, 91, 85), # 3 班
english = c(78, 85, 88, 70, 92,
82, 80, 90, 88, 75,
88, 90, 78, 92, 85)
)
# 班级统计
class_stats <- scores |>
group_by(class) |>
summarise(
人数 = n(),
数学平均 = mean(math),
数学中位 = median(math),
数学标准差 = sd(math),
数学最高 = max(math),
数学最低 = min(math),
数学IQR = IQR(math),
英语平均 = mean(english)
)
print(class_stats)
# A tibble: 3 × 9
# class 人数 数学平均 数学中位 数学标准差 数学最高 数学最低 数学IQR 英语平均
# <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 1班 5 81.6 85 11.4 92 65 17 82.6
# 2 2班 5 82 80 10.5 95 70 20 83
# 3 3班 5 84.4 85 6.02 91 76 12 86.6
(2) 多指标分列(across)
# 一次对所有数值列统计
scores |>
group_by(class) |>
summarise(across(where(is.numeric), list(
mean = mean,
sd = sd,
median = median
)))
(3) tapply 旧写法(备用)
# 用 tapply
tapply(scores$math, scores$class, mean)
# 1班 2班 3班
# 81.6 82.0 84.4
10. 实战:1000 学生综合成绩报告
下面是一个完整工作流示例,把本课所有描述统计串起来。
▶ 示例:1000 学生 5 课程综合报告
# ============================================
# 1000 学生 5 课程综合成绩报告
# 功能:完整描述统计 + 分组 + 排名
# ============================================
library(dplyr)
library(tidyr)
library(ggplot2)
# 1. Prepare data
set.seed(42)
n <- 1000
students <- tibble(
id = 1:n,
class = sample(c("1班", "2班", "3班", "4班", "5班"), n, replace = TRUE),
gender = sample(c("男", "女"), n, replace = TRUE),
math = round(rnorm(n, 80, 12)),
english = round(rnorm(n, 78, 15)),
physics = round(rnorm(n, 75, 14)),
chemistry = round(rnorm(n, 76, 13)),
biology = round(rnorm(n, 80, 10))
)
cat("=== 数据规模:", nrow(students), "行 ===\n")
# 2. 全校综合统计
cat("\n=== 全校综合统计 ===\n")
overall <- students |>
summarise(across(c(math, english, physics, chemistry, biology),
list(mean = mean, sd = sd, median = median),
.names = "{.col}_{.fn}"))
print(overall)
# 3. 按班级分组统计
cat("\n=== 各班数学统计 ===\n")
class_stats <- students |>
group_by(class) |>
summarise(
人数 = n(),
平均 = round(mean(math), 2),
中位 = median(math),
标准差 = round(sd(math), 2),
最高 = max(math),
最低 = min(math),
Q1 = quantile(math, 0.25),
Q3 = quantile(math, 0.75),
IQR = round(IQR(math), 2)
) |>
arrange(desc(平均))
print(class_stats)
# 4. 按性别分组
cat("\n=== 各性别 5 课平均 ===\n")
gender_stats <- students |>
group_by(gender) |>
summarise(across(c(math, english, physics, chemistry, biology),
mean, .names = "{.col}_平均"))
print(gender_stats)
# 5. 每名学生总分和排名
cat("\n=== Top 10 学生(总分)===\n")
students <- students |>
mutate(total = math + english + physics + chemistry + biology,
average = round(total / 5, 2))
top10 <- students |>
arrange(desc(total)) |>
head(10) |>
select(id, class, gender, total, average)
print(top10)
# 6. 找出异常值(IQR 法)
cat("\n=== 数学成绩异常值学生 ====\n")
math_q1 <- quantile(students$math, 0.25)
math_q3 <- quantile(students$math, 0.75)
math_iqr <- IQR(students$math)
lower <- math_q1 - 1.5 * math_iqr
upper <- math_q3 + 1.5 * math_iqr
outliers <- students |>
filter(math < lower | math > upper) |>
select(id, class, math)
cat("正常范围:", lower, "-", upper, "\n")
cat("异常值数量:", nrow(outliers), "\n")
print(head(outliers, 5))
# 7. 用 summary() 看单科
cat("\n=== 数学 summary ===\n")
print(summary(students$math))
# 8. 用 skimr 高级报告
library(skimr)
cat("\n=== skimr 报告 ===\n")
print(skim(students |> select(math, english, physics)))
# 9. 写出报告
write_csv(students, "student_report.csv")
write_csv(class_stats, "class_stats.csv")
cat("\n=== 报表已输出 ===\n")
预期输出(节选):
=== 各班数学统计 ===
# A tibble: 5 × 9
class 人数 平均 中位 标准差 最高 最低 Q1 Q3 IQR
<chr> <int> <dbl> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl>
1 5班 201 81.0 81 12.0 114 44 72 90 18
2 1班 195 80.6 81 11.6 115 47 73 88 15
3 3班 203 79.8 80 11.8 110 49 71 88 17
4 2班 201 79.4 80 12.1 109 44 70 88 18
5 4班 200 79.1 79 12.4 116 45 70 89 19
❓ 常见问题
na.rm = TRUE 跳过 NA。否则含 NA 向量结果都是 NA。e1071::skewness() 和 e1071::kurtosis()。skewness > 0 右偏,< 0 左偏;kurtosis > 0 比正态更尖,< 0 更平。📖 小节
- 描述统计 3 大类:集中趋势(mean/median/mode)/ 离散程度(sd/var/IQR)/ 分布形态(skewness/kurtosis)
- mean 受极端值影响,median 更稳健——有偏分布优先用 median
- sd 是与原数据同单位的标准差,var 是 sd²
- IQR = Q3 - Q1,是稳健的离散指标,常用于异常值检测
summary(x)一行 6 指标,最常用;skim(x)20+ 指标,详细报告- 分组描述:
dplyr::group_by() |> summarise(across(where(is.numeric), mean)) - NA 处理:所有函数加
na.rm = TRUE - 异常值:< Q1 - 1.5×IQR 或 > Q3 + 1.5×IQR(稳健)
📝 作业
-
基础题:构造 1 个向量
x <- c(85, 90, 78, 92, 88, NA, 75, 95),分别用mean()median()sd()var()计算(注意na.rm = TRUE),验证 8 个结果。 -
基础题:用
summary()和quantile(x, c(0, 0.25, 0.5, 0.75, 1))计算同一向量的统计指标,对比两个结果。 -
基础题:构造 1 个数据框(3 班 × 5 学生 × 数学/英语两科),用
dplyr::group_by + summarise + across一次算出所有班所有科目的 mean 和 sd。 -
进阶题:模拟 1000 个学生 5 门课成绩,用
skimr::skim()生成完整报告,识别出数学成绩异常值(IQR 法),统计异常学生比例。 -
挑战题:完整工作流——模拟 1000 学生 5 课成绩:① 整体描述 ② 按班级分组描述 ③ 按性别分组 ④ 排名 ⑤ 异常值检测 ⑥ 写出 CSV 报告 + Markdown 文本报告(用
knitr::kable()渲染表格)。把过程截图保存。