Nummerierung von Zeilen innerhalb von Gruppen in einem Datenrahmen

163

Arbeiten mit einem ähnlichen Datenrahmen:

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 

Ich versuche, innerhalb jeder Gruppe eine Spalte mit Nummerierung hinzuzufügen. Wenn Sie dies auf diese Weise tun, werden die Kräfte von R offensichtlich nicht genutzt:

 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  

Was wäre ein guter Weg, um dies zu tun?

eli-k
quelle
1
Ich würde vorschlagen, etwas wie "seq Along Levels" oder "Counting Along Replicates" in den
Fragentitel aufzunehmen,
2
@crazysantaclaus Wenn das der Titel wäre, hätte ich nicht gefunden, wonach ich gesucht habe :-( Ich habe buchstäblich nach "wie man Zeilen innerhalb von Gruppen in einem
Datenrahmen nummeriert

Antworten:

280

Verwenden Sie ave, ddply, dplyroder data.table:

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

oder:

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

oder:

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

oder (der speichereffizienteste, da er durch Bezugnahme innerhalb zugewiesen wird DT):

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

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]
mnel
quelle
2
Es könnte erwähnenswert sein, dass hier aveein Float anstelle eines Int angegeben wird. Alternativ könnte, ändern df$valzu seq_len(nrow(df)). Ich bin gerade hier drüben darauf gestoßen
Frank
1
Interessanterweise data.tablescheint diese Lösung schneller zu sein als die Verwendung von 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
Vielen Dank! Die dplyrLösung ist gut. Aber wenn Sie, wie ich, immer wieder seltsame Fehler bekommen, wenn Sie diesen Ansatz ausprobieren, stellen Sie sicher, dass Sie keine Konflikte zwischen plyrund dplyrwie in diesem Beitrag erklärt bekommen. Dies kann vermieden werden, indem Sie explizitdplyr::mutate(...)
EcologyTom
2
Eine andere data.tableMethode istsetDT(df)[, id:=rleid(val), by=.(cat)]
Chinsoon12
Wie ändere library(plyr)und library(dplyr)beantworte ich, um die Spalte mit dem Rangwert in absteigender Reihenfolge zu erstellen?
Przemyslaw Remin
26

Dafür Frage vollständiger, eine Basis-R-Alternative mit sequenceund rle:

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

was das beabsichtigte Ergebnis ergibt:

> 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

Wenn df$cates sich um eine Faktorvariable handelt, müssen Sie sie as.characterzuerst einschließen :

df$num <- sequence(rle(as.character(df$cat))$lengths)
Jaap
quelle
Gerade bemerkt, erfordert diese Lösung, dass die catSpalte sortiert wird?
zx8754
@ zx8754 ja, es sei denn, Sie möchten durch aufeinanderfolgende Vorkommen voncat
Jaap
9

Hier ist eine Option, bei der eine forSchleife nach Gruppen und nicht nach Zeilen verwendet wird (wie bei OP).

for (i in unique(df$cat)) df$num[df$cat == i] <- seq_len(sum(df$cat == i))
ein kleiner Junge
quelle
9

Hier ist ein kleiner Verbesserungstrick, mit dem 'val' innerhalb der Gruppen sortiert werden kann:

# 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
quelle
Kannst du nicht nach group_by sortieren?
Zcoleman
6

Ich möchte eine data.tableVariante mit der rank()Funktion hinzufügen , die die zusätzliche Möglichkeit bietet, die Reihenfolge zu ändern, sie also etwas flexibler als die seq_len()Lösung macht und den Funktionen row_number in RDBMS ziemlich ähnlich ist.

# 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
quelle
5

Eine andere dplyrMöglichkeit könnte sein:

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
quelle
3
In einigen Fällen ist die 1:n()Verwendung seq_len(n())sicherer, wenn Sie in Ihrer Abfolge von Operationen eine Situation haben, in der Sie n()möglicherweise zurückkehren 0, da 1:0Sie einen Vektor mit einer Länge von zwei und einen Vektor mit seq_len(0)einer Länge von Null erhalten, wodurch ein Längeninkongruenzfehler mit vermieden wird mutate().
Brian Stamper
0

Verwenden der rowid()Funktion 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
AKRosenblad
quelle
1
Vielen Dank für Ihre Antwort, aber es scheint bereits im letzten Vorschlag in @ mnels Antwort behandelt worden zu sein
eli-k