13

Is there a way to map to any type with purrr::map

library(tidyverse)
library(lubridate)

df <- data_frame(id = c(1, 1, 1, 2, 2, 2), 
                 val = c(1, 2, 3, 1, 2, 3), 
                 date = ymd("2017-01-01") + days(1:6))

df1 <- df %>% nest(-id) %>% 
  mutate(first_val = map_dbl(data, ~ .$val[1]), 
         first_day = map(data, ~ .$date[1]))

I would like first_day to be a column of type <date> as in df. I have tried flatten, but this does not work as it coerces the column to numeric.

user438383
  • 5,716
  • 8
  • 28
  • 43
johannes
  • 14,043
  • 5
  • 40
  • 51

4 Answers4

11

purrr is type-stable and this takes some getting used to.

In this case, it returns a list where you expect a <date>.

A simple and "stable" solution to you case would be to replace the second map with a map_dbl and have the output turned back to a <date> object using lubridate's as_date, like this:

df3 <- df %>% nest(-id) %>% 
   mutate(first_val = map_dbl(data, ~ .$val[1]), 
          first_day = as_date(map_dbl(data, ~ .$date[1])))

You get:

# A tibble: 2 × 4
  id             data                 first_val  first_day
 <dbl>          <list>                  <dbl>     <date>
 1              <tibble [3 × 2]>         1      2017-01-02
 2              <tibble [3 × 2]>         1      2017-01-05

Which is what you wanted (for this example).

EDIT: for any other types (other than <date>) you would have to find a different solution, however, the standard types are covered by the dedicated map_lgl, map_dbl, map_chr, etc.

Adi Sarid
  • 799
  • 8
  • 13
6

An alternative to the map_dbl() %>% as_date() is to use unnest() on the output column of interest:

library(tidyverse)
library(lubridate)
#> 
#> Attaching package: 'lubridate'
#> The following object is masked from 'package:base':
#> 
#>     date

df <- data_frame(id = c(1, 1, 1, 2, 2, 2), 
                 val = c(1, 2, 3, 1, 2, 3), 
                 date = ymd("2017-01-01") + days(1:6))

df %>% nest(-id) %>% 
  mutate(first_val = map_dbl(data, ~ .$val[1]), 
         first_day = map(data, ~ .$date[1])) %>% 
  unnest(first_day)
#> # A tibble: 2 x 4
#>      id data             first_val first_day 
#>   <dbl> <list>               <dbl> <date>    
#> 1     1 <tibble [3 × 2]>         1 2017-01-02
#> 2     2 <tibble [3 × 2]>         1 2017-01-05

Created on 2018-11-17 by the reprex package (v0.2.1)

Matifou
  • 7,968
  • 3
  • 47
  • 52
4

With purrr 1.0.0, you can use map_vec.

map_vec() (along with map2_vec(), and pmap_vec()) handles more types of vectors. map_vec() extends map_lgl(), map_int(), map_dbl(), and map_chr() to arbitrary types of vectors, like dates, factors, and date-times:

df %>% nest(data = -id) %>% 
  mutate(first_val = map_dbl(data, ~ .$val[1]), 
         first_day = map_vec(data, ~ .$date[1]))

output

# A tibble: 2 × 4
     id data             first_val first_day 
  <dbl> <list>               <dbl> <date>    
1     1 <tibble [3 × 2]>         1 2017-01-02
2     2 <tibble [3 × 2]>         1 2017-01-05

map_vec will always return a simpler vector with the correct vector class (erroring if there is no common type), but you can also specify it with .ptype:

df %>% nest(data = -id) %>% 
  mutate(first_val = map_vec(data, ~ .$val[1], .ptype = integer()), 
         first_day = map_vec(data, ~ .$date[1], .ptype = Date()))
Maël
  • 45,206
  • 3
  • 29
  • 67
1

You could rely on purrr's reduce() with c():

library(tidyverse)
library(lubridate)

df <- tibble(id = c(1, 1, 1, 2, 2, 2), 
             val = c(1, 2, 3, 1, 2, 3), 
             date = ymd("2017-01-01") + days(1:6))

df1 <- df %>% nest(data = -id) %>% 
    mutate(first_val = map_dbl(data, ~ .$val[1]), 
           first_day = reduce(map(data, ~ .$date[1]), c))

Result:

> df1
# A tibble: 2 × 4
     id data             first_val first_day 
  <dbl> <list>               <dbl> <date>    
1     1 <tibble [3 × 2]>         1 2017-01-02
2     2 <tibble [3 × 2]>         1 2017-01-05
Salim B
  • 2,409
  • 21
  • 32