Ordonner les lignes de trame de données en fonction du vecteur avec un ordre spécifique


158

Existe-t-il un moyen plus simple de s'assurer que les lignes d'un bloc de données sont ordonnées selon un vecteur «cible» comme celui que j'ai implémenté dans le court exemple ci-dessous?

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")

Cela semble en quelque sorte un peu trop "compliqué" pour faire le travail:

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

Réponses:


232

Essayez 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

Cela fonctionnera tant que votre targetcontient exactement les mêmes éléments que df$name, et qu'aucun des deux ne contient de valeurs en double.

De ?match:

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

Par conséquent, matchtrouve les numéros de ligne qui correspondent targetaux éléments de, puis nous retournons dfdans cet ordre.


Génial, c'est plus comme ça et exactement ce que je cherchais! Merci beaucoup
Rappster

1
une question, que se passe-t-il si la colonne que je souhaite faire correspondre a des valeurs de répétition? comme b,c,a,d,b,c,a,d. J'ai essayé matchmais ça ne marche pas bien.
Yulong

@Yulong: Je pense que vous devriez explicitement vous assurer que les doublons sont supprimés avant le tir match(). Ce qui me vient à l'esprit duplicated(), c'est unique()une autre routine personnalisée qui "garde" les éléments désirés tout en jetant les autres. HTH
Rappster

@Edward c'est une bonne solution. Cependant, cela modifie également les indices. Comment puis-je également les conserver dans l'ordre croissant (1, 2, 3, 4)?
Hasan Iqbal

2
pas sûr que ce soit le moyen le plus propre, mais avec uniquement des fonctions "de base", cela devrait fonctionner si vous avez des doublons dans 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

Je préfère utiliser ***_join dans dplyrchaque fois que je dois faire correspondre les données. Un essai possible pour cela

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

Notez que l'entrée pour ***_joinrequire tbls ou data.frame


Ouais, les fonctions * _join dans dplyrsont vraiment sympas. Finissez par les utiliser beaucoup maintenant aussi
Rappster

Dans ce cas, il est recommandé de déclarer l'ordre cible sous forme de tibble, pour éviter la conversion de data.frame () en facteurs. target <- tibble(name = c("b", "c", "a", "d"))
Nettle

2
Et avec la syntaxe du tuyau:df %>% right_join(tibble(name = target), by = "name")
Frank

18

Cette méthode est un peu différente, elle m'a fourni un peu plus de flexibilité que la réponse précédente. En le transformant en un facteur ordonné, vous pouvez l'utiliser à bon escient arrangeet ainsi de suite. J'ai utilisé reorder.factor du gdatapackage.

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)

Ensuite, utilisez le fait qu'il est maintenant commandé:

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

Si vous souhaitez revenir à l'ordre d'origine (alphabétique), utilisez simplement as.character()pour le remettre à l'état d'origine.


2
Quelqu'un en connaît-il une version data.table?
Reilstein

2
@Reilstein setDT(df)[ , name := factor(name, levels = target)]. Alors voyez les deux data.tableréponses ici
Henrik

4

Nous pouvons ajuster les niveaux de facteur en fonction targetet l'utiliser dansarrange

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

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

Ou orderet utilisez-le dansslice

df %>% slice(order(factor(name, levels = target)))

2
La meilleure solution IMO
stevec

1
Les meilleures et les plus simples solutions pour moi.
Matt_B

0

Si vous ne souhaitez utiliser aucune bibliothèque et que vos données se reproduisent, vous pouvez également utiliser whichavec sapply.

new_order <- sapply(target, function(x,df){which(df$name == x)}, df=df)
df        <- df[new_order,]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.