Numérotation des lignes dans des groupes dans un bloc de données

163

Travailler avec un bloc de données similaire à celui-ci:

set.seed(100)  
df <- data.frame(cat = c(rep("aaa", 5), rep("bbb", 5), rep("ccc", 5)), val = runif(15))             
df <- df[order(df$cat, df$val), ]  
df  

   cat        val  
1  aaa 0.05638315  
2  aaa 0.25767250  
3  aaa 0.30776611  
4  aaa 0.46854928  
5  aaa 0.55232243  
6  bbb 0.17026205  
7  bbb 0.37032054  
8  bbb 0.48377074  
9  bbb 0.54655860  
10 bbb 0.81240262  
11 ccc 0.28035384  
12 ccc 0.39848790  
13 ccc 0.62499648  
14 ccc 0.76255108  
15 ccc 0.88216552 

J'essaye d'ajouter une colonne avec la numérotation dans chaque groupe. Faire de cette façon n'utilise évidemment pas les pouvoirs de R:

 df$num <- 1  
 for (i in 2:(length(df[,1]))) {  
   if (df[i,"cat"]==df[(i-1),"cat"]) {  
     df[i,"num"]<-df[i-1,"num"]+1  
     }  
 }  
 df  

   cat        val num  
1  aaa 0.05638315   1  
2  aaa 0.25767250   2  
3  aaa 0.30776611   3  
4  aaa 0.46854928   4  
5  aaa 0.55232243   5  
6  bbb 0.17026205   1  
7  bbb 0.37032054   2  
8  bbb 0.48377074   3  
9  bbb 0.54655860   4  
10 bbb 0.81240262   5  
11 ccc 0.28035384   1  
12 ccc 0.39848790   2  
13 ccc 0.62499648   3  
14 ccc 0.76255108   4  
15 ccc 0.88216552   5  

Quelle serait une bonne façon de faire cela?

eli-k
la source
1
Je suggérerais d'ajouter quelque chose comme "seq le long des niveaux" ou "compter le long des répliques" dans le titre de la question car c'est ainsi que j'ai trouvé cette question et c'est exactement ce que je cherchais
crazysantaclaus
2
@crazysantaclaus Si tel était le titre, je n'aurais pas trouvé ce que je cherchais :-( Je cherchais littéralement "comment numéroter des lignes dans des groupes dans un
bloc de

Réponses:

280

Utilisez ave, ddply, dplyrou data.table:

df$num <- ave(df$val, df$cat, FUN = seq_along)

ou:

library(plyr)
ddply(df, .(cat), mutate, id = seq_along(val))

ou:

library(dplyr)
df %>% group_by(cat) %>% mutate(id = row_number())

ou (le plus efficace en mémoire, comme il l'attribue par référence dans DT):

library(data.table)
DT <- data.table(df)

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]
mnel
la source
2
Cela vaut peut-être la peine de mentionner que avedonne ici un float au lieu d'un int. Alternativement, pourrait changer df$valen seq_len(nrow(df)). Je viens de rencontrer ceci ici: stackoverflow.com/questions/42796857/…
Frank
1
Fait intéressant, cette data.tablesolution semble être plus rapide que d'utiliser frank: library(microbenchmark); microbenchmark(a = DT[, .(val ,num = frank(val)), by = list(cat)] ,b =DT[, .(val , id = seq_len(.N)), by = list(cat)] , times = 1000L)
hannes101
4
Merci! La dplyrsolution est bonne. Mais si, comme moi, vous continuiez à avoir des erreurs étranges en essayant cette approche, assurez-vous que vous plyrdplyrdplyr::mutate(...)
n'obtenez
2
une autre data.tableméthode estsetDT(df)[, id:=rleid(val), by=.(cat)]
chinsoon12
Comment modifier library(plyr)et library(dplyr)réponses pour rendre la colonne de classement val par ordre décroissant?
Przemyslaw Remin
26

Pour faire cela question plus complète, une alternative de base R avec sequenceet rle:

df$num <- sequence(rle(df$cat)$lengths)

ce qui donne le résultat attendu:

> df
   cat        val num
4  aaa 0.05638315   1
2  aaa 0.25767250   2
1  aaa 0.30776611   3
5  aaa 0.46854928   4
3  aaa 0.55232243   5
10 bbb 0.17026205   1
8  bbb 0.37032054   2
6  bbb 0.48377074   3
9  bbb 0.54655860   4
7  bbb 0.81240262   5
13 ccc 0.28035384   1
14 ccc 0.39848790   2
11 ccc 0.62499648   3
15 ccc 0.76255108   4
12 ccc 0.88216552   5

S'il df$cats'agit d'une variable de facteur, vous devez d'abord l'envelopper as.character:

df$num <- sequence(rle(as.character(df$cat))$lengths)
Jaap
la source
Juste remarqué, cette solution nécessite catle tri des colonnes?
zx8754
@ zx8754 oui, sauf si vous voulez numéroter par occurrences consécutives decat
Jaap
9

Voici une option utilisant une forboucle par groupes plutôt que par lignes (comme OP l'a fait)

for (i in unique(df$cat)) df$num[df$cat == i] <- seq_len(sum(df$cat == i))
un petit garçon
la source
9

Voici une petite astuce d'amélioration qui permet de trier 'val' à l'intérieur des groupes:

# 1. Data set
set.seed(100)
df <- data.frame(
  cat = c(rep("aaa", 5), rep("ccc", 5), rep("bbb", 5)), 
  val = runif(15))             

# 2. 'dplyr' approach
df %>% 
  arrange(cat, val) %>% 
  group_by(cat) %>% 
  mutate(id = row_number())
Andrii
la source
Vous ne pouvez pas trier après le group_by?
zcoleman
6

Je voudrais ajouter une data.tablevariante en utilisant la rank()fonction qui offre la possibilité supplémentaire de modifier la commande et la rend donc un peu plus flexible que la seq_len()solution et est assez similaire aux fonctions row_number dans SGBDR.

# Variant with ascending ordering
library(data.table)
dt <- data.table(df)
dt[, .( val
   , num = rank(val))
    , by = list(cat)][order(cat, num),]

    cat        val num
 1: aaa 0.05638315   1
 2: aaa 0.25767250   2
 3: aaa 0.30776611   3
 4: aaa 0.46854928   4
 5: aaa 0.55232243   5
 6: bbb 0.17026205   1
 7: bbb 0.37032054   2
 8: bbb 0.48377074   3
 9: bbb 0.54655860   4
10: bbb 0.81240262   5
11: ccc 0.28035384   1
12: ccc 0.39848790   2
13: ccc 0.62499648   3
14: ccc 0.76255108   4

# Variant with descending ordering
dt[, .( val
   , num = rank(-val))
    , by = list(cat)][order(cat, num),]
hannes101
la source
5

Une autre dplyrpossibilité pourrait être:

df %>%
 group_by(cat) %>%
 mutate(num = 1:n())

   cat      val   num
   <fct>  <dbl> <int>
 1 aaa   0.0564     1
 2 aaa   0.258      2
 3 aaa   0.308      3
 4 aaa   0.469      4
 5 aaa   0.552      5
 6 bbb   0.170      1
 7 bbb   0.370      2
 8 bbb   0.484      3
 9 bbb   0.547      4
10 bbb   0.812      5
11 ccc   0.280      1
12 ccc   0.398      2
13 ccc   0.625      3
14 ccc   0.763      4
15 ccc   0.882      5
tmfmnk
la source
3
Dans certains cas, au lieu d' 1:n()utiliser seq_len(n())est plus sûr, dans le cas où dans votre séquence d'opérations vous avez une situation où n()pourrait revenir 0, car 1:0vous donne un vecteur de longueur deux tandis que seq_len(0)donne un vecteur de longueur zéro, évitant ainsi une erreur de discordance de longueur avec mutate().
Brian Stamper du
0

Utilisation de la rowid()fonction dans data.table:

> set.seed(100)  
> df <- data.frame(cat = c(rep("aaa", 5), rep("bbb", 5), rep("ccc", 5)), val = runif(15))
> df <- df[order(df$cat, df$val), ]  
> df$num <- data.table::rowid(df$cat)
> df
   cat        val num
4  aaa 0.05638315   1
2  aaa 0.25767250   2
1  aaa 0.30776611   3
5  aaa 0.46854928   4
3  aaa 0.55232243   5
10 bbb 0.17026205   1
8  bbb 0.37032054   2
6  bbb 0.48377074   3
9  bbb 0.54655860   4
7  bbb 0.81240262   5
13 ccc 0.28035384   1
14 ccc 0.39848790   2
11 ccc 0.62499648   3
15 ccc 0.76255108   4
12 ccc 0.88216552   5
AKRosenblad
la source
1
Merci pour votre réponse, mais cela semble déjà couvert dans la dernière suggestion de la réponse de @ mnel
eli-k