Conditionally rename columns in list, based on separate character vector stored in data.frame

Viewed 108

I have a list of tibbles called lst:

> lst
[[1]]
# A tibble: 2 x 4
  temp1    temp2 temp3    id
  <chr>    <dbl> <dbl> <dbl>
1 Metric 1   150  1234   201
2 Metric 2   190  3456   201

[[2]]
# A tibble: 2 x 4
  temp1    temp2 temp3    id
  <chr>    <dbl> <dbl> <dbl>
1 Metric 1   190  1231   202
2 Metric 2   120  3356   202

I also have a separate tibble called df, with a column containing character vectors to rename the columns in lst:

# A tibble: 2 x 2
  colnames                                                      id
  <chr>                                                      <dbl>
1 c(' ','Ranking 1 for School A', 'Ranking 2 for School A')    201
2 c(' ', 'Ranking 1 for School B', 'Ranking 2 for School B')   202

I'm looking for a way, ideally using some form of map from purrr, to drop the id column and rename the columns for each tibble in lst, based on the values in df.

Any advice is very much appreciated. Thank you in advance.

Desired output:

[[1]]
# A tibble: 2 x 3
  ` `      `Ranking 1 for School A` `Ranking 2 for School A`
  <chr>                       <dbl>                    <dbl>
1 Metric 1                      150                     1234
2 Metric 2                      190                     3456

[[2]]
# A tibble: 2 x 3
  ` `      `Ranking 1 for School B` `Ranking 2 for School B`
  <chr>                       <dbl>                    <dbl>
1 Metric 1                      190                     1231
2 Metric 2                      120                     3356

Data:

lst <- list(structure(list(temp1 = c("Metric 1", "Metric 2"), temp2 = c(150, 
190), temp3 = c(1234, 3456), id = c(201, 201)), row.names = c(NA, 
-2L), class = c("tbl_df", "tbl", "data.frame")), structure(list(
    temp1 = c("Metric 1", "Metric 2"), temp2 = c(190, 120), temp3 = c(1231, 
    3356), id = c(202, 202)), row.names = c(NA, -2L), class = c("tbl_df", 
"tbl", "data.frame")))

df <- structure(list(colnames = c("c(' ','Ranking 1 for School A', 'Ranking 2 for School A')", 
"c(' ', 'Ranking 1 for School B', 'Ranking 2 for School B')"), 
    id = c(201, 202)), row.names = c(NA, -2L), class = c("tbl_df", 
"tbl", "data.frame"))
4 Answers

Taken at facevalue, this is what I would do,

library(tidyverse)

1:length(lst) %>% map(
        .f = function(x) {
                
                # Store list
                tmp <- lst[[x]] %>% 
                        select(-"id")
                
                
                # Rename Colums
                colnames(tmp) <- paste((df[x,"colnames"])) %>%
                                    parse(text = .) %>% 
                                       eval()
                
                # Return the modified data 
                tmp
                
        }
)

Note: This, obviously, assumes that lst and the colnames are stored sequentially, such that index 1 in list uses index 1 in df[,"colnames"].

library(tidyverse)

map2(lst, pmap(df, ~.), ~ set_names(.x[-4], eval(parse(text = .y))))
#> [[1]]
#> # A tibble: 2 x 3
#>   ` `      `Ranking 1 for School A` `Ranking 2 for School A`
#>   <chr>                       <dbl>                    <dbl>
#> 1 Metric 1                      150                     1234
#> 2 Metric 2                      190                     3456
#> 
#> [[2]]
#> # A tibble: 2 x 3
#>   ` `      `Ranking 1 for School B` `Ranking 2 for School B`
#>   <chr>                       <dbl>                    <dbl>
#> 1 Metric 1                      190                     1231
#> 2 Metric 2                      120                     3356

Created on 2021-07-01 by the reprex package (v2.0.0)

You can also use the following solution. First we separate the colname variable in the second data frame:

library(dplyr)
library(purrr)

df %>%
  mutate(colnames = gsub("[c()]", "", colnames)) %>%
  separate(colnames, into = paste("col", 1:3, sep = "_"), sep = ",\\s?") -> DF

DF
# A tibble: 2 x 4
  col_1 col_2                   col_3                      id
  <chr> <chr>                   <chr>                   <dbl>
1 ' '   'Ranking 1 for Shool A' 'Ranking 2 for Shool A'   201
2 ' '   'Ranking 1 for Shool B' 'Ranking 2 for Shool B'   202

Then we use it to change the old column names in the elements of our list:

lst %>%
  map(~ .x %>% 
        set_names(DF %>% filter(id == .x$id) %>% unlist()) %>%
        select(-length(.)))

[[1]]
# A tibble: 2 x 3
  `' '`    `'Ranking 1 for Shool A'` `'Ranking 2 for Shool A'`
  <chr>                        <dbl>                     <dbl>
1 Metric 1                       150                      1234
2 Metric 2                       190                      3456

[[2]]
# A tibble: 2 x 3
  `' '`    `'Ranking 1 for Shool B'` `'Ranking 2 for Shool B'`
  <chr>                        <dbl>                     <dbl>
1 Metric 1                       190                      1231
2 Metric 2                       120                      3356

I see you preferred tidyverse answers and already have at least one good one. So I thought I'd share a non-tidyverse approach in case anyone that comes along later is interested...

library(qdapRegex)
for(i in 1:length(lst)){
  # extract field names based on 'id'
  new_names <- qdapRegex::rm_between(df[df$id == lst[[i]]$id,"colnames"], "'", "'", extract = TRUE)
  # rename fields
  names(lst[[i]]) <- new_names[[1]]
  # drop NA field
  lst[[i]] <- lst[[i]][!is.na(names(lst[[i]]))]
}
Related