how to set names in a dynamically long list

Viewed 108

Given the following data:

test = data.frame(x = c(NA,1,1,2,3,4),
                  y = c(NA,1,2,3,4,4))

I want to perform some calculations and store these as new columns. The calculations, however, might result in a variable amount of columns. E.g. suppose I want store for each row the column index of the column(s) that contain the minimum per row. E.g. in row 1, both columns contain the minimum, hence I need to create two columns.

Using the tidyverse approach, I know I can use the set_names argument when passing my function as a list. But this doesn't work when I don't know the number of columns my calculation will create. See also here: https://community.rstudio.com/t/how-to-handle-lack-of-names-with-unnest-wider/40496

My approach for the calculations:

library(tidyverse)

test %>%
rowwise() %>%
  mutate(dist = min(c_across(everything())),
         code = list(which(c_across(cols = c(everything(), -dist)) == dist))) %>%
  ungroup() %>%
  unnest_wider(code)

which automatically names the unnested columns with "...1" and "...2":

# A tibble: 6 x 5
      x     y  dist  ...1  ...2
  <dbl> <dbl> <dbl> <int> <int>
1    NA    NA    NA    NA    NA
2     1     1     1     1     2
3     1     2     1     1    NA
4     2     3     2     1    NA
5     3     4     3     1    NA
6     4     4     4     1     2

But that's not what I want. I also tried to use the named_repair argument within the unnest_wider, i.e. unnest_wider(code, names_repair = ~paste0("code", .x)) but this renames all columns.

Any ideas (preferably in the tidyverse approach)? Expected outcome:

# A tibble: 6 x 5
      x     y  dist  code_1  code_2
  <dbl> <dbl> <dbl> <int>    <int>
1    NA    NA    NA    NA       NA
2     1     1     1     1        2
3     1     2     1     1       NA
4     2     3     2     1       NA
5     3     4     3     1       NA
6     4     4     4     1        2

EDITED to add an example where one row contains only missings.

Edit 2: this is my current solution. But it is really ugly and requires to stop half way through. Problem here is that the rename_with function doesn't recognize the on-the-fly generated "length_code" column when I put everything into one pipe.

test2 <- test %>%
rowwise() %>%
  mutate(dist = min(c_across(everything())),
         code = list(which(c_across(cols = c(everything(), -dist)) == dist)),
         length_code  = length(code)) %>%
  ungroup() %>%
  unnest_wider(code) %>%

test3 <- test2 %>%
  rename_with(.cols = starts_with("..."), .fn = ~paste0("code_", 1:max(test2$length_code)))

which gives:

# A tibble: 6 x 6
      x     y  dist code_1 code_2 length_code
  <dbl> <dbl> <dbl>  <int>  <int>       <int>
1    NA    NA    NA     NA     NA           0
2     1     1     1      1      2           2
3     1     2     1      1     NA           1
4     2     3     2      1     NA           1
5     3     4     3      1     NA           1
6     4     4     4      1      2           2
0 Answers
Related