Ich habe einen data.frame

library(dplyr)

ID <- c(1,1,1,1,2,2,3,3,3,3,4,4,5)
Score <- c(20,22,34,56,78,98,56,43,45,33,24,54,22)
Quarter <- c("Q1","Q2","Q3","Q4","Q1","Q2","Q1","Q2","Q3","Q4","Q1","Q2","Q1")
df <- data.frame(ID,Score,Quarter)

Ich möchte mich nur mit den Daten befassen, die alle 4 Quartale haben (Q1, Q2, Q3, Q4 in Spalte "Quartale"). Eine Möglichkeit, wie ich dies tun könnte, ist eine Teilmenge, wenn die ID viermal vorhanden ist, da sie in jedem Quartal wiederholt wird. Es fällt mir schwer, die Anzahl der IDs zu unterschätzen. Ich habe es versucht:

filter(df, count(df, vars = ID)==4)

Aber es hat nicht funktioniert und die Anleitung wäre sehr dankbar. Vielen Dank

2
Jake 17 Apr. 2018 im 05:22

4 Antworten

Beste Antwort

Eine Möglichkeit besteht darin, mit n_distinct eindeutige Werte für jedes ID abzurufen und die Gruppe mit allen 4 Werten zu filtern.

library(dplyr)
df %>%
   group_by(ID) %>%
   filter(n_distinct(Quarter) == 4)


#    ID Score Quarter
#  <dbl> <dbl> <fct>  
#1  1.00  20.0 Q1     
#2  1.00  22.0 Q2     
#3  1.00  34.0 Q3     
#4  1.00  56.0 Q4     
#5  3.00  56.0 Q1     
#6  3.00  43.0 Q2     
#7  3.00  45.0 Q3     
#8  3.00  33.0 Q4     

Eine äquivalente Basis-R-Implementierung unter Verwendung von ave wäre

df[as.numeric(ave(df$Quarter, df$ID, FUN = function(x) length(unique(x)))) == 4, ]
3
Ronak Shah 17 Apr. 2018 im 02:27

Hier sind einige Alternativen. Die letzten drei sind Basislösungen.

# 1 ist eine SQL-Lösung, die einen einspaltigen Datenrahmen df0 mit nur den IDs mit 4 Vierteln erstellt, der dann mit df verbunden wird, wodurch alle anderen IDs eliminiert werden.

# 2 ist eine dplyr-Lösung, die die Gruppen filtert und nur diejenigen mit 4 Zeilen beibehält.

# 3 ist eine data.table-Lösung, die die Zeilen für ID-Gruppen mit 4 Zeilen und NULL für die anderen Gruppen zurückgibt. Dies hat zur Folge, dass die anderen Gruppen eliminiert werden.

# 4 ist eine Zoolösung, die df in ein breites Zooobjekt mit Vierteln oben und ID als Zeitindex konvertiert. Anschließend werden alle Zeilen mit einer NA entfernt und mithilfe von fortify.zoo in das Original zurückversetzt, wobei auch die Reihenfolge in eine sortierte Reihenfolge zurückgesetzt wird. Die letzte Zeile der Lösung kann weggelassen werden, wenn die Zeilenreihenfolge keine Rolle spielt. Interessanterweise verwendet es keine Kenntnis der Nummer 4.

# 5 ist eine Basislösung, die df in eine Liste von Datenrahmen aufteilt, einen pro ID, und dann Filter verwendet, um diejenigen mit 4 Zeilen zu extrahieren. Schließlich setzt es alles wieder zusammen.

# 6 ist eine Basislösung, die einen Vektor mit einem Element pro Zeile von df erstellt, der die Anzahl der Zeilen (einschließlich der aktuellen Zeile) mit der ID in dieser Zeile enthält. Verwenden Sie dann subset, um df auf die Zeilen zu reduzieren, für die dieser Vektor gleich 4 ist.

# 7 ist eine Basislösung, die df in eine Liste von Datenrahmen aufteilt, einen pro ID, und dann Reduce verwendet, um darüber zu iterieren und den aktuellen Datenrahmen an das anzuhängen, was wir bisher haben, falls vorhanden 4 Reihen oder einfach behalten, was wir bisher haben, wenn nicht.

# 1
library(sqldf)
sqldf("with df0 as (
  select ID from df group by ID having count(*) = 4
)
select * from df join df0 using (ID)")

# 2
library(dplyr)
df %>% group_by(ID) %>% filter(n() == 4) %>% ungroup

# 3 
library(data.table)
as.data.table(df)[, if (nrow(.SD) == 4) .SD, by = ID]

# 4
library(zoo)
z <- read.zoo(df, split = "Quarter")
df2 <- fortify.zoo(na.omit(z), melt = TRUE, names = names(df)[c(1, 3:2)])
df2 <- df2[order(df2$ID, df2$Quarter), ]

# 5
do.call("rbind", Filter(function(x) nrow(x) == 4, split(df, df$ID)))

# 6
subset(df, ave(ID, ID, FUN = length) == 4)

# 7
Reduce(function(x, y) if (nrow(y) == 4) rbind(x, y) else x, split(df, df$ID))
3
G. Grothendieck 17 Apr. 2018 im 11:25

Hier ist eine andere base R Methode, die table, rowSums und %in% verwendet. Wir erhalten die Häufigkeit der Spalten 'ID' und 'Quarter' mit table, konvertieren sie in logische matrix, wobei 0 Werte TRUE sind und alle anderen FALSE (!table(...)) die Zeilenreihenfolge summe (rowSums), konvertiere in logisches vector, erhalte die names der Elemente, die WAHR sind, und erstelle einen Vergleich mit der ID unter Verwendung von %in% bis subset der Datensatz

subset(df, ID %in% names(which(!rowSums(!table(df[c(1,3)])))))
#   ID Score Quarter
#1   1    20      Q1
#2   1    22      Q2
#3   1    34      Q3
#4   1    56      Q4
#7   3    56      Q1
#8   3    43      Q2
#9   3    45      Q3
#10  3    33      Q4
2
akrun 17 Apr. 2018 im 02:40

Ich habe gerade herausgefunden, dass ich das auch kann:

df[df$ID %in% names(table(df$ID))[table(df$ID)==4],]

Das gewünschte Ergebnis wird erzielt, wenn nur die Anzahl der IDs verwendet werden

1
Jake 17 Apr. 2018 im 03:06