Search code examples
rloopsdplyrmultiple-columns

Compute multiple new columns by name pattern in R


I have data with population numbers, births and deaths by year and country, disaggregated by gender and age. I would like to compute the net migration rate for each year-country-gender-age combo. Here is what the data looks like:

data

The formula to compute the net migration rate (following the naming convention of the data) would be: 2001_netmigration = 2001_pop - 2000_deaths + 2000_births - 2000_pop . I want to perform this for all years from 2001 to 2020., i.e. over all columns.

I tried the following code:

n <- 2001

while(n <= 2020){
  aux  <- aux %>% 
    mutate(., paste0(n,"_netmigr") = paste0(n,"_pop") - paste0((n-1),"_deaths") + 
             paste0((n-1),"_births") - paste0((n-1),"_pop"), .after = paste0(n,"_pop"))
}

When I manually run the code inside the while loop using actual names instead of the paste0 commands, it works exactly as I want it to. Is there a way to iteratively specify/identify names that I am not seeing?

Thankful for any insights!


Solution

  • Here's some sample data:

    library(tidyr)
    
    tb <- expand_grid(country = letters[1:5], sex = c("male", "female"))
    for (yr in 2000:2020) tb[[paste0(yr, "_pop")]] <- sample(1e6, nrow(tb))
    for (yr in 2000:2020) tb[[paste0(yr, "_births")]] <- sample(1e6, nrow(tb))
    for (yr in 2000:2020) tb[[paste0(yr, "_deaths")]] <- sample(1e6, nrow(tb))
    
    tb
    # A tibble: 10 × 65
       country sex    `2000_pop` `2001_pop` `2002_pop` `2003_pop` `2004_pop`
       <chr>   <chr>       <int>      <int>      <int>      <int>      <int>
     1 a       male       494854     125496     441605     850152     564524
     2 a       female      15675     700400     884402     722577     488377
     3 b       male       863598     430942     178898     962331     762543
     ...
    

    Let's reshape:

    tb <- tb |> 
            pivot_longer(starts_with("20"), names_to = c("year", "var"), 
                           names_sep = "_") |> 
            pivot_wider(names_from = "var")
    tb
    # A tibble: 210 × 6
       country sex   year     pop births deaths
       <chr>   <chr> <chr>  <int>  <int>  <int>
     1 a       male  2000  494854 692068 890029
     2 a       male  2001  125496 420085 334800
     3 a       male  2002  441605 341633 816369
     4 a       male  2003  850152 310789 766912
     ...
    

    Now your data is tidy, and no for loop or column name munging is required:

    tb$net_migr <- tb$pop - tb$deaths + tb$births
    # or
    tb <- tb |> mutate(net_migr = pop - deaths + births)
    

    If you want to, you can now return tb to wide format. (But why would you want to?)