From 267271bf73107c92c7b3070a06ae122eb844faf8 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Sun, 6 May 2018 19:25:18 +0200 Subject: [PATCH 1/6] get back historical flatmap() from 2f76d3e4 --- R/map-vector.R | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 R/map-vector.R diff --git a/R/map-vector.R b/R/map-vector.R new file mode 100644 index 00000000..b3550fd9 --- /dev/null +++ b/R/map-vector.R @@ -0,0 +1,38 @@ +#' Map a function and flatten the result by one-level +#' +#' \code{flatmap()} is equivalent to \code{map()} followed by +#' \code{flatten()}. You can also provide \code{.type} to check the +#' resulting type conforms to you expectations. +#' +#' Compared to \code{\link{map_lgl}()}, \code{\link{map_chr}()}, etc, +#' \code{flatmap()} is adapted to functions returning a variable +#' number of elements. +#' @inheritParams map +#' @param .type A string indicating which type you expect the results +#' of \code{.f} should be. +#' @export +#' @seealso \code{\link{map_lgl}()}, \code{\link{map_chr}()}, +#' \code{\link{map_dbl}()}, \code{\link{map_int}()} +#' @examples +#' # Sample a variable number of elements from each column and +#' # concatenate the results +#' var_select <- function(x) sample(x, size = rdunif(1, 5)) +#' c(mtcars) %>% flatmap(var_select) +#' +#' # You can also check that the results are of expected type +#' \dontrun{ +#' c(mtcars) %>% flatmap(var_select, .type = "character") +#' } +#' c(mtcars) %>% flatmap(var_select, .type = "numeric") +flatmap <- function(.x, .f, ..., .type) { + out <- map(.x, .f = .f, ...) + if (!missing(.type)) { + if (!is_scalar_character(.type)) { + stop(".type must be a string") + } + if (!can_simplify(out, .type)) { + stop("Results do not conform to .type", call. = FALSE) + } + } + flatten(out) +} From bf3d6654e2c10f46fd68bbb5504cdaceff2dc9f3 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Sun, 6 May 2018 20:23:54 +0200 Subject: [PATCH 2/6] add map_flat functions --- R/map-vector.R | 38 -------------------- R/map_flat.R | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 38 deletions(-) delete mode 100644 R/map-vector.R create mode 100644 R/map_flat.R diff --git a/R/map-vector.R b/R/map-vector.R deleted file mode 100644 index b3550fd9..00000000 --- a/R/map-vector.R +++ /dev/null @@ -1,38 +0,0 @@ -#' Map a function and flatten the result by one-level -#' -#' \code{flatmap()} is equivalent to \code{map()} followed by -#' \code{flatten()}. You can also provide \code{.type} to check the -#' resulting type conforms to you expectations. -#' -#' Compared to \code{\link{map_lgl}()}, \code{\link{map_chr}()}, etc, -#' \code{flatmap()} is adapted to functions returning a variable -#' number of elements. -#' @inheritParams map -#' @param .type A string indicating which type you expect the results -#' of \code{.f} should be. -#' @export -#' @seealso \code{\link{map_lgl}()}, \code{\link{map_chr}()}, -#' \code{\link{map_dbl}()}, \code{\link{map_int}()} -#' @examples -#' # Sample a variable number of elements from each column and -#' # concatenate the results -#' var_select <- function(x) sample(x, size = rdunif(1, 5)) -#' c(mtcars) %>% flatmap(var_select) -#' -#' # You can also check that the results are of expected type -#' \dontrun{ -#' c(mtcars) %>% flatmap(var_select, .type = "character") -#' } -#' c(mtcars) %>% flatmap(var_select, .type = "numeric") -flatmap <- function(.x, .f, ..., .type) { - out <- map(.x, .f = .f, ...) - if (!missing(.type)) { - if (!is_scalar_character(.type)) { - stop(".type must be a string") - } - if (!can_simplify(out, .type)) { - stop("Results do not conform to .type", call. = FALSE) - } - } - flatten(out) -} diff --git a/R/map_flat.R b/R/map_flat.R new file mode 100644 index 00000000..cb3043da --- /dev/null +++ b/R/map_flat.R @@ -0,0 +1,94 @@ +#' Map a function then flatten the result by one-level +#' +#' @description +#' +#' The map_flap functions are equivalent to applying [map()] +#' followed by a flatten function to remove one level of hierarchy +#' from the list obtained by using [map()]. +#' +#' * `map_flat()` is equivalent to [map()] followed by [flatten()]. +#' +#' * `map_flat_lgl()`, `map_flat_int()`, `map_flap_dbl()`, `map_flat_chr()` and +#' `map_flat_raw` is equivalent to [map()] followed respectively by +#' [flatten_lgl()], [flatten_int()], [flatten_dbl()], [flatten_chr()] and +#' [flatten_raw()] +#' +#' * `map_flat_dfr()` and `map_flat_dfc()` is equivalent to [map()] followed +#' respectively by [flatten_dfr()] and [flatten_dfc()] +#' +#' @return `map_flat` returns a list, `map_flat_lgl()` a logical +#' vector, `map_flat_int()` an integer vector, `map_flat_dbl()` a +#' double vector, and `map_flat_chr()` a character vector. +#' +#' `map_flat_dfr()` and `map_flat_dfc()` return data frames created by +#' row-binding and column-binding respectively. They require dplyr to +#' be installed. +#' +#' @inheritParams map +#' @export +#' @family map variants +#' @seealso [map_lgl()], [map_chr()], [map_dbl()], [map_int()], [map_dfr()], [map_dfc()] +#' @examples +#' # Sample a variable number of elements from each column and +#' # concatenate the results +#' var_select <- function(x) sample(x, size = rdunif(1, 5)) +#' c(mtcars) %>% flatmap(var_select) +#' +#' # You can also check that the results are of expected type +#' \dontrun{ +#' c(mtcars) %>% flatmap(var_select, .type = "character") +#' } +#' c(mtcars) %>% flatmap(var_select, .type = "numeric") +map_flat <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten(out) +} + +#' @rdname map_flat +#' @export +map_flat_lgl <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_lgl(out) +} + +#' @rdname map_flat +#' @export +map_flat_int <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_int(out) +} + +#' @rdname map_flat +#' @export +map_flat_chr <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_chr(out) +} + +#' @rdname map_flat +#' @export +map_flat_dbl <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_dbl(out) +} + +#' @rdname map_flat +#' @export +map_flat_dfr <- function(.x, .f, ..., .id = NULL) { + out <- map(.x, .f = .f, ...) + flatten_dfr(out, .id = .id) +} + +#' @rdname map_flat +#' @export +map_flat_dfc <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_dfc(out) +} + +#' @rdname map_flat +#' @export +map_flat_raw <- function(.x, .f, ...) { + out <- map(.x, .f = .f, ...) + flatten_raw(out) +} From 01425ea680684daac12423c1e0055904421c9585 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 7 May 2018 10:43:41 +0200 Subject: [PATCH 3/6] add tests for map_flat --- R/map_flat.R | 22 ++++++++++++++++------ tests/testthat/test-map_flat.R | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 tests/testthat/test-map_flat.R diff --git a/R/map_flat.R b/R/map_flat.R index cb3043da..bab30ded 100644 --- a/R/map_flat.R +++ b/R/map_flat.R @@ -16,6 +16,10 @@ #' * `map_flat_dfr()` and `map_flat_dfc()` is equivalent to [map()] followed #' respectively by [flatten_dfr()] and [flatten_dfc()] #' +#' Compared to [map_lgl()], [map_chr()], etc, +#' map_flat functions are adapted to functions returning a variable +#' number of elements. +#' #' @return `map_flat` returns a list, `map_flat_lgl()` a logical #' vector, `map_flat_int()` an integer vector, `map_flat_dbl()` a #' double vector, and `map_flat_chr()` a character vector. @@ -27,18 +31,24 @@ #' @inheritParams map #' @export #' @family map variants -#' @seealso [map_lgl()], [map_chr()], [map_dbl()], [map_int()], [map_dfr()], [map_dfc()] +#' @seealso [map_lgl()], [map_chr()], [map_dbl()], [map_int()], [map_dfr()], +#' [map_dfc()], [map_raw()] #' @examples #' # Sample a variable number of elements from each column and #' # concatenate the results #' var_select <- function(x) sample(x, size = rdunif(1, 5)) -#' c(mtcars) %>% flatmap(var_select) -#' -#' # You can also check that the results are of expected type +#' # map var_select on each mtcars column then flatten the result +#' # by on level +#' c(mtcars) %>% map_flat(var_select) # a list +#' c(mtcars) %>% map_flat_dbl(var_select) # a numeric vector +#' c(mtcars) %>% map_flat_chr(var_select) # a character vector +#' # equivalent to +#' c(mtcars) %>% map(var_select) %>% flatten_dbl() +#' # as number of value is different in each element +#' # map_dbl won't work #' \dontrun{ -#' c(mtcars) %>% flatmap(var_select, .type = "character") +#' c(mtcars) %>% map_dbl(var_select) #' } -#' c(mtcars) %>% flatmap(var_select, .type = "numeric") map_flat <- function(.x, .f, ...) { out <- map(.x, .f = .f, ...) flatten(out) diff --git a/tests/testthat/test-map_flat.R b/tests/testthat/test-map_flat.R new file mode 100644 index 00000000..1714d2c5 --- /dev/null +++ b/tests/testthat/test-map_flat.R @@ -0,0 +1,25 @@ +context("map_flat") + +test_that("map function and flatten one level", { + expect_equal(map_flat(list(1:2, 3:6), `*`, 2), list(2, 4, 6, 8, 10, 12)) +}) + +test_that("map_flat variants are correct types", { + set.seed(45) + return_var <- function(x, type) vector(type, rpois(1, 5)) + expect_type(map_flat_dbl(c(mtcars), return_var, "double"), "double") + expect_type(map_flat_chr(c(mtcars), return_var, "character"), "character") + expect_type(map_flat_int(c(mtcars), return_var, "integer"), "integer") + expect_type(map_flat_lgl(c(mtcars), return_var, "logical"), "logical") + expect_type(map_flat_raw(c(mtcars), return_var, "raw"), "raw") +}) + +test_that("row and column binding work", { + mtcar_mod <- mtcars %>% + split(.$cyl) %>% + map(~ lm(mpg ~ wt, data = .x)) + f_coef <- function(x) as.data.frame(t(as.matrix(coef(x)))) + expect_length(mtcar_mod %>% map_flat_dfr(f_coef), 2) + expect_length(mtcar_mod %>% map_flat_dfr(f_coef, .id = "col"), 3) + expect_length(mtcar_mod %>% map_flat_dfc(f_coef), 6) +}) From e6c708de83d2a358f0d59e555d9cf50ee5698cb4 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 7 May 2018 10:57:07 +0200 Subject: [PATCH 4/6] typo --- R/map_flat.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/map_flat.R b/R/map_flat.R index bab30ded..b43400fa 100644 --- a/R/map_flat.R +++ b/R/map_flat.R @@ -9,11 +9,11 @@ #' * `map_flat()` is equivalent to [map()] followed by [flatten()]. #' #' * `map_flat_lgl()`, `map_flat_int()`, `map_flap_dbl()`, `map_flat_chr()` and -#' `map_flat_raw` is equivalent to [map()] followed respectively by +#' `map_flat_raw` are equivalent to [map()] followed respectively by #' [flatten_lgl()], [flatten_int()], [flatten_dbl()], [flatten_chr()] and #' [flatten_raw()] #' -#' * `map_flat_dfr()` and `map_flat_dfc()` is equivalent to [map()] followed +#' * `map_flat_dfr()` and `map_flat_dfc()` are equivalent to [map()] followed #' respectively by [flatten_dfr()] and [flatten_dfc()] #' #' Compared to [map_lgl()], [map_chr()], etc, From 6d77d60870cd27fa55c7ecd428bae7fc1c2d9951 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 9 May 2018 02:09:40 +0200 Subject: [PATCH 5/6] add precision on map vs map_flat variants --- R/map_flat.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/R/map_flat.R b/R/map_flat.R index b43400fa..79f537f1 100644 --- a/R/map_flat.R +++ b/R/map_flat.R @@ -16,10 +16,20 @@ #' * `map_flat_dfr()` and `map_flat_dfc()` are equivalent to [map()] followed #' respectively by [flatten_dfr()] and [flatten_dfc()] #' +#' @section About map vs map_flat Variants: +#' #' Compared to [map_lgl()], [map_chr()], etc, #' map_flat functions are adapted to functions returning a variable #' number of elements. #' +#' The map variants works by coercing the list to +#' a vector of corresponding type - for that each element of the list resulting +#' map must be a length 1 atomic vector, otherwise it will return an error. +#' +#' map_flat variants don't have that limitation as they unlist one level before +#' trying to return a vector - it is more like concatenating map results, whatever the size +#' of each elements in the resulting list after applying the function. +#' #' @return `map_flat` returns a list, `map_flat_lgl()` a logical #' vector, `map_flat_int()` an integer vector, `map_flat_dbl()` a #' double vector, and `map_flat_chr()` a character vector. From db2cbec58e1ad44fbebb01ad34958dd98dc0fca0 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 9 May 2018 02:10:15 +0200 Subject: [PATCH 6/6] add simpler example --- R/map_flat.R | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/R/map_flat.R b/R/map_flat.R index 79f537f1..f1f4717b 100644 --- a/R/map_flat.R +++ b/R/map_flat.R @@ -44,6 +44,22 @@ #' @seealso [map_lgl()], [map_chr()], [map_dbl()], [map_int()], [map_dfr()], #' [map_dfc()], [map_raw()] #' @examples +#' # It can be useful to map a function but flatten the result +#' # to a vector. It is like concatenating map results. +#' c("one piece", "four red pieces") %>% +#' map_flat_chr(~strsplit(.x, " ")[[1]]) +#' # this is equivalent to +#' c("one piece", "four red pieces") %>% +#' map(~strsplit(.x, " ")[[1]]) %>% +#' flatten_chr() +#' # Using map_chr() does not work here because resulting +#' # elements before coercion is not of length 1, +#' # so coercion failed. +#' \dontrun{ +#' c("one piece", "four red pieces") %>% +#' map_chr(~strsplit(.x, " ")[[1]]) +#' } +#' #' # Sample a variable number of elements from each column and #' # concatenate the results #' var_select <- function(x) sample(x, size = rdunif(1, 5)) @@ -54,11 +70,6 @@ #' c(mtcars) %>% map_flat_chr(var_select) # a character vector #' # equivalent to #' c(mtcars) %>% map(var_select) %>% flatten_dbl() -#' # as number of value is different in each element -#' # map_dbl won't work -#' \dontrun{ -#' c(mtcars) %>% map_dbl(var_select) -#' } map_flat <- function(.x, .f, ...) { out <- map(.x, .f = .f, ...) flatten(out)