首页
/ 使用 R + ggplot2 可视化“数量”数据:Data-Science-For-Beginners 鸟类翼展实战(折线图、散点图与条形图)

使用 R + ggplot2 可视化“数量”数据:Data-Science-For-Beginners 鸟类翼展实战(折线图、散点图与条形图)

2026-09-08 19:01:06作者:宣海椒Queenly

本篇指南以 Data-Science-For-Beginners 课程第 9 课 Visualizing Quantities 的 R 语言版本(3-Data-Visualization/R/09-visualization-quantities/README.md)为骨架,基于仓库自带的明尼苏达州鸟类数据集,完整演示如何用 R 生态中最流行的可视化包 ggplot2(配合 dplyrtidyr 等管道工具)绘制并优化折线图、散点图与条形图。读完本文,你将掌握“数量(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 中把该知识点归入“根据数据选择正确图表”的基础能力培养。

打开 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.csvfileEncoding = "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 表示右对齐,避免旋转后标签错位),得到下图。

旋转 x 轴标签并补全坐标轴说明后的最大翼展折线图,纵轴为翼展(厘米),图中可清晰看到数条远高于主分布的高耸折线(离群点)

即使旋转到 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 eagleMaxWingspan 为 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 种)。明尼苏达号称“万湖之州”,鸭雁类占榜首毫不意外。

按 Category 计数并降序排列的水平条形图,可见 Ducks/Geese/Waterfowl 类别数量最多

✅ 试试其他列计数(例如按 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)条形之上而非并列堆叠,于是蓝色条上露出的“橙色头”就是该类别的长度跨度:

同一 Category 上叠加最大体长(蓝)与最小体长(橙)的水平条形图,可直观比较每个类别内部的长度范围

由此可以得出一个相当有趣的规律:鸟的体型越大,其长度范围通常也越大。这正是“可视化数量”的价值——把原始表格中隐藏的数值关系变成一眼可读的图形证据。

动手挑战与配套作业

🚀 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() 清洗 → 用分组计数与水平条形图回答“有多少” → 用叠加条形图比较区间。这套方法论可复用于任何“带数值维度的分类型数据集”,也是后续课程中学习分布、比例与关系可视化的共同基础。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395