Brandon Bertelsen
Brandon Bertelsen

Reputation: 44648

Moving columns within a data.frame() without retyping

Is there a method for moving a column from one position in a data.frame to the next - without typing an entirely new data.frame()

For example:

a <- b <- c <- d <- e <- f <- g <- 1:100
df <- data.frame(a,b,c,d,e,f,g)

Now let's say I wanted "g" in front of "a"

I could retype it, as

df <- data.frame(g,a,b,c,d,e,f)

But is there not a quicker way? (Imagine 1500+ columns)

Upvotes: 76

Views: 115995

Answers (17)

Matthew Son
Matthew Son

Reputation: 1425

For data.table users :

Use setcolorder().

setDT(df) # convert into data.table

setcolorder(df,'g') # column g comes first if not all column names are mentioned
setcolorder(df, 7)  # same as above

df

       g   a   b   c   d   e   f
  1:   1   1   1   1   1   1   1
  2:   2   2   2   2   2   2   2
  3:   3   3   3   3   3   3   3
  4:   4   4   4   4   4   4   4
  5:   5   5   5   5   5   5   5
  6:   6   6   6   6   6   6   6
  7:   7   7   7   7   7   7   7

In case when column 'a' and 'b' should be moved to rightmost:

setcolorder(df,3:7) 
df

       c   d   e   f   g   a   b
  1:   1   1   1   1   1   1   1
  2:   2   2   2   2   2   2   2
  3:   3   3   3   3   3   3   3
  4:   4   4   4   4   4   4   4
  5:   5   5   5   5   5   5   5
  6:   6   6   6   6   6   6   6
  7:   7   7   7   7   7   7   7

Upvotes: 1

Holger Brandl
Holger Brandl

Reputation: 11192

Use relocate from dplyr package

mtcars %>% 
   # dplyr::relocate(disp) %>% ## simply make disp the first column
   relocate(starts_with("c"), .after = disp)  %>% ## more complex column order shuffling
   head(3)

Note, that the function was added with version 1.0, see https://www.tidyverse.org/blog/2020/03/dplyr-1-0-0-select-rename-relocate/

Upvotes: 8

Ferroao
Ferroao

Reputation: 3043

I would like to contribute another universal working approach, similar to the previous answers of rcs, Manuel and Scott Kaiser, which only work in specific cases:

move<-function(new.pos,nameofcolumn,dfname) {
  col_idx <- grep(nameofcolumn, names(dfname))
  if (length(col_idx)==0){print("invalid column name");return(dfname)} else {
  if(new.pos>ncol(dfname)){print("invalid column number");return(dfname)} else {
  if (new.pos==1) {
    b<-dfname[ , c( col_idx, c((new.pos):ncol(dfname))[-(abs(new.pos-1-col_idx))] )]  
    }
  else if(col_idx==1 & new.pos==ncol(dfname)){
    b<-dfname[ , c((1:(new.pos-1)+1), col_idx )] 
    }
  else if(col_idx==1){
    b<-dfname[ , c((1:(new.pos-1)+1), col_idx, c((new.pos+1):ncol(dfname)) )] 
    }
  else if(new.pos==ncol(dfname)){
    b<-dfname[ , c((1:(new.pos))[-col_idx], col_idx)] 
    }
  else if(new.pos>col_idx){
    b<-dfname[ , c((1:(new.pos))[-col_idx], col_idx, c((new.pos+1):ncol(dfname)) )] 
    } 
  else{
    b<-dfname[ , c((1:(new.pos-1)), col_idx, c((new.pos):ncol(dfname))[-(abs(new.pos-1-col_idx))] )]
    }
  return(b)
  if(length(ncol(b))!=length(ncol(dfname))){print("error")}
  }
}}

Usage:

a <- b <- c <- d <- e <- f <- g <- 1:5
df <- data.frame(a,b,c,d,e,f,g)
move(1,"g",df)

Upvotes: 0

Dimitris Patikas
Dimitris Patikas

Reputation: 9

Here is one function that might help

  • df: the dataframe
  • ColName: the name of the column(s) to be moved
  • Position: the column number that you want the moved column to appear

moveCol <- function(df,ColName,Position=1) {
    D <- dim(df)[2]
    DFnames <- names(df)
    if (Position>D+1 | Position<1) {
        warning(paste0('Column position ',sprintf('%d',Position), ' is out of range [1-',sprintf('%d',D),']'))
        return()
    }
    for (i in ColName) {
        x <- i==DFnames
        if (all(!x)) {
            warning(paste0('Column \"', i, '\" not found'))
        } else {
            D1 <- seq(D)
            D1[x] = Position - 0.5
            df<- df[order(D1)]
        }
    }
    return(df)
}

Upvotes: -1

Kevin Rosenfield
Kevin Rosenfield

Reputation: 51

I found a pretty simple way of doing this that suited my needs and doesn't take much time.

You have the following column names: "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"

Move "d" to second position (after "a"):

attach(df)

df <- cbind(a, d, df[,c(2:3,5:10)])

Move "j" to 4th position (after "c"):

df <- cbind(df[,c(1:3)], j, df[,c(4:9)])

Upvotes: 1

Holger Brandl
Holger Brandl

Reputation: 11192

Most solutions seem overly verbose or lack encapsulation. Here's another way to solve the problem

push_left <- function(df, pushColNames){
    df[, c(pushColNames, setdiff(names(df), pushColNames))]
}

push_left(iris, c("Species", "Sepal.Length"))

Upvotes: 1

LC-datascientist
LC-datascientist

Reputation: 2096

Here is a simple but flexible function I wrote to move a column anywhere in a data frame.

move.col <- function(df, move_this, next_to_this, before = FALSE) {
  if (before==FALSE)
    df[,c(match(setdiff(names(df)[1:which(names(df)==next_to_this)],move_this),names(df)),
          match(move_this,names(df)),
          match(setdiff(names(df)[which(names(df)==next_to_this):ncol(df)],c(next_to_this,move_this)),names(df)))]
  else
    df[,c(match(setdiff(names(df)[1:(which(names(df)==next_to_this))],c(next_to_this,move_this)),names(df)),
          match(move_this,names(df)),
          match(setdiff(names(df)[(which(names(df)==next_to_this)):ncol(df)],move_this),names(df)))]
}

Usage: Specify the data frame (df), the column name you want to move (move_this), and the column name of which you want to move beside (next_to_this). By default, the function will move the move_this column after the next_to_this column. You can specify before = TRUE to move move_this before next_to_this.

Examples:

  1. Move "b" after "g" (i.e., make "b" last column).

move.col(df, "b", "g")

  1. Move "c" after "e".

move.col(df, "c", "e")

  1. Move "g" before "a" (i.e., make "g" first column).

move.col(df, "g", "a", before=TRUE)

  1. Move "d" and "f" before "b" (i.e., move multiple columns).

move.col(df,c("d","f"),"b", before=TRUE)

Upvotes: 1

Sayak
Sayak

Reputation: 193

This is a very old post , but I developed this code which dynamically changes column position within a dataframe. Just change the value of n and Column Name ("g" here) and get dataframe with new column arrangements.

df1 = subset(df, select = c(head(names(df),n=3),"g", names(df) [! names(df) %in% c(head(names(df),n=3),"g")]))

Upvotes: 4

Ken Williams
Ken Williams

Reputation: 23975

The subset function has a nice select argument that gives a convenient way to select ranges of columns by name:

df <- subset(df, select=c(g,a:f))

Upvotes: 79

Insilico
Insilico

Reputation: 926

Here's a similar way I used to move 'n'th column to 2nd position in a huge data frame based on the column name.

Move a column to first position:

## Move a column with name "col_name"  to first column 
colX <- grep("^col_name", colnames(df.original)) 
# get the column position from name 

df.reordered.1 <- df.original[,c(colX,1:(colX-1), (colX+1):length(df.original))]  
# get new reordered data.frame
# if the column is the last one, error "undefined columns selected" will show up. Then do the following command instead of this

df.reordered.1 <- df.original[,c(colX,1:(colX-1)]  
# get new reordered data.frame, if the column is the last one

From anywhere to To 'n'th position

## Move a column with name "col_name"  to column position "n", 
## where n > 1 (in a data.frame "df.original")

colX <- grep("^col_name", colnames(df.original)) 
# get the column position from name 

n <- 2 
# give the new expected column position (change to the position you need) 

df.reordered.2 <- df.original[,c(1:(n-1), colX, n:(colX-1), (colX+1):length(df.original))] 
# get new reordered data.frame

## Optional; to replace the original data frame with sorted data.frame 
## if the sorting looks good
df.original <- df.reordered.2
rm(df.reordered.2) # remove df

Upvotes: 1

Scott Kaiser
Scott Kaiser

Reputation: 317

@David asked how to move "G" to an arbitrary position, such as 4. Building on @rcs answer,

new.pos <- 4
col_idx <- grep("g", names(df))
df      <- df[ , c((1:new.pos)[-col_idx], col_idx, c((new.pos):ncol(df))[-col_idx])]

Upvotes: -2

St&#233;phane Laurent
St&#233;phane Laurent

Reputation: 84529

If the reordering is a shift, as in your example, you can use the shift function from the taRifx package. It acts on vectors, hence apply it to the column names:

> a <- b <- c <- d <- e <- f <- g <- 1:5
> df <- data.frame(a,b,c,d,e,f,g)
> df[, taRifx::shift(seq_along(df),-1)]
  g a b c d e f
1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5

In fact the shift function can also be applied to a data frame, but not as expected. You can write a function:

> shift_df <- function(df, n) df[, taRifx::shift(seq_along(df),n)]
> shift_df(df, -1)
  g a b c d e f
1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5
> shift_df(df, 2)
  c d e f g a b
1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5

Upvotes: 0

Sergey
Sergey

Reputation: 31

This is slightly more elegant and allows to arrange first few leftmost columns and leave the rest unarranged to the right.

ordered_columns_leftside=c('var10','var34','var8')
df=df[c(ordered_columns_leftside, setdiff(names(df),ordered_columns_leftside))]

Upvotes: 3

Sam Firke
Sam Firke

Reputation: 23014

Use select from the dplyr package and its everything() function to move specific columns to the start or end of a data.frame.

Move to the beginning:

library(dplyr)
df %>%
  select(g, everything())

Move to the end:

df %>%
  select(-a, everything())

Or without the %>% pipe operator, those would be select(df, g, everything()) and select(df, -a, everything()) respectively.

Upvotes: 61

jpmarindiaz
jpmarindiaz

Reputation: 1649

Here is my solution

df[c(7,1:6)]

or you can also reorder by column name:

df[c("g",names(df)[-7])]

Upvotes: 15

A5C1D2H2I1M1N2O1R2T1
A5C1D2H2I1M1N2O1R2T1

Reputation: 193517

I wrote this function recently called moveme. It's designed to work on vectors, with the intent of shuffling column orders around.

Here's the function:

moveme <- function (invec, movecommand) {
  movecommand <- lapply(strsplit(strsplit(movecommand, ";")[[1]], 
                                 ",|\\s+"), function(x) x[x != ""])
  movelist <- lapply(movecommand, function(x) {
    Where <- x[which(x %in% c("before", "after", "first", 
                              "last")):length(x)]
    ToMove <- setdiff(x, Where)
    list(ToMove, Where)
  })
  myVec <- invec
  for (i in seq_along(movelist)) {
    temp <- setdiff(myVec, movelist[[i]][[1]])
    A <- movelist[[i]][[2]][1]
    if (A %in% c("before", "after")) {
      ba <- movelist[[i]][[2]][2]
      if (A == "before") {
        after <- match(ba, temp) - 1
      }
      else if (A == "after") {
        after <- match(ba, temp)
      }
    }
    else if (A == "first") {
      after <- 0
    }
    else if (A == "last") {
      after <- length(myVec)
    }
    myVec <- append(temp, values = movelist[[i]][[1]], after = after)
  }
  myVec
}

Usage is simple. Try these out:

moveme(names(df), "g first")
moveme(names(df), "g first; a last; e before c")

Of course, using it to reorder the columns in your data.frame is straightforward:

df[moveme(names(df), "g first")]

And for data.tables (moves by reference, no copy) :

setcolorder(dt, moveme(names(dt), "g first"))

The basic options are:

  • first
  • last
  • before
  • after

Compounded moves are separated by a semicolon.

Upvotes: 65

rcs
rcs

Reputation: 68839

Here is one way to do it:

> col_idx <- grep("g", names(df))
> df <- df[, c(col_idx, (1:ncol(df))[-col_idx])]
> names(df)
[1] "g" "a" "b" "c" "d" "e" "f"

Upvotes: 61

Related Questions