Ordene las filas del marco de datos según el vector con un orden específico

158

¿Hay una manera más fácil de garantizar que las filas de un marco de datos se ordenen de acuerdo con un vector "objetivo" como el que implementé en el breve ejemplo a continuación?

df <- data.frame(name = letters[1:4], value = c(rep(TRUE, 2), rep(FALSE, 2)))

df
#   name value
# 1    a  TRUE
# 2    b  TRUE
# 3    c FALSE
# 4    d FALSE

target <- c("b", "c", "a", "d")

Esto de alguna manera parece ser demasiado "complicado" para hacer el trabajo:

idx <- sapply(target, function(x) {
    which(df$name == x)
})
df <- df[idx,]
rownames(df) <- NULL

df 
#   name value
# 1    b  TRUE
# 2    c FALSE
# 3    a  TRUE
# 4    d FALSE
Rapero
fuente

Respuestas:

232

Prueba match:

df <- data.frame(name=letters[1:4], value=c(rep(TRUE, 2), rep(FALSE, 2)))
target <- c("b", "c", "a", "d")
df[match(target, df$name),]

  name value
2    b  TRUE
3    c FALSE
1    a  TRUE
4    d FALSE

Funcionará siempre que targetcontenga exactamente los mismos elementos df$namey ninguno contenga valores duplicados.

De ?match:

match returns a vector of the positions of (first) matches of its first argument 
in its second.

Por lo tanto, matchencuentra los números de fila que coinciden con targetlos elementos de 'y luego regresamos dfen ese orden.

Eduardo
fuente
¡Genial, eso es más y exactamente lo que estaba buscando! Muchas gracias
Rappster
1
una pregunta, ¿qué pasa si la columna que me gustaría hacer coincidir tiene valores repetidos? como b,c,a,d,b,c,a,d. Lo intenté matchpero no funciona bien.
Yulong
@Yulong: Creo que tendría que asegurarse explícitamente de que se eliminen los duplicados antes de disparar match(). Lo que viene a la mente es duplicated(), unique()o alguna otra rutina personalizada que "mantiene" los elementos deseados mientras desecha los demás. HTH
Rappster
@ Edward es una buena solución. Sin embargo, también cambia los índices. ¿Cómo puedo mantenerlos también en orden ascendente (1, 2, 3, 4)?
Hasan Iqbal
2
no estoy seguro de que sea la forma más limpia, pero con solo funciones "básicas", esto debería funcionar si tiene duplicados en df:df <- data.frame(name=letters[c(1:4, 1:4)], value=c(rep(TRUE, 2), rep(FALSE, 2),rep(TRUE, 2), rep(FALSE, 2) )) target <- c("b", "c", "a", "d") df[order(unlist(sapply(df$name, function(x) which(target == x)))),]
Erica Fary
21

Yo prefiero usar ***_join en dplyrcada vez que necesito para que coincida con los datos. Un posible intento para esto

left_join(data.frame(name=target),df,by="name")

Tenga en cuenta que la entrada para ***_joinrequiere tbls o data.frame

Lerong
fuente
Sí, las funciones * _join en dplyrson realmente agradables. Termine usándolos mucho por ahora también
Rappster
En este caso, recomendamos declarar el orden de destino como un tibble, para evitar la conversión de data.frame () a factores. target <- tibble(name = c("b", "c", "a", "d"))
Ortiga
2
Y con sintaxis de tubería:df %>% right_join(tibble(name = target), by = "name")
Frank
18

Este método es un poco diferente, me proporcionó un poco más de flexibilidad que la respuesta anterior. Al convertirlo en un factor ordenado, puede usarlo bien arrangey tal. Usé reorder.factor del gdatapaquete.

df <- data.frame(name=letters[1:4], value=c(rep(TRUE, 2), rep(FALSE, 2)))
target <- c("b", "c", "a", "d")

require(gdata)
df$name <- reorder.factor(df$name, new.order=target)

Luego, use el hecho de que ahora está ordenado:

require(dplyr)
df %>%
  arrange(name)
    name value
1    b  TRUE
2    c FALSE
3    a  TRUE
4    d FALSE

Si desea volver al orden original (alfabético), solo use as.character()para volver al estado original.

MattV
fuente
2
¿Alguien sabe una versión data.table de esto?
Reilstein
2
@Reilstein setDT(df)[ , name := factor(name, levels = target)]. Entonces vea las dos data.tablerespuestas aquí
Henrik
4

Podemos ajustar los niveles de factores basados ​​en targety usarlo enarrange

library(dplyr)
df %>% arrange(factor(name, levels = target))

#  name value
#1    b  TRUE
#2    c FALSE
#3    a  TRUE
#4    d FALSE

O orderúsalo y úsalo enslice

df %>% slice(order(factor(name, levels = target)))
Ronak Shah
fuente
2
La mejor solución OMI
stevec
1
Las mejores y más simples soluciones para mí.
Matt_B
0

Si no desea usar ninguna biblioteca y tiene recurrencias en sus datos, también puede usar whichcon sapply.

new_order <- sapply(target, function(x,df){which(df$name == x)}, df=df)
df        <- df[new_order,]
eonurk
fuente