简介:这份PDF资料面向R语言初学者与数据分析入门者,系统讲解描述性统计分析的完整方法体系,帮助读者快速掌握从基础统计量计算到分组分析与可视化的核心技能。内容覆盖summary()、sapply()等内置函数,自定义偏度与峰度计算函数,以及Hmisc、psych、doBy等扩展包的describe()与summaryBy()用法,并演示aggregate()、by()的分组统计思路,最后借助ggplot2绘制直方图与箱线图直观呈现数据分布特征。资源包共1个PDF文件,约98KB,篇幅精炼、代码示例完整,可直接对照mtcars等内置数据集动手练习。目前已有552人学习,适合希望夯实数据科学基础、为后续数据挖掘与机器学习做准备的读者参考。
1. 描述性统计分析:为什么你的第一张表就劝退了审稿人
很多人做数据分析,第一步就是summary()一把梭,然后把控制台输出直接贴进报告。结果审稿人看到的是:变量名是英文缩写、缺失值用 NA 表示、连续变量只给了个均值,分类变量连频数都没排序。这不是描述性统计,这是把原始输出当成了分析结果。
R 语言里做描述性统计分析,核心不是“会跑函数”,而是知道什么变量该用什么统计量、什么统计量该用什么表呈现、什么表该配什么图。连续变量看分布形状和离群值,分类变量看频数和占比,有序变量还得考虑中位数和四分位距。这些判断决定了你后面用 t 检验还是 Wilcoxon、用卡方还是 Fisher。
这篇内容面向已经能用 R 读数据、但描述性统计总做得“差点意思”的从业者。我会从统计量选择讲到批量计算、从单变量讲到分组汇总、从表格讲到图形,最后落到一个可复用的描述性统计函数上。每一步都有代码,每个参数都有解释,每个坑我都踩过。
2. 统计量选择与单变量描述:均值和中位数到底该信谁
2.1 连续变量的分布诊断:先看图再算数
拿到一个连续变量,不要急着算均值。先画直方图和箱线图,看分布形状、偏态和离群值。R 基础绘图里hist()和boxplot()够用,但ggplot2的geom_histogram()和geom_boxplot()更灵活。
# 模拟一组右偏数据:收入分布 set.seed(42) income <- c(rnorm(200, mean = 8000, sd = 2000), rnorm(50, mean = 25000, sd = 5000)) # 混入高收入群体 # 基础绘图:直方图 + 箱线图 par(mfrow = c(1, 2)) hist(income, breaks = 30, main = "收入分布直方图", xlab = "收入", col = "steelblue", border = "white") boxplot(income, main = "收入箱线图", ylab = "收入", col = "lightgreen") par(mfrow = c(1, 1))breaks = 30控制直方图柱子数量,太少看不出形状,太多全是噪声。箱线图里超出 1.5 倍四分位距的点会被标为离群值,但注意:离群值不一定是错误数据,可能是真实的高收入个体。
# 计算关键统计量 stats <- c( mean = mean(income), median = median(income), sd = sd(income), IQR = IQR(income), skewness = mean((income - mean(income))^3) / sd(income)^3 ) print(round(stats, 2))右偏分布下,均值会被高收入群体拉高,中位数更能代表“典型收入”。skewness大于 0 说明右偏,大于 1 就是严重右偏,这时候报告里应该同时给中位数和四分位距,而不是只给均值±标准差。
2.2 分类变量与有序变量的频数表:别漏了缺失值
分类变量用table()看频数,但table()默认不显示 NA。如果缺失值很多,你会在后续建模时才发现问题。
# 模拟分类数据 set.seed(123) education <- sample(c("高中及以下", "大专", "本科", "硕士及以上", NA), size = 300, replace = TRUE, prob = c(0.2, 0.25, 0.35, 0.15, 0.05)) # 基础频数表(不含 NA) table(education) # 包含 NA 的频数表 table(education, useNA = "ifany") # 带占比的频数表 prop.table(table(education, useNA = "ifany")) * 100useNA = "ifany"表示有缺失值就显示,"always"则强制显示。占比计算用prop.table()包一层,乘 100 转百分比。对于有序变量,比如满意度“非常不满意→非常满意”,应该用factor()指定levels和ordered = TRUE,这样中位数和四分位距才有意义。
# 有序因子 satisfaction <- factor( sample(c("非常不满意", "不满意", "一般", "满意", "非常满意"), size = 200, replace = TRUE), levels = c("非常不满意", "不满意", "一般", "满意", "非常满意"), ordered = TRUE ) summary(satisfaction) # 有序因子会给出各水平频数 median(as.numeric(satisfaction)) # 转数值后求中位数有序变量的中位数计算需要先转数值,但报告时要用原始标签解释。常见做法是:频数表 + 累积百分比 + 中位数所在等级。
2.3 缺失值模式:描述性统计的第一道暗坑
缺失值不是“少几个数”那么简单。如果某个变量缺失率超过 20%,均值和中位数的可靠性都会大打折扣。更麻烦的是,缺失可能不是随机的。
# 模拟带缺失的数据框 df <- data.frame( age = c(rnorm(95, 35, 10), rep(NA, 5)), income = c(rnorm(90, 8000, 2000), rep(NA, 10)), education = sample(c("本科", "硕士", NA), 100, replace = TRUE, prob = c(0.5, 0.3, 0.2)) ) # 缺失值统计 missing_summary <- data.frame( variable = names(df), n_missing = sapply(df, function(x) sum(is.na(x))), pct_missing = sapply(df, function(x) mean(is.na(x))) * 100 ) print(missing_summary) # 缺失值模式可视化(用 VIM 包,需安装) # library(VIM) # aggr(df, numbers = TRUE, prop = TRUE)sapply()对每列算缺失数和缺失率。如果缺失率超过 5%,报告里必须说明;超过 20%,要考虑插补或敏感性分析。VIM::aggr()能画出缺失模式组合图,但需要额外安装包。我一般先用上面的表格快速筛查,再决定要不要深入。
3. 分组描述与批量计算:从单变量到分层汇总
3.1 用 dplyr 做分组汇总:summarise 的三种写法
单变量描述只是起点,实际分析里更多是“按性别、按地区、按处理组分”。dplyr的group_by()+summarise()是标准做法,但写法有讲究。
library(dplyr) # 构造示例数据 set.seed(456) df <- data.frame( group = sample(c("对照组", "实验组"), 200, replace = TRUE), sex = sample(c("男", "女"), 200, replace = TRUE), age = rnorm(200, 40, 12), score = rnorm(200, 75, 10) ) # 写法一:单变量分组汇总 df %>% group_by(group) %>% summarise( n = n(), mean_age = mean(age, na.rm = TRUE), sd_age = sd(age, na.rm = TRUE), median_score = median(score, na.rm = TRUE), iqr_score = IQR(score, na.rm = TRUE) ) # 写法二:多变量分组 + across df %>% group_by(group, sex) %>% summarise( across(c(age, score), list( mean = ~mean(.x, na.rm = TRUE), sd = ~sd(.x, na.rm = TRUE), median = ~median(.x, na.rm = TRUE) ), .names = "{.col}_{.fn}") )across()里list()定义多个函数,.names控制输出列名。~mean(.x, na.rm = TRUE)是 lambda 写法,.x代表当前列。na.rm = TRUE几乎必须加,否则有缺失值就返回 NA。
3.2 用 data.table 提速:大数据集下的分组统计
数据量超过百万行时,dplyr会明显变慢。data.table的语法更紧凑,速度也更快。
library(data.table) # 转 data.table dt <- as.data.table(df) # 分组汇总:按 group 和 sex 算 age 和 score 的均值、标准差 dt[, .( n = .N, age_mean = mean(age, na.rm = TRUE), age_sd = sd(age, na.rm = TRUE), score_mean = mean(score, na.rm = TRUE), score_sd = sd(score, na.rm = TRUE) ), by = .(group, sex)].N是内置符号,表示当前分组的行数。by = .(group, sex)指定分组变量。data.table的[语法是dt[i, j, by],i 是筛选,j 是计算,by 是分组。习惯之后比dplyr更顺手,尤其是需要链式操作时。
3.3 批量描述性统计:一次输出所有变量的汇总表
变量多了以后,一个个写summarise()不现实。我一般写一个通用函数,自动判断变量类型,连续变量给均值、标准差、中位数、四分位距,分类变量给频数和占比。
library(dplyr) library(tidyr) desc_stats <- function(data, vars = names(data)) { results <- lapply(vars, function(v) { x <- data[[v]] if (is.numeric(x)) { data.frame( variable = v, type = "连续", n = sum(!is.na(x)), missing = sum(is.na(x)), mean = round(mean(x, na.rm = TRUE), 2), sd = round(sd(x, na.rm = TRUE), 2), median = round(median(x, na.rm = TRUE), 2), iqr = round(IQR(x, na.rm = TRUE), 2) ) } else { tbl <- table(x, useNA = "ifany") data.frame( variable = v, type = "分类", level = names(tbl), n = as.integer(tbl), pct = round(as.numeric(prop.table(tbl)) * 100, 1) ) } }) do.call(rbind, results) } # 使用 desc_stats(df, c("age", "score", "group", "sex"))这个函数的核心是lapply()遍历变量,is.numeric()判断类型,连续变量和分类变量分别返回不同结构的数据框,最后rbind()合并。注意分类变量返回的是长表,每个水平一行,这样导出到 Excel 或 Word 时更好排版。
4. 可视化描述:让审稿人一眼看懂你的数据
4.1 连续变量分组箱线图:ggplot2 的标准配方
箱线图是描述性统计里最实用的图形,能同时展示中位数、四分位距和离群值。分组箱线图还能直观对比组间差异。
library(ggplot2) ggplot(df, aes(x = group, y = score, fill = sex)) + geom_boxplot(alpha = 0.7, outlier.color = "red", outlier.size = 1.5) + stat_summary(fun = mean, geom = "point", shape = 18, size = 3, color = "darkblue", position = position_dodge(0.75)) + labs(title = "各组得分分布", x = "组别", y = "得分", fill = "性别") + theme_minimal(base_size = 13) + theme(plot.title = element_text(hjust = 0.5))stat_summary()叠加均值点,菱形表示均值,箱线图中横线是中位数。position_dodge(0.75)让均值点和箱线图对齐。outlier.color = "red"把离群值标红,方便检查。
4.2 分类变量马赛克图与堆叠条形图
两个分类变量的关系用马赛克图或堆叠条形图。马赛克图用ggmosaic包,堆叠条形图用geom_bar()。
# 堆叠条形图:展示 group 和 sex 的交叉频数 ggplot(df, aes(x = group, fill = sex)) + geom_bar(position = "fill") + scale_y_continuous(labels = scales::percent) + labs(title = "各组性别构成", x = "组别", y = "占比", fill = "性别") + theme_minimal(base_size = 13)position = "fill"把条形高度归一化到 1,scales::percent把纵轴转成百分比。如果要看绝对频数,用position = "dodge"分组并列。
4.3 缺失值可视化:用 naniar 包快速诊断
缺失值模式用图看比表格更直观。naniar包的vis_miss()和gg_miss_upset()是常用工具。
# library(naniar) # vis_miss(df, cluster = TRUE) # gg_miss_upset(df)vis_miss()用黑色块表示缺失,cluster = TRUE按缺失模式聚类。gg_miss_upset()展示缺失组合的频数。如果缺失集中在某几个变量且同时出现,说明可能有结构性原因,不是随机缺失。
5. 避坑与排查:描述性统计里最容易翻车的五件事
5.1 均值算出来是 NA:na.rm 忘了加
现象:mean(x)返回 NA,但数据里明明只有几个缺失值。
原因:R 的mean()、sd()、median()默认na.rm = FALSE,只要有一个 NA,结果就是 NA。
解决:所有统计函数加na.rm = TRUE。但注意:加之前先确认缺失率,缺失太多时na.rm = TRUE会掩盖问题。
mean(x, na.rm = TRUE) # 正确 mean(x) # 有 NA 就返回 NA5.2 分类变量被当成数值:类型判断翻车
现象:用summary()看分类变量,输出的是Min. / 1st Qu. / Median,而不是频数。
原因:数据读入时,分类变量被存成了 numeric 或 character,没有转成 factor。
解决:读数据时用stringsAsFactors = FALSE,然后手动转 factor。或者用readr::read_csv(),它默认不转 factor,更可控。
df$group <- as.factor(df$group) df$education <- factor(df$education, levels = c("高中及以下", "大专", "本科", "硕士及以上"), ordered = TRUE)5.3 分组汇总后行数对不上:缺失分组被丢弃
现象:group_by()+summarise()后,某些分组消失了。
原因:分组变量里有 NA,dplyr默认把 NA 组丢弃。
解决:加group_by(group, .drop = FALSE)保留空组,或者先用tidyr::replace_na()把 NA 替换成“未知”。
df %>% mutate(group = replace_na(group, "未知")) %>% group_by(group) %>% summarise(n = n())5.4 百分比加起来不是 100:四舍五入累积误差
现象:频数表里百分比加起来是 99.9% 或 100.1%。
原因:每个百分比独立四舍五入,累积误差。
解决:要么保留一位小数并接受微小误差,要么用round()后手动调整最大项。更严谨的做法是报告原始频数,百分比只做参考。
pct <- round(prop.table(table(x)) * 100, 1) # 检查总和 sum(pct)5.5 图形中文乱码:字体没设对
现象:ggplot2画图时中文显示为方框或问号。
原因:默认字体不支持中文。
解决:theme_minimal(base_family = "SimHei")或theme(text = element_text(family = "STHeiti"))。Windows 用windowsFonts()注册字体,Mac 用par(family = "STHeiti")。
# Windows windowsFonts(SimHei = windowsFont("SimHei")) ggplot(df, aes(x = group, y = score)) + geom_boxplot() + theme_minimal(base_family = "SimHei") # Mac ggplot(df, aes(x = group, y = score)) + geom_boxplot() + theme_minimal(base_family = "STHeiti")6. 把描述性统计做成可复用函数:一个具体技巧
前面几章拆了统计量选择、分组汇总、可视化和避坑。最后一章落到一个具体技巧:把描述性统计封装成一个函数,自动判断变量类型、自动处理缺失值、自动输出可导出的表格。
我一般会写一个desc_by_group()函数,输入数据框、分组变量、待描述变量,输出一个长表,包含分组、变量、水平、统计量、值。这样无论是导出 CSV 还是用knitr::kable()渲染,都很方便。
desc_by_group <- function(data, group_var, vars) { library(dplyr) library(tidyr) results <- lapply(vars, function(v) { x <- data[[v]] g <- data[[group_var]] if (is.numeric(x)) { data.frame( group = g, variable = v, level = "连续", n = x, stringsAsFactors = FALSE ) %>% group_by(group, variable, level) %>% summarise( n = sum(!is.na(n)), mean = round(mean(n, na.rm = TRUE), 2), sd = round(sd(n, na.rm = TRUE), 2), median = round(median(n, na.rm = TRUE), 2), iqr = round(IQR(n, na.rm = TRUE), 2), .groups = "drop" ) } else { data.frame(group = g, variable = v, level = x) %>% count(group, variable, level) %>% group_by(group, variable) %>% mutate(pct = round(n / sum(n) * 100, 1)) %>% ungroup() } }) bind_rows(results) } # 使用示例 result <- desc_by_group(df, "group", c("age", "score", "sex")) print(result) # 导出 CSV # write.csv(result, "desc_stats.csv", row.names = FALSE, fileEncoding = "UTF-8")这个函数的关键点:is.numeric()分流连续和分类变量;连续变量用group_by()+summarise()算统计量;分类变量用count()算频数再算占比;最后bind_rows()合并。fileEncoding = "UTF-8"避免中文导出乱码。
我自己的习惯是:每次拿到新数据,先跑一遍这个函数,把输出保存成 CSV,再用ggplot2画几张关键图。这样描述性统计部分基本不会返工。如果审稿人问“为什么用中位数不用均值”,我能立刻从输出里找到偏度和离群值证据。
还有一个细节:函数里round()的位数我一般设 2,但报告里通常只保留 1 位小数。多算一位是为了避免累积误差,展示时再round(1)。这个习惯帮我省过好几次“百分比加起来不对”的麻烦。
希望帮到你。
本文还有配套的精品资源,点击获取