Eric Fail
Eric Fail

Reputation: 7958

change several column names() in data.frame() with str_replace_all()

I read this this question and practiced matching patterns, but I am still not figuring it.

I have a panel with the same measure, several times per year. Now, I want to rename them in a logical way. My raw data looks a bit like this,

set.seed(667)
dta <- data.frame(id = 1:6,
                  R1213 =  runif(6), 
                  R1224 = runif(6, 1, 2),
                  R1255 = runif(6, 2, 3),
                  R1235 = runif(6, 3, 4))

# install.packages(c("tidyverse"), dependencies = TRUE)
require(tidyverse)
(tbl <- dta %>% as_tibble())
#> # A tibble: 6 x 5
#>      id R1213 R1224 R1255 R1235
#>   <int> <dbl> <dbl> <dbl> <dbl>
#> 1     1 0.488  1.60  2.07  3.07
#> 2     2 0.692  1.42  2.76  3.19
#> 3     3 0.262  1.34  2.33  3.82
#> 4     4 0.330  1.77  2.61  3.93
#> 5     5 0.582  1.92  2.15  3.86
#> 6     6 0.930  1.88  2.56  3.59    

Now, I use str_replace_all() to rename them, here with only one variable in where I use pate, and everything is fine (it might also be possible to optimize this in other ways, if so please feel to let me know),

names(tbl) <- tbl %>% names() %>% 
               str_replace_all('^R1.[125].$', 'A') %>% 
               str_replace_all('^R1.[3].$', paste0('A.2018.', 1))
tbl
#> # A tibble: 6 x 5
#>      id     A     A     A A.2018.1
#>   <int> <dbl> <dbl> <dbl>    <dbl>
#> 1     1 0.488  1.60  2.07     3.07
#> 2     2 0.692  1.42  2.76     3.19
#> 3     3 0.262  1.34  2.33     3.82
#> 4     4 0.330  1.77  2.61     3.93
#> 5     5 0.582  1.92  2.15     3.86
#> 6     6 0.930  1.88  2.56     3.59

Eveything call A is actually from the same year, let's say 2017, but with the suffix .1, .2, etc. need to appended. I start over and again use paste0('A.2017.', 1:3), but this time with three suffices,

tbl <- dta %>% as_tibble()
names(tbl) <- tbl %>% names() %>% 
               str_replace_all('^R1.[125].$', paste0('A.2017.', 1:3)) %>% 
               str_replace_all('^R1.[7].$', paste0('A.2018.', 1))
tbl
#> Warning message:
#> In stri_replace_all_regex(string, pattern, fix_replacement(replacement),  :
#>   longer object length is not a multiple of shorter object length
#> > tbl
#> # A tibble: 6 x 5
#>      id A.2017.2 A.2017.3 A.2017.1 R1235
#>   <int>    <dbl>    <dbl>    <dbl> <dbl>
#> 1     1    0.488     1.60     2.07  3.07
#> 2     2    0.692     1.42     2.76  3.19
#> 3     3    0.262     1.34     2.33  3.82
#> 4     4    0.330     1.77     2.61  3.93
#> 5     5    0.582     1.92     2.15  3.86
#> 6     6    0.930     1.88     2.56  3.59

this does come out, but the order is reversed and I am told longer object length is not a multiple of shorter object length, but isen't 3 the right length? I am looking to do this in a cleaner and simpler way. Also, I don't really like names(tbl) <-, if that can be done in a more elegant way.

Upvotes: 1

Views: 1207

Answers (1)

Sam Abbott
Sam Abbott

Reputation: 466

Building on David's suggestion - how about something like the following using dplyr::rename_at?

library(dplyr)

## Get data
set.seed(667)
dta <- data.frame(id = 1:6,
                  R1213 =  runif(6), 
                  R1224 = runif(6, 1, 2),
                  R1255 = runif(6, 2, 3),
                  R1235 = runif(6, 3, 4)) %>% 
  as_tibble()


## Rename
dta <- dta %>% 
  rename_at(.vars = grep('^R1.[125].$', names(.)), 
            .funs = ~paste0("A.2017.", 1:length(.)))

dta            
#> # A tibble: 6 x 5
#>      id A.2017.1 A.2017.2 A.2017.3 R1235
#>   <int>    <dbl>    <dbl>    <dbl> <dbl>
#> 1     1    0.196     1.74     2.51  3.49
#> 2     2    0.478     1.85     2.06  3.69
#> 3     3    0.780     1.32     2.21  3.26
#> 4     4    0.705     1.49     2.49  3.33
#> 5     5    0.942     1.59     2.66  3.58
#> 6     6    0.906     1.90     2.87  3.93

Vectorised solution for multiple patterns

For a complete solution that can be used for multiple patterns and replacements, we can make use of purr::map2_dfc as follows.

library(dplyr)
library(purrr)

## Get data
set.seed(667)
dta <- data.frame(id = 1:6,
                  R1213 =  runif(6), 
                  R1224 = runif(6, 1, 2),
                  R1255 = runif(6, 2, 3),
                  R1235 = runif(6, 3, 4)) %>% 
  as_tibble()

## Define a function to keep a hold out data set, then rename iteratively for each pattern and replacement.
rename_multiple_years <- function(df, patterns, 
                                  replacements,
                                  hold_out_var = "id") {

  hold_out_df <- df %>% 
    select_at(.vars = hold_out_var)

  rename_df <- map2_dfc(patterns, replacements, function(pattern, replacement) {
    df %>% 
      rename_at(.vars = grep(pattern, names(.)), 
                .funs = ~paste0(replacement, 1:length(.))) %>% 
      select_at(.vars = grep(replacement, names(.)))
  })

  final_df <- bind_cols(hold_out_df, rename_df)

  return(final_df)
}

## Call function on specified patterns and replacements
renamed_dta <- dta %>% 
  rename_multiple_years(patterns = c("^R1.[125].$", "^R1.[3].$"),
                        replacements = c("A.2017.", "A.2018."))
renamed_dta
#> # A tibble: 6 x 5
#>      id A.2017.1 A.2017.2 A.2017.3 A.2018.1
#>   <int>    <dbl>    <dbl>    <dbl>    <dbl>
#> 1     1    0.196     1.74     2.51     3.49
#> 2     2    0.478     1.85     2.06     3.69
#> 3     3    0.780     1.32     2.21     3.26
#> 4     4    0.705     1.49     2.49     3.33
#> 5     5    0.942     1.59     2.66     3.58
#> 6     6    0.906     1.90     2.87     3.93

Towards tidy data

Now that the variables have been renamed you might find it useful to have your data in a tidy format. The following using tidyr::gather might be useful.

library(tidyr)
library(dplyr)

#Use tidy dataframe gather all variables, split by "." and drop A column (or keep if a measurement id)
renamed_dta %>% 
  gather(key = "measure", value = "value", -id) %>% 
  separate(measure, c("A", "year", "measure"), "[[.]]") %>% 
  select(-A)
#> # A tibble: 24 x 4
#>       id year  measure value
#>    <int> <chr> <chr>   <dbl>
#>  1     1 2017  1       0.196
#>  2     2 2017  1       0.478
#>  3     3 2017  1       0.780
#>  4     4 2017  1       0.705
#>  5     5 2017  1       0.942
#>  6     6 2017  1       0.906
#>  7     1 2017  2       1.74 
#>  8     2 2017  2       1.85 
#>  9     3 2017  2       1.32 
#> 10     4 2017  2       1.49 
#> # ... with 14 more rows

Upvotes: 2

Related Questions