简介:这份PDF文档面向R语言初学者与数据分析入门者,系统讲解描述性统计分析的完整方法体系,帮助读者快速掌握从基础统计量到分组可视化探索数据集特征的核心技能。资源共1个PDF文件,压缩包约98KB,内容以代码示例与函数讲解为主,涵盖summary()、sapply()等基础函数,自定义偏度峰度计算,以及Hmisc、psych、doBy等扩展包的describe()与summaryBy()用法,并演示aggregate()、by()的分组统计思路。文档还结合ggplot2绘制直方图与箱线图,直观呈现数据分布、离散程度与异常值,形成从数值计算到图形展示的完整链路。目前已有552人学习,适合希望用R语言打牢数据探索基础、为后续数据挖掘与机器学习做准备的读者参考。
1. 描述性统计分析在 R 里的真实定位:别急着上模型,先把数据摸透
很多人拿到一份数据集,第一反应是直接套随机森林或者 XGBoost,跑完发现 AUC 只有 0.6 出头,回头查半天才发现某一列有几十个缺失值、两列高度共线、还有一个变量全是异常点。这类翻车现场我见过太多次,根子都在跳过了描述性统计分析这一步。R 语言里做描述性统计不是什么高深技术,但它决定了你后面所有分析的地基稳不稳。summary()、sapply()、describe()这些函数看起来简单,真正用对、用全、用出信息量的人并不多。这篇内容面向的是手上有一份数据、想快速摸清分布特征、识别异常值、为后续建模或报告打基础的人。不管你是刚接触 R 的新手,还是用了几年但一直靠默认参数混日子的熟手,下面这些函数组合、分组策略和可视化搭配,都能直接抄去改。我会把每个函数的参数含义、输出怎么读、什么场景选哪个方案讲清楚,也会把那些年我踩过的坑一并倒出来。
2. 基础函数三件套:summary、sapply 与自定义统计量
2.1 summary() 能给你什么,以及它给不了什么
summary()是 R 里最顺手的入口函数,没有之一。对数值型向量它返回最小值、第一四分位数、中位数、均值、第三四分位数和最大值;对因子或字符型向量它返回频数统计。这意味着你一条命令就能同时看到数值列的分布骨架和分类列的类别平衡情况。
# 加载内置数据集 mtcars data(mtcars) # 选定三个数值变量 myvars <- c("mpg", "hp", "wt") # 基础 summary 输出 summary(mtcars[myvars])逻辑说明:mtcars是 R 自带的数据集,包含 32 辆车的 11 项指标。myvars是一个字符向量,用来索引需要的列。summary()接受数据框后会对每一列分别计算统计量。
参数说明:summary()本身没有太多可调参数,但你可以通过digits参数控制输出的小数位数,比如summary(mtcars[myvars], digits = 3)。另外注意,如果某列含有NA,summary()默认会返回NA's的计数,这个信息很关键——它直接告诉你缺失值的规模。
summary()的局限也很明显:它不给标准差、不给偏度峰度、不给标准误。如果你需要这些,就得往下走。
2.2 sapply() 批量计算:一行代码替代十行循环
sapply()的作用是对列表或向量中的每个元素应用同一个函数,并返回简化后的结果。在描述性统计场景里,它让你可以一次性对多个变量计算多个统计量,不用写 for 循环。
# 对每个变量计算均值和标准差 sapply(mtcars[myvars], mean) sapply(mtcars[myvars], sd) # 一次计算多个统计量 sapply(mtcars[myvars], function(x) c( n = length(x), mean = mean(x), sd = sd(x), min = min(x), max = max(x), median = median(x) ))逻辑说明:第一个sapply对myvars中的每一列应用mean,返回一个命名向量。第二个同理计算标准差。第三个用匿名函数把多个统计量打包成一个向量返回,sapply会自动把结果整理成矩阵,行是统计量名,列是变量名。
参数说明:sapply的第二个参数可以是函数名(如mean),也可以是匿名函数。如果某些列有缺失值,记得加na.rm = TRUE,比如sapply(mtcars[myvars], mean, na.rm = TRUE)。不加的话,只要该列有一个NA,结果就是NA。这是新手最常翻车的地方之一。
常见做法是:先用summary()扫一遍全局,再用sapply()补齐标准差、标准误、变异系数这些summary()不提供的量。
2.3 自定义函数:偏度与峰度怎么算才靠谱
偏度和峰度是判断数据分布形状的核心指标。偏度衡量分布的对称性,峰度衡量尾部的厚重程度。R 基础包里没有直接计算这两个统计量的函数,所以需要自己写。
# 自定义描述性统计函数 mystats <- function(x, na.omit = FALSE) { if (na.omit) { x <- x[!is.na(x)] } m <- mean(x) n <- length(x) s <- sd(x) skew <- sum((x - m)^3 / s^3) / n kurt <- sum((x - m)^4 / s^4) / n - 3 return(c(n = n, mean = m, stdev = s, skew = skew, kurtosis = kurt)) } # 应用到多个变量 sapply(mtcars[myvars], mystats)逻辑说明:函数内部先判断是否剔除缺失值,然后依次计算样本量、均值、标准差、偏度和超额峰度。偏度公式用的是三阶矩除以标准差的三次方,峰度用的是四阶矩除以标准差的四次方再减 3(超额峰度,正态分布峰度为 0)。
参数说明:na.omit是一个逻辑开关,默认FALSE。如果数据有缺失值且你不想手动清理,调用时传na.omit = TRUE即可。注意这里的偏度和峰度都是基于矩的估计,样本量小于 30 时结果波动会比较大,解读时要谨慎。
我一般会把这个函数存成一个单独的 R 脚本,每次分析新数据时source()进来直接用,省得反复复制粘贴。
3. 第三方包怎么选:Hmisc 与 psych 的 describe() 对比
3.1 Hmisc::describe() 的输出结构与适用场景
Hmisc包的describe()函数输出的信息量比基础summary()大得多。它会给出每个变量的样本量、缺失值数量、唯一值数量、均值、中位数、四分位数、极值,以及最频繁出现的几个值。
# 安装并加载 Hmisc install.packages("Hmisc") library(Hmisc) # 使用 describe 函数 describe(mtcars[myvars])逻辑说明:describe()接受数据框或向量,返回一个列表,每个元素对应一个变量的详细统计信息。控制台输出会分段展示,第一段是变量基本信息,第二段是分位数,第三段是频数最高的几个值。
参数说明:describe()有几个实用参数。describe(mtcars[myvars], digits = 2)控制小数位;describe(mtcars[myvars], trim = 0.05)会额外给出截尾均值,对异常值敏感的场景很有用。另外describe()对因子型变量的处理也很细致,会列出每个水平的计数和比例。
这个函数特别适合在数据清洗阶段使用,因为你能一眼看到哪些变量有大量缺失、哪些变量的分布严重偏斜、哪些分类变量的某些水平几乎没样本。
3.2 psych::describe() 的差异化优势
psych包也提供了一个describe()函数,名字一样但输出格式和默认行为不同。它的优势在于输出更紧凑,而且默认就包含偏度和峰度,不需要额外指定。
# 安装并加载 psych install.packages("psych") library(psych) # 使用 psych 的 describe describe(mtcars[myvars])逻辑说明:psych::describe()返回一个数据框,每行是一个变量,列包括n、mean、sd、median、min、max、range、skew、kurtosis、se。这个格式非常适合直接导出成 CSV 或者嵌入 R Markdown 报告。
参数说明:常用参数包括describe(mtcars[myvars], skew = FALSE)可以关闭偏度输出;describe(mtcars[myvars], ranges = FALSE)关闭极差;describe(mtcars[myvars], quant = c(.25, .75))可以自定义需要输出的分位数。还有一个很实用的参数fast = TRUE,当数据量很大时开启它可以显著加快计算速度。
两个包怎么选?我的习惯是:数据探索阶段用Hmisc::describe(),因为信息全、排版清晰;需要把统计量导出做表或者写报告时用psych::describe(),因为返回的是数据框,后续处理方便。两个都装上不冲突,注意调用时加包名前缀就行。
4. 分组描述性统计:aggregate、by 与 summaryBy 的取舍
4.1 aggregate() 的公式接口与多列聚合
实际分析中,我们很少只看整体统计量,更多时候需要按某个分类变量分组后分别计算。aggregate()是 R 基础包里做这件事的标准方案,用的是公式接口。
# 按 cyl 分组计算 mpg 的均值 aggregate(mpg ~ cyl, data = mtcars, mean) # 按 cyl 分组计算多个变量的多个统计量 aggregate(cbind(mpg, hp, wt) ~ cyl, data = mtcars, function(x) c( mean = mean(x), sd = sd(x) ))逻辑说明:公式左边是待计算的变量,右边是分组变量。data参数指定数据框。第三个参数是任意函数。当左边用cbind()合并多个变量时,aggregate()会对每个变量分别应用函数。
参数说明:aggregate()有一个na.action参数,默认是na.omit,意味着含有缺失值的行会被整行删除。如果你的数据缺失较多,建议先确认删除后每组还剩多少样本。另外drop = TRUE是默认行为,会丢弃没有数据的组合水平。
aggregate()的短板在于它一次只能返回一个统计量(除非你用cbind配合返回向量的函数),而且输出格式在变量多的时候不太好看。
4.2 by() 与自定义函数的组合拳
by()函数的思想是"按组拆分,逐组应用函数",它比aggregate()更灵活,因为你可以返回任意结构的结果。
# 定义分组统计函数 dstats <- function(x) sapply(x, mystats) # 按 cyl 分组,对 myvars 中每个变量计算自定义统计量 by(mtcars[myvars], mtcars$cyl, dstats)逻辑说明:by()的第一个参数是要分析的数据框,第二个是分组变量(这里是mtcars$cyl),第三个是应用函数。dstats内部又调用了sapply和之前定义的mystats,所以最终输出是每个组一份完整的描述性统计结果。
参数说明:by()的返回结果是一个by类对象,直接打印会分段显示。你可以用do.call(rbind, result)把它转成矩阵,或者用unlist()展平。注意分组变量如果是因子,by()会按水平顺序输出;如果是数值向量,会按唯一值排序。
这个组合适合需要一次性拿到所有组、所有变量的所有统计量的场景。缺点是输出格式需要后处理,不如summaryBy()直接。
4.3 doBy::summaryBy() 的表格化输出
doBy包的summaryBy()函数在输出格式上做得最好,直接返回一个规整的数据框,适合后续导出或绘图。
# 安装并加载 doBy install.packages("doBy") library(doBy) # 按 cyl 分组计算多个统计量 summaryBy(mpg + hp + wt ~ cyl, data = mtcars, FUN = mystats)逻辑说明:公式左边是待分析变量,右边是分组变量。FUN参数指定统计函数,这里用了自定义的mystats。返回结果是一个数据框,每行是一个组,列名会自动加上统计量后缀。
参数说明:summaryBy()支持FUN传入多个函数,比如FUN = c(mean, sd),但输出列名会变得比较长。另外它有一个keep.names参数,控制是否保留原始变量名作为列名前缀。如果分组变量有多个,公式写成mpg ~ cyl + gear即可。
三个函数的选择逻辑:快速看一眼用aggregate();需要复杂自定义统计量用by();需要规整输出做报告用summaryBy()。没有哪个绝对最好,看场景。
5. 可视化搭配与避坑指南
5.1 ggplot2 直方图与箱线图的参数调优
数值统计量告诉你分布的形状,但真正让你"看到"异常值和分布的,还是图。ggplot2是 R 里最成熟的绘图方案,直方图和箱线图是描述性统计阶段最常用的两种图。
# 安装并加载 ggplot2 install.packages("ggplot2") library(ggplot2) # 直方图:查看 mpg 的分布 ggplot(mtcars, aes(x = mpg)) + geom_histogram(binwidth = 2, fill = "#4A90D9", color = "white", alpha = 0.8) + labs(title = "mpg 分布直方图", x = "mpg", y = "频数") + theme_minimal() # 箱线图:按 cyl 分组查看 mpg 分布 ggplot(mtcars, aes(x = factor(cyl), y = mpg, fill = factor(cyl))) + geom_boxplot(alpha = 0.7, outlier.color = "red", outlier.size = 2) + labs(title = "不同气缸数下的 mpg 箱线图", x = "气缸数", y = "mpg") + theme_minimal() + theme(legend.position = "none")逻辑说明:直方图的binwidth控制每个柱子的宽度,这个参数对图形解读影响很大——太宽会掩盖细节,太窄会显得毛躁。箱线图按cyl分组,outlier.color和outlier.size把异常点标红放大,方便一眼识别。
参数说明:geom_histogram的bins和binwidth二选一,建议用binwidth因为更直观。geom_boxplot的coef参数控制 whisker 的延伸范围,默认 1.5 倍 IQR,改成 3 会减少被标记为异常点的数量。fill和color分别控制填充色和边框色,alpha控制透明度。
我一般会直方图和箱线图搭配着看:直方图看整体形状和峰的数量,箱线图看组间差异和异常值。
5.2 避坑与常见问题排查
现象一:sapply计算结果全是NA。原因:数据列中存在缺失值,且没有设置na.rm = TRUE。解决:在调用时加上na.rm = TRUE,比如sapply(df, mean, na.rm = TRUE)。但要注意,加了这个参数后样本量会变化,最好同时输出有效样本数。
现象二:aggregate()报错 "arguments must have the same length"。原因:公式左边的变量和分组变量长度不一致,通常是因为数据框经过了筛选但分组变量没有同步更新。解决:确保data参数统一指向同一个数据框,不要混用df$col和df。
现象三:describe()输出中某个变量显示 "unique values" 数量异常高。原因:该列可能是字符型但被当成了因子,或者数值列中混入了非数值字符导致整列被读成字符。解决:用str(df)检查每列类型,必要时用as.numeric()强制转换,转换前先处理非数值字符。
现象四:箱线图把大量点标为异常值。原因:数据本身偏态严重,或者coef默认值 1.5 对你的数据太严格。解决:先看直方图确认分布形状,如果确实偏态,可以调大coef到 2 或 3,或者改用小提琴图geom_violin()展示完整分布。
现象五:summaryBy()输出列名乱码或过长。原因:FUN传入多个函数时,列名会自动拼接函数名和变量名,中文环境下可能出现编码问题。解决:在脚本开头设置Sys.setlocale("LC_ALL", "Chinese"),或者改用psych::describeBy()替代。
6. 进阶技巧:把描述性统计嵌进可复现流程
前面讲的都是单次操作的技巧,但真正让描述性统计发挥价值的,是把它固化成一个可重复执行的流程。我现在的习惯是:每拿到一份新数据,先跑一个固定的 R 脚本,输出一份完整的描述性统计报告,包含数值统计量、分组统计量和关键可视化。这个脚本我用了三年多,改过十几版,下面把核心结构拆给你。
# ============================================================ # 描述性统计自动化脚本模板 # 用法:修改 data_path 和 target_vars 后直接 source() # ============================================================ # 依赖包检查与加载 required_pkgs <- c("psych", "ggplot2", "doBy") for (pkg in required_pkgs) { if (!require(pkg, character.only = TRUE)) { install.packages(pkg) library(pkg, character.only = TRUE) } } # 读取数据(替换为你的实际路径) data_path <- "your_data.csv" df <- read.csv(data_path, stringsAsFactors = FALSE) # 指定需要分析的变量 target_vars <- c("var1", "var2", "var3") group_var <- "group_col" # 第一步:整体描述性统计 overall_stats <- psych::describe(df[, target_vars]) print(overall_stats) # 第二步:分组描述性统计 group_stats <- doBy::summaryBy( paste(target_vars, collapse = " + ") |> as.formula(), data = df, FUN = function(x) c( n = length(x), mean = mean(x, na.rm = TRUE), sd = sd(x, na.rm = TRUE), median = median(x, na.rm = TRUE) ) ) print(group_stats) # 第三步:缺失值概览 na_summary <- data.frame( variable = names(df[, target_vars]), na_count = sapply(df[, target_vars], function(x) sum(is.na(x))), na_pct = sapply(df[, target_vars], function(x) round(sum(is.na(x)) / length(x) * 100, 2)) ) print(na_summary) # 第四步:批量直方图 for (v in target_vars) { p <- ggplot(df, aes(x = .data[[v]])) + geom_histogram(binwidth = diff(range(df[[v]], na.rm = TRUE)) / 30, fill = "#4A90D9", color = "white", alpha = 0.8) + labs(title = paste(v, "分布"), x = v, y = "频数") + theme_minimal() print(p) } # 第五步:导出统计结果 write.csv(overall_stats, "overall_stats.csv", row.names = TRUE) write.csv(group_stats, "group_stats.csv", row.names = FALSE) write.csv(na_summary, "na_summary.csv", row.names = FALSE)这段脚本的核心思路是"一次编写,反复使用"。几个关键设计点值得展开说。
第一,依赖包自动检查。require()配合character.only = TRUE可以在包未安装时自动安装,省去手动操作的麻烦。这在换电脑或者分享给同事时特别有用。
第二,psych::describe()的输出直接是一个数据框,可以无缝导出成 CSV。我通常会把这份 CSV 和原始数据放在同一个目录下,方便追溯。
第三,分组统计用了summaryBy()配合匿名函数,返回的统计量可以自由组合。注意这里用了na.rm = TRUE,因为分组后每组样本量可能很小,缺失值的影响会被放大。
第四,缺失值概览单独做了一张表。很多人只看summary()里的NA's计数,但不知道缺失比例。加一列百分比,一眼就能判断哪些变量需要插补、哪些可以直接丢弃。
第五,批量直方图用for循环加.data[[v]]的写法,这是ggplot2在编程环境中正确引用变量名的标准做法。直接写aes(x = v)会报错,因为aes默认按非标准求值处理。
第六,导出环节把三份结果分别存成 CSV。我一般还会加一个时间戳后缀,比如paste0("overall_stats_", Sys.Date(), ".csv"),这样每次运行都会生成新文件,不会覆盖历史结果。
从那以后我每次拿到新数据,第一件事就是改路径和变量名,然后source()整个脚本,三分钟内拿到一份完整的描述性统计报告。这个习惯帮我省掉了大量重复劳动,也让我在早期就能发现数据里的各种问题。希望帮到你。
本文还有配套的精品资源,点击获取