1.3 select()

如今,数据集有几百个甚至几千个变量已经司空见惯。这种情况下,如何找出真正感兴趣的变量经常是一个挑战。通过基于变量名的操作,select()函数可以让我们快速生成一个有用的变量子集。

glimpse(flights)
#> Rows: 336,776
#> Columns: 19
#> $ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013...
#> $ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
#> $ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
#> $ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 55...
#> $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 60...
#> $ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2,...
#> $ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 8...
#> $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 8...
#> $ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7,...
#> $ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6"...
#> $ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301...
#> $ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N...
#> $ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LG...
#> $ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IA...
#> $ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149...
#> $ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 73...
#> $ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6...
#> $ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59...
#> $ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-0...
# 选择 year,month,day 三个变量
flights %>% select(year, month, day)
#> # A tibble: 336,776 x 3
#>    year month   day
#>   <int> <int> <int>
#> 1  2013     1     1
#> 2  2013     1     1
#> 3  2013     1     1
#> 4  2013     1     1
#> 5  2013     1     1
#> 6  2013     1     1
#> # ... with 336,770 more rows

顺便说一句,如果把变量名变成字符串或者它在所有变量中的顺序也可以正常工作,如 flights %>% select("year", "month", "day")flights %>% select(1, 2, 3)和上面代码会返回一样结果,但是这两种方法都不值得推荐。

关于 Non-standard evalution {{ var }} !!enquo(var) 或者 .data[[var]] 将会使 select()按照我们希望的那样工作。

这里的问题同样适用于 dplyr 中的其他采用惰性求值的函数。另外, all_of(var).env$var 可以处理相反的问题。

select() 还可以重命名变量,但应该避免这样使用它,因为这样会丢掉所有未明确提及的变量。我们应该使用 select() 函数的变体 rename() 函数来重命名变量,它会把未提及的那些变量按照原名字放到生成的数据框里:

其他常见的select()函数用法如下所示:

还可以在select()函数中使用一些辅助函数:
* starts_with("abc"):匹配以名字以“abc”开头的列

  • ends_with("xyz"): 匹配名字以“xyz”结尾的列
  • contains("ijk"),匹配名字包含“ijk”的列
  • matches("(.)\\1"):选择名字符合正则表达式要求的列,后面将具体讲述正则表达式
  • num_range("x",c(1,2,3)),选择名字为“x1”、“x2”、“x3”的列
  • one_of(character_1,···,character_n):如果某个列的名字出现在序列里,则选出它
  • everything():匹配所有(剩余)变量,当想要将几个变量移到数据集开头时,这种方法很有用
  • last_col(offset = n):选择倒数第n列,不设置offset时,默认选择最后一列

利用这些帮助函数,我们可以为选择列设置任意数目的条件,select()中以逗号分隔的列表示“或” 关系,如:

注意:
所有帮助函数都忽略大小写:

如果要区分大小写,可以设置任意帮助函数的参数ignore.case = FALSE

1.3.1 练习

Exercise 1.7 flights 中选择 dep_timedep_delayarr_timearr_delay,找出尽可能多的方法

先查看这些变量的位置:

Exercise 1.8 如果在select()中多次计入一个变量名,会发生什么情况?

select()函数将会忽略重复出现的变量,只选出一列,同时也不会报错:

##mutate()

除了选择现有的列,经常还需要添加新列。新列是现有列的函数,这就 mutate() 函数的作用。
mutate() 总是将新列添加在最后,格式为 新列名= 表达式。为了便于观察它的效果,我们需要先创建一个更狭窄的数据集,以便能看到新变量。
例如,我们希望创建两个新列gainhours,分别表示飞机在飞行过程中弥补的延误时间 (gain = arr_dealy - dep_delay),然后把飞行时间换算成小时 hours = air_time / 60

一旦新列被创建,就可以立即使用。例如,可能想知道对gain做时间上的平均:

以上的数据转换也可以通过mutate()一次完成:

如果只想在保留新变量,可以使用transmute()

1.3.2 常用创建函数

有多种函数可以帮助mutate()创建新变量。比较重要的一点是,这些函数必须是向量化的:它能接受一个向量作为输入,并返回一个向量作为输出,而且输入和输出向量长度相等。下面介绍一些比较常用的函数。

**算术运算符 +、-、*、/、^**
它们都是向量化的,使用所谓的“循环法则(recycling rules)”。如果一个参数比另一个参数短,那么前者会自动扩展到相同的长度,但某个参数是单个数值时,这种方式是最有效的,如air_time * 60 或者 hours * 60 + minute等。
算术运算符的另一个用途是与我们后面将很快学到的聚集函数结合起来使用。例如,x / sum(x)可以计算出x的各个分量在总数中的比例,y - mean(y)计算出y的各个分量与均值之间的差异。

模运算符 %/% 和 %
%/%(整除)和%%(求余)满足x == y * (x %/% y) + (x %% y, 这两个运算符在Python中分别是// 和 % 。
模运算非常好用,因为它可以拆分整数。例如,在flights数据集中,可以根据dep_time计算出 hourminute

对数函数 log()/log2()/log10()
在处理取值范围变化多个数量级的数据时,对数变换很有用。其他条件相同的情况下,更推荐使用log2()函数,因为它的解释很容易,对数变换后的变量每增加一个单位,意味着原始变量加倍 ; 减少一个单位,则原始数据变为原来的一半。

偏移函数
lead()lag()函数分别将一个向量向前或向后移动指定的单位:

累加和滚动聚合
R的基础包提供了计算累加和、累加积、累加最小值和累加最大值的函数:cumsum()、cumprod()、cummax()、cummin();dplyr包还提供了cummean()函数以计算累积平均值。

逻辑比较 >、<=、>、>=、==、!=
如果要进行一系列复杂的逻辑运算,最好将中间结果保存在新变量中,这样就可以检查每一步是否都符合预期。

排秩
排秩函数有很多,从min_rank()开始,它可以完成最常用的排秩任务。默认的排秩方式是,最小的值获得最前面的秩(升序),使用desc(x)可以让最大的值获得前面的名次,NA值对应的秩也是NA:

min_rank()函数把相同值赋予相同的秩,如果有n个值秩相同为x,则下一个值的秩会直接从x+n开始 如果min_rank()无法满足需要,可以看一下它的一些变体:

  • row_number(),相同值不同秩
  • dense_rank:相同值的秩相同,但下一个值的秩不会跳转
  • percent_rank(): 将秩按照比例压缩为[0,1]的值
  • ntile():breaks the input vector into n buckets.

1.3.3 Exercises

Exercise 1.9 虽然现在的dep_timesched_dep_time变量方便阅读,但不适合计算,因为它们实际上并不是连续型数值。将它们转换为一种更方便的表示形式,即从 0 点开始的分钟数

xyz 表示 x 点 yz 分,则总分钟数为x %/% 100 * 60 + x %% 100 ; 但有一个问题是,由于 0 点是用2400代表的,经过这样的转换它变为 1440,我们希望它变为 0,所以在外层再套一个%% 1440

  1. 比较dep_time、sched_dep_time 和 dep_delay,这三者应该是何种关系?

如上所示,经过分钟的转换后,有1236行的dep_delay 不等于 dep_time - sched_dep_time. 有趣的是,这些差值全部等于1440。
> the discrepancies could be because a flight was scheduled to depart before midnight, but was delayed after midnight. All of these discrepancies are exactly equal to 1440 (24 hours), and the flights with these discrepancies were scheduled to depart later in the day.

  1. 使用排秩函数找出10个出发延误时间最长的航班
  1. 1:3 + 1:10会返回什么?为什么?

当一个向量中的值不够用时,这个向量会被循环使用 1:3 + 1:10等价于c(1 + 1, 2 + 2, 3 + 3, 1 + 4, 2 + 5, 3 + 6, 1 + 7, 2 + 8, 3 + 9, 1 + 10)