Search code examples
rtidyverserelocate

Move several chunks of columns dynamically to another position


My data is:

df <- data.frame(a   = 1:2,
                 x   = 1:2,
                 b   = 1:2,
                 y   = 3:4,
                 x_2 = 1:2,
                 y_2 = 3:4,
                 c   = 1:2,
                 x_3 = 5:6,
                 y_3 = 1:2)

I now want to put together the x vars, and the y vars so that the order of columns would be:

a, x, x_2, x_3, b, y, y_2, y_3, c

I thought, I could use tidyverse's relocate function in combination with lapply or map or reduce (?), but it doesn't work out.

E.g. if I do:

move_names <- c("x", "y")

library(tidyverse)
moved_data <- lapply(as.list(move_names), function(x)
{
  df <- df |> 
    relocate(!!!syms(paste0(x, "_", 2:3)),
             .after = all_of(x))
}
)

It does the moving for x and y separately, but it creates separate list, but I want to have just my original df with relocated columns.

Update:

I should have been clear that my real data frame has ~500 columns where the to-be-moved columns are all over the place. So providing the full vector of desired column name order won't be feasible.

What I instead have: I have the names of my original columns, i.e. x and y, and I have the names of the to-be-moved columns, i.e. x_2, x_3, y_2, y_3.


Solution

  • Ok, this is probably the worst workaround ever and I don't really understand what exactly I'm doing (especially with the <<-), but it is does the trick.

    My general idea after realizing the problem a bit more with the help of you guys here was to "loop" through both of my x and y names, remove these new _2 and _3 columns from the vector of column names and re-append them after their "base" x and y columns.

    search_names <- c("x", "y")
    
    df_names <- names(df)
    
    new_names <- lapply(search_names, function(x)
    {
      start <- which(df_names == x)
    
      without_new_names <- setdiff(df_names, paste0(x, "_", 2:3))
      
      df_names <<- append(without_new_names, values = paste0(x, "_", 2:3), after = start)
    })[[length(search_names)]]
    
    df |> 
      relocate(any_of(new_names))
    
      a x x_2 x_3 b y y_2 y_3 c
    1 1 1   1   5 1 3   3   1 1
    2 2 2   2   6 2 4   4   2 2