☰
CrashData:R语言清洗航空事故数据的完整实践指南
2026/10/13 11:47:22 网站建设 项目流程

简介:CrashData 是一份面向数据分析与航空安全研究者的 R 语言数据采集脚本资源,用于从 planecrashinfo.com 逐年抓取飞机失事的历史记录,解决公开事故数据分散、难以批量获取的问题。作者以略显取巧但可用的方式将网页内容整理为规整的 data.frame,方便后续做趋势分析、可视化或统计建模,适合具备 R 基础、关注航空事故数据的中级学习者。压缩包共 2 个文件,包含 1 个 R 脚本与 1 个 md 说明文档,整体仅 1KB,轻量易读,脚本负责抓取与结构化,说明文档交代用法与背景。目前已有 1422 人学习下载。读者可直接获得可复用的数据抓取逻辑、逐年提取思路以及开箱即用的数据框输出,省去自行解析网页的时间,也能在此基础上扩展字段、清洗缺失值或接入自己的分析流程,是入门网络数据采集与航空事故研究的实用起点。

1. 从公开事故记录到可分析数据集:CrashData 到底解决什么问题

做航空安全分析、事故统计建模或者时间序列研究的人,大概率都遇到过同一个尴尬:网上能查到的坠机信息不少,但真要拉一份结构化的历史数据下来,要么字段残缺,要么格式混乱,要么得手动一页页抄。CrashData 这个资源包瞄准的就是这个痛点——它把 planecrashinfo.com 上公开的飞机失事历史记录,整理成一份可以直接用 R 加载、清洗、分析的本地数据集。换句话说,它不教你造轮子,而是把轮子拆好、标好尺寸递到你手上。

这份资源适合三类人:一是做航空安全或事故频率建模的研究者,需要一份可复现的底层数据;二是学 R 语言数据清洗的练习者,想拿真实脏数据练手;三是做可视化或统计报告的人,想快速拿到一份带时间、地点、机型、伤亡人数的结构化表。它不涉及任何敏感信息,全部来自公开事故记录,重点在于“把散落的网页表格变成能跑代码的数据框”。下面我从数据获取、字段解析、清洗逻辑到常见翻车点,一步步拆开讲。

2. 数据获取与原始结构:从网页表格到本地文件的完整链路

2.1 为什么选 planecrashinfo.com 作为数据源

公开的航空事故记录来源不少,但 planecrashinfo.com 的特点是时间跨度长、字段相对固定、页面结构简单。它按年份组织事故列表,每条记录通常包含日期、地点、机型、注册号、运营商、死亡人数、事故描述等。对于用 R 做分析的人来说,这种“一个年份一个页面、表格行列规整”的结构,比那些需要登录、分页、动态加载的站点友好得多。

常见做法是先用rvest把每个年份页面的表格抓下来,再合并成一个大表。但这里有个前提:你得先确认目标页面的 HTML 结构是否稳定。我一般会先抓一年看看列数和列名,再决定是直接读<table>还是逐行解析<tr>。CrashData 资源包里已经包含了整理好的原始抓取结果和对应的 R 脚本,省去了从零写爬虫的环节。如果你要自己复现,建议先检查页面是否有反爬限制,以及年份覆盖范围是否满足你的研究区间。

2.2 用 R 读取和合并多年度数据

假设你已经把各年份的页面保存为本地 HTML 文件,或者直接用read_html()在线读取,下面这段代码展示了如何把多个年份的表格合并成一个数据框。注意年份范围要根据你的实际需求调整,不要盲目全量抓取。

library(rvest) library(dplyr) library(purrr) # 定义要抓取的年份范围,建议先小范围测试 years <- 2000:2020 # 用一个函数封装单年抓取逻辑 fetch_year <- function(year) { url <- paste0("http://www.planecrashinfo.com/", year, "/", year, ".htm") page <- read_html(url) # 抓取页面中的表格,通常第一个表格就是事故列表 tbl <- page %>% html_node("table") %>% html_table(fill = TRUE) # 加上年份列,方便后续合并 tbl$year <- year return(tbl) } # 批量抓取,用 possibly 避免单年失败导致整体中断 safe_fetch <- possibly(fetch_year, otherwise = NULL) all_data <- map(years, safe_fetch) %>% compact() %>% bind_rows()

这段代码的核心逻辑是“分年抓取、统一加年份列、最后按行合并”。possibly()的作用是容错——某一年页面结构变了或者网络超时,不会让整个循环崩掉,而是返回 NULL 被compact()过滤掉。参数方面,html_table(fill = TRUE)里的fill很关键,因为原始表格可能存在空单元格,不加这个参数会报错或者列数对不齐。年份范围建议先跑 3 到 5 年,确认字段一致后再扩大。

2.3 字段映射与类型转换的注意事项

抓下来的原始表格通常是字符型,列名也可能带空格或特殊符号。直接拿来分析会很难受,所以需要做一轮重命名和类型转换。常见做法是把日期列转成Date类型,把死亡人数转成数值型,把地点和运营商保留为字符型。这里有个坑:原始数据里的日期格式可能是“Jan 1, 2000”这种,直接as.Date()会返回 NA,需要指定格式或者用lubridate的mdy()。

library(lubridate) clean_data <- all_data %>% rename( date_raw = Date, location = Location, operator = Operator, aircraft = Aircraft, fatalities = Fatalities ) %>% mutate( # 日期解析,注意原始格式可能是 "Jan 1, 2000" date = mdy(date_raw), # 死亡人数可能包含 "123 (123)" 这种格式,需要提取数字 fatalities_num = as.numeric(str_extract(fatalities, "\\d+")), # 年份从日期中提取,用于分组统计 year = year(date) ) %>% filter(!is.na(date)) # 去掉日期解析失败的行

这段清洗逻辑的重点是“先重命名、再解析、最后过滤”。mdy()能自动识别月日年顺序,比as.Date()省心。str_extract()用来从混合字符串里抠出第一个数字,因为原始死亡人数字段有时会写成“123 (123)”表示总死亡和机上死亡。过滤掉日期解析失败的行是必要的,否则后续按时间聚合会出错。如果你发现某一年大量日期解析失败,大概率是页面格式变了,需要单独检查那一年的原始 HTML。

3. 数据清洗与特征工程:把脏表变成可建模的数据框

3.1 处理缺失值与重复记录的策略

公开事故记录里缺失值很常见,尤其是早期年份的运营商、机型字段。直接删行会损失样本,全部填充又可能引入偏差。我一般会分字段处理:日期和死亡人数这种关键字段,缺失就删;地点和运营商这种描述性字段,缺失就保留 NA,分析时再按需过滤。重复记录方面,同一年份页面里偶尔会出现同一事故被列两次的情况,通常是因为表格跨页或者抓取时重复读取。可以用日期加地点加机型做联合去重。

dedup_data <- clean_data %>% # 关键字段缺失直接剔除 filter(!is.na(date), !is.na(fatalities_num)) %>% # 按日期、地点、机型联合去重,保留第一条 distinct(date, location, aircraft, .keep_all = TRUE)

distinct()的参数.keep_all = TRUE表示保留所有列,只按指定列去重。这里选日期、地点、机型三个字段是因为它们组合起来基本能唯一标识一次事故。如果你发现去重后样本量骤降,可能是某个字段的格式不统一,比如地点里多了空格或者大小写不一致,需要先做标准化。

3.2 构造时间维度特征:年份、十年期与事故频率

做时间序列分析时,原始日期粒度太细,通常需要聚合到年或十年。用lubridate可以很方便地提取年份、月份、十年期。事故频率则可以用count()按年份统计,再和总航班量或乘客数做归一化——不过后者需要额外数据源,CrashData 本身不包含。这里先展示如何构造年份和十年期字段,以及如何计算每年的每起事故平均死亡人数。

feature_data <- dedup_data %>% mutate( year = year(date), decade = floor(year / 10) * 10, month = month(date, label = TRUE) ) %>% group_by(year) %>% mutate( accidents_per_year = n(), avg_fatalities = mean(fatalities_num, na.rm = TRUE) ) %>% ungroup()

floor(year / 10) * 10是构造十年期的常用技巧,比如 1987 年归到 1980 年代。group_by()加mutate()可以在不丢失原始行的情况下添加聚合特征,比summarise()更适合后续做逐行分析。na.rm = TRUE在计算均值时排除缺失值,避免整列变 NA。如果你要做事故频率的年度趋势图,直接用count(feature_data, year)更简洁。

3.3 地点与运营商的文本标准化

地点字段经常包含国家、城市、机场名混在一起的情况,运营商字段可能有缩写和全称并存。如果要做分组统计,不标准化会导致同一个实体被拆成多个类别。常见做法是用stringr做大小写统一、去空格、替换常见缩写。比如把“U.S.”和“United States”统一成“USA”,把“Intl”和“International”统一。这一步没有万能规则,需要根据你的分析目标决定粒度。

library(stringr) normalized_data <- feature_data %>% mutate( location_clean = location %>% str_to_upper() %>% str_replace_all("U\\.S\\.", "USA") %>% str_replace_all("UNITED STATES", "USA") %>% str_trim(), operator_clean = operator %>% str_to_upper() %>% str_replace_all("AIRLINES", "AIR") %>% str_trim() )

这段代码展示了文本标准化的基本套路:先统一大写,再替换常见变体,最后去首尾空格。str_replace_all()支持正则,所以“U\.S\.”里的反斜杠是转义点号。注意不要过度标准化,比如把“AIR”和“AIRWAYS”合并可能会丢失信息。我一般会先跑一遍table()看看有哪些高频值,再决定替换规则。

4. 避坑与排查:抓取和清洗中最容易翻车的五个点

4.1 页面结构变动导致表格列错位

现象:某一年抓下来的数据框列数和列名跟其他年份对不上,合并时报错或者出现大量 NA。原因:planecrashinfo.com 的页面模板并非完全统一,早期年份和近年份的表格列数可能不同,或者某些年份的表格嵌套了额外标签。解决:不要假设所有年份结构一致。先写一个检查函数,输出每年的列数和列名,对比后再决定是统一重命名还是分组合并。如果差异太大,可以考虑按年份分别保存,分析时再按需合并。

4.2 日期格式解析失败导致大量 NA

现象:mdy()或as.Date()之后,date列出现大量 NA,过滤后样本量骤减。原因:原始日期字段可能包含“Jan 1, 2000”“1 January 2000”“2000-01-01”等多种格式,单一解析函数无法覆盖。解决:先用unique()看看日期字段有哪些模式,再写一个分支解析函数。常见做法是用parse_date_time()指定多个候选格式,或者用case_when()按模式分别处理。解析失败的行不要急着删,先导出看看是不是格式特殊。

4.3 死亡人数字段混入非数字字符

现象:as.numeric()转换后产生大量 NA,并伴随警告“NAs introduced by coercion”。原因:死亡人数字段可能包含“123 (123)”“约 100”“unknown”等非纯数字内容。解决:先用str_extract()提取第一个连续数字,再转数值。对于“unknown”这类,可以统一替换为 NA 并在分析时排除。不要直接用as.numeric()硬转,否则会丢失信息且难以排查。

4.4 重复抓取导致同一条事故出现多次

现象:合并后的数据框里,同一天同一地点的事故出现两到三次。原因:可能是页面本身重复列出,也可能是抓取时循环逻辑有误,比如年份范围重叠或者 URL 拼接错误。解决:用distinct()按关键字段去重,同时检查年份循环是否有多余的重复。建议在抓取阶段就打印每年的记录数,异常高的年份重点排查。

4.5 在线抓取被限流或超时

现象:批量抓取时部分年份返回空表或报错,重试后又正常。原因:目标站点对频繁请求有限制,或者本地网络不稳定。解决:在循环里加Sys.sleep()控制请求频率,一般 1 到 2 秒比较安全。用possibly()或tryCatch()包裹单次抓取,失败时记录年份并跳过,最后统一重试失败年份。不要一次性抓几十年,分批次跑更稳妥。

5. 进阶用法:用 R 做事故趋势可视化与交互式查询

5.1 快速绘制年度事故数与死亡人数趋势图

清洗完的数据可以直接用ggplot2出图。下面这段代码展示了如何把年度事故数和平均死亡人数画在同一张图上,双轴虽然不推荐,但用于快速探索很方便。更常见的做法是分面或者用两条独立的时间序列。

library(ggplot2) yearly_summary <- normalized_data %>% group_by(year) %>% summarise( accidents = n(), total_fatalities = sum(fatalities_num, na.rm = TRUE), avg_fatalities = mean(fatalities_num, na.rm = TRUE) ) ggplot(yearly_summary, aes(x = year)) + geom_line(aes(y = accidents, color = "事故数"), size = 1) + geom_line(aes(y = avg_fatalities, color = "平均死亡人数"), size = 1) + scale_color_manual(values = c("事故数" = "steelblue", "平均死亡人数" = "firebrick")) + labs( title = "年度事故数与平均死亡人数趋势", x = "年份", y = "数值", color = "指标" ) + theme_minimal()

这段代码先用group_by()和summarise()生成年度汇总表,再用ggplot2画两条线。scale_color_manual()用来指定颜色映射,避免默认配色难以区分。注意size参数在较新版本的ggplot2里被linewidth替代,如果报错就换成linewidth。这张图适合快速看趋势,如果要发表或做报告,建议分开画或者用分面。

5.2 用 DT 包做交互式事故查询表

如果你想把数据分享给不写代码的同事,可以用DT包生成一个可搜索、可排序的 HTML 表格。下面这段代码展示了如何把清洗后的数据渲染成交互表,并限制默认显示行数。

library(DT) datatable( normalized_data %>% select(date, location_clean, operator_clean, aircraft, fatalities_num) %>% arrange(desc(date)), filter = "top", options = list( pageLength = 15, autoWidth = TRUE, scrollX = TRUE ), caption = "飞机失事历史记录(清洗后)" )

filter = "top"会在表格顶部加一排筛选框,方便按地点或运营商过滤。pageLength = 15控制默认每页显示行数,避免一次性渲染太多行导致卡顿。scrollX = TRUE在列数较多时启用横向滚动。这个表可以直接嵌入 R Markdown 文档,导出 HTML 后发给别人用浏览器打开。

5.3 一个我踩过的坑:不要用原始数据直接做回归

早期我图省事,直接把抓下来的原始表扔进lm()做死亡人数和年份的回归,结果系数完全不可解释。后来发现两个问题:一是死亡人数字段里混了非数字,被强制转换后产生大量 NA,实际参与回归的样本只剩一小部分;二是不同年份的事故记录完整度不一样,早期年份漏记严重,直接回归等于用有偏样本拟合。从那以后我每次拿到这类历史数据,都强制走一遍“字段类型检查 → 缺失模式可视化 → 样本完整性评估”三步,确认数据质量后再建模。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询