purrr不爲人知的技巧

purrr 是一個拓展R函數式編程能力的包。它會涉及到不少東西,在這篇文章中,我會展現在purrr中最重要的(至少對我來講)幾個函數。編程

map函數來擺脫循環

library(purrr)

numbers <- list(11, 12, 13, 14)

map_dbl(numbers, sqrt)
## [1] 3.316625 3.464102 3.605551 3.741657

你可能想知道爲何這可能比for循環更受歡迎?由於它更簡潔,你不須要初始化任何類型的結構來保存結果。若是用google 「create empty list in R」,你會發現它很廣泛。然而,有了map函數族,將不須要初始化結構。map_dbl函數會返回一個實數原子列表(atomic list),map函數會返回一個列表,去試一下吧。less

在某些list上使用map

map_if()

#創造一個輔助函數,若是爲偶數則返回TRUE
is_even <- function(x){
  !as.logical(x %% 2)
}
map_if(numbers, is_even, sqrt)
## [[1]]
## [1] 11
## 
## [[2]]
## [1] 3.464102
## 
## [[3]]
## [1] 13
## 
## [[4]]
## [1] 3.741657

map_at()

map_at(numbers, c(1,3), sqrt)
## [[1]]
## [1] 3.316625
## 
## [[2]]
## [1] 12
## 
## [[3]]
## [1] 3.605551
## 
## [[4]]
## [1] 14

map_if()map_at()map擁有更多的參數;若是是map_if(),則是用一個判斷函數(一個返回TRUE 或者FALSE的函數),map_at則是用一個位置向量。這樣只有在知足某一條件時纔會映射你的函數,這也是不少人google尋找的東西。函數式編程

在多個參數中映射一個函數

numbers2 <- list(1, 2, 3, 4)

map2(numbers, numbers2, `+`)
## [[1]]
## [1] 12
## 
## [[2]]
## [1] 14
## 
## [[3]]
## [1] 16
## 
## [[4]]
## [1] 18

map_2函數能夠輸入2個參數來函數

你可使用map_2函數將兩個列表映射到一個函數,你甚至可使用pmap()函數將任意數量的列表映射到任何函數。oop

若是出現問題,不要中止執行你的函數

possible_sqrt <- possibly(sqrt, otherwise = NA_real_)

numbers_with_error <- list(1, 2, 3, "spam", 4)

map(numbers_with_error, possible_sqrt)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 1.414214
## 
## [[3]]
## [1] 1.732051
## 
## [[4]]
## [1] NA
## 
## [[5]]
## [1] 2

另外一個很常見的問題:即便報錯,也要繼續執行你的循環。在大多數狀況下,循環會在錯誤處中止,可是你想讓他繼續跑下去,看看哪裏出錯了。去google 「skip error in a loop」 你會發現有不少人也想這樣作。其實只要結合map()函數與possibly()函數就能夠了。大多數解決方案能夠會說使用tryCatch函數,但我我的感受它不太好用。google

若是出現錯誤,不要中止執行函數並捕獲錯誤

safe_sqrt <- safely(sqrt, otherwise = NA_real_)

map(numbers_with_error, safe_sqrt)
## [[1]]
## [[1]]$result
## [1] 1
## 
## [[1]]$error
## NULL
## 
## 
## [[2]]
## [[2]]$result
## [1] 1.414214
## 
## [[2]]$error
## NULL
## 
## 
## [[3]]
## [[3]]$result
## [1] 1.732051
## 
## [[3]]$error
## NULL
## 
## 
## [[4]]
## [[4]]$result
## [1] NA
## 
## [[4]]$error
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
## 
## 
## [[5]]
## [[5]]$result
## [1] 2
## 
## [[5]]$error
## NULL

safely函數與possibly函數很類似,可是它會在列表中返回列表。所以元素是結果和伴隨錯誤消息的列表。若是沒有錯誤,則返回NULL。若是有錯誤,則返回錯誤信息。atom

轉置一個列表

safe_result_list <- map(numbers_with_error, safe_sqrt)

transpose(safe_result_list)
## $result
## $result[[1]]
## [1] 1
## 
## $result[[2]]
## [1] 1.414214
## 
## $result[[3]]
## [1] 1.732051
## 
## $result[[4]]
## [1] NA
## 
## $result[[5]]
## [1] 2
## 
## 
## $error
## $error[[1]]
## NULL
## 
## $error[[2]]
## NULL
## 
## $error[[3]]
## NULL
## 
## $error[[4]]
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
## 
## $error[[5]]
## NULL

這裏咱們轉置了一個列表。這意味着咱們仍然返回列表中的列表,可是第一個列表裏面全是results,可經過safe_result_list$result獲得;第二個列表中全是errors,能夠經過 safe_result_list$error獲得,這是頗有用的。spa

將函數應用到列表的更底層

transposed_list <- transpose(safe_result_list)

transposed_list %>%
    at_depth(2, is_null)
## Warning: at_depth() is deprecated, please use `modify_depth()` instead
## $result
## $result[[1]]
## [1] FALSE
## 
## $result[[2]]
## [1] FALSE
## 
## $result[[3]]
## [1] FALSE
## 
## $result[[4]]
## [1] FALSE
## 
## $result[[5]]
## [1] FALSE
## 
## 
## $error
## $error[[1]]
## [1] TRUE
## 
## $error[[2]]
## [1] TRUE
## 
## $error[[3]]
## [1] TRUE
## 
## $error[[4]]
## [1] FALSE
## 
## $error[[5]]
## [1] TRUE

有時候處理列表嵌套列表的數據會很棘手,特別是當咱們想在子列表中應用一個函數時。可是使用at_depth()函數將會變得很簡單。code

對列表元素進行命名

name_element <- c("sqrt()", "ok?")

set_names(transposed_list, name_element)
## $`sqrt()`
## $`sqrt()`[[1]]
## [1] 1
## 
## $`sqrt()`[[2]]
## [1] 1.414214
## 
## $`sqrt()`[[3]]
## [1] 1.732051
## 
## $`sqrt()`[[4]]
## [1] NA
## 
## $`sqrt()`[[5]]
## [1] 2
## 
## 
## $`ok?`
## $`ok?`[[1]]
## NULL
## 
## $`ok?`[[2]]
## NULL
## 
## $`ok?`[[3]]
## NULL
## 
## $`ok?`[[4]]
## <simpleError in sqrt(x = x): non-numeric argument to mathematical function>
## 
## $`ok?`[[5]]
## NULL

對列表進行reduce操做,使之變成一個值

reduce(numbers, `*`)
## [1] 24024

下面是 accumulate()函數:orm

accumulate(numbers, `*`)
## [1]    11   132  1716 24024

它會保存中間結果。
若用accumulate_right() 則爲 從右向左

這個函數很是經常使用,你能夠reduce任何東西:

矩陣:

mat1 <- matrix(rnorm(10), nrow = 2)
mat2 <- matrix(rnorm(10), nrow = 2)
mat3 <- matrix(rnorm(10), nrow = 2)
list_mat <- list(mat1, mat2, mat3)

reduce(list_mat, `+`)#結果等同於mat1+mat2+mat3
##             [,1]       [,2]       [,3]     [,4]      [,5]
## [1,] -2.48530177  1.0110049  0.4450388 1.280802 1.3413979
## [2,]  0.07596679 -0.6872268 -0.6579242 1.615237 0.8231933

甚至數據框:

df1 <- as.data.frame(mat1)
df2 <- as.data.frame(mat2)
df3 <- as.data.frame(mat3)

list_df <- list(df1, df2, df3)

reduce(list_df, dplyr::full_join)
## Joining, by = c("V1", "V2", "V3", "V4", "V5")
## Joining, by = c("V1", "V2", "V3", "V4", "V5")
##           V1         V2          V3          V4         V5
## 1 -0.6264538 -0.8356286  0.32950777  0.48742905  0.5757814
## 2  0.1836433  1.5952808 -0.82046838  0.73832471 -0.3053884
## 3 -0.8969145  1.5878453 -0.08025176  0.70795473  1.9844739
## 4  0.1848492 -1.1303757  0.13242028 -0.23969802 -0.1387870
## 5 -0.9619334  0.2587882  0.19578283  0.08541773 -1.2188574
## 6 -0.2925257 -1.1521319  0.03012394  1.11661021  1.2673687

但願你能喜歡這些有用的列變函數。

原文地址

解釋下map,reduce

舉例說明,好比咱們有一個函數$f(x)=x^2$,要把這個函數做用在一個list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就能夠用map()實現以下:

舉例說明,好比咱們有一個函數f(x)=x^2,要把這個函數做用在一個list(1, 2, 3, 4, 5, 6, 7, 8, 9)上,就能夠用map()實現以下:  map(list(1:9),function(x)x^2)
            f(x) = x * x

                  │
                  │
  ┌───┬───┬───┬───┼───┬───┬───┬───┐
  │   │   │   │   │   │   │   │   │
  ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼

[ 1   2   3   4   5   6   7   8   9 ]

  │   │   │   │   │   │   │   │   │
  │   │   │   │   │   │   │   │   │
  ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼

[ 1   4   9  16  25  36  49  64  81 ]
reduce(list(x1, x2, x3, x4),f) = f(f(f(x1, x2), x3), x4)
相關文章
相關標籤/搜索