¿Cómo se reordenan las columnas en un marco de datos?

311

¿Cómo cambiaría esta entrada (con la secuencia: tiempo, entrada, salida, archivos):

Time   In    Out  Files
1      2     3    4
2      3     4    5

¿A esta salida (con la secuencia: tiempo, fuera, dentro, archivos)?

Time   Out   In  Files
1      3     2    4
2      4     3    5

Aquí están los datos ficticios de R:

table <- data.frame(Time=c(1,2), In=c(2,3), Out=c(3,4), Files=c(4,5))
table
##  Time In Out Files
##1    1  2   3     4
##2    2  3   4     5
Catherine
fuente
44
help(Extract)también conocido como?'['
Joris Meys
3
Además de las sugerencias de @ Joris, intente leer las secciones 2.7 y 5 del manual "Una introducción a R": cran.r-project.org/doc/manuals/R-intro.html
Gavin Simpson
3
Una cuestión adicional: todas las respuestas requieren la lista completa de columnas; de lo contrario, se generarán subconjuntos. ¿Qué sucede si solo queremos enumerar algunas columnas para ordenarlas como las primeras, pero también retener todas las demás?
000andy8484

Respuestas:

341

Su marco de datos tiene cuatro columnas como esta df[,c(1,2,3,4)]. Tenga en cuenta que la primera coma significa mantener todas las filas, y el 1,2,3,4 se refiere a las columnas.

Para cambiar el orden como en la pregunta anterior, haga df2[,c(1,3,2,4)]

Si desea generar este archivo como un csv, haga write.csv(df2, file="somedf.csv")

richiemorrisroe
fuente
35
Esto está bien cuando tiene un número limitado de columnas, pero qué pasa si tiene, por ejemplo, 50 columnas, tomaría demasiado tiempo escribir todos los números o nombres de columna. ¿Cuál sería una solución más rápida?
Herman Toothrot
54
@ user4050: en ese caso, puede usar la sintaxis ":", por ejemplo, df [, c (1,3,2,4,5: 50)].
dalloliogm
1
para poner las columnas en ídolos al inicio: idcols <- c ("nombre", "id2", "inicio", "duración"); cols <- c (idcols, nombres (cts) [- which (nombres (cts)% en% idcols)]); df <- df [cols]
kasterma
13
@ user4050: también puede usarlo df[,c(1,3,2,4:ncol(df))]cuando no sabe cuántas columnas hay.
arekolek
1
También puede usar dput (colnames (df)), imprime los nombres de columna en formato de caracteres R. Luego puede reorganizar los nombres.
Chris
168
# reorder by column name
data <- data[c("A", "B", "C")]

#reorder by column index
data <- data[c(1,3,2)]
Xavier Guardiola
fuente
1
Pregunta como principiante, ¿puede combinar pedidos por índice y por nombre? Por ejemplo data <- data[c(1,3,"Var1", 2)]?
Bram Vanroy
66
@BramVanroy no, c(1,3,"Var1", 2)se leerá como c("1","3","Var1", "2")porque los vectores pueden contener datos de un solo tipo, por lo que los tipos se promueven al tipo más general presente. Debido a que no hay columnas con los nombres de los caracteres "1", "3", etc., obtendrá "columnas indefinidas". list(1,3,"Var1", 2)mantiene los valores sin promoción de tipo, pero no puede usar a listen el contexto anterior.
Terry Brown
1
¿Por qué funciona el mtcars[c(1,3,2)]subconjunto? Hubiera esperado un error relacionado con dimensiones incorrectas o similares ... ¿No debería ser así mtcars[,c(1,3,2)]?
landroni
data.frames son listas bajo el capó con columnas como elementos de primer orden
petermeissner
106

También puede usar la función de subconjunto:

data <- subset(data, select=c(3,2,1))

Debería usar mejor el operador [] como en las otras respuestas, pero puede ser útil saber que puede hacer un subconjunto y una operación de reordenamiento de columna en un solo comando.

Actualizar:

También puede usar la función de selección del paquete dplyr:

data = data %>% select(Time, out, In, Files)

No estoy seguro de la eficiencia, pero gracias a la sintaxis de dplyr, esta solución debería ser más flexible, especialmente si tiene muchas columnas. Por ejemplo, lo siguiente reordenará las columnas del conjunto de datos mtcars en el orden opuesto:

mtcars %>% select(carb:mpg)

Y lo siguiente reordenará solo algunas columnas y descartará otras:

mtcars %>% select(mpg:disp, hp, wt, gear:qsec, starts_with('carb'))

Lea más sobre la sintaxis select de dplyr .

dalloliogm
fuente
55
Hay algunas razones para no usar subset(), vea esta pregunta .
MERose
2
Gracias. En cualquier caso, ahora usaría la función de selección del paquete dplyr, en lugar del subconjunto.
dalloliogm
87
Cuando quiera traer un par de columnas al lado izquierdo y no dejar caer las otras, me parece everything()particularmente impresionante; mtcars %>% select(wt, gear, everything())
guyabel
2
Aquí hay otra forma de usar la función everything () select_helper para reorganizar las columnas a la derecha / final. stackoverflow.com/a/44353144/4663008 github.com/tidyverse/dplyr/issues/2838 Parece que necesitará usar 2 select () 's para mover algunas columnas al extremo derecho y otras a la izquierda.
Arthur Yip
1
La nueva función dplyr :: relocate es exactamente para esto. vea la respuesta de H 1 a continuación
Arthur Yip
39

Como se menciona en este comentario , las sugerencias estándar para reordenar columnas en a data.frameson generalmente engorrosas y propensas a errores, especialmente si tiene muchas columnas.

Esta función permite reorganizar las columnas por posición: especifique un nombre de variable y la posición deseada, y no se preocupe por las otras columnas.

##arrange df vars by position
##'vars' must be a named vector, e.g. c("var.name"=1)
arrange.vars <- function(data, vars){
    ##stop if not a data.frame (but should work for matrices as well)
    stopifnot(is.data.frame(data))

    ##sort out inputs
    data.nms <- names(data)
    var.nr <- length(data.nms)
    var.nms <- names(vars)
    var.pos <- vars
    ##sanity checks
    stopifnot( !any(duplicated(var.nms)), 
               !any(duplicated(var.pos)) )
    stopifnot( is.character(var.nms), 
               is.numeric(var.pos) )
    stopifnot( all(var.nms %in% data.nms) )
    stopifnot( all(var.pos > 0), 
               all(var.pos <= var.nr) )

    ##prepare output
    out.vec <- character(var.nr)
    out.vec[var.pos] <- var.nms
    out.vec[-var.pos] <- data.nms[ !(data.nms %in% var.nms) ]
    stopifnot( length(out.vec)==var.nr )

    ##re-arrange vars by position
    data <- data[ , out.vec]
    return(data)
}

Ahora la solicitud del OP se vuelve tan simple como esto:

table <- data.frame(Time=c(1,2), In=c(2,3), Out=c(3,4), Files=c(4,5))
table
##  Time In Out Files
##1    1  2   3     4
##2    2  3   4     5

arrange.vars(table, c("Out"=2))
##  Time Out In Files
##1    1   3  2     4
##2    2   4  3     5

Para intercambiar Timey Filescolumnas adicionalmente , puede hacer esto:

arrange.vars(table, c("Out"=2, "Files"=1, "Time"=4))
##  Files Out In Time
##1     4   3  2    1
##2     5   4  3    2
Landroni
fuente
Muy buena función. Agregué una versión modificada de esta función a mi paquete personal .
Deleet
1
Esto es realmente útil: me ahorrará mucho tiempo cuando solo quiera mover una columna desde el final de un tibble muy ancho hasta el principio
Mrmoleje
Wow, me encanta esto
OfTheAzureSky
37

Una dplyrsolución (parte del tidyverseconjunto de paquetes) es usar select:

select(table, "Time", "Out", "In", "Files") 

# or

select(table, Time, Out, In, Files)
Ben G
fuente
2
La mejor opción para mi. Incluso si tuviera que instalarlo, es claramente la posibilidad más clara.
Garini
15
Tidyverse (dplyr de hecho) también tiene la opción de seleccionar grupos de columnas, por ejemplo para mover la variable Species al frente: select(iris, Species, everything()) . También tenga en cuenta que las citas no son necesarias.
Paul Rougieux
3
Es importante tener en cuenta que esto eliminará todas las columnas que no se especifiquen explícitamente a menos que se incluya everything()como en el comentario de
PaulRougieux
dplyr's grouptambién reorganizará las variables, así que ten cuidado cuando uses eso en una cadena.
David Tonhofer
26

Tal vez sea una coincidencia que el orden de las columnas que desea tenga nombres de columnas en orden alfabético descendente. Como ese es el caso, simplemente podrías hacer:

df<-df[,order(colnames(df),decreasing=TRUE)]

Eso es lo que uso cuando tengo archivos grandes con muchas columnas.

usuario3482899
fuente
!! WARNING !! data.tablese convierte TARGETen un vector int: TARGET <- TARGET[ , order(colnames(TARGET), decreasing=TRUE)] para arreglar eso: TARGET <- as.data.frame(TARGET) TARGET <- TARGET[ , order(colnames(TARGET), decreasing=TRUE)]
Zachary Ryan Smith
12

Las tres respuestas mejor calificadas tienen una debilidad.

Si su marco de datos se ve así

df <- data.frame(Time=c(1,2), In=c(2,3), Out=c(3,4), Files=c(4,5))

> df
  Time In Out Files
1    1  2   3     4
2    2  3   4     5

entonces es una mala solución para usar

> df2[,c(1,3,2,4)]

Hace el trabajo, pero acaba de introducir una dependencia en el orden de las columnas en su entrada.

Este estilo de programación frágil se debe evitar.

El nombramiento explícito de las columnas es una mejor solución

data[,c("Time", "Out", "In", "Files")]

Además, si tiene la intención de reutilizar su código en una configuración más general, simplemente puede

out.column.name <- "Out"
in.column.name <- "In"
data[,c("Time", out.column.name, in.column.name, "Files")]

lo cual también es bastante bueno porque aísla por completo los literales. Por el contrario, si usa dplyr'sselect

data <- data %>% select(Time, out, In, Files)

entonces estarías configurando a aquellos que leerán tu código más tarde, incluido tú mismo, por un poco de engaño. Los nombres de columna se utilizan como literales sin aparecer en el código como tal.

Vrokipal
fuente
3

dplyrLa versión 1.0.0incluye la relocate()función para reordenar fácilmente las columnas:

dat <- data.frame(Time=c(1,2), In=c(2,3), Out=c(3,4), Files=c(4,5))

library(dplyr) # from version 1.0.0 only

dat %>%
  relocate(Out, .before = In)

o

dat %>%
  relocate(Out, .after = Time)
27 ϕ 9
fuente
2
data.table::setcolorder(table, c("Out", "in", "files"))
Hossein Noorazar
fuente
Por favor, indique la biblioteca de la que toma la función setcolorder.
Triamus
1

El único que he visto funcionar bien es desde aquí .

 shuffle_columns <- 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
}

Usar así:

new_df <- iris[shuffle_columns(names(iris), "Sepal.Width before Sepal.Length")]

Funciona de maravilla.

Cibernético
fuente