简介这份PDF资料面向R语言初学者与数据分析入门者系统讲解描述性统计分析的完整方法体系帮助读者从基础函数过渡到分组分析与可视化实践。内容覆盖summary()、sapply()等内置函数偏度与峰度的自定义函数写法Hmisc与psych包的describe()进阶用法以及aggregate()、by()、doBy包的summaryBy()分组统计方案并配合ggplot2绘制直方图与箱线图展示数据分布特征。资源包共1个PDF文件约98KB篇幅精炼、代码示例完整便于随查随用。目前已有553人学习适合希望快速掌握R语言描述性统计技能、为后续数据挖掘与机器学习打基础的读者参考。1. 描述性统计不是跑个 summary 就完事一份能直接复现的 R 语言分析底稿很多人拿到一份数据第一反应是summary(df)敲下去看到一串 min、max、median 就以为描述性统计做完了。真到写报告或者给业务方解释的时候才发现均值被极端值拉飞了、分组之后整体分布完全反过来、偏度峰度压根没算这些坑一个都躲不掉。这份 R 语言描述性统计分析指南本质上是一套从基础函数到分组聚合再到可视化的完整操作底稿覆盖summary()、sapply()、自定义偏度峰度函数、Hmisc与psych两个 describe 实现、aggregate()/by()/summaryBy()三种分组路线以及ggplot2的分布图。适合刚上手 R 的分析新人照着抄也适合有基础但想把手头统计流程规范化的从业者对照检查。下面按「先立住原理、再跑通代码、最后避开坑」的顺序拆。2. 基础函数与自定义统计量从 summary() 到偏度峰度的可控计算2.1 为什么 summary() 只能当第一眼不能当结论summary()的价值在于快它一次性给出数值型变量的最小值、下四分位数、中位数、均值、上四分位数、最大值对因子和逻辑向量则给频数。这个输出适合做数据体检的第一道筛查看最小值最大值有没有离谱的量纲错误看中位数和均值差多远来判断偏斜方向。但它的问题也很直接——不给标准差、不给方差、不给偏度峰度样本量 n 也不在输出里。也就是说summary()能告诉你「数据长什么样」但撑不起「数据分布形状如何」这个结论。我一般会把它当成流水线的第一站跑完立刻做两件事一是看均值和中位数的差距差距大就说明有偏或者有极端值后面必须补箱线图和直方图二是看四分位距如果 Q3 减 Q1 很小而 max 很大基本可以判定右侧有长尾。这两步判断不需要额外代码但能决定后面要不要上稳健统计量。# 加载内置数据集 mtcars作为全流程的演示数据 data(mtcars) # 选定三个数值型变量每加仑英里数、马力、车重 myvars - c(mpg, hp, wt) # 第一眼体检四分位数 均值 极值 summary(mtcars[myvars])这段代码里myvars是一个字符向量作用是先把分析范围圈定避免在几十列的宽表里反复写列名。mtcars[myvars]用字符向量做列索引返回的是一个只含三列的数据框这是 R 里做变量子集最稳的写法之一。参数上没有玄学唯一要注意的是summary()对字符型列会给频数而不是统计量所以选列时最好先str()看一眼类型。2.2 sapply() 批量套函数一次算清均值、标准差、极值sapply()的作用是对列表或向量的每个元素应用同一个函数并尽量把结果简化成向量或矩阵。放到数据框上它会对每一列分别调用你给的函数返回一个以列名为名字的向量。这个特性让它特别适合批量算描述性统计量——你不需要为均值、标准差、方差各写一遍循环。# 对三列分别计算均值返回命名向量 sapply(mtcars[myvars], mean) # 标准差衡量离散程度单位与原变量一致 sapply(mtcars[myvars], sd) # 方差标准差的平方量纲被平方解释时要小心 sapply(mtcars[myvars], var) # 中位数与极值用于和均值对照判断偏斜 sapply(mtcars[myvars], median) sapply(mtcars[myvars], range)这里的关键理解是sapply的第二个参数是一个函数对象mean、sd、var都是现成函数直接传名字即可不要写成mean()。如果你传了带括号的调用R 会先求值再把结果当参数传进去直接报错。另一个常见需求是处理缺失值mean和sd都支持na.rm TRUE写法是sapply(mtcars[myvars], mean, na.rm TRUE)多余参数会透传给被调用的函数这是sapply一个很好用但容易被忽略的细节。2.3 自定义函数算偏度与峰度公式、na.omit 与返回值设计偏度和峰度不在基础函数里必须自己写。偏度衡量分布的不对称方向峰度衡量尾部厚度相对于正态分布的偏离。项目里给的实现用的是总体矩公式偏度等于三阶中心矩除以标准差三次方再除以 n峰度等于四阶中心矩除以标准差四次方再除以 n 再减 3。减 3 是为了让正态分布的峰度归零这样解释起来直观——正值表示尖峰厚尾负值表示平顶。# 自定义描述性统计函数 # na.omit 控制是否在计算前剔除缺失值 mystats - function(x, na.omit FALSE) { if (na.omit) { x - x[!is.na(x)] # 逻辑索引剔除 NA } 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)这段函数有三个设计点值得说。第一na.omit参数默认FALSE意味着有缺失值时结果会是NA这是故意的——静默剔除缺失值会让样本量悄悄变化报告里对不上数。第二返回值用命名向量而不是列表是为了让sapply能把多列结果拼成一个矩阵行是统计量、列是变量一眼能横向比较。第三sd(x)在 n 等于 1 时返回NA所以这个函数不适合对单观测分组使用后面分组聚合时会再提到这个边界。提示偏度峰度的公式有样本版和总体版之分不同软件默认口径不完全一致。跨工具对比数值前先确认对方用的是哪一种否则小数点后的差异会被误读成数据问题。3. Hmisc 与 psych 两套 describe()选哪个、输出怎么读3.1 Hmisc::describe() 的输出结构与适用场景Hmisc包的describe()走的是「详细报告」路线。它对数值型变量给出观测数、缺失数、唯一值数、均值、分位数、标准差以及低位数高位数各五个的明细还会顺带给出偏度峰度。对因子型变量则给频数表。这个函数的输出是文本报告形式信息密度高适合在探索阶段直接打印到控制台通读也适合贴进分析日志。# 安装只需一次已装过可跳过 install.packages(Hmisc) library(Hmisc) # 对选定列生成详细描述性统计报告 describe(mtcars[myvars])describe()不需要额外参数就能跑但有两个实用开关describe(x, digits 3)控制小数位避免输出过长对含大量唯一值的变量它会提示唯一值过多这时候要警惕是不是把 ID 列误选进来了。它的短板是结果不易直接转成数据框做后续加工想要结构化输出还得自己解析或者换psych。3.2 psych::describe() 返回数据框更适合流水线与导出psych包的describe()返回的是一个数据框列包括变量名、有效样本量、均值、标准差、中位数、截尾均值、绝对中位差、最小值、最大值、偏度、峰度、标准误。这个结构最大的好处是可以直接接write.csv()导出或者接dplyr做筛选排序。做批量报告、多数据集横向对比时我基本都用这一套。install.packages(psych) library(psych) # 返回数据框便于后续加工与导出 desc_tab - describe(mtcars[myvars]) print(desc_tab) # 只保留关心的几列并导出 out - desc_tab[, c(vars, n, mean, sd, median, skew, kurtosis)] write.csv(out, desc_summary.csv, row.names FALSE)参数上describe()支持fast TRUE跳过一些耗时计算数据量大时能明显提速quant参数可以指定要哪些分位数。要注意的是它默认对缺失值做剔除处理n列反映的是有效样本量如果各变量缺失情况不同n会不一致报告里必须把这个差异写清楚不能默认所有变量都是同一个样本量。3.3 两套实现的取舍报告型输出 vs 结构化输出把两者放在一起对比选择逻辑就清楚了。维度Hmisc::describe()psych::describe()返回类型文本报告数据框分位数明细低五位 高五位可配置分位数偏度峰度有有直接导出不方便一行 write.csv适合阶段探索通读批量报告、流水线我的习惯是探索阶段用Hmisc通读一遍找异常定稿阶段用psych生成结构化表格进报告。两个包都装不冲突函数名相同但用::限定命名空间就不会打架这一点在脚本里务必写清楚别只library两个包然后直接调describe()谁覆盖谁取决于加载顺序这种玄学问题排查起来很浪费时间。4. 分组描述性统计aggregate、by、summaryBy 三条路线怎么选4.1 aggregate() 公式接口单统计量最快aggregate()用公式接口表达「按什么分组、对什么求什么」写法紧凑适合快速看某个指标在不同组间的差异。它的局限是一次只方便算一个统计量想同时要均值和标准差得调两次或者传自定义函数返回多值。# 按气缸数分组计算 mpg 的组均值 aggregate(mpg ~ cyl, data mtcars, mean) # 一次算多个统计量FUN 返回向量结果会展开成多列 aggregate(mpg ~ cyl, data mtcars, FUN function(x) c(mean mean(x), sd sd(x), n length(x)))公式左边是待统计变量右边是分组变量data指定数据框。第二个例子里FUN返回命名向量aggregate会把每个名字变成一列这是它比很多人以为的更能打的地方。注意分组变量如果是数值型它会按唯一值分组如果本意是分成区间得先用cut()离散化。4.2 by() 分组套自定义函数多统计量一次到位by()的思路是把数据框按分组变量切开对每个子集调用同一个函数。配合前面写的mystats可以一次性拿到每组的样本量、均值、标准差、偏度、峰度。# 包装一层对子集内每列应用 mystats dstats - function(x) sapply(x, mystats) # 按 cyl 分组对三列分别算全套统计量 by(mtcars[myvars], mtcars$cyl, dstats)这里by()的第二个参数是分组向量长度必须和数据行数一致且顺序对齐。返回的是一个by类列表打印出来按组分块。它的坑在于分组里有小组时sd()会因为样本量不足返回NAmystats里的偏度峰度也会跟着变成NA或NaN这不是代码错是数据本身撑不起这个统计量报告里要如实标注而不是硬填 0。4.3 doBy::summaryBy()公式化多变量多统计量doBy包的summaryBy()把公式接口和多统计量结合得比较顺左边可以写多个变量相加右边写分组变量FUN传自定义函数。install.packages(doBy) library(doBy) # 多变量 多统计量 分组一次成型 summaryBy(mpg hp wt ~ cyl, data mtcars, FUN mystats)返回的是数据框列名形如mpg.n、mpg.mean方便后续筛选和导出。参数上要注意FUN返回的向量必须有名字否则列名会变成不好读的默认形式。三条路线里快速单指标用aggregate要全套统计量且接受列表输出用by要结构化数据框直接进报告用summaryBy按输出形态选比按函数新旧选更靠谱。5. 可视化与数值对不上ggplot2 分布图的四个排查点5.1 直方图 binwidth 决定你看到什么直方图是判断分布形状最直接的工具但binwidth选错结论会完全反过来。分箱太宽会把双峰抹成单峰分箱太窄会把噪声放大成假峰。library(ggplot2) # 直方图binwidth 控制分箱宽度直接影响形状判断 ggplot(mtcars, aes(x mpg)) geom_histogram(binwidth 1, fill blue, color black) # 对比更宽的分箱 ggplot(mtcars, aes(x mpg)) geom_histogram(binwidth 4, fill steelblue, color black)binwidth 1对 mpg 这种取值范围二十多的变量比较合适能看出大致形状又不至于碎。实操里我一般会试两到三个宽度如果形状在不同宽度下稳定结论才敢写如果一变宽度形状就变说明样本量不够支撑形状判断这时候老老实实报数值统计量别硬解读分布。5.2 箱线图看分组差异与异常值箱线图把中位数、四分位距和异常值压缩在一张图里配合分组变量能直接看出组间差异和离群点。# 按气缸数分组看 mpg 分布异常值以点显示 ggplot(mtcars, aes(x factor(cyl), y mpg)) geom_boxplot(fill lightgray)factor(cyl)这一步不能省cyl是数值型直接传给x会被当成连续变量处理箱线图就画不出来。箱线图里的点默认按 1.5 倍四分位距判定为异常值这个阈值是约定不是真理业务上是否算异常要结合量纲判断。5.3 数值表与图形互相验证的三个动作图形和数值对不上是高频翻车点我固定做三个交叉验证第一箱线图的中位数横线位置和summary()的中位数是否一致第二直方图的峰值位置和psych::describe()里的中位数、均值是否同向第三分组箱线图各组的中位数排序和aggregate()算出来的组均值排序是否一致。三者只要有一处矛盾先查缺失值处理和分组变量类型八成问题出在这两处。注意ggplot2默认会丢弃含缺失值的行并在控制台给警告图形上的样本量和数值表里的有效样本量可能不一致报告里要统一口径。6. 避坑与排查五条血泪经验6.1 均值被极端值拉飞中位数却纹丝不动现象summary()里均值远大于中位数直方图右侧拖出长尾。原因分布右偏或存在极端大值均值对极端值不稳健。解决报告里同时给均值和中位数并补一个截尾均值psych::describe()里的trimmed列必要时对变量做对数变换后再看分布别只报均值就下结论。6.2 分组后 sd 返回 NA整张表跟着崩现象by()或summaryBy()输出里某些组的标准差、偏度、峰度全是NA。原因该组有效样本量小于 2sd()无法计算后续统计量连锁失效。解决分组前先table()看各组样本量小组要么合并、要么单独说明不要用na.rm硬压压掉的是信息不是错误。6.3 两个 describe() 打架结果取决于加载顺序现象同时library(Hmisc)和library(psych)后调describe()返回类型时而是文本时而是数据框。原因同名函数被后加载的包覆盖。解决脚本里一律用Hmisc::describe()和psych::describe()显式限定命名空间别依赖加载顺序这种黑匣子行为。6.4 分组变量是数值型箱线图和聚合结果都不对现象ggplot箱线图报错或只画出一条线aggregate分组数异常多。原因分组变量没转成因子被当成连续变量处理。解决分组前统一factor()并在脚本开头用str()确认类型这一步花十秒能省半小时排查。6.5 缺失值静默剔除样本量对不上现象数值表里的 n 和原始数据行数不一致图形上的点比预期少。原因各函数对缺失值的默认处理不同有的剔除有的返回 NA。解决分析前先跑一遍colSums(is.na(df))摸清缺失分布在自定义函数里显式控制na.omit报告里写明每个统计量对应的有效样本量。7. 把统计底稿固化成可复用脚本一个函数收口全流程做到这一步零散的函数调用已经能跑通但每次换数据集都要重抄一遍代码迟早出错。我的做法是写一个收口函数输入数据框和变量名向量输出一张包含样本量、均值、标准差、中位数、偏度、峰度的结构化表同时把分组统计和图形作为可选分支挂上去。这样换数据只改变量名口径永远一致。# 收口函数一次产出结构化描述性统计表 desc_report - function(df, vars, group NULL) { sub - df[, vars, drop FALSE] # 圈定变量dropFALSE 防止单列退化成向量 base - psych::describe(sub) # 结构化基础统计量 base - base[, c(vars, n, mean, sd, median, skew, kurtosis)] if (!is.null(group)) { # 可选分组分支 g - df[[group]] if (!is.factor(g)) g - factor(g) # 强制因子避免连续变量误判 grp_tab - doBy::summaryBy( reformulate(vars, response group), # 动态拼公式避免手写字符串 data df, FUN mystats ) return(list(overall base, by_group grp_tab)) } base } # 用法整体 按 cyl 分组 res - desc_report(mtcars, c(mpg, hp, wt), group cyl) res$overall res$by_group这个函数里有几个细节是踩过坑才加的。drop FALSE保证只选一列时仍然是数据框否则psych::describe()收到的输入类型会变。reformulate()动态拼公式避免把变量名硬编码进字符串再paste后者在变量名含特殊字符时容易翻车。分组分支里先做因子转换把前面第 6.4 条的问题在入口处堵死。返回列表而不是强行合并成一张表是因为整体统计和分组统计的粒度不同混在一起反而不好读。验证这套脚本是否可靠我固定跑三个检查用mtcars这种内置数据跑一遍结果和手工summary()对得上故意插几个NA进去确认n列随之下降而不是报错把分组变量换成因子和数值两种类型各跑一次输出应当一致。三个检查都过这套底稿才敢用到真实数据上。从那以后我每次拿到新数据集都强制先走一遍「str()看类型 →colSums(is.na())看缺失 →summary()看量纲 → 收口函数出表 → 图形交叉验证」这条固定链路中间任何一步对不上就停下来查不带着疑问往下做。这套习惯帮我挡掉过不少看起来是统计问题、实际是数据类型问题的假故障。希望帮到你。本文还有配套的精品资源点击获取