R: R 描述统计

最后更新:2026-08-26

前面 22 课我们学了 R 的"工具"——向量、数据框、ggplot2。现在开始 R 真正的"价值"——统计分析。这一课先学最基础的"描述统计":用 R 算均值、中位数、标准差等关键指标,把数据"总结"成几个数字。

读完这一课你就能用 R 3 行代码给任何数据集生成"统计报告"。

1. 你将学到



2. 一个学生成绩报告的故事

(1) 痛点:1000 个学生怎么"总结"?

Bob老师期末要给 1000 个学生出成绩报告,每个学生 5 门课,要给每门课算:

手算?Excel 公式?要 2 小时;用 R summary()——

(2) R 的解法

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) 三大类概述

100%
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() 平均值

R
x <- c(85, 90, 78, 92, 88)

mean(x)              # [1] 86.6
mean(x, na.rm = TRUE) # 跳过 NA
mean(x, trim = 0.1)   # 截尾平均(去掉最大最小 10%)
⚠️ 注意mean 受极端值影响大。数据有 1 个 10000 离群点,mean 会被拉高。有偏分布用 median

(2) median() 中位数

R
median(c(85, 90, 78, 92, 88))  # [1] 88
median(c(85, 90, 78, 92, 10000))  # [1] 90  ← 不受 10000 影响
💡 提示median 比 mean 更稳健。实际项目优先用 median(尤其收入、房价等有偏分布)。

(3) 众数(特殊函数)

R
# R 没有内置 mode(),用 DescTools 包
install.packages("DescTools")
library(DescTools)

Mode(c(1, 2, 2, 3, 3, 3, 4))  # [1] 3


5. 离散程度

(1) sd() / var() 标准差 / 方差

R
x <- c(85, 90, 78, 92, 88)

sd(x)    # [1] 5.32  ← 标准差
var(x)   # [1] 28.3  ← 方差(sd^2)

标准差 = 数据点距离平均值的"平均距离"。值越大数据越分散

(2) range() 极值

R
x <- c(85, 90, 78, 92, 88)

range(x)              # [1] 78 92  ← 最小最大
diff(range(x))        # [1] 14   ← 极差

(3) IQR() 四分位距

R
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
💡 提示IQR 是稳健的离散指标——不受极端值影响,常用于识别异常值(Q1 - 1.5*IQR ~ Q3 + 1.5*IQR)。

(4) mad() 中位数绝对偏差

R
mad(c(85, 90, 78, 92, 10000))  # 极度稳健的离散指标
# [1] 4.45


6. 分位数

(1) quantile() 分位数

R
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() 一行综合统计

R
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 行搞定!

R
# 数据框
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 高级综合统计

R
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

R
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

R
# 一次对所有数值列统计
scores |>
  group_by(class) |>
  summarise(across(where(is.numeric), list(
    mean = mean,
    sd = sd,
    median = median
  )))

(3) tapply 旧写法(备用)

R
# 用 tapply
tapply(scores$math, scores$class, mean)
# 1班 2班 3班 
# 81.6 82.0 84.4


10. 实战:1000 学生综合成绩报告

下面是一个完整工作流示例,把本课所有描述统计串起来。

▶ 示例:1000 学生 5 课程综合报告

R 📖 仅展示
# ============================================
# 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")
逻辑代码 73 行(超过 40 行限制,仅展示)

预期输出(节选):

TEXT 📖 仅展示
=== 各班数学统计 ===
# 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

❓ 常见问题

Q sd 还是 var?
A 单位统一用 sd
Q summary() 和 skim() 选哪个?
A summary 简洁(6 指标),skim 详细(20+ 指标)。
Q NA 怎么处理?
A 所有函数加 na.rm = TRUE 跳过 NA。否则含 NA 向量结果都是 NA。
Q 偏度/峰度怎么算?
Ae1071::skewness()e1071::kurtosis()。skewness > 0 右偏,< 0 左偏;kurtosis > 0 比正态更尖,< 0 更平。

📖 小节


📝 作业

  1. 基础题:构造 1 个向量 x <- c(85, 90, 78, 92, 88, NA, 75, 95),分别用 mean() median() sd() var() 计算(注意 na.rm = TRUE),验证 8 个结果。

  2. 基础题:用 summary()quantile(x, c(0, 0.25, 0.5, 0.75, 1)) 计算同一向量的统计指标,对比两个结果。

  3. 基础题:构造 1 个数据框(3 班 × 5 学生 × 数学/英语两科),用 dplyr::group_by + summarise + across 一次算出所有班所有科目的 mean 和 sd。

  4. 进阶题:模拟 1000 个学生 5 门课成绩,用 skimr::skim() 生成完整报告,识别出数学成绩异常值(IQR 法),统计异常学生比例。

  5. 挑战题:完整工作流——模拟 1000 学生 5 课成绩:① 整体描述 ② 按班级分组描述 ③ 按性别分组 ④ 排名 ⑤ 异常值检测 ⑥ 写出 CSV 报告 + Markdown 文本报告(用 knitr::kable() 渲染表格)。把过程截图保存。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏