3
votes

I would like to know how to use purrr::map where .f is a composition of two different functions.

First, let's create a list on which to map a composite function:

library(tidyverse)

# create a list
x <- list(mtcars, tibble::as_tibble(iris), c("x", "y", "z"))

# extracting class of objects
purrr::map(.x = x, .f = class)
#> [[1]]
#> [1] "data.frame"
#> 
#> [[2]]
#> [1] "tbl_df"     "tbl"        "data.frame"
#> 
#> [[3]]
#> [1] "character"

Now let's say I want to extract the first element of the class of each element in the list:

# this works but uses `map` twice
purrr::map(.x = x, .f = class) %>%
  purrr::map(.x = ., .f = `[[`, i = 1L)

#> [[1]]
#> [1] "data.frame"
#> 
#> [[2]]
#> [1] "tbl_df"
#> 
#> [[3]]
#> [1] "character"

That works, but I want to avoid using map twice and would like to compose a function that can extract class and its first element in a single step. So I tried to compose such a function but it doesn't play nicely with map

# error

purrr::map(.x = x, .f = purrr::compose(class, `[[`, i = 1L))
#> Can't convert an integer vector to function

# no error but not the expected output

purrr::map(.x = x, .f = purrr::compose(class, `[[`), i = 1L)
#> [[1]]
#> [1] "numeric"
#> 
#> [[2]]
#> [1] "numeric"
#> 
#> [[3]]
#> [1] "character"

How can I do this?

3
Wouldn't map(x, ~ first(class(.x))) workakrun
Or using compose: purrr::map(x, purrr::compose(first, class)) or purrr::map(x, purrr::compose(~.[[1]], class)). You can't really pass different parameters to different parts of the function in compose from outside the composition.MrFlick
@akrun That worked! purrr::map(x, ~class(.x)[[1]]) works as well if I was hell-bent on using [[. Can you post your answer and I'll accept it.Indrajeet Patil

3 Answers

5
votes

If we are using the ~, just wrapping the first would get the expected output

library(purrr)
map(x, ~ first(class(.)))
1
votes

We can directly use compose() without the formula syntax:

library(tidyverse)

x <- list(mtcars, tibble::as_tibble(iris), c("x", "y", "z"))

map(x, compose(first, class))
#> [[1]]
#> [1] "data.frame"
#> 
#> [[2]]
#> [1] "tbl_df"
#> 
#> [[3]]
#> [1] "character"

Created on 2021-06-30 by the reprex package (v2.0.0)

0
votes

From ?compose

compose(..., .dir = c("backward", "forward"))

... Functions to apply in order (from right to left by default) etc ...

.dir If "backward" (the default), the functions are called in the reverse order, from right to left, as is conventional in mathematics. If "forward", they are called from left to right.

so we just need to inverse the functions order. Also compose doesn't know i=1L belong to which function so compose will attach it to the last function in this case class, so we need to defined i=1L explecitly to the intended function.

purrr::map(.x = x, .f = purrr::compose(~.x[[1]], class))
[[1]]
[1] "data.frame"

[[2]]
[1] "tbl_df"

[[3]]
[1] "character"