If you want to call a function that returns multiple values on a column of a data.frame, and append these values as new columns to the data.frame, you can mutate(map()) |> unnest_wider(). If it's long-running, we can make use of the .progress argument of map to display a progress message from cli
library(tidyverse)
df <- tibble(
id = letters[1:6],
dim_h = c(2, 3, 4, 5, 6, 7),
dim_w = c(3, 4, 5, 6, 7, 8)
)
chop <- function(dim){
Sys.sleep(0.5)
return(list(half=dim/2, thirds=dim/3))
}
df |>
mutate(dim_h_chopped = map(dim_h, chop, .progress="chopping")) |>
unnest_wider(dim_h_chopped, names_sep = "_")
#> chopping ■■■■■■■■■■■ 33% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■ 50% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■■■■■■ 67% | ETA: 1s
#> chopping ■■■■■■■■■■■■■■■■■■■■■■■■■■ 83% | ETA: 1s
#> # A tibble: 6 × 5
#> id dim_h dim_w dim_h_chopped_half dim_h_chopped_thirds
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 a 2 3 1 0.667
#> 2 b 3 4 1.5 1
#> 3 c 4 5 2 1.33
#> 4 d 5 6 2.5 1.67
#> 5 e 6 7 3 2
#> 6 f 7 8 3.5 2.33
We can expand to multiple columns by adding across() to the mix,
but since the progress message resets for each new column, it'd be great to include the name of the column in the progress message to provide some sense of where we are in the big picture.
df |>
mutate(across(starts_with("dim_"), ~ map(.x, chop, .progress = "chopping"), .names="{.col}_chopped")) |>
unnest_wider(ends_with("_chopped"), names_sep = "_")
#> chopping ■■■■■■■■■■■ 33% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■ 50% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■■■■■■ 67% | ETA: 1s
#> chopping ■■■■■■■■■■■■■■■■■■■■■■■■■■ 83% | ETA: 1s
#> chopping ■■■■■■■■■■■ 33% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■ 50% | ETA: 2s
#> chopping ■■■■■■■■■■■■■■■■■■■■■ 67% | ETA: 1s
#> chopping ■■■■■■■■■■■■■■■■■■■■■■■■■■ 83% | ETA: 1s
#> # A tibble: 6 × 7
#> id dim_h dim_w dim_h_chopped_half dim_h_chopped_thirds dim_w_chopped_half
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 a 2 3 1 0.667 1.5
#> 2 b 3 4 1.5 1 2
#> 3 c 4 5 2 1.33 2.5
#> 4 d 5 6 2.5 1.67 3
#> 5 e 6 7 3 2 3.5
#> 6 f 7 8 3.5 2.33 4
#> # ℹ 1 more variable: dim_w_chopped_thirds <dbl>
Can we build a better progress message?
# can we include the column name in the progress message?
df |>
mutate(across(c(dim_h, dim_w), ~ map(.x, chop, .progress = glue::glue("chopping {.x}")), .names="{.col}_chopped")) |>
unnest_wider(ends_with("_chopped"), names_sep = "_")
#> Error in `mutate()`:
#> ℹ In argument: `across(...)`.
#> Caused by error in `across()`:
#> ! Can't compute column `dim_h_chopped`.
#> Caused by error:
#> ! Failed to evaluate glue component {.x}
#> Caused by error:
#> ! object '.x' not found
Why can't glue() find .x, when two arguments to the left .x works just fine? (of course, .x probably isn't what we want here anyway--we want the name of .x, but I sense this is a start).
How can we get the column name into the progress message?
Created on 2025-10-07 with reprex v2.1.1
{.col} is specific to .names=, it is not a generic glue() thing nor available to other functions..x, I suggest cur_column() instead.library(dplyr)
library(tidyr)
library(purrr)
df |>
mutate(across(starts_with("dim_"), ~ map(.x, chop, .progress = glue::glue("chopping {cur_column()}")), .names="{.col}_chopped")) |>
unnest_wider(ends_with("_chopped"), names_sep = "_")
# chopping dim_h ■■■■■■■■■■■ 33% | ETA: 2s
# chopping dim_h ■■■■■■■■■■■■■■■■ 50% | ETA: 2s
# chopping dim_h ■■■■■■■■■■■■■■■■■■■■■ 67% | ETA: 1s
# chopping dim_h ■■■■■■■■■■■■■■■■■■■■■■■■■■ 83% | ETA: 1s
# chopping dim_w ■■■■■■■■■■■ 33% | ETA: 2s
# chopping dim_w ■■■■■■■■■■■■■■■■ 50% | ETA: 2s
# chopping dim_w ■■■■■■■■■■■■■■■■■■■■■ 67% | ETA: 1s
# chopping dim_w ■■■■■■■■■■■■■■■■■■■■■■■■■■ 83% | ETA: 1s
# # A tibble: 6 × 7
# id dim_h dim_w dim_h_chopped_half dim_h_chopped_thirds dim_w_chopped_half dim_w_chopped_thirds
# <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 a 2 3 1 0.667 1.5 1
# 2 b 3 4 1.5 1 2 1.33
# 3 c 4 5 2 1.33 2.5 1.67
# 4 d 5 6 2.5 1.67 3 2
# 5 e 6 7 3 2 3.5 2.33
# 6 f 7 8 3.5 2.33 4 2.67
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With