library(nycflights13)
library(tidyverse)
#使用dplyr进行数据转换
#加载 tidyverse 时,仔细查看输出的冲突信息,
#它会告诉你 dplyr 覆盖了基础 R 包中的哪些 函数。
#如果想要在加载 dplyr 后使用这些函数的基础版本,
#那么你应该使用它们的完整名 称:stats::filter() 和 stats::lag()。
#nycflights13这个数据框包含了2013年从纽约市出发的所有336776次航班的信息。
flights
#只显示了前几行和适合屏幕宽度的几列。
#(要想看到整个数据集,可以使用 View(flights) 在 RStudio 查看器中打开数据集。)
View(flights)
#输出有差别是因为 flights 是一个 tibble。
#列名下面有一行 3 个或 4 个字母的缩写。它们描述了每个变量的类型。
#• int 表示整数型变量。
#• dbl 表示双精度浮点数型变量,或称实数。
#• chr 表示字符向量,或称字符串。
#• dttm 表示日期时间(日期 + 时间)型变量。
#• lgl 表示逻辑型变量,是一个仅包括 TRUE 和 FALSE 的向量。
#• fctr 表示因子,R 用其来表示具有固定数目的值的分类变量。
#• date 表示日期型变量。
#学习 5 个 dplyr 核心函数
#• 按值筛选观测(filter())。
#• 对行进行重新排序(arrange())。
#• 按名称选取变量(select())。
#• 使用现有变量的函数创建新变量(mutate())。
#• 将多个值总结为一个摘要统计量(summarize())。
#5 个函数的工作方式都是相同的。
#(1) 第一个参数是一个数据框。
#(2) 随后的参数使用变量名称(不带引号)描述了在数据框上进行的操作。
#(3) 输出结果是一个新数据框。
#filter() 函数可以基于观测的值筛选出一个观测子集。
#第一个参数是数据框名称,
#第二个参数以及随后的参数是用来筛选数据框的表达式。
#筛选出 1 月 1 日的所有航班
filter(flights, month == 1, day == 1)
#dplyr 函数从来不修改输入,
#因此,如果想要保存函数结果,那么你就需要使用赋值操作符 <-
jan1 <- filter(flights, month == 1, day == 1)
#R 要么输出结果,要么将结果保存在一个变量中。
#如果想同时完成这两种操作,那么你可以用括号将赋值语句括起来
(jan1 <- filter(flights,month==1,day==1))
#R 提供了一套标准的比较运算符:>、>=、<、<=、!=(不等于)和 ==(等于)。
#易犯错误使用 = 而不是 == 来测试是否相等
filter(flights, month = 1)
#> Error: filter() takes unnamed arguments. Do you need `==`?
#使用 == 进行比较浮点数
sqrt(2) ^ 2 == 2
1/49 * 49 == 1
#计算机使用的是有限精度运算(显然无法存储无限位的数),
#因此请记住,你看到的每个 数都是一个近似值。
#比较浮点数是否相等时,不能使用 ==,而应该使用 near()
near(sqrt(2) ^ 2, 2)
near(1 / 49 * 49, 1)
#filter() 中的多个参数是由“与”组合起来的
#每个表达式都必须为真才能让一行观测包含在输出中。
#使用布尔运算符:& 表示“与”、| 表示 “或”、! 表示“非”
#找出 11 月或 12 月出发的所有航班
filter(flights, month == 11 | month == 12)
#不能写成filter(flights, month == 11 | 12) 这种形式
#代码中的含义是找出所有出发月份为 11 | 12 的航班。
#11 | 12 这个 逻辑表达式的值为 TRUE
#TRUE 就是 1,所以这段代码找出的不是11月或12月出发的航班,
#而是 1 月出发的所有航班。
#这种问题有一个有用的简写形式:x %in% y。
#这会选取出x是y中的一个值时的所有行。
nov_dec <- filter(flights, month %in% c(11, 12))
#德摩根定律将复杂的筛选条件进行简化
# !(x & y)等价于!x | !y
# !(x | y)等价于!x & !y
#延误时间(到达或出发)不多于2小时的航班
filter(flights, !(arr_delay > 120 | dep_delay > 120))
filter(flights, arr_delay <= 120, dep_delay <= 120)
#除 & 和 | 之外,R 中还有 && 和 || 运算符。
#先不要使用这两个运算符!
#只要 filter() 函数中使用的是复杂的、包含多个部分的表达式,
#就需要考虑用一个明确的 变量来代替它。
#这样检查代码会容易很多。
#缺失值,或称 NA(not available, 不可用)。
#NA 表示未知的值,因此缺失值是“可传染的”。
NA > 5
#> [1] NA
10 == NA
#> [1] NA
NA + 10
#> [1] NA
NA / 2
#> [1] NA
NA == NA
#> [1] NA
# 令x为Mary的年龄。我们不知道她有多大。
x <- NA
# 令y为John的年龄。我们不知道他有多大。
y <- NA
# John和Mary的年龄是相同的吗?
x == y
#> [1] NA
# 我们不知道!
#如果想要确定一个值是否为缺失值,可以使用 is.na() 函数:
is.na(x)
#> [1] TRUE
#filter() 只能筛选出条件为 TRUE 的行;
#它会排除那些条件为 FALSE 和 NA 的行。
#如果想保留缺失值,可以明确指出:
df <- tibble(x = c(1, NA, 3))
filter(df, x > 1)
filter(df, is.na(x) | x > 1)
#到达时间延误 2 小时或更多的航班。
filter(flights,arr_delay>=120)
#飞往休斯顿(IAH机场或HOU机场)的航班。
filter(flights,dest=='IAH'|dest=='HOU')
filter(flights,dest %in% c('IAH','HOU'))
#由联合航空(United)、美利坚航空(American)或三角洲航空(Delta)运营的航班。
filter(flights,carrier %in% c('UA','AA','DL'))
#夏季(7月、8月和9月)出发的航班。
filter(flights,month %in% c(7,8,9))
filter(flights, month >= 7, month <= 9)
filter(flights, between(month,7,9))
#到达时间延误超过 2 小时,但出发时间没有延误的航班。
filter(flights,dep_delay<=0,arr_delay>120)
filter(flights,!(dep_delay>0|arr_delay<=120))
#延误至少 1 小时,但飞行过程弥补回 30 分钟的航班。
filter(flights, dep_delay >= 60, dep_delay - arr_delay > 30)
#出发时间在午夜和早上6点之间(包括0点和6点)的航班。
filter(flights, dep_time<=600)
#dep_time 有缺失值的航班有多少?
filter(flights, is.na(dep_time))
NA ^ 0
#[1] 1
NA | TRUE
#[1] TRUE
FALSE & NA
#[1] FALSE
NA * 0
#[1] NA
#使用arrange()排列行
#arrange() 函数的工作方式与 filter() 函数非常相似,
#但前者不是选择行,而是改变行的顺序。
#它接受一个数据框和一组作为排序依据的列名(或者更复杂的表达式)作为参数。
#如果列名不只一个,那么就使用后面的列在前面排序的基础上继续排序:
arrange(flights, year, month, day)
#使用 desc() 可以按列进行降序排序
arrange(flights, desc(arr_delay))
#缺失值总是排在最后:
df <- tibble(x = c(5, 2, NA))
arrange(df, x)
arrange(df, desc(x))
#如何使用 arrange() 将缺失值排在最前面?(提示:使用 is.na()。)
arrange(df, desc(is.na(x)))
#对 flights 排序以找出延误时间最长的航班。找出出发时间最早的航班。
arrange(flights,desc(arr_delay))
arrange(flights,dep_time)
#对 flights 排序以找出速度最快的航班。
#哪个航班的飞行时间最长?哪个最短?
#使用select()选择列
#select() 函数可以让你快速生成一个有用的变量子集。
# 按名称选择列
select(flights, year, month, day)
# 选择“year”和“day”之间的所有列(包括“year”和“day”)
select(flights, year:day)
# 选择不在“year”和“day”之间的所有列(不包括“year”和“day”)
select(flights, -(year:day))
#还可以在 select () 函数中使用一些辅助函数。
# • starts_with("abc"):匹配以“abc”开头的名称。
# • ends_with("xyz"):匹配以“xyz”结尾的名称。
# • contains("ijk"):匹配包含“ijk”的名称。
# • matches("(.)\\1"):选择匹配正则表达式的那些变量。
# 这个正则表达式会匹配名称中有重复字符的变量。
# • num_range("x", 1:3):匹配 x1、x2 和 x3。
#select() 可以重命名变量,但我们很少这样使用它,
#因为这样会丢掉所有未明确提及的变 量。
#我们应该使用 select() 函数的变体 rename() 函数来重命名变量,以保留所有未明确 提及的变量:
rename(flights, tail_num = tailnum)
#将 select() 函数和 everything() 辅助函数结合起来使用。
#当想要将几个变量移到数据框开头时,这种用法非常奏效:
select(flights, time_hour, air_time, everything())
#从 flights 数据集中选择 dep_time、dep_delay、arr_time 和 arr_delay,通过头脑风暴找出尽可能多的方法。
select(flights, starts_with("dep_"), starts_with("arr_"))
select(flights,dep_time,dep_delay,arr_time,arr_delay)
#select忽略重复项,只选第一个。
select(flights, year, month, day, year, year)
# one_of() 函数
vars <- c(
"year", "month", "day", "dep_delay", "arr_delay"
)
select(flights,one_of(vars))
#选择辅助函数处理大小写的默认方式忽略大小写
select(flights, contains("TIME"))
# contains忽略了大小写,有个参数可以改变
select(flights, contains("TIME", ignore.case = FALSE))
#使用mutate()添加新变量
#除了选择现有的列,我们还经常需要添加新列,新列是现有列的函数。
#这就是 mutate() 函数的作用。
#mutate() 总是将新列添加在数据集的最后,
#因此我们需要先创建一个更狭窄的数据集,以便能够看到新变量。
#当使用 RStudio 时,查看所有列的最简单的方法就是使用 View() 函数
flights_sml <- select(flights,
year:day,
ends_with("delay"),
distance,
air_time
)
mutate(flights_sml,
gain = arr_delay - dep_delay,
speed = distance / air_time * 60
)
#一旦创建,新列就可以立即使用
mutate(flights_sml,
gain = arr_delay - dep_delay,
hours = air_time / 60,
gain_per_hour = gain / hours
)
#如果只想保留新变量,可以使用 transmute() 函数:
transmute(flights,
gain = arr_delay - dep_delay,
hours = air_time / 60,
gain_per_hour = gain / hours
)
#创建新变量的多种函数可供你同 mutate() 一同使用。
#最重要的一点是,这种函数必须是向量化的:
#它必须接受一个向量作为输入,并返回一个向量作为输出,
#而且输入向量与输出向量具有同样数目的分量。
#算术运算符:+、-、*、/、^
#当某个参数是单个数值时,这种方式是最有效的:
# air_ time / 60、hours * 60 + minute 等。
#聚集函数
#x / sum(x)可以计算出各个分量在总数中的比例,
#y – mean(y)可以计算出分量与均值之间的差值
#模运算符:%/% 和 %%
#%/%(整数除法)和%%(求余)满足x == y * (x %/% y) + (x %% y)。
#模运算非常好用,因为它可以拆分整数。
#在航班数据集中,你可以根据 dep_time 计算出 hour 和 minute:
transmute(flights,
dep_time,
hour = dep_time %/% 100,
minute = dep_time %% 100
)
#对数函数:log()、log2() 和 log10()
#在处理取值范围横跨多个数量级的数据时,对数是特别有用的一种转换方式。
#它还可以将乘法转换成加法。
#其他条件相同的情况下,推荐使用 log2() 函数,
#因为很容易对其进行解释:对数标度的数值增加 1 个单位,意味着初始数值加倍;
#减少1个单位,则意味着初始数值减半。
#偏移函数
#lead() 和 lag() 函数可以返回一个序列的领先值和滞后值。
#它们可以计算出序列的移动差值(如x – lag(x))或发现序列何时发生了变化(x != lag(x))。
#它们与group_by() 组合使用时特别有用,你很快就会学到 group_by() 这个函数
(x <- 1:10)
#> [1] 1 2 3 4 5 6 7 8 9 10
lag(x)
#> [1] NA 1 2 3 4 5 6 7 8 9
lead(x)
#> [1] 2 3 4 5 6 7 8 9 10 NA
#累加和滚动聚合
#R 提供了计算累加和、累加积、累加最小值和累加最大值的函数:
#cumsum()、cumprod()、 commin() 和 cummax();
#dplyr 还提供了 cummean() 函数以计算累加均值。
#如果想要计算滚动聚合(即滚动窗口求和),那么可以尝试使用 RcppRoll 包:
cumsum(x)
#> [1] 1 3 6 10 15 21 28 36 45 55
cummean(x)
#> [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5
#逻辑比较:<、<=、>、>= 和 !=
# 如果需要进行一系列复杂的逻辑运算,
# 那么最好将中间结果保存在新变量中,
# 这样就可以检查是否每一步都符合预期。
#排秩
#min_rank() 函数可以完成最常用的排秩任务 (如第一、第二、第三、第四)。
#默认的排秩方式是,最小的值获得最前面的名次,
#使用desc(x) 可以让最大的值获得最前面的名次
y <- c(1, 2, 2, NA, 3, 4)
min_rank(y)
#> [1] 1 2 2 NA 4 5
min_rank(desc(y))
#> [1] 5 3 3 NA 2 1
# min_rank() 无法满足需要,
# 那么可以看一下其变体 row_number()、dense_rank()、 percent_rank()、cume_dist() 和 ntile()
row_number(y)
#> [1] 1 2 3 NA 4 5
dense_rank(y)
#> [1] 1 2 2 NA 3 4
percent_rank(y)
#> [1] 0.00 0.25 0.25 NA 0.75 1.00
cume_dist(y)
#> [1] 0.2 0.6 0.6 NA 0.8 1.0
#使用summarize()进行分组摘要
#它可以将数据框折叠成一行
summarize(flights, delay = mean(dep_delay, na.rm = TRUE))
# A tibble: 1 × 1
#> delay
#> <dbl>
#> 1 12.6
#group_by() 可以将分析单位从整个数据集更改为单个分组。
#在分组后的数据框上使用 dplyr 函数时,它们会自动地应用到每个分组。
#group_by() 和 summarize() 的组合构成了使用 dplyr 包时最常用的操作之一
#分组摘要
#对按日期分组的一个数据框应用与上面完全相同的代码,
#那么我们就可以得到每日平均延误时间
by_day <- group_by(flights, year, month, day)
summarize(by_day, delay = mean(dep_delay, na.rm = TRUE))
#介绍一个功能强大的新概念:管道
#研究每个目的地的距离和平均延误时间之间的关系
#完成数据准备需要 3 步。
#(1) 按照目的地对航班进行分组。
by_dest <- group_by(flights, dest)
#(2) 进行摘要统计,计算距离、平均延误时间和航班数量。
delay <- summarize(by_dest,
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
)
#(3) 通过筛选除去噪声点和火奴鲁鲁机场,因为到达该机场的距离几乎是到离它最近机场的距离的 2 倍。
delay <- filter(delay, count > 20, dest != "HNL")
#这段代码写起来有点令人泄气,因为不得不对每个中间数据框命名,尽管我们根本不关心这一点。
#命名是很难的,这样做会影响我们的分析速度。
#解决这个问题的另一种方法是使用管道,%>%
delays <- flights %>%
group_by(dest) %>%
summarize(
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
) %>%
filter(count > 20, dest != "HNL")
#这种方法的重点在于转换的过程,而不是转换的对象,
#这使得代码具有更好的可读性。
#你可以将其读作一串命令式语句:分组,然后摘要统计,然后进行筛选。
#在阅读代码时,%>% 最好读作“然后”。
#使用这种方法时,x %>% f(y)会转换为f(x, y),
#x %>% f(y) %>% g(z)会转换为g(f(x, y), z),以此类推。
#支持管道操作是 tidyverse 中的 R 包的核心原则之一。
#唯一的例外就是 ggplot2:它是在发 现管道方式前开发的。
#ggplot2 的下一个版本 ggvis 支持管道操作,遗憾的是其还没有达到成熟完备的程度。
#缺失值
#如果输入中有缺失值,那么输出也会是缺失值。
#所有聚合函数都有一个 na.rm 参数,它可以在计算前除去缺失值
#缺失值表示取消的航班,
flights %>%
group_by(year, month, day) %>%
summarize(mean = mean(dep_delay, na.rm = TRUE))
#我们也可以通过先去除取消的航班来解决缺失值问题。
not_cancelled <- flights %>%
filter(!is.na(dep_delay), !is.na(arr_delay))
not_cancelled %>%
group_by(year, month, day) %>%
summarize(mean = mean(dep_delay))
#计数
#聚合操作中包括一个计数(n())或非缺失值的计数(sum(!is_na()))
#确保自己没有基于非常少量的数据作出结论
#具有最长平均延误时间的飞机(通过机尾编号进行识别)
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarize(
delay = mean(arr_delay)
)
ggplot(data = delays, mapping = aes(x = delay)) +
geom_freqpoly(binwidth = 10)
#画一张航班数量和平均延误时间的散点图
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n() )
ggplot(data = delays, mapping = aes(x = n, y = delay)) +
geom_point(alpha = 1/10)
#当航班数量非常少时,平均延误时间的变动特别大。
#这张图的形状非常能够说明问题:
#当绘制均值(或其他摘要统计量)和分组规模的关系时,
#你总能看到随着样本量的增加,变动在不断减小。
#通常应该筛选掉那些观测数量非常少的分组,
#这样你就可以避免受到特别小的分组中的极端变动的影响,
#进而更好地发现数据模式。
#展示了将 ggplot2 集成到 dplyr 工作流中的一种有效方式。
#从 %>% 过渡到 + 会令人感到不适应,但掌握其中的要领后,这种写法是非常方便的:
delays %>%
filter(n > 25) %>%
ggplot(mapping = aes(x = n, y = delay)) +
geom_point(alpha = 1/10)
#棒球击球手的平均表现与击球次数之间的关系。
#击球手的能力(用打击率 ba 衡量)与击球机会数量(用打数 ab 衡量)
batting <- as_tibble(Lahman::Batting) # 转换成tibble,以便输出更美观
batters <- batting %>%
group_by(playerID) %>%
summarize(
ba = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
ab = sum(AB, na.rm = TRUE) )
batters %>%
filter(ab > 100) %>%
ggplot(mapping = aes(x = ab, y = ba)) +
geom_point() +
geom_smooth(se = FALSE)
#> `geom_smooth()` using method = 'gam'
#常用的摘要函数
#只使用均值、计数和求和是远远不够的
#位置度量
#我们已经使用过 mean(x),但 median(x) 也非常有用。
#均值是总数除以个数;
#中位数则 是这样一个值:50% 的 x 大于它,同时 50% 的 x 小于它。
#有时候需要将聚合函数和逻辑筛选组合起来使用。
not_cancelled %>%
group_by(year, month, day) %>%
summarize(
# 平均延误时间:
avg_delay1 = mean(arr_delay),
# 平均正延误时间:
avg_delay2 = mean(arr_delay[arr_delay > 0])
)
#分散程度度量:sd(x)、IQR(x) 和 mad(x)
#均方误差(又称标准误差,standard deviation,sd)是分散程度的标准度量方式。
#四分位距 IQR() 和绝对中位差 mad(x) 基本等价,更适合有离群点的情况:
# 为什么到某些目的地的距离比到其他目的地更多变?
not_cancelled %>%
group_by(dest) %>%
summarize(distance_sd = sd(distance)) %>%
arrange(desc(distance_sd))
#秩的度量:min(x)、quantile(x, 0.25) 和 max(x)
#分位数是中位数的扩展。
#例如,quantile(x, 0.25)会找出x中按从小到大顺序大于前 25% 而小于后75% 的值:
# 每天最早和最晚的航班何时出发?
not_cancelled %>%
group_by(year, month, day) %>%
summarize(
first = min(dep_time),
last = max(dep_time)
)
#定位度量:first(x)、nth(x, 2) 和 last(x)
#这几个函数的作用与 x[1]、x[2] 和 x[length(x)] 相同,
#只是当定位不存在时(比如尝试从只有两个元素的分组中得到第三个元素),
#前者允许你设置一个默认值。
#例如,我们可以找出每天最早和最晚出发的航班:
not_cancelled %>%
group_by(year, month, day) %>%
summarize(
first_dep = first(dep_time),
last_dep = last(dep_time)
)
#这些函数对筛选操作进行了排秩方面的补充。
#筛选会返回所有变量,每个观测在单独的一行中:
not_cancelled %>%
group_by(year, month, day) %>%
mutate(r = min_rank(desc(dep_time))) %>%
filter(r %in% range(r))
#range()返回的是一个向量里面最大值和最小值
#计数
#你已经见过 n(),它不需要任何参数,并返回当前分组的大小。
#计算出非缺失值的数量,可以使用 sum(!is.na(x))。
#要想计算出唯一值的数量,可以使用 n_ distinct(x)
# 哪个目的地具有最多的航空公司?
not_cancelled %>%
group_by(dest) %>%
summarize(carriers = n_distinct(carrier)) %>%
arrange(desc(carriers))
#dplyr 提供了一个简单的辅助函数,用于只需要计数的情况:
not_cancelled %>%
count(dest)
#还可以选择提供一个加权变量。
#例如,你可以使用以下代码算出每架飞机飞行的总里程数(实际上就是求和)
not_cancelled %>%
count(tailnum, wt = distance)
#逻辑值的计数和比例:sum(x > 10) 和 mean(y == 0)
#当与数值型函数一同使用时,TRUE 会转换为 1,FALSE 会转换为 0。
#这使得 sum() 和 mean() 非常适用于逻辑值:
#sum(x) 可以找出 x 中 TRUE 的数量,mean(x) 则可以找出比例。
# 多少架航班是在早上5点前出发的?(这通常表明前一天延误的航班数量)
not_cancelled %>%
group_by(year, month, day) %>%
summarize(n_early = sum(dep_time < 500))
# 延误超过1小时的航班比例是多少?
not_cancelled %>%
group_by(year, month, day) %>%
summarize(hour_perc = mean(arr_delay > 60))
#按多个变量分组
#当使用多个变量进行分组时,每次的摘要统计会用掉一个分组变量。
daily <- group_by(flights, year, month, day)
(per_day <- summarize(daily, flights = n()))
(per_month <- summarize(per_day, flights = sum(flights)))
(per_year <- summarize(per_month, flights = sum(flights)))
#在循序渐进地进行摘要分析时,需要小心:使用求和与计数操作是没问题的,
#但如果想要 使用加权平均和方差的话,就要仔细考虑一下,
#在基于秩的统计数据(如中位数)上是无法进行这些操作的。
#换句话说,对分组求和的结果再求和就是对整体求和,
#但分组中位数的中位数可不是整体的中位数。
#取消分组
#如果想要取消分组,并回到未分组的数据继续操作,
#那么可以使用 ungroup() 函数:
daily %>%
ungroup() %>% # 不再按日期分组
summarize(flights = n()) # 所有航班
#这些函数都可以和 group_by() 函数联合起来使用,
#group_by() 函数可以改变以上每个函数的作用范围,
#让其从在整个数据集上操作变为在每个分组上分别操作。
#一架航班 50% 的时间会提前 15 分钟,50% 的时间会延误 15 分钟。
not_cancelled %>%
group_by(tailnum) %>%
summarise(delay_perc1=mean(dep_delay==-15),delay_perc2=mean(arr_delay==-15))%>%
filter(delay_perc1==0.5,delay_perc2==0.5)
#分组新变量(和筛选器)
#虽然与 summarize() 函数结合起来使用是最有效的,
#但分组也可以与 mutate() 和 filter()函数结合,以完成非常便捷的操作。
#找出每个分组中最差的成员:
flights_sml %>%
group_by(year, month, day) %>%
filter(rank(desc(arr_delay)) < 10)
#找出大于某个阈值的所有分组:
popular_dests <- flights %>%
group_by(dest) %>%
filter(n() > 365)
popular_dests
#对数据进行标准化以计算分组指标:
popular_dests %>%
filter(arr_delay > 0) %>%
mutate(prop_delay = arr_delay / sum(arr_delay)) %>%
select(year:day, dest, arr_delay, prop_delay)