用 R 与 ggplot2 讲好一个数据故事:Data Science for Beginners 线图、散点图与条形图作业实战解析
【免费下载链接】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 课程第 09 课(R 版本)的课后作业展开:在明尼苏达州鸟类数据集(data/birds.csv)中,针对某一种特定鸟类深入挖掘数据,并运用本课学过的线图、散点图与条形图三种图表类型,在 notebook 中构建一个完整的数据叙事。读完本文,你将掌握 ggplot2 从数据清洗、异常值发现到分组比较的完整可视化流程,并能够按照作业评估标准交出一份兼具清晰注释、完整叙事与美观图表的作品。
作业目标:从"画图"到"讲故事"
作业原文(见 translations/da/3-Data-Visualization/R/09-visualization-quantities/assignment.md,英文原版见 translations/en/3-Data-Visualization/R/09-visualization-quantities/assignment.md)给出了明确的任务描述:
在本课中,你使用线图(line charts)、散点图(scatterplots)和条形图(bar charts)来展示这个数据集中的有趣事实。本作业要求你更深入地挖掘数据集,去发现关于某一种特定类型鸟类的有趣信息。例如,你可以编写一个脚本,将你能找到的关于雪雁(Snow Geese)的所有有趣数据可视化出来。请在 notebook 中使用上述三种图表类型来构建一个故事。
该作业的配套讲义 translations/da/3-Data-Visualization/R/09-visualization-quantities/README.md(R 版本原稿见 3-Data-Visualization/R/09-visualization-quantities/README.md)提供了完成作业所需的全部技术手段,包括:
- 用
ggplot2绘制线图与散点图,观察鸟类最大翼展的分布; - 通过
geom_text()标注异常值; - 用
subset()过滤疑似录入错误的数据; - 用
dplyr的group_by()、summarise()与gather()分组统计并绘制条形图; - 用
coord_flip()将条形图转为水平展示; - 通过叠加(superimpose)多个
geom_bar()层进行多指标对比。
因此,本作业本质上是把讲义中的每一段代码,从"跟着课文学"升级为"围绕一个主题自主综合运用"。下面我们按作业要求的"三种图表讲一个故事"的路径完整走一遍。
第一步:准备数据,选定主角
作业要求的数据集存放在仓库根目录的 data/ 文件夹下。打开 R 控制台,导入数据集并查看前几行:
birds <- read.csv("data/birds.csv", fileEncoding = "UTF-8-BOM") head(birds)以文件头 5 行为例,数据结构是文本与数值的混合体:
| 索引 | Name | ScientificName | Category | Order | Family | Genus | ConservationStatus | MinLength | MaxLength | MinBodyMass | MaxBodyMass | MinWingspan | MaxWingspan |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Black-bellied whistling-duck | Dendrocygna autumnalis | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Dendrocygna | LC | 47 | 56 | 652 | 1020 | 76 | 94 |
| 1 | Fulvous whistling-duck | Dendrocygna bicolor | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Dendrocygna | LC | 45 | 53 | 712 | 1050 | 85 | 93 |
| 2 | Snow goose | Anser caerulescens | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 64 | 79 | 2050 | 4050 | 135 | 165 |
| 3 | Ross's goose | Anser rossii | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 57.3 | 64 | 1066 | 1567 | 113 | 116 |
| 4 | Greater white-fronted goose | Anser albifrons | Ducks/Geese/Waterfowl | Anseriformes | Anatidae | Anser | LC | 64 | 81 | 1930 | 3310 | 130 | 165 |
fileEncoding = "UTF-8-BOM"用于正确处理带 BOM 头的 CSV 文件。作业示例选定的"主角"是雪雁(Snow goose,学名Anser caerulescens),它在数据集中对应的记录为:体长 64–79 厘米、体重 2050–4050 克、翼展 135–165 厘米,隶属 Ducks/Geese/Waterfowl 类、Anseriformes 目、Anatidae 科、Anser 属,保护状态为 LC(无危)。选定一个具体物种后,作业的关键就成了:围绕它的这些数值维度,设计出三种图表来回答问题——它体长区间在同类中的位置?它的翼展在全数据集中算大还是小?它所属类别的数量构成如何?
第二步:线图与散点图——发现异常值,完成第一层叙事
先装包,再画线图
用ggplot2的第一步是安装并载入包:
install.packages("ggplot2") library("ggplot2") ggplot(data = birds, aes(x = Name, y = MaxWingspan, group = 1)) + geom_line()ggplot()指定数据集与 x、y 轴变量;group = 1让 ggplot2 把所有离散的 x 值视为一个序列进行连线;geom_line()负责绘制线图。此时可以看到一个非常明显的事实:至少有一个极端异常值——2000 多厘米的翼展意味着超过 20 米,这显然不可能是明尼苏达州真实存在的鸟类,多半是录入时多打了一个 0。
为了让图表可读,作业还要求对坐标轴进行打磨。在 README.md 的"构建鸟翼展线图"一节中,给出了完整的标签优化方案:
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(axis.text.x = element_text(angle = 45, hjust = 1))将 x 轴标签旋转 45 度,xlab()/ylab()分别设置坐标轴标题,ggtitle()设置图表标题。生成的效果如讲义中的配图所示:
用散点图给异常值"点名"
即使旋转 45 度,几百个鸟类名称仍无法全部展示。讲义的第二步策略是:只标注异常值,隐藏其余 x 轴标签,改用散点图腾出标注空间:
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 厘米的个体输出鸟名标签;hjust = 0, vjust = 0让标签从点的右上方开始排布;三行theme()参数分别隐藏了 x 轴标题、刻度文字与刻度线,让画面更清爽。
过滤异常值,让数据"讲道理"
这一散点图会揭示:秃鹰(Bald Eagle)与草原隼(Prairie Falcon)的最大翼展各被多加了一个 0。讲义随即用subset()生成剔除异常值的新数据框,并重新绘图:
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())过滤后数据更加连贯、可解释。需要留意的是:过滤后的数据集中最大翼展约为 250 厘米(如号手天鹅 Trumpeter swan),此时若仍沿用> 500的阈值,geom_text()将不会再输出任何标签。作为实战改进,你可以将阈值下调到过滤后数据的合理区间(例如MaxWingspan > 240),从而继续在高翼展个体上做标注——这也正是"围绕具体问题调整可视化参数"这一作业能力点的体现。
第三步:条形图——用分组统计回答"数量"问题
讲义在"探索条形图"一节提出了一组可以用条形图回答的问题:
- 数据集里有多少种鸟类类别,各自数量是多少?
- 有多少鸟是灭绝、濒危、稀有或常见的?
- 在林奈分类体系中,各属、各目分别有多少种?
分组汇总 + 堆叠条形图
先安装并载入数据处理所需的包,然后按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()对六个数值列求均值并记录组内个体数n;gather("key", "value", - c(Category, n))把宽表转成长表,使六个指标可以作为同一个 x 轴类别下的多根"柱";geom_bar(stat = "identity")直接使用数值作为柱高;scale_fill_manual()为六个指标指定一组明确的色板。
coord_flip:水平条形图更易读
上述堆叠图因未分组数据过多而难以阅读,讲义随即给出更实用的方案:先统计每个类别下的鸟类数量,再用coord_flip()转成水平条形图:
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列转成与排序一致的因子(factor),保证 ggplot2 按排序后的顺序绘制;coord_flip()将坐标轴对调,形成横向条形图。这幅图可以一眼看出:该地区数量最多的鸟类类别是 Ducks/Geese/Waterfowl——考虑到明尼苏达州是"万湖之地",这个结果并不意外。你也可以沿这一思路做其他计数统计,例如按ConservationStatus、Order或Genus计数,通常会有新的发现。
比较与叠加:两两对比的进阶叙事
讲义"比较数据"一节展示了基于类别对最大体长进行排序比较的做法:
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()结果符合直觉:蜂鸟的最大体长最小,而鹈鹕与鹅类最大。na.rm = TRUE用于在聚合时忽略缺失值。更进一步,还可以在同一张图上叠加两个geom_bar()层,同时呈现某一类别的最小与最大体长:
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"让两层柱从同一基线开始绘制、相互覆盖,从而直观比较每个类别体长的上界与下界。这种"同坐标轴多几何层"的手法,正是作业中"用三种图表讲一个完整故事"所需要的进阶技巧。
作业示范:围绕雪雁构建三段式叙事
将以上技术串起来,一份达标的作业可以按如下结构组织(数据来自 data/birds.csv 中的 Snow goose 记录):
- 开场(线图/散点图):先绘制全数据集的翼展分布,指出雪雁在整条曲线中的位置——其翼展区间 135–165 厘米处于中上水平;同时借助异常值标注完成对数据集质量的第一印象判断,引出"为什么要过滤数据"这一话题。
- 展开(散点图):过滤掉秃鹰与草原隼两处录入错误后,聚焦鸭雁类(Ducks/Geese/Waterfowl),对比雪雁与同属的雪雁(Ross's goose,翼展 113–116 厘米)、白额雁(Greater white-fronted goose,130–165 厘米)在体长与体重上的差异。
- 收束(条形图):用
dplyr::count()按Category统计数量,说明雪雁所属类别在明尼苏达州是数量最多的类群;再叠加MaxLength与MinLength两层条形图,展示鸭雁类在体长维度上的跨度。
每一步都配上注释(例如"# 发现雪雁记录中翼展 135–165 cm 处于该类群中位区间"),并用文字串起因果,就同时满足了评分标准中的"清晰注释"与"扎实叙事"两项。
评估标准自检清单
作业的评估标准(见 assignment.md)分为三档:
| 档次 | 要求 |
|---|---|
| 优秀(Exemplary) | 脚本包含清晰注释、有说服力的叙事、吸引人的图表 |
| 合格(Adequate) | 上述三项缺一项 |
| 需改进(Needs Improvement) | 上述三项缺两项 |
对照此表,提交前可以逐项自检:
- 注释:每个代码块是否有
#注释说明意图?关键步骤(装包、分组、过滤、叠加)是否解释清楚? - 叙事:图表之间是否有逻辑递进(发现 → 过滤 → 比较 → 结论)?能否回答"雪雁这个物种在数据集中有什么独特之处"?
- 美观:是否应用了
theme()、xlab()/ylab()、ggtitle()、scale_fill_manual()、coord_flip()等让图表更专业的修饰手段?
仓库中还提供了 Python 版本的第 09 课参考答案 3-Data-Visualization/09-visualization-quantities/solution/notebook.ipynb,其中用pandas与matplotlib完成了"观察翼展 → 名称与翼展对应 → 标注异常值"的同一分析路径,可作为对照参考(注意该 notebook 为 Python 内核,与本文的 R/ggplot2 实现语言不同)。
延伸挑战
讲义末尾的挑战环节建议:本数据集提供了特定生态系统内多种鸟类的丰富信息,可以继续在网上寻找其他与鸟类相关的数据集,围绕新数据练习构图,去发现此前未意识到的规律。R 生态中,除了ggplot2,还可以尝试lattice与plotly等可视化包,将同样的"数据 → 美学 → 几何"思维迁移到更多工具上——这正是本课乃至整个 Data Science for Beginners 课程希望沉淀下来的核心能力。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考