使用 R + ggplot2 可视化“数量”数据:Data-Science-For-Beginners 鸟类翼展实战(折线图、散点图与条形图)
本篇指南以 Data-Science-For-Beginners 课程第 9 课 Visualizing Quantities 的 R 语言版本(3-Data-Visualization/R/09-visualization-quantities/README.md)为骨架,基于仓库自带的明尼苏达州鸟类数据集,完整演示如何用 R 生态中最流行的可视化包 ggplot2(配合 dplyr、tidyr 等管道工具)绘制并优化折线图、散点图与条形图。读完本文,你将掌握“数量(quantity)”类数据的分析套路:从识别离群点、过滤脏数据,到按类别分组计数、比较与叠加数据,最终能独立用 R 脚本讲述一张数据集里的“故事”。如果你更习惯 Python/Matplotlib,可对照阅读对应的主课文档 3-Data-Visualization/09-visualization-quantities/README.md。
本课在课程中的定位与配套资源
这一课是课程第 3 部分(Data Visualization)的第一个 R 语言实践,主题聚焦于**数量(quantities)**的可视化——也就是“数据集中某一数值特征有多大、有多少、如何分布”。课程在 1-Introduction/README.md 所描述的路标图(Sketchnote)sketchnotes/09-Visualizing-Quantities.png 中把该知识点归入“根据数据选择正确图表”的基础能力培养。
- 数据文件:data/birds.csv,存放在仓库根目录的 data 文件夹下,是一份已经过清洗的鸟类观测数据;
- R 代码路径:3-Data-Visualization/R/09-visualization-quantities/README.md;
- 配套练习:3-Data-Visualization/R/09-visualization-quantities/assignment.md;
- 图示输出:3-Data-Visualization/R/09-visualization-quantities/images。
打开 R 控制台或 RStudio,跟着本文逐段执行即可复现全部图表。
认识数据与可视化框架
birds.csv:明尼苏达鸟类数据集长什么样
本课的核心数据是明尼苏达州的鸟类记录,包含表头外的 442 条观测,每条记录给出了从目(Order)到属(Genus)的 Linnaeus 分类学信息、保护状态,以及体长、体重、翼展的最小/最大值等 6 个数值维度。列结构如下(与课程中 head() 输出一致):
| Name | ScientificName | Category | Order | Family | Genus | ConservationStatus | MinLength | MaxLength | MinBodyMass | MaxBodyMass | MinWingspan | MaxWingspan |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Black-bellied whistling-duck | Dendrocygna autumnalis | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Dendrocygna | LC | 47 | 56 | 652 | 1020 | 76 | 94 |
| Fulvous whistling-duck | Dendrocygna bicolor | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Dendrocygna | LC | 45 | 53 | 712 | 1050 | 85 | 93 |
| Snow goose | Anser caerulescens | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 64 | 79 | 2050 | 4050 | 135 | 165 |
| Ross's goose | Anser rossii | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 57.3 | 64 | 1066 | 1567 | 113 | 116 |
| Greater white-fronted goose | Anser albifrons | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 64 | 81 | 1930 | 3310 | 130 | 165 |
导入并查看数据前 5 行:
birds <- read.csv("../../data/birds.csv", fileEncoding = "UTF-8-BOM")
head(birds)
注意:数据集路径
../../data/birds.csv是相对于本课文件夹的写法。文件实际位于仓库根目录 data/birds.csv;fileEncoding = "UTF-8-BOM"用于剥离 CSV 文件开头的 BOM 头(该文件第一列Name前带 BOM 标记,不加此参数首列名会变成\ufeffName)。
从 data/birds.csv 的源码看,这份数据是纯文本 CSV,列之间以逗号分隔,数值列单位隐含为“厘米/克”。由于 Python 版课程同样读取该文件,两套语言教程共享同一份数据,这正是仓库设计上的特点——语言不同、数据一致、结论可比。
ggplot2 与“图形语法”:Plot = Data + Aesthetics + Geometry
ggplot2 是一套声明式的绘图系统,理论基础是 Wilkinson 提出的 Grammar of Graphics(图形语法)。它将一张图拆解为若干语义组件(比例尺 scale、图层 layer 等),用户只需告诉 ggplot2“把哪些变量映射到哪些美学属性、用哪种几何对象来画”,剩下的渲染细节由包自动完成。
✅ Plot = Data + Aesthetics + Geometry
- Data(数据):要使用的数据集(例如
birds);- Aesthetics(美学映射):要研究的变量,即
aes(x = ..., y = ...)指定的横纵轴变量;- Geometry(几何对象):图表的类型,如折线
geom_line()、散点geom_point()、条形geom_bar()。
选择哪种几何对象,取决于你的数据结构和想讲述的故事。本课给出了一张选择速查表:
- 分析趋势:折线(line)、柱状(column);
- 比较数值:条形(bar)、柱状、饼图(pie)、散点;
- 展示部分与整体的关系:饼图;
- 展示数据分布:散点图、条形图;
- 展示数值间关系:折线、散点、气泡图。
用最少的代码绘制单变量或多变量图形,正是 ggplot2 成为 R 中最流行的可视化包的原因。
用折线图观察鸟类的最大翼展
第一步:绘出所有鸟的 MaxWingspan
安装并加载 ggplot2 后,用一句 ggplot() 即可完成第一张折线图:
install.packages("ggplot2")
library("ggplot2")
ggplot(data = birds, aes(x = Name, y = MaxWingspan, group = 1)) +
geom_line()
ggplot() 的第一参数是数据集,aes() 指定横轴 Name(鸟名)与纵轴 MaxWingspan(最大翼展),group = 1 告诉 ggplot 把所有点当作一条连续的折线来连接;geom_line() 负责绘制折线。
立刻能注意到的问题:图中存在至少一个离群点——某些鸟的翼展高达 2000+ 厘米,也就是 20 多米,仿佛明尼苏达州游荡着翼手龙。显然,这些数值需要调查。
虽然你可以在 Excel 里快速排序找出这些可疑数据(多半是录入时多敲了一位数字),但更好的方式是留在绘图工作流里,用可视化的手段一边看一边处理。
第二步:旋转横轴标签,标注坐标轴
图中横轴有几百个鸟名,直接展示必然重叠。可以在 theme() 中把 x 轴文本旋转 45 度,并用 xlab() / ylab() / ggtitle() 补齐轴标签与标题:
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")
这里 element_text(angle = 45, hjust = 1) 控制文本旋转角度与水平对齐方式(hjust = 1 表示右对齐,避免旋转后标签错位),得到下图。
即使旋转到 45 度,鸟种太多依然无法逐个阅读。换一种策略:只标注离群点,并把标签直接放进图内。
用散点图定位并标注离群点
改用散点几何 geom_point() 能留出更多标注空间。下面这段代码把超过 500 厘米翼展的鸟名直接以文本标签画在数据点旁,同时把 x 轴标签整体隐藏以“去 clutter”:
ggplot(data = birds, aes(x = Name, y = MaxWingspan)) +
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_text(aes(label = ifelse(...)))逐点判断:当MaxWingspan > 500时用鸟名做标签,否则留空;hjust = 0, vjust = 0让标签从点的右下方向外偏移,避免与点重叠;theme(axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank())依次隐藏 x 轴标题、刻度文本与刻度线。
说明:原文档该段代码在
geom_text(...)前缺少行连接符+,会直接导致语法错误;本文已将其补全为可连续执行的管道链。
观察散点图可以确认离群点到底是谁。原始输出图见 MaxWingspan-scatterplot.png。
过滤离群点,得到更干净的数据
调查发现,Bald eagle(白头海雕)与 Prairie falcon(草原隼) 的最大翼展疑似各被多加了一个 0。对仓库数据 data/birds.csv 做实际检查可以印证这一点:Bald eagle 的 MaxWingspan 为 2300 厘米、Prairie falcon 为 1100 厘米,而其余 440 条记录全部落在 500 厘米以内——即便对于大型猛禽,翼展也应在 2 米上下量级。
用 subset() 剔除这两个记录,得到新数据框:
birds_filtered <- subset(birds, MaxWingspan < 500)
ggplot(data = birds_filtered, aes(x = Name, y = MaxWingspan)) +
geom_point() +
ylab("Wingspan (CM)") +
xlab("Birds") +
ggtitle("Max Wingspan in Centimeters") +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
过滤后翼展分布不再被“拉平”,主分布清晰可读:
值得一提:
subset(birds, MaxWingspan < 500)会把 442 条记录收敛为 441 条(课程中MaxWingspan < 500的过滤条件恰好同时剔除上述两只鸟)。若你担心某只翼展 < 500 的鸟被误伤,也可以显式写!(Name %in% c("Bald eagle", "Prairie falcon")),两种写法结果一致。
数据在翼展维度上干净之后,下一步是把注意力转向这个数据集“有多少”类型的问题:
- 数据集里有多少种鸟类 Category,各类数量是多少?
- 有多少鸟是灭绝(extinct)、濒危(endangered)、稀有(rare)还是常见(common)?
- 按 Linnaeus 分类术语,各个属(Genus)和目(Order)各有多少种?
折线图与散点图能展示数值与分布,而回答“数量/多少”需要另一类几何对象——条形图。
用条形图探索鸟类类别分组
当需要展示数据的分组时,条形图最实用。先加载数据处理三件套,并按 Category 分组聚合多个数值列,绘制叠加条形图:
install.packages("dplyr")
install.packages("tidyverse")
library(lubridate)
library(scales)
library(dplyr)
library(ggplot2)
library(tidyverse)
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")
这条管道做的事情是:group_by(Category) 按类别分组 → summarise() 对各数值列求均值 → gather() 把 6 个数值列“宽转长”为 key/value 两列 → 交给 ggplot,用 fill = key 按指标着色,geom_bar(stat = "identity") 直接以 value 作为条形高度(而不是默认计数)。scale_fill_manual 中 6 个十六进制色值分别对应 6 个数值指标。输出见 stacked-bar-chart.png。
这个堆叠条形图包含太多未分组的冗余数据,几乎不可读——绘图前应只选择真正关心的列。
分类计数:count() 与 coord_flip() 的水平条形图
先只关心“每个类别有多少种鸟”,用 dplyr::count() 计数并排序:
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转成因子并显式指定levels,保证 ggplot 按我们排序好的顺序(而不是字母序)绘制; coord_flip()将坐标轴翻转,得到水平条形图,适合类别较多、名称较长的场景。
课程输出图如下。一眼就能看出:数量最多的类别是 Ducks/Geese/Waterfowl。对数据实际核算,前几名是:Ducks/Geese/Waterfowl(45 种)、New World warblers(41 种)、Sandpipers/Allies(34 种)、Gulls/Terns/Skimmers(28 种)、New World sparrows(26 种)。明尼苏达号称“万湖之州”,鸭雁类占榜首毫不意外。
✅ 试试其他列计数(例如按 ConservationStatus、Order、Genus),看看有没有让你意外的结果?
比较分组数据:聚合、叠加与“越大的鸟范围越大”
条形图同样适合比较不同分组的数值。下面先按 Category 聚合出最大翼展/体长类指标,再比较各组的最大体长:
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()
summarise() 中用 max(..., na.rm = TRUE) 求各组最大值,na.rm = TRUE 避免个别缺失值把整组拉成 NA;arrange(Category) 让分组结果有序。输出图见 comparingdata.png。结论毫不意外:蜂鸟(Hummingbirds)的最大体长最小,而鹈鹕(Pelicans)和雁类(Geese)则大得多——数据符合直觉,恰好说明可视化结果可信。
更进阶的技巧是叠加(superimposing)两组数据:把同一类别的最小体长与最大体长画成两层条形,直观呈现每个类别的长度范围:
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()
注意这里两次 geom_bar() 都使用了 position = "identity",让橙色(MinLength)条形直接叠印在蓝色(MaxLength)条形之上而非并列堆叠,于是蓝色条上露出的“橙色头”就是该类别的长度跨度:
由此可以得出一个相当有趣的规律:鸟的体型越大,其长度范围通常也越大。这正是“可视化数量”的价值——把原始表格中隐藏的数值关系变成一眼可读的图形证据。
动手挑战与配套作业
🚀 Challenge
本数据集只是明尼苏达鸟类生态的一角。挑战目标是:在互联网上寻找其他鸟类数据集,围绕这些数据练习绘制不同类型的图表,看看能否发现之前不知道的事实。
配套作业:Lines, Scatters, and Bars
完成本课配套作业 3-Data-Visualization/R/09-visualization-quantities/assignment.md:
- 任务:深入挖掘
birds.csv,针对某一种鸟(例如 Snow goose)写一个 R 脚本,使用折线图、散点图和条形图这三种图,在 notebook 里讲一个完整的故事; - 评分标准:Exemplary(优秀)要求脚本具备良好注释、扎实的故事线叙事与赏心悦目的图表;若缺少其中一种元素为 Adequate;缺少两种及以上为 Needs Improvement。
复习与自学建议
- 本课是 R 版可视化的第一课。将 Python 主课 3-Data-Visualization/09-visualization-quantities/README.md 中的
Matplotlib实现与本课的ggplot2实现对照阅读,能同时理解两种语言下“数据 → 转换 → 映射 → 几何对象 → 输出”的统一流程; - 进一步研究其他适合可视化分组数据的 R 包,例如面向 R 传统绘图的
lattice,以及面向交互式图表(可缩放、可悬停)的plotly,体会声明式ggplot2与命令式/交互式绘图的差异。
通过本课,你已经走通了 R 生态中“数量”可视化的完整闭环:读入清洗过的数据 → 用折线图粗览全貌 → 用散点图定位离群点 → 用 subset() 清洗 → 用分组计数与水平条形图回答“有多少” → 用叠加条形图比较区间。这套方法论可复用于任何“带数值维度的分类型数据集”,也是后续课程中学习分布、比例与关系可视化的共同基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00



