R语言描述性统计全流程指南:从数据清洗到分组分析 简介面向R语言初学者与数据科学从业者的描述性统计分析指南系统梳理了从基础函数到高级扩展的完整知识路径。内容围绕summary()、sapply()等内置函数展开说明如何快速获取最小值、最大值、四分位数、均值等核心统计量理解数据集的中心趋势与离散程度随后逐步深入到自定义函数计算偏度、峰度并介绍Hmisc包与psych包中describe()函数的详备输出。分组分析部分清晰演示aggregate()、by()和doBy包summaryBy()的适用场景便于按类别比较不同子集的统计特征同时配合ggplot2绘制的直方图与箱线图直观呈现数据分布形状并帮助识别异常值。每个函数均配有可运行的R代码示例方便读者边学边练。文档以PDF格式发布全包仅1个文件、约98KB足够轻量却覆盖全面。目前已有552人学习既是数据分析课堂的补充读物也是项目前期快速探索数据集时的高效参考。1. 描述性统计不是算个平均数R语言里这一步决定了后面所有分析的可靠性拿到一批数据很多人第一件事就是跑回归、建模型结果输出了一堆看似显著的数字回头一看连数据里有几列是字符型都没搞清楚。在R语言中做描述性统计分析本质上就是在建模之前先把数据“摸一遍”——每一列是什么类型、有没有缺失、分布长什么样、不同组之间差异大不大。这一步做扎实了后面所有推断统计才有意义做敷衍了后面翻车了都不知道该怪谁。这个指南适合三类人刚接触R、想把Excel习惯迁移过来的分析师写数据报告时需要快速给出分组均值、中位数、方差的从业者以及已经在跑模型但总觉得结果“哪里不对劲”的开发者。R语言的描述性统计并不难难的是知道每个函数在背后做了什么、参数怎么设、边界在哪。下面从数据体检开始一步一步把整个流程拆开讲透。2. 数据读入后的第一轮体检变量类型、缺失值与数据分布预览描述性统计的起点不是 mean() 和 sd()而是先把数据读进来搞清楚每一列到底是什么。很多后续的“统计结果不对”根源都在这一阶段埋下了雷。2.1 读入数据后先做的三件事str()、head()、summary()我一般拿到一份数据不管它是 CSV、Excel 还是从数据库导出的第一件事永远是先跑三个函数str()、head()、summary()。这三个函数分别回答了“数据结构是什么”“数据长什么样”“数据分布大概什么样”是R里面做描述性统计之前的标准体检动作。# 读取一份CSV数据 df - read.csv(sales_data.csv, stringsAsFactors FALSE) # 第一步看结构 str(df) # 第二步看前几行 head(df, 10) # 第三步看整体摘要 summary(df)str() 的输出会告诉你每一列的类型——是 numeric、character、integer 还是 factor同时会显示每列的前几个取值。这个信息极其关键因为 R 对不同类型的数据处理方式完全不同numeric 可以算均值character 只能算频数。head() 则用来肉眼检查数据是不是读歪了比如某列应该全是数字结果混进去了“N/A”这种文本这时候 head() 一眼就能看出来。summary() 是R自带的描述性统计快速通道对数值列它会输出最小值、下四分位数、中位数、均值、上四分位数、最大值对 factor 和 character 列会输出频数统计对存在缺失值的列还会单独标出 NA 的数量。2.2 变量类型错了所有统计量都是白算类型转换的时机与代价字符串类型的数值列是描述性统计里最常见的“隐形杀手”。最典型的现象是你读入的数据里有一列明明是销售额但summary() 输出里没有 Min./Median/Mean取而代之的是一长串文本频数。这说明该列被R识别成了 character 类型。为什么会出现这种情况通常是因为源数据里某个单元格混入了“12,345”这种带千分位分隔符的写法或者有几行写成了“-”表示无数据R为了不丢失信息干脆把整列都按字符处理。# 查看该列的原始内容确认问题单元格 table(df$sales) # 清理逗号和特殊符号再转换为数值 df$sales_clean - as.numeric(gsub([,-], , df$sales)) # 转换后核对类型和摘要 str(df$sales_clean) summary(df$sales_clean)这个过程中最需要留意的是 as.numeric() 的返回值如果某个单元格无法转换R不会报错而是返回 NA 并给出警告。很多新手看到 warning 就忽略结果后面算均值时发现少了一堆数据。我建议转换后立刻对比一下 sum(is.na(df$sales_clean)) 和 sum(is.na(df$sales))确认新增的 NA 数量是否合理。如果新增的 NA 数量恰好等于之前看到的异常值单元格数量说明转换成功如果多出很多说明原始数据里还有没发现的脏数据这时候要认真检查源文件。2.3 缺失值的处理策略na.rmTRUE 不是万能解药R里面几乎所有的描述性统计函数都带一个 na.rm 参数。na.rmTRUE 的意思是“计算前先把缺失值删掉”。这个参数好用但也容易让人养成坏习惯一遇到缺失就 na.rmTRUE从不追究缺失值从哪来、为什么缺失。# 错误示范什么都不管直接算 mean(df$sales_clean) # 正确示范先统计缺失情况 na_count - sum(is.na(df$sales_clean)) cat(缺失数量, na_count, 占比, round(na_count / nrow(df) * 100, 2), %\n) # 缺失较多时先观察缺失行在其他列上的表现 df[is.na(df$sales_clean), ]如果缺失占比低于 5%可以直接 na.rmTRUE 不管它对结果影响不大如果缺失占比在 5% 到 20% 之间需要检查缺失行是否集中在某个时间段或某个分组——比如某个月的销售额全是缺失那可能是当月数据没录而不是随机缺失如果缺失超过 20%用 na.rmTRUE 算出来的均值基本没有代表性这时候应该考虑是数据质量问题还是业务口径问题而不是急着算统计量。描述性统计的可靠性建立在“你清楚知道数据里有什么”这个前提上缺失值的排查是其中绕不开的一步。3. 集中趋势与离散程度的度量从 mean、median 到 IQR 的完整工具箱描述性统计的核心目标是用少数几个数字概括一批数据的特征。这批数字分两类集中趋势数据中心在哪和离散程度数据分散到什么程度。R 里对应的函数并不复杂但参数和数据形态的适配才是真正的分水岭。3.1 mean()、median()、which.max()三个函数解决“典型值”问题集中趋势最常用的指标是均值mean和中位数median。均值对异常值极其敏感——一批数据里只要混入一个 100 万均值可能从几千直接跳到几万而中位数几乎不受影响。所以我的习惯是先看 summary() 里 Mean 和 Median 的差值如果两者相差超过 20%就说明数据大概率存在偏态分布这时应重点报告中位数而不是均值。# 计算集中趋势指标 x - df$sales_clean # 均值、中位数、截尾均值 mean_value - mean(x, na.rm TRUE) median_value - median(x, na.rm TRUE) # 截尾均值去掉前后各 5% 的数据后再算均值 trimmed_mean - mean(x, na.rm TRUE, trim 0.05) # 众数R 没有内置函数用 table() which.max() 实现 mode_value - names(table(x))[which.max(table(x))] # 输出结果 cat(均值, mean_value, 中位数, median_value, 截尾均值, trimmed_mean, 众数, mode_value, \n)这里重点说明两个参数。第一个是 na.rmTRUE这个参数在集中趋势计算中几乎是必填的因为缺失值在数据集中太常见了。第二个是 trim这个参数容易被忽略但非常实用——trim0.05 的含义是排序后砍掉最前和最后各 5% 的数据再对剩下的 90% 求均值。它比单纯的中位数更稳健比纯均值更有代表性对于极端值较多的业务数据比如订单金额、响应耗时截尾均值往往比普通均值更接近真实水平。众数在 R 中没有独立函数实际工作中用得不多但 by 分组统计时偶尔会用到table() 加 which.max() 这个写法足够应付 90% 以上的场景。3.2 sd()、var()、IQR()、range()离散程度的四个维度集中趋势告诉你数据的“中心”离散程度告诉你数据的“宽度”。R 中对应的函数是 sd() 算标准差、var() 算方差、IQR() 算四分位距、range() 算极值范围。这四个指标各有各的应用场景标准差和方差描述数据围绕均值的平均波动幅度适合近似正态分布的数据。IQR上四分位数减下四分位数稳健、不受极端值影响适合偏态数据。range()返回最小值和最大值用来快速判断数据的取值范围是否合理。# 计算离散程度 x - df$sales_clean # 标准差与方差 sd_value - sd(x, na.rm TRUE) var_value - var(x, na.rm TRUE) # 四分位距与极值 iqr_value - IQR(x, na.rm TRUE) range_value - range(x, na.rm TRUE) # 自定义输出 cat(标准差, sd_value, \n) cat(四分位距, iqr_value, \n) cat(取值范围, range_value[1], 到 , range_value[2], \n)这里有一个细节值得多说一句sd() 使用的是样本标准差公式分母是 n-1 而不是 n。在 R 里默认所有方差和标准差都是按样本数据处理的如果你做的是描述性统计而不是推断统计样本标准差也是标准做法不需要额外改参数。还有一点IQR() 函数在 R 中默认使用 type7 的分位数算法这跟 SPSS、Python 的默认算法一致但如果你跟某份旧报告核对数字时发现 IQR 对不上可以先查一下对方用的是哪个分位数类型避免白费力气排查。3.3 summary() 和 fivenum()快速输出结构化的统计报告当需要快速浏览一批数值变量的统计特征时summary() 是最方便的入口。它对数据框直接调用时会遍历所有列并自动选择合适的方式输出——数值列输出六数概括因子列输出频数。而 fivenum() 是一个更底层的函数直接返回最小值、下四分位数、中位数、上四分位数、最大值这五个数不包含均值适合做自定义输出时的底层数据来源。# 对数值列批量执行概要统计 summary(df[, c(sales_clean, cost, profit)]) # 单独看某一列的五数概括 fivenum(df$sales_clean, na.rm TRUE)fivenum() 有一个特殊之处它的分位数计算方式跟 summary() 和 quantile() 不一样用的是最朴素的二分法。结果在大多数情况下非常接近但在样本量小比如 n11或数据存在大量重复值时会略有偏差。如果你的报告需要跟其他工具核对数字建议统一用 quantile() 替代 fivenum()因为 quantile() 可以通过 type 参数精确控制算法可复现性更强。summary() 则更适合放在分析报告的最前面让读者三秒钟内知道这批数据的体量和大致分布。4. 分组描述与分布形态从整体统计到切片对比整体描述性统计只回答了“这批数据整体怎么样”但在实际业务中更常见的问题是“华东区和华南区的销售额差异大吗”“高价值客户和低价值客户的消费金额分布有什么不同”。这就是分组描述性统计的用武之地。4.1 group_by() summarise()R语言中最常用的分组统计组合R 有两个流派可以做分组描述——基础 R 的 aggregate() 和 tidyverse 的 dplyr 包。我强烈推荐 dplyr 的组合group_by() 负责指定分组变量summarise() 负责定义要计算的统计量。写起来清晰扩展性强而且在分组后可以同时计算多个指标一次搞定。# 加载 dplyr library(dplyr) # 按区域分组计算多个描述性统计量 df %% group_by(region) %% summarise( n n(), # 样本量 avg_sales mean(sales_clean, na.rm TRUE), # 均值 med_sales median(sales_clean, na.rm TRUE), # 中位数 sd_sales sd(sales_clean, na.rm TRUE), # 标准差 iqr_sales IQR(sales_clean, na.rm TRUE), # 四分位距 missing_count sum(is.na(sales_clean)) # 缺失值数量 )summarise() 里可以放任意多个统计函数每一行是一个分组每一列是一个指标。我特别建议把 n()样本量和缺失值数量一起放在输出里因为只看均值不看样本量的话很容易被小样本分组带偏。比如某分组只有 3 条数据均值再高也没有实际参考价值。此外group_by() 支持多变量分组比如 group_by(region, channel) 就能得到“每个区域的每个渠道”的统计表——这在做交叉分析时非常有用。4.2 分位数与分布形态quantile()、skewness()、kurtosis() 的三个判断依据描述性统计不仅要看中心和数据宽度还要判断数据分布的形状。两个经典的指标是偏度skewness衡量分布是否对称和峰度kurtosis衡量分布的尖峭程度。R 基础包里没有内置这两个函数最常见的做法是加载 e1071 包也可以自己写一个简化版公式。# 加载 e1071 包 library(e1071) # 计算偏度和峰度 skew_value - skewness(df$sales_clean, na.rm TRUE) kurt_value - kurtosis(df$sales_clean, na.rm TRUE) cat(偏度, skew_value, \n) cat(峰度, kurt_value, \n) # 直接基于分位数判断分布形状 quantile(df$sales_clean, probs c(0.25, 0.5, 0.75), na.rm TRUE)对偏度的解读有一个通用的经验阈值偏度绝对值小于 0.5分布基本对称0.5 到 1 之间是轻度右偏或左偏大于 1 则属于严重偏态均值失去代表性。峰度的解读稍微复杂一些但记住一条就够了标准正态分布的峰度是 0有的软件输出的是 3比如 SPSS差异源自算法基准不是算错了峰度大于 0 意味着数据比正态分布更尖、尾部更厚极端值出现的概率更高。quantile() 的 probs 参数用于按百分比切分数据这在报告“P25 到 P75 之间包含了多少数据”时非常直观。4.3 分组后还需要注意dplyr 的 group_by 状态不会自动解除很多人用 dplyr 做一次分组统计后紧接着做其他操作时发现结果还是按照原来的分组输出一头雾水。这是 group_by() 的一个设计特性group_by() 会修改数据框的“分组属性”而这个属性会一直保留直到你主动调用 ungroup() 解除。# 先分组统计 df_grouped - df %% group_by(region) %% summarise(avg_sales mean(sales_clean, na.rm TRUE)) # 再对整个数据框做处理 df_summary_total - df_grouped %% ungroup() %% # 解除分组避免意外行为 summarise(overall_avg mean(avg_sales))如果不写 ungroup()第二段代码里 summarise(overall_avg mean(avg_sales)) 会按照 region 分组计算每一组的平均值而不是整个数据框的总平均值得到的结果看起来跟预期相近但数字偏大或偏小非常隐蔽。这个坑在数据分析流程较长、管道链路较多的时候尤其容易翻车。我的习惯是分组统计结束后立刻 ungroup()宁可多写一行也不给后续操作留下隐患。5. 描述性统计避坑指南六个最容易翻车的细节与排查方法描述性统计看起来简单实际执行时坑一点也不少。这章把最常遇到的六个问题按“现象 → 原因 → 解决”拆开讲每一个都是真实项目中踩过的。5.1 factor 类型列用 summary() 输出了一长串“频数表”看不到均值现象summary(df$region) 输出的不是统计描述而是每个区域的样本计数。原因region 被识别成了 factor 类型或者字符串summary() 对因子变量的处理方式就是对每个水平计数。解决先确认该列是否本就该是分类变量——如果是频数表本身就是正确的描述性统计如果该列应该是数值比如编号是 001、002需要先转成数值类型再计算。# 判断类型 class(df$region) # 如果确实是分类变量频数统计就是正确用途 table(df$region)5.2 sd() 返回 NA 而不是报错现象sd(df$sales_clean) 输出 NA没有任何报错信息。原因列里存在缺失值而 sd() 默认不忽略 NA返回 NA 是种“安全失败”的机制。解决加上 na.rmTRUE。注意一个更隐蔽的变体如果数据分组后某组只有一个样本sd() 的公式 n-1 会导致除以零同样返回 NA——这是数学定义决定的不是函数的 bug可以通过在 summarise 中增加 n() 检查来识别这类样本量过小的分组。5.3 mean() 的结果与手工计算的不一致现象在 Excel 里选中数据算出来的平均值跟 R 里 mean() 的结果相差很大。原因最常见的原因是 R 把某些看起来是数值的单元格按 character 处理了比如“12,345”这种带逗号的写法mean() 无法计算或者计算时把非数值元素排除但没告诉你。解决先跑 str() 检查类型再跑 sum(is.na(x)) 检查缺失最后确认是否有异常值导致均值偏移必要时用 median() 或 trim 参数交叉验证。5.4 分组统计时某组数据全部被忽略现象group_by(region) 后输出里少了某个区域。原因该组的数据全部是 NA或者该组的分组变量本身包含空字符串和 NAgroup_by() 默认剔除 NA 分组。解决检查分组变量中是否有 NA 或空值可以用 table(df$region, useNA ifany) 查看缺失作为一个独立水平的情况如果 NA 本身是有效分组比如“未知区域”需要先把这个信息反映到数据里用显式字符串替换。5.5 量纲不一致导致统计结果没有意义现象数据框里有销售额元和毛利率%两列统一跑 summary() 后输出的最小值和最大值跨度巨大看不出任何规律。原因量纲不同的变量放在同一张表格里比较本身没有意义。解决在实际项目中描述性统计应分组输出或者对数值列做标准化。常用处理是对数值列分别标准化或者针对“必须对比”的列使用 scale() 统一量纲后输出但这会改变数据含义需要谨慎。5.6 忘了看警告信息现象as.numeric() 转换后出现 Warning message直接忽略继续算。原因R 的 many warnings 容易被忽视但警告信息里通常会告诉你“强制转换引入 NA”。解决养成立刻查看警告的习惯可以用 warnings() 查看完整内容确认有多少个值被转成了 NA再判断是否影响最终结论。在重复性的数据处理流程中我会显式输出检查语句# 转换前看原始列里有多少个非数字的单元格 non_numeric - grep([^0-9.-], df$sales, value TRUE) length(non_numeric)6. 把描述性统计结果批量输出一键生成多变量统计表的实用技巧描述性统计做了很多次之后你会发现瓶颈不在计算而在“写代码的重复性”。每次都是读数据、跑 summary、分组统计、写报告这套流程完全可以封装成一个自定义函数。我提供一个在实际项目中用了很久的模板输入一个数据框和需要分析的数值列名输出一张包含均值、中位数、标准差、IQR、缺失值数量和有效样本量的综合表格。# 自定义批量描述性统计函数 describe_df - function(data, cols) { result - lapply(cols, function(col) { x - data[[col]] data.frame( variable col, n_total length(x), n_missing sum(is.na(x)), mean mean(x, na.rm TRUE), median median(x, na.rm TRUE), sd sd(x, na.rm TRUE), iqr IQR(x, na.rm TRUE), q25 quantile(x, probs 0.25, na.rm TRUE), q75 quantile(x, probs 0.75, na.rm TRUE) ) }) # 合并成一张表 do.call(rbind, result) } # 使用示例 num_cols - c(sales_clean, cost, profit) desc_table - describe_df(df, num_cols) print(desc_table)lapply 的作用是对 cols 里的每一列执行同一个计算逻辑然后把结果合并成数据框。do.call(rbind, result) 把列表里的多个数据框按行堆叠形成一张纵向的汇总表。这个函数有几个可以扩展的地方如果想加入分组逻辑可以在函数内部加一个 group_var 参数把 lapply 替换成 split() 加 lapply如果想输出到 Excel可以配合 writexl 包把 desc_table 写成 xlsx。这样整个描述性统计环节就从一个每次都要重写的“项目”变成了一个稳定、可复用、可以日后再用的“工具”。把最繁琐、最容易出错的部分封装起来每次只关注数据质量和业务解释这才是使用 R 做描述性统计分析的正确打开方式希望帮到你。本文还有配套的精品资源点击获取