Numerazione di righe all'interno di gruppi in un frame di dati


163

Lavorare con un frame di dati simile a questo:

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 

Sto cercando di aggiungere una colonna con numerazione all'interno di ciascun gruppo. Farlo in questo modo ovviamente non sta usando i poteri di 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  

Quale sarebbe un buon modo per farlo?


1
Vorrei suggerire di aggiungere qualcosa come "seq along livelli" o "counting along replicates" nel titolo della domanda in quanto è così che ho trovato questa domanda ed è esattamente quello che stavo cercando
crazysantaclaus

2
@crazysantaclaus Se quello fosse il titolo, non avrei trovato quello che cercavo :-( Stavo letteralmente cercando "come numerare le righe all'interno dei gruppi in un frame di dati"
Zimano

Risposte:


280

Utilizzare ave, ddply, dplyro data.table:

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

o:

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

o:

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

oppure (il più efficiente in termini di memoria, in quanto assegnato per riferimento all'interno DT):

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

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]

2
Potrebbe valere la pena ricordare che avedà un float invece di un int qui. In alternativa, potrebbe cambiare df$valin seq_len(nrow(df)). Mi sono appena imbattuto qui: stackoverflow.com/questions/42796857/…
Frank

1
È interessante notare che questa data.tablesoluzione sembra essere più veloce dell'uso 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
Grazie! La dplyrsoluzione è buona Ma se, come me, hai continuato a ricevere strani errori durante il tentativo di questo approccio, assicurati di non avere conflitti tra plyre dplyrcome spiegato in questo post Può essere evitato chiamando esplicitamentedplyr::mutate(...)
EcologyTom

2
un altro data.tablemetodo èsetDT(df)[, id:=rleid(val), by=.(cat)]
chinsoon12

Come modificare library(plyr)e library(dplyr)risposte per rendere la colonna val di classificazione in ordine decrescente?
Przemyslaw Remin,

26

Per averlo fatto domanda più completa, un'alternativa di base R con sequencee rle:

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

che dà il risultato desiderato:

> 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

Se df$catè una variabile fattore, devi as.characterprima includerla :

df$num <- sequence(rle(as.character(df$cat))$lengths)

Appena notato, questa soluzione richiede che la catcolonna sia ordinata?
zx8754,

@ zx8754 sì, a meno che tu non voglia numerare per occorrenze consecutive dicat
Jaap

9

Ecco un'opzione che utilizza un forciclo per gruppi anziché per righe (come ha fatto OP)

for (i in unique(df$cat)) df$num[df$cat == i] <- seq_len(sum(df$cat == i))

9

Ecco un piccolo trucco di miglioramento che consente di ordinare 'val' all'interno dei gruppi:

# 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())

Non riesci a ordinare dopo il group_by?
Zcoleman

6

Vorrei aggiungere una data.tablevariante usando la rank()funzione che fornisce l'ulteriore possibilità di cambiare l'ordinamento e quindi rende un po 'più flessibile della seq_len()soluzione ed è abbastanza simile alle funzioni row_number in RDBMS.

# 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),]

5

Un'altra dplyrpossibilità potrebbe essere:

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

3
In alcuni casi invece di 1:n()usare seq_len(n())è più sicuro, nel caso in cui nella sequenza di operazioni si presenti una situazione in cui n()potrebbe tornare 0, perché 1:0ti dà un vettore di lunghezza due mentre ti seq_len(0)dà un vettore di lunghezza zero, evitando così un errore di mancata corrispondenza della lunghezza con mutate().
Brian Stamper,

0

Utilizzando la rowid()funzione in 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

1
Grazie per la tua risposta, ma sembra essere già stato trattato nell'ultimo suggerimento nella risposta di @ mnel
eli-k
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.