使用 R 与 ggplot2 可视化数据分布:直方图与密度图实战(Data Science for Beginners 第 10 课)
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
数据科学的日常工作中,除了看"多少"(数量),更常需要回答"如何分布"(distribution)——数据沿坐标轴如何组织、集中在哪个区间、是否存在偏态。本课以明尼苏达州鸟类数据集(data/birds.csv)为例,系统讲解如何用 R 的ggplot2包构建直方图(histogram)、2D 直方图与密度图(density plot)来洞察数据分布,并处理文本型变量的可视化难题。读完本文,你将掌握bins、adjust、alpha、fill等核心参数的实际用法,并能独立完成"任意数据集 → 分布探索"的完整流程。
前置准备:加载数据并清洗异常值
本课延续上一课《使用 R 与 ggplot2 可视化数量》的工作流。在上一课中我们发现,Bald Eagle 与 Prairie Falcon 的MaxWingspan疑似多了一个 0,属于录入错误,因此先加载数据并按MaxWingspan < 500过滤掉这些异常值。
在 R 控制台中执行:
library(ggplot2) birds <- read.csv("data/birds.csv", fileEncoding = "UTF-8-BOM") birds_filtered <- subset(birds, MaxWingspan < 500) head(birds_filtered)说明:
fileEncoding = "UTF-8-BOM"用于正确处理带 BOM 头的 UTF-8 文件,避免第一列列名出现乱码。从仓库数据文件 data/birds.csv 的原始内容可以看到,文件第一行确实以 BOM 字符开头,13 列分别为Name, ScientificName, Category, Order, Family, Genus, ConservationStatus, MinLength, MaxLength, MinBodyMass, MaxBodyMass, MinWingspan, MaxWingspan。
过滤后的数据前几行如下(法语文档与英文原版中均给出同一份示例,反映的是雁鸭类水鸟的测量数据):
| 名称 | 学名 | 类别 | 目 | 科 | 属 | 保护状态 | 最小体长 | 最大体长 | 最小体重 | 最大体重 | 最小翼展 | 最大翼展 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 黑腹树鸭 | Dendrocygna autumnalis | 鸭/鹅/水禽 | Anseriformes | Anatidae | Dendrocygna | LC | 47 | 56 | 652 | 1020 | 76 | 94 |
| 茶色树鸭 | Dendrocygna bicolor | 鸭/鹅/水禽 | Anseriformes | Anatidae | Dendrocygna | LC | 45 | 53 | 712 | 1050 | 85 | 93 |
| 雪雁 | Anser caerulescens | 鸭/鹅/水禽 | Anseriformes | Anatidae | Anser | LC | 64 | 79 | 2050 | 4050 | 135 | 165 |
| 罗斯雁 | Anser rossii | 鸭/鹅/水禽 | Anseriformes | Anatidae | Anser | LC | 57.3 | 64 | 1066 | 1567 | 113 | 116 |
| 白额雁 | Anser albifrons | 鸭/鹅/水禽 | Anseriformes | Anatidae | Anser | LC | 64 | 81 | 1930 | 3310 | 130 | 165 |
先用散点图初探分布:为什么它不够用
分布探索的直觉起点是散点图——把每个观测值按类别摊开来看。按鸟类的"目"(Order)绘制MaxLength的散点,并用coord_flip()把坐标轴翻转成横向,便于阅读较长的类别名:
ggplot(data = birds_filtered, aes(x = Order, y = MaxLength, group = 1)) + geom_point() + ggtitle("Max Length per order") + coord_flip()这张图给出了不同目的鸟体长的大致分布轮廓,但它并非展示"真实分布"的最优形式:点的堆叠程度难以量化,密集区间与稀疏区间无法用视觉精确比较。这正是直方图(Histogram)的用武之地——它把连续数值轴切成若干等宽区间(bins),用柱高表示落入每个区间的观测频数。
直方图入门:bins 参数如何改变解读
ggplot2通过geom_histogram()提供直方图能力。它的形态类似柱状图,但 x 轴是连续数值变量,柱子的"升起与落下"直接反映数据的集中与分散。构建直方图的前提是数值型数据。
对过滤后的数据绘制MaxBodyMass(最大体重)的分布,先设定 10 个区间:
ggplot(data = birds_filtered, aes(x = MaxBodyMass)) + geom_histogram(bins = 10) + ylab('Frequency')可以看到,数据集里 400 多种鸟中的大多数,其最大体重落在 2000(克)以下,分布呈现明显的右偏(长尾朝右)形态。
bins是直方图最关键的分辨率参数:值越大,柱子越细,分布细节越丰富;值越小,曲线越平滑,但可能掩盖多峰结构。把bins提高到 30:
ggplot(data = birds_filtered, aes(x = MaxBodyMass)) + geom_histogram(bins = 30) + ylab('Frequency')粒度变细后,可以观察到更多局部的起伏。若希望得到一个左侧偏斜更小、形态更"规整"的分布图,可以进一步对数据范围做过滤——只保留体重在 1~60 之间的鸟,仍用 30 个区间:
birds_filtered_1 <- subset(birds_filtered, MaxBodyMass > 1 & MaxBodyMass < 60) ggplot(data = birds_filtered_1, aes(x = MaxBodyMass)) + geom_histogram(bins = 30) + ylab('Frequency')✅ 动手练习:尝试更换不同的过滤区间与数据点(例如
MinLength、MaxWingspan)。若想观察数据的完整分布,可去掉体重过滤,直接对birds_filtered绘制带标签的分布图,比较"全量"与"局部"两种视角的差异。
二维直方图:同时比较两个分布的关系
直方图还能提供更强的颜色与标注能力。当需要比较两个分布之间的关系时,可以使用geom_bin2d()构建二维直方图:x 轴与 y 轴各为一个数值变量,格子颜色越亮代表该区域的观测越密集。
以MaxBodyMass与MaxLength为例,ggplot2内置的viridis色板用更鲜艳的颜色凸显高密度收敛点:
ggplot(data = birds_filtered_1, aes(x = MaxBodyMass, y = MaxLength)) + geom_bin2d() + scale_fill_continuous(type = "viridis")从图中可以看出,两者沿一条预期的对角线方向存在相关性,并在某个区间出现一个特别强的收敛点——体重中等偏小的鸟占据了样本的主体。二维直方图是快速判断"两变量是否共同集中"的有效工具。
文本数据的分布:以 IUCN 保护状态为例
直方图默认针对数值数据效果最佳。那么面对文本型(分类)数据时,如何考察分布?
鸟类数据集中还包含类别(Category)、属(Genus)、科(Family)以及保护状态(ConservationStatus)等文本信息。我们关心的问题是:不同保护状态下的鸟,其最小翼展分布如何?
首先需要理解保护状态缩写,它们来自IUCN 红色名录(IUCN Red List)的物种状态分类:
- CR:极危(Critically Endangered)
- EN:濒危(Endangered)
- EX:灭绝(Extinct)
- LC:无危(Least Concern)
- NT:近危(Near Threatened)
- VU:易危(Vulnerable)
由于ConservationStatus是文本值,直接作为直方图的 x 轴无法工作,需要做一步重编码变换:把各状态映射为有序的数值/字符标签(x1~x6),使它们能按逻辑顺序参与绘图:
birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'EX'] <- 'x1' birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'CR'] <- 'x2' birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'EN'] <- 'x3' birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'NT'] <- 'x4' birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'VU'] <- 'x5' birds_filtered_1$ConservationStatus[birds_filtered_1$ConservationStatus == 'LC'] <- 'x6'接下来,把MinWingspan作为 x 轴、ConservationStatus作为填充色,绘制叠加直方图。这里用到三个关键参数:
position = "identity":各状态的柱子不堆叠,而是直接叠加,便于比较形状差异;alpha = 0.4:设置透明度,避免相互遮挡时完全看不清;scale_fill_manual():手动指定填充颜色与图例标签。
ggplot(data = birds_filtered_1, aes(x = MinWingspan, fill = ConservationStatus)) + geom_histogram(position = "identity", alpha = 0.4, bins = 20) + scale_fill_manual( name = "Conservation Status", values = c("red", "green", "blue", "pink"), labels = c("Endangered", "Near Threatened", "Vulnerable", "Least Concern") )结果显示:最小翼展与保护状态之间没有明显的相关性——不同状态类别的分布相互重叠,形态接近。这是一个典型的"负结果"探索,同样有价值:它说明仅凭翼展这一维度无法区分濒危与否。你可以用同样的方法测试数据集中的其他要素(如体长、体重、目),或者更换不同的过滤条件,看看能否发现某种关联。
密度图:让分布曲线流动起来
细心的读者会发现,此前所有直方图都是"阶梯状"的——柱子边缘生硬,无法形成平滑的拱形轮廓。若要展示更平滑的密度形态,应改用密度图(density plot),ggplot2中对应geom_density()。
先对birds_filtered_1的MinWingspan绘制密度曲线:
ggplot(data = birds_filtered_1, aes(x = MinWingspan)) + geom_density()可以看到,这条曲线与之前的直方图形状相呼应,但变得平滑流畅。同理,之前那张"锯齿感"较强的MaxBodyMass直方图(30 bins 版本),也可以用密度图重新表达,得到非常光滑的形态:
ggplot(data = birds_filtered_1, aes(x = MaxBodyMass)) + geom_density()adjust 参数:控制平滑程度
密度图默认的平滑程度未必适合所有数据。若希望曲线"平滑但不过度平滑",可调整adjust参数——它控制核密度估计的带宽缩放倍数,值越小,曲线对局部细节越敏感、越显粗糙:
ggplot(data = birds_filtered_1, aes(x = MaxBodyMass)) + geom_density(adjust = 1/5)对比上一张图可以看出,adjust = 1/5保留了更多局部起伏,同时整体仍比直方图平滑。
✅ 动手练习:查阅
geom_density()的其他参数(如bw、kernel、trim)并逐一实验,理解它们对曲线形态的影响。
按分组填充:一次看遍所有目的密度
密度图组合能力极强——只需几行代码,就能把所有目的体重密度叠加展示。给Order加上fill并设置透明度alpha = 0.5,各目之间的分布差异一目了然:
ggplot(data = birds_filtered_1, aes(x = MaxBodyMass, fill = Order)) + geom_density(alpha = 0.5)这种"分组密度叠加"是直方图难以媲美的:它既展示了每个组的分布形态,又通过半透明填充保留了组间重叠信息,非常适合横向比较。
深入一步:geom_density_2d 与自学方向
密度图家族还有一个进阶成员:geom_density_2d(),即"一维或多维的连续概率密度曲线"。它在二维平面上用等高线刻画两个变量联合分布的密度,可视为本文二维直方图(geom_bin2d)的平滑版本。建议阅读官方文档理解其工作机制,并尝试与geom_point()叠加使用。
🚀 挑战:直方图的行业应用调研
直方图是一种比基础散点图、柱状图、折线图更"进阶"的图形类型。请上网搜索直方图的优秀应用案例:它通常被用于哪些场景、能够证明什么问题、集中在哪些学科领域(如统计学、医学检验、质量控制、影像处理等)?结合你找到的案例,回到本数据集继续试验。
复习小结
本课围绕"分布可视化"完成了从入门到进阶的完整路线:
- 数据准备:读取 data/birds.csv,用
subset(birds, MaxWingspan < 500)剔除异常值; - 直方图:
geom_histogram()+bins控制粒度,过滤子集改善偏态; - 二维直方图:
geom_bin2d()+viridis色板考察双变量收敛关系; - 文本数据分布:IUCN 状态重编码 +
fill/alpha/scale_fill_manual叠加直方图; - 密度图:
geom_density()平滑分布,adjust控制平滑程度,按Order分组叠加。
每次可视化前都要问自己:数据是数值型还是文本型?我想回答的是"集中在哪里"还是"组间有何差异"?这决定了直方图、二维直方图还是密度图更合适。
课后作业:技能实战
将本课技术迁移到新数据集:从 Kaggle 等平台挑选一个你感兴趣的数据集,编写 R 脚本讲述其中故事,并确保至少使用 5 个直方图来发现数据事实。评分标准可参考关联作业文档 translations/fr/3-Data-Visualization/R/10-visualization-distributions/assignment.md:
| 优秀 | 合格 | 待改进 |
|---|---|---|
| 脚本含数据集来源等完整注释,且至少使用 5 个直方图发现数据事实 | 脚本注释不完整或存在错误 | 脚本无注释且包含错误 |
相关资源:本课配套 Python 版本见 3-Data-Visualization/10-visualization-distributions/README.md,上一课《可视化数量》见 3-Data-Visualization/R/09-visualization-quantities/README.md,英文原版分布课文档见 3-Data-Visualization/R/10-visualization-distributions/README.md。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考