用 ggplot2 可视化数量数据:Data-Science-For-Beginners 鸟类数据集实战指南(R 语言版)
2026/9/22 13:01:04 网站建设 项目流程

用 ggplot2 可视化数量数据:Data-Science-For-Beginners 鸟类数据集实战指南(R 语言版)

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

本篇文章源自 Data-Science-For-Beginners 仓库第 9 课「Visualizing Quantities」的 R 语言版本,核心目标是带领读者用ggplot2围绕"数量(quantity)"这一概念,基于明尼苏达州鸟类清洗数据集完成从折线图、散点图到柱状图的完整可视化实战。读完本文,你将掌握图形语法(Grammar of Graphics)的绘图心智模型、ggplot2的图层式 API 用法、异常值的发现与过滤方法,以及用dplyr/tidyverse对分组数据进行聚合后再绘图的完整链路。

课程背景与数据集准备

本课使用的数据是仓库根目录 data/birds.csv,它是一份经过清洗的明尼苏达州鸟类数据集。与同课 Python 版本(3-Data-Visualization/09-visualization-quantities/README.md,使用 Matplotlib)不同,本 R 语言版本的教学环境是 R 控制台(R console),配套的完整教学文档位于 3-Data-Visualization/R/09-visualization-quantities/README.md,所有绘图的输出图片可参考 3-Data-Visualization/R/09-visualization-quantities/images。

首先打开 R 控制台,读取数据集并查看前 5 行:

birds <- read.csv("../../data/birds.csv", fileEncoding = "UTF-8-BOM") head(birds)

提示:数据文件位于仓库根目录的/data文件夹下,read.csv中的相对路径依据你启动 R 会话的位置调整;fileEncoding = "UTF-8-BOM"用于正确处理带 BOM 头的 UTF-8 编码 CSV,避免列名出现乱码。

head()返回的数据是文本与数字的混合体,包含 13 个字段:

NameScientificNameCategoryOrderFamilyGenusConservationStatusMinLengthMaxLengthMinBodyMassMaxBodyMassMinWingspanMaxWingspan
0Black-bellied whistling-duckDendrocygna autumnalisDucks/Geese/WaterfowlAnseriformesAnatidaeDendrocygnaLC475665210207694
1Fulvous whistling-duckDendrocygna bicolorDucks/Geese/WaterfowlAnseriformesAnatidaeDendrocygnaLC455371210508593
2Snow gooseAnser caerulescensDucks/Geese/WaterfowlAnseriformesAnatidaeAnserLC647920504050135165
3Ross's gooseAnser rossiiDucks/Geese/WaterfowlAnseriformesAnatidaeAnserLC57.36410661567113116
4Greater white-fronted gooseAnser albifronsDucks/Geese/WaterfowlAnseriformesAnatidaeAnserLC648119303310130165

可以看到数据既有物种分类的文本字段(NameScientificNameCategoryOrderFamilyGenusConservationStatus),也有生物学测量的数值字段(MinLengthMaxLengthMinBodyMassMaxBodyMassMinWingspanMaxWingspan,单位分别为厘米与克)。

认识 ggplot2:基于"图形语法"的声明式绘图

ggplot2 是 R 生态中最受欢迎的可视化包,既能绘制简单图表,也能胜任复杂图形。它的底层设计基于The Grammar of Graphics(图形语法)——一种把图形拆解为"标度(scales)""图层(layers)"等语义组件的通用可视化方案。换句话说,用户只需用少量代码声明性地描述"如何把变量映射到美学属性、使用什么图形元素",剩下的渲染细节全部交给ggplot2处理。

安装并加载它:

install.packages("ggplot2") library("ggplot2")

绘图的核心公式可以概括为:

图形(Plot)= 数据(Data) + 美学(Aesthetics) + 几何(Geometry)

  • 数据(Data):即你要使用的数据集(dataframe)
  • 美学(Aesthetics):指定要研究的变量,即 x 轴与 y 轴变量
  • 几何(Geometry):图表的类型,如折线图、柱状图、散点图等

在代码层面,一切绘图都从ggplot()函数开始,它接收数据集data,以及aes()(aesthetics 的缩写)中声明的 x、y 变量映射;随后通过+逐层叠加几何对象函数(如geom_line()geom_point()geom_bar()),这正是"图层式"语法的体现——每一层都可以独立添加或修改。

如何选择合适的几何对象(图表类型)

"几何"决定了图形的呈现方式,选择哪个几何对象取决于你的数据形态以及你想讲述的故事:

  • 分析趋势(trends):折线图(line)、柱状图(column)
  • 比较数值(compare values):柱状图(bar)、柱状图(column)、饼图(pie)、散点图(scatterplot)
  • 展示部分与整体的关系(parts to a whole):饼图(pie)
  • 展示数据分布(distribution):散点图(scatterplot)、柱状图(bar)
  • 展示数值间的关系(relationships):折线图(line)、散点图(scatterplot)、气泡图(bubble)

例如,本课关注的三个问题都围绕"数量"展开:数据集中有多少种鸟类类别、各类别数量是多少?有多少鸟处于灭绝、濒危、稀有或常见状态?在林奈分类学中各类属(genus)与目(order)各有多少种?——这些问题天然适合用折线图观察数值分布、用散点图定位异常、用柱状图比较分组计数。

绘制翼展折线图:你的第一个 ggplot 图形

假设你想直观地看到这些鸟类的最大翼展(MaxWingspan),可以先绘制一张基础的折线图:

ggplot(data = birds, aes(x = Name, y = MaxWingspan, group = 1)) + geom_line()

这段代码做了三件事:通过ggplot(data=birds, aes(x=Name, y=MaxWingspan, group=1))指定数据集与 x(鸟类名称)、y(最大翼展)映射;group=1告诉 ggplot 将所有点视为同一组,从而把散点按顺序连成一条连续的折线;geom_line()则声明几何对象为折线。

观察这张图,你会立刻注意到至少一个明显的离群值(outlier)——翼展超过 2000 厘米意味着 20 米以上,莫非明尼苏达上空还有翼龙出没?这些异常点很可能是原始数据的录入错误(typo)。虽然你也可以用 Excel 排序快速定位它们,但本课主张直接"在绘图中工作",通过可视化本身推进分析流程。

完善折线图:旋转轴标签与添加标题

鸟类名称通常很长,默认水平排列的 x 轴标签会互相重叠。可以通过theme()把标签旋转 45 度,并用xlab()ylab()ggtitle()分别设置 x 轴标题、y 轴标题与图表标题:

ggplot(data = birds, aes(x = Name, y = MaxWingspan, group = 1)) + geom_line() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + xlab("Birds") + ylab("Wingspan (CM)") + ggtitle("Max Wingspan in Centimeters")

这里theme()是 ggplot2 的全局样式接口:element_text(angle = 45, hjust = 1)把 x 轴刻度文本旋转 45° 并右对齐(hjust=1),xlab()/ylab()设置轴标题,ggtitle()为图形命名。

然而即使旋转 45°,43 种鸟的名称标签仍然过多、难以阅读。换个策略:只标注离群值,并把标签放进图表内部——改用散点图(scatterplot)为标注腾出空间。

用散点图定位离群值:只标注重点

ggplot(data = birds, aes(x = Name, y = MaxWingspan, group = 1)) + geom_point() + geom_text(aes(label = ifelse(MaxWingspan > 500, as.character(Name), '')), hjust = 0, vjust = 0) + theme(axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank()) + ylab("Wingspan (CM)") + ggtitle("Max Wingspan in Centimeters")

这段代码的意图是:geom_point()绘制散点;geom_text()配合ifelse(MaxWingspan > 500, as.character(Name), '')只为翼展超过 500 厘米的鸟添加名称标签,其余标签为空字符串;theme()axis.title.xaxis.text.xaxis.ticks.x全部置空,隐藏 x 轴刻度,从而大幅清理画面。

实战提示:原文档中的这段代码在theme(...)一行末尾缺少+连接符、且ggtitle()末尾多了一个悬空的+,直接复制会触发 R 的语法错误。上面给出的版本已修正连接符,可直接运行。

通过这张图你能发现:**白头海雕(Bald Eagle)与草原隼(Prairie Falcon)**的标签明显异常——它们的最大翼展记录疑似多打了一个 0。现实世界中你很难见到翼展 25 米的白头海雕。

过滤数据:剔除异常值

既然找到了两个错误记录,就创建一个剔除离群值的新 dataframe,再重新绘制散点图:

birds_filtered <- subset(birds, MaxWingspan < 500) ggplot(data = birds_filtered, aes(x = Name, y = MaxWingspan, group = 1)) + geom_point() + ylab("Wingspan (CM)") + xlab("Birds") + ggtitle("Max Wingspan in Centimeters") + geom_text(aes(label = ifelse(MaxWingspan > 500, as.character(Name), '')), hjust = 0, vjust = 0) + theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())

subset(birds, MaxWingspan < 500)生成只保留翼展小于 500 厘米记录的新 dataframebirds_filtered,随后的ggplot()基于它绘制散点图。过滤掉异常值后,数据更加连贯、易于理解。

实战提示:过滤后所有MaxWingspan均已小于 500,因此代码中geom_text()ifelse(MaxWingspan > 500, ...)条件实际上不会命中任何标签——它被保留在原始课程代码中,其语义是"在保留离群值的情况下仅标注异常点",你可以在理解后按需删减这一层。

现在,至少在翼展维度上我们拥有了一份更干净的数据集。折线图与散点图能展示数值及其分布,接下来我们把目光转向数据本身蕴含的"数量"问题:数据集中有多少种鸟类类别、各有多少?有多少鸟处于灭绝/濒危/稀有/常见状态?林奈分类体系下各属(genus)与目(order)分别有多少种?——这些正是柱状图的用武之地。

用柱状图探索分组:安装并加载 dplyr / tidyverse

柱状图非常适合展示数据的分组情况。首先安装并加载数据处理与绘图的配套包:

install.packages("dplyr") install.packages("tidyverse") library(lubridate) library(scales) library(dplyr) library(ggplot2) library(tidyverse)

然后基于过滤后的数据,按Category(鸟类类别)分组,对各类别计算样本数n()以及各测量列的平均值,再经gather()变长后绘制堆叠柱状图:

birds_filtered %>% group_by(Category) %>% summarise( n = n(), MinLength = mean(MinLength), MaxLength = mean(MaxLength), MinBodyMass = mean(MinBodyMass), MaxBodyMass = mean(MaxBodyMass), MinWingspan = mean(MinWingspan), MaxWingspan = mean(MaxWingspan) ) %>% gather("key", "value", -c(Category, n)) %>% ggplot(aes(x = Category, y = value, group = key, fill = key)) + geom_bar(stat = "identity") + scale_fill_manual(values = c("#D62728", "#FF7F0E", "#8C564B", "#2CA02C", "#1F77B4", "#9467BD")) + xlab("Category") + ggtitle("Birds of Minnesota")

这段管道(%>%)代码的执行逻辑是:

  1. group_by(Category):按鸟类类别分组;
  2. summarise(...):每组内计算样本数n=n(),以及MinLengthMaxLengthMinBodyMassMaxBodyMassMinWingspanMaxWingspan六列的平均值(mean);
  3. gather("key", "value", -c(Category, n)):把六列测量值折叠为key(指标名)与value(数值)两列,使数据从宽表转为长表,便于按key分色绘制;
  4. ggplot(...) + geom_bar(stat="identity"):用柱高直接对应valuestat="identity"表示不对数据做统计变换,直接用数值绘图),以key作为填充分组;
  5. scale_fill_manual(values = c("#D62728", ...)):为 6 个指标手动指定 6 种十六进制颜色;
  6. xlab("Category") + ggtitle("Birds of Minnesota"):设置轴标题与图名。

这张堆叠柱状图的问题在于:未分组的原始数据过多,画面拥挤、难以阅读。既然目标是"看类别里有多少只鸟",我们应当只选择想要绘制的数据——按类别计数。

简化柱状图:水平条形图展示各类别数量

由于类别很多,可以把图表转为水平方向并调整高度,让所有数据都能展示:

birds_count <- dplyr::count(birds_filtered, Category, sort = TRUE) birds_count$Category <- factor(birds_count$Category, levels = birds_count$Category) ggplot(birds_count, aes(Category, n)) + geom_bar(stat = "identity") + coord_flip()

这里的要点:

  • dplyr::count(birds_filtered, Category, sort = TRUE):统计Category列每个唯一值的出现次数,生成新 dataframebirds_count,并按计数降序排列(sort=TRUE);
  • factor(birds_count$Category, levels = birds_count$Category):把类别列转为有序因子,因子的水平顺序与排序后的数据一致,保证绘制时条形按数量从多到少排列;
  • geom_bar(stat = "identity"):直接用n作为柱高;
  • coord_flip():翻转坐标系,把竖直柱变成水平条形,解决类别名称过长导致的拥挤问题。

这张图一目了然:本地区数量最多的鸟类类别是Ducks/Geese/Waterfowl(鸭/鹅/水禽)。明尼苏达州是"万湖之州"(land of 10,000 lakes),这个结果完全符合直觉。你还可以尝试用同一数据集做其他维度的计数统计,看看是否有出乎意料的结果。

比较数据:按类别聚合最大体长

通过构造新的聚合轴,可以比较不同分组下的数据。试试按类别比较鸟类的最大体长(MaxLength):

birds_grouped <- birds_filtered %>% group_by(Category) %>% summarise( MaxLength = max(MaxLength, na.rm = TRUE), MinLength = max(MinLength, na.rm = TRUE) ) %>% arrange(Category) ggplot(birds_grouped, aes(Category, MaxLength)) + geom_bar(stat = "identity") + coord_flip()

这段代码先用group_by(Category)分组,再用summarise()取每组MaxLengthMinLength的最大值(na.rm = TRUE忽略缺失值),arrange(Category)按类别排序,最后绘制水平条形图。

结果并不令人意外:蜂鸟(Hummingbirds)的MaxLength远小于鹈鹕(Pelicans)或鹅(Geese)——当数据符合生物学常识时,往往说明数据质量是可靠的。

实战提示:原文档中MinLength也使用了max()聚合。如果你希望展示的是各类别的最小体长,应把第二行改为min(MinLength, na.rm = TRUE);保留max()则会得到各类别"最小体长记录中的最大值",两者语义不同。

叠加柱状图:同时呈现最小与最大体长

更有意思的做法是把同一类别的最小体长与最大体长叠加在同一张图上,直观对比区间:

ggplot(data = birds_grouped, aes(x = Category)) + geom_bar(aes(y = MaxLength), stat = "identity", position = "identity", fill = 'blue') + geom_bar(aes(y = MinLength), stat = "identity", position = "identity", fill = 'orange') + coord_flip()

与之前的关键区别在于position = "identity":它告诉 ggplot 不要对条形做堆叠(stack)或并排(dodge)处理,而是让两层条形直接重叠绘制——蓝色条形代表MaxLength,橙色条形绘制在其上层代表MinLengthcoord_flip()继续用于横向展示。注意图层的叠加顺序:后写的geom_bar()(橙色)绘制在前面,覆盖在蓝色条形上,因此需要较大的数值层先绘制(本例中MaxLength在先,符合直觉)。

在这张图中,你可以看到每个鸟类类别的MinLengthMaxLength形成的范围区间。基于该数据可以合理推断:鸟的体型越大,其体长区间往往也越大——一个相当有趣的规律。

挑战与作业:把三种图表讲成一个故事

本课的课后挑战鼓励你跳出这份数据集:在互联网上寻找其他鸟类数据集,用本课学到的折线图、散点图与柱状图反复练习,去发现那些你未曾注意的事实。

对应的课后作业详见 3-Data-Visualization/R/09-visualization-quantities/assignment.md,要求是:深入挖掘数据集,围绕某一种鸟(例如雪雁 Snow Goose)编写脚本,把你能找到的所有有趣数据用上述三种图形可视化,在脚本中讲一个有注解、有叙事、图表美观的完整故事。评分维度包括:脚本注释与叙事是否扎实、图形是否美观(Exemplary 标准),缺少任一要素为 Adequate,缺少两项以上为 Needs Improvement。

回顾与延伸阅读

本课的核心收获可以概括为一条完整的数据可视化工作流:

  1. 读取并检查数据read.csv(..., fileEncoding="UTF-8-BOM")+head()
  2. 从数据出发提问:围绕"数量"提出可被图表回答的问题;
  3. 先画再想:用折线图快速观察整体形态与异常;
  4. 用散点定位异常geom_point()+geom_text()只标注关键点;
  5. 清洗数据subset()剔除离群值,建立干净的分析基线;
  6. 聚合与比较dplyrgroup_by()/summarise()/count()完成分组统计,再用geom_bar()+coord_flip()比较数量;
  7. 叠加与细节:用position="identity"叠加图层,用theme()xlab()ylab()ggtitle()完善可读性。

如果想进一步了解 R 生态中的其他可视化方案,可以研究LatticePlotly(R 版)等包;本课程对应的课前/课后测验由仓库内的 quiz-app 提供(本课测验编号为 16 与 17)。同时,同一课还有基于 Python + Matplotlib 的实现,见 3-Data-Visualization/09-visualization-quantities/README.md 与其 notebook.ipynb,两套实现的数据集与叙事完全一致,非常适合对照学习。课程配套的速记图(sketchnote)位于 sketchnotes/09-Visualizing-Quantities.png,可作为视觉速览参考。

至此,你已经掌握了用 R 与ggplot2可视化数量数据的完整方法:从理解图形语法,到选择合适的几何对象,再到发现离群值、清洗数据、分组聚合与叠加图层——这套方法论同样适用于任何"有多少""哪个最多""分布如何"这类数量型问题。

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询