第 22 章 tidyverse中的dot

本章介绍tidyverse的语法中经常遇到., 不同的场景,含义不同。因此很有必要弄清楚各自的含义。

library(tidyverse)

22.1 每一行的 . 各自代表什么意思呢?

read_csv("./data/wages.csv") %>%
  mutate(letter = str_extract(race, "(?<=h)(.)")) %>%
  select(., -letter) %>%
  mutate_at(vars(race), ~ as.factor(.)) %>%
  mutate_at(vars(sex), ~ if_else(. == "male", 1, 0)) %>%
  filter_if(~ is.numeric(.), all_vars(. != 0)) %>%
  split(.$sex) %>%
  map(~ lm(earn ~ ., data = .)) %>%
  map_dfr(~ broom::tidy(.), .id = "sex")

回答之前,我们先介绍一些相关知识点

22.2 占位符

管道符号%>% 主要功能是传递参数。

  • y %>% f() is equivalent to f(y)

  • y %>% f(x, .) is equivalent to f(x, y)

  • z %>% f(x, y, arg = .) is equivalent to f(x, y, arg = z)

我们经常这样写

mtcars %>%
  select(cyl, disp, hp) %>%
  head(2)
## # A tibble: 2 x 3
##     cyl  disp    hp
##   <dbl> <dbl> <dbl>
## 1     6   160   110
## 2     6   160   110

实际上,这里是有占位符的

mtcars %>%
  select(., cyl, disp, hp) %>%
  head(., 2)
## # A tibble: 2 x 3
##     cyl  disp    hp
##   <dbl> <dbl> <dbl>
## 1     6   160   110
## 2     6   160   110

22.3 Lambda函数

.出现在函数.f的位置上, 就是 purrr 风格的Lambda函数~ fun(.)

mtcars %>%
  select_at(vars(contains("ar")), ~ toupper(.)) %>%
  head(3)
## # A tibble: 3 x 2
##    GEAR  CARB
##   <dbl> <dbl>
## 1     4     4
## 2     4     4
## 3     4     1

有时候程序员会将~toupper(.)简写成 toupper

mtcars %>%
  select_at(vars(contains("ar")), toupper) %>%
  head(3)
## # A tibble: 3 x 2
##    GEAR  CARB
##   <dbl> <dbl>
## 1     4     4
## 2     4     4
## 3     4     1

22.4 正则表达式

words <- "the fattest cat."
words %>% str_replace_all("t.", "-")
## [1] "-e fa-es-ca-"
words %>% str_replace_all("t\\.", "-")
## [1] "the fattest ca-"

22.5 Unary funciton (只带一个参数的函数)

mean_rm <- . %>% mean(na.rm = T)

c(1, 2, 3, NA) %>% mean_rm()
## [1] 2

等价于

# is equivalent to
c(1, 2, 3, NA) %>% mean(., na.rm = T)
## [1] 2

22.6 more placeholder

iris %>% subset(1:nrow(.) %% 30 == 0)
## # A tibble: 5 x 5
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
##          <dbl>       <dbl>        <dbl>       <dbl>
## 1          4.7         3.2          1.6         0.2
## 2          5.2         2.7          3.9         1.4
## 3          5.5         2.5          4           1.3
## 4          6           2.2          5           1.5
## 5          5.9         3            5.1         1.8
## # ... with 1 more variable: Species <fct>
1:10 %>% {
  c(min(.), max(.))
}
## [1]  1 10

22.7 当mutate遇到map

dplyr::mutate遇到purrr::map,情况就复杂很多了。然而,这种情况,tidyverse比比皆是。我就多说几句吧

iris %>%
  head(3) %>%
  mutate(., r_sum = pmap_dbl(select_if(., is.numeric), sum))
## # A tibble: 3 x 6
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
##          <dbl>       <dbl>        <dbl>       <dbl>
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3            1.4         0.2
## 3          4.7         3.2          1.3         0.2
## # ... with 2 more variables: Species <fct>,
## #   r_sum <dbl>

这里mutate()行,有两个., 实际这两个.都是等待iris %>% head(3)传来的data.frame

df <- tibble(
  mean = c(1, 2),
  sd = c(2, 4)
)
df
## # A tibble: 2 x 2
##    mean    sd
##   <dbl> <dbl>
## 1     1     2
## 2     2     4
df %>%
  dplyr::mutate(., rand = map(mean, ~ rnorm(5, .))) %>%
  tidyr::unnest_wider(rand)
## # A tibble: 2 x 7
##    mean    sd  ...1    ...2  ...3  ...4  ...5
##   <dbl> <dbl> <dbl>   <dbl> <dbl> <dbl> <dbl>
## 1     1     2 0.925 -0.802   2.99 0.937  2.66
## 2     2     4 4.41   0.0853  2.25 2.58   2.95
  • 第一个 ., 是df
  • 第二个 ., 是df中的mean
df %>%
  dplyr::mutate(rand = map2(mean, sd, ~ rnorm(5, .x, .y))) %>%
  tidyr::unnest_wider(rand)
## # A tibble: 2 x 7
##    mean    sd   ...1  ...2  ...3  ...4  ...5
##   <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
## 1     1     2  0.227  1.37 0.326  1.93  4.11
## 2     2     4 -4.65   2.15 6.33  -2.17  3.66
  • mean传给 .x
  • sd传给 .y

再来一个变态的。(我们不一定要这样写,但我们尽可能的要明白它的意思。)

df <- tribble(
  ~a, ~b,
  1, 10,
  2, 11
)


df %>%
  dplyr::mutate(., sum = purrr::pmap_dbl(., ~ sum(...)))
## # A tibble: 2 x 3
##       a     b   sum
##   <dbl> <dbl> <dbl>
## 1     1    10    11
## 2     2    11    13

22.8 Dot dot dot

commas <- function(...) {
  stringr::str_c(..., collapse = ", ")
}


commas(letters[1:10])
## [1] "a, b, c, d, e, f, g, h, i, j"

22.9 Don’t confuse

注意:有些函数的参数前缀是 .

mutate_all(.tbl, .funs, ...)

mutate_if(.tbl, .predicate, .funs, ...)

mutate_at(.tbl, .vars, .funs, ..., .cols = NULL)

select_all(.tbl, .funs = list(), ...)

rename_all(.tbl, .funs = list(), ...)

22.10 小结

  • tidyvere中
    • 占位符(时常经常和 %>% 一起)
    • Lambda函数
    • 一元函数(LHS)
  • 其他情形
    • 回归公式
    • 正则表达式
  • 注意
    • 有些函数参数以 . 前缀(不要混淆喔! )

22.11 回答问题

现在回答本章开始的问题

read_csv("./demo_data/wages.csv") %>%
  dplyr::mutate(letter = str_extract(race, "(?<=h)(.)")) %>%
  dplyr::select(., -letter) %>%
  dplyr::mutate_at(vars(race), ~ as.factor(.)) %>%
  dplyr::mutate_at(vars(sex), ~ if_else(. == "male", 1, 0)) %>%
  dplyr::filter_if(~ is.numeric(.), all_vars(. != 0)) %>%
  split(.$sex) %>%
  purrr::map(~ lm(earn ~ ., data = .)) %>%
  purrr::map_dfr(., ~ broom::tidy(.), .id = "sex")
## # A tibble: 8 x 6
##   sex   term     estimate std.error statistic   p.value
##   <chr> <chr>       <dbl>     <dbl>     <dbl>     <dbl>
## 1 1     (Interc~ -121846.   37449.    -3.25    1.21e- 3
## 2 1     height       977.     515.     1.90    5.84e- 2
## 3 1     sex           NA       NA     NA      NA       
## 4 1     racehis~     578.    7934.     0.0728  9.42e- 1
## 5 1     raceoth~   -2035.   11514.    -0.177   8.60e- 1
## 6 1     racewhi~   12823.    5284.     2.43    1.56e- 2
## 7 1     ed          5234.     601.     8.71    4.30e-17
## 8 1     age          406.      95.5    4.25    2.52e- 5
  • 第1行:路径中.代表当前位置,如果是..表示上一级目录
  • 第2行:正则表达式,代表任何字符
  • 第3行:占位符,等待数据框的传入,也可以简写select(-letter)
  • 第4行: lambda函数,~ as.factor(.)也可以简写as.factor~(.)要么都写,要么都不写
  • 第5行:同上,lambda函数
  • 第6行:第一个.代表lambda函数; 第二个.也是lambda函数,但这里它是all_vars(expr)中expr的一种特有写法,代表所有数值型变量,*行方向构成的向量, all_vars(. != 0)函数返回TRUE或FALSE,从而帮助filter()是否筛选该行
  • 第7行:占位符,代表上面传来的数据框
  • 第8行:回归模型lm中,第一个.代表除因变量earn之外所有的变量,第二个.占位符,留给上面的数据框
  • 第9行:第一个.是占位符,代表上面传来的list,第二个.lambda函数,依次对list的元素迭代处理,第二个.是参数名,.id是特有的一个符号。