对 R 中的 map 函数感到困惑

ran*_*dms 4 r purrr

我用 purrr 库在 R 中编写了以下内容。好吧,它开始变得更加复杂,但是在尝试了 20 次之后,我将其简化为基本知识,以了解发生了什么。

map(list(1:60), function(a) { 
    if (a < 2) {
        return(1)
    } else {
        return(0)
    }})
Run Code Online (Sandbox Code Playgroud)
[[1]]
[1] 1
Run Code Online (Sandbox Code Playgroud)

警告消息:在 if (a < 2) { 中:

条件的长度 > 1 并且只使用第一个元素

预期结果是一个列表,其中前两个元素的值是 1,其余元素的值是 0。

这里发生了什么?

akr*_*run 6

If we place it in a list, it would be treated as a single unit

library(purrr)
map(1:60, ~ if(.x > 2) 1 else 0)
Run Code Online (Sandbox Code Playgroud)

Note that the ~ is a compact way in tidyverse for anonymous function call (function(x) if(x > 2) 1 else 0)

Or if we are creating a list of 60 elements, then as.list would be useful

map(as.list(1:60), ~ if(.x > 2) 1 else 0)
Run Code Online (Sandbox Code Playgroud)

For this, we don't need a loop though, as it can be unlistted and simply do a comparison (>) with 2 to create a logical vector which can be coerced to binary with as.integer or (+ - converts the TRUE -> 1, FALSE -> 0)

+(unlist(as.list(1:60)) > 2)
Run Code Online (Sandbox Code Playgroud)

and if the output needs to be a list, wrap with as.list

as.list(+(unlist(as.list(1:60)) > 2))
Run Code Online (Sandbox Code Playgroud)

In the OP's code, what happened is

list(1:60)
#[[1]]
#[1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#[43] 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
Run Code Online (Sandbox Code Playgroud)

the input is a list with only single element having a length of 60. With map, it loops through that single element, but if/else is not vectorized

According to ?Control, the usage is

if(cond) cons.expr else alt.expr

where

cond - A length-one logical vector that is not NA. Conditions of length greater than one are currently accepted with a warning, but only the first element is used. An error is signalled instead when the environment variable

and this results in the warning message i.e. if/else expects the list element to have a length of just 1. Instead, we can use ifelse which is vectorized

map(list(1:60), ~ ifelse(.x > 2, 1, 0))
#[[1]]
#[1] 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Run Code Online (Sandbox Code Playgroud)

Or just extract the list element

ifelse(list(1:60)[[1]] > 2, 1, 0)
Run Code Online (Sandbox Code Playgroud)