tidyverse / purrr

A functional programming toolkit for R
https://purrr.tidyverse.org/
Other
1.28k stars 272 forks source link

Inconsistent behaviour of map and pluck when indexing by name and location #1152

Closed JBGruber closed 1 month ago

JBGruber commented 1 month ago

I was reading the pluck documentation when I stumbled on this interesting note in the examples:

# The map() functions use pluck() by default to retrieve multiple
# values from a list:

But when I wanted to try it, I got stuck. What my problem was is that mixing indexing by name and location does not work with map(), but I use it all the time with pluck. Here is an example where I would expect the outcome of both map calls to be the same:

obj1 <- list("a", list(1, elt = "foo"))
obj2 <- list("b", list(2, elt = "bar"))
x <- list(obj1, obj2)

purrr::map(x, c(2, "elt"))
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL
purrr::map(x, \(i) purrr::pluck(i, 2, "elt"))
#> [[1]]
#> [1] "foo"
#> 
#> [[2]]
#> [1] "bar"

Created on 2024-10-01 with reprex v2.1.1

Session info ``` r sessioninfo::session_info() #> ─ Session info ─────────────────────────────────────────────────────────────── #> setting value #> version R version 4.4.1 (2024-06-14) #> os EndeavourOS #> system x86_64, linux-gnu #> ui X11 #> language #> collate en_GB.UTF-8 #> ctype en_GB.UTF-8 #> tz Europe/Amsterdam #> date 2024-10-01 #> pandoc 3.1.11 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown) #> #> ─ Packages ─────────────────────────────────────────────────────────────────── #> package * version date (UTC) lib source #> cli 3.6.3 2024-06-21 [1] CRAN (R 4.4.1) #> digest 0.6.37 2024-08-19 [1] CRAN (R 4.4.1) #> evaluate 0.24.0 2024-06-10 [1] CRAN (R 4.4.1) #> fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.1) #> fs 1.6.4 2024-04-25 [1] CRAN (R 4.4.0) #> glue 1.7.0 2024-01-09 [1] CRAN (R 4.4.0) #> htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.4.0) #> knitr 1.48 2024-07-07 [1] CRAN (R 4.4.1) #> lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.4.0) #> magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.4.0) #> purrr 1.0.2 2023-08-10 [1] CRAN (R 4.4.0) #> reprex 2.1.1 2024-07-06 [1] CRAN (R 4.4.1) #> rlang 1.1.4 2024-06-04 [1] CRAN (R 4.4.1) #> rmarkdown 2.28 2024-08-17 [1] CRAN (R 4.4.1) #> rstudioapi 0.16.0 2024-03-24 [1] CRAN (R 4.4.0) #> sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.4.0) #> vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.4.0) #> withr 3.0.1 2024-07-31 [1] CRAN (R 4.4.1) #> xfun 0.47 2024-08-17 [1] CRAN (R 4.4.1) #> yaml 2.3.10 2024-07-26 [1] CRAN (R 4.4.1) #> #> [1] /home/johannes/R/x86_64-pc-linux-gnu-library/4.4 #> [2] /usr/lib/R/library #> #> ────────────────────────────────────────────────────────────────────────────── ```
joakimlinde commented 1 month ago

The c(2, "elt") argument looks odd to me. For vectors in R all elements have to be of the same type. R will coerce this into c("2", "elt"). Did you mean to say list(2, "elt") ?

purrr::map(x, list(2, "elt"))
#> [[1]]
#> [1] "foo"
#>
#> [[2]]
#> [1] "bar"
JBGruber commented 1 month ago

It totally makes sense that list works and the approach with c does not! I didn't even consider that, thanks! But maybe it would make sense to add this as an example here:

https://github.com/tidyverse/purrr/blob/700ce1973aa2dac714b7fbbbc619ead0fe946b0d/R/pluck.R#L82

If I make this mistake, others might as well? Maybe:

#' # if you want to mix location and name indexing, use list
#' purrr::map_chr(x, list(2, "elt"))
joakimlinde commented 1 month ago

There is already an example similar to this.

# Use a list to build an extractor that mixes numeric indices and names,
# and .default to provide a default value if the element does not exist
l2 |> map(list("num", 3))
#> [[1]]
#> [1] 3
#> 
#> [[2]]
#> [1] 103
#> 
#> [[3]]
#> NULL
#> 
l2 |> map_int(list("num", 3), .default = NA)
#> [1]   3 103  NA
JBGruber commented 1 month ago

Great!