Für jede Zeile in einem R-Datenrahmen

173

Ich habe einen Datenrahmen, und für jede Zeile in diesem Datenrahmen muss ich einige komplizierte Suchvorgänge durchführen und einige Daten an eine Datei anhängen.

Der dataFrame enthält wissenschaftliche Ergebnisse für ausgewählte Wells aus 96 Well-Platten, die in der biologischen Forschung verwendet werden. Ich möchte also Folgendes tun:

for (well in dataFrame) {
  wellName <- well$name    # string like "H1"
  plateName <- well$plate  # string like "plate67"
  wellID <- getWellID(wellName, plateName)
  cat(paste(wellID, well$value1, well$value2, sep=","), file=outputFile)
}

In meiner prozeduralen Welt würde ich so etwas tun:

for (row in dataFrame) {
    #look up stuff using data from the row
    #write stuff to the file
}

Was ist der "R-Weg", um dies zu tun?

Carl Coryell-Martin
quelle
Was ist deine Frage hier? Ein data.frame ist ein zweidimensionales Objekt, und das Schleifen über die Zeilen ist eine völlig normale Vorgehensweise, da Zeilen üblicherweise Sätze von 'Beobachtungen' der 'Variablen' in jeder Spalte sind.
Dirk Eddelbuettel
16
Am Ende mache ich: for (Index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # mach Sachen mit der Reihe}, die mir nie sehr hübsch erschienen.
Carl Coryell-Martin
1
Ruft getWellID eine Datenbank auf oder so? Ansonsten hat Jonathan wahrscheinlich recht und Sie könnten dies vektorisieren.
Shane

Antworten:

103

Sie können dies mit der apply()Funktion versuchen

> d
  name plate value1 value2
1    A    P1      1    100
2    B    P2      2    200
3    C    P3      3    300

> f <- function(x, output) {
 wellName <- x[1]
 plateName <- x[2]
 wellID <- 1
 print(paste(wellID, x[3], x[4], sep=","))
 cat(paste(wellID, x[3], x[4], sep=","), file= output, append = T, fill = T)
}

> apply(d, 1, f, output = 'outputfile')
knguyen
quelle
76
Seien Sie vorsichtig, da der Datenrahmen in eine Matrix konvertiert wird und Sie am Ende ( x) einen Vektor haben. Aus diesem Grund müssen im obigen Beispiel numerische Indizes verwendet werden. Mit dem by () -Ansatz erhalten Sie einen data.frame, der Ihren Code robuster macht.
Darren Cook
hat bei mir nicht funktioniert. Die Apply-Funktion behandelte jedes x, das f gegeben wurde, als Zeichenwert und nicht als Zeile.
Zahy
3
Beachten Sie auch, dass Sie die Spalten nach Namen referenzieren können. Also: wellName <- x[1]könnte auch sein wellName <- x["name"].
Founddrama
1
Als Darren Robust erwähnte, meinte er so etwas wie das Verschieben der Ordnungen der Säulen. Diese Antwort würde nicht funktionieren, während die mit by () noch funktionieren würde.
HelloWorld
120

Sie können die by()Funktion verwenden:

by(dataFrame, 1:nrow(dataFrame), function(row) dostuff)

Aber das direkte Durchlaufen der Zeilen ist selten das, was Sie wollen. Sie sollten stattdessen versuchen, zu vektorisieren. Kann ich fragen, was die eigentliche Arbeit in der Schleife macht?

Jonathan Chang
quelle
5
Dies wird nicht gut funktionieren, wenn der 1:0
Datenrahmen
10
Die einfache Lösung für den Fall mit 0 Zeilen besteht darin, seq_len () zu verwenden und seq_len(nrow(dataFrame))anstelle von einzufügen 1:nrow(dataFrame).
Jim
13
Wie implementieren Sie eigentlich (Zeile)? Ist es Datenrahmen $ Spalte? Datenrahmen [somevariableNamehere]? Wie sagt man eigentlich, dass es eine Reihe ist? Der Pseudocode "function (row) dostuff" wie würde das eigentlich aussehen?
uh_big_mike_boi
1
@Mike, ändern Sie dostuffdiese Antwort in str(row) Sie sehen mehrere Zeilen in der Konsole, beginnend mit "'data.frame': 1 obs von x Variablen". Seien Sie jedoch vorsichtig, wenn Sie zu ändern dostuff, rowwird kein data.frame-Objekt für die äußere Funktion als Ganzes zurückgegeben. Stattdessen wird eine Liste von einzeiligen Datenrahmen zurückgegeben.
Pwilcox
91

Erstens ist Jonathans Argument zur Vektorisierung richtig. Wenn Ihre Funktion getWellID () vektorisiert ist, können Sie die Schleife überspringen und einfach cat oder write.csv verwenden:

write.csv(data.frame(wellid=getWellID(well$name, well$plate), 
         value1=well$value1, value2=well$value2), file=outputFile)

Wenn getWellID () nicht vektorisiert ist, sollte Jonathans Empfehlung zur Verwendung byoder Knguyens Vorschlag von applyfunktionieren.

Andernfalls können Sie Folgendes fortun , wenn Sie es wirklich verwenden möchten :

for(i in 1:nrow(dataFrame)) {
    row <- dataFrame[i,]
    # do stuff with row
}

Sie können auch versuchen, das foreachPaket zu verwenden, obwohl Sie sich mit dieser Syntax vertraut machen müssen. Hier ist ein einfaches Beispiel:

library(foreach)
d <- data.frame(x=1:10, y=rnorm(10))
s <- foreach(d=iter(d, by='row'), .combine=rbind) %dopar% d

Eine letzte Option ist die Verwendung einer Funktion aus dem plyrPaket. In diesem Fall ist die Konvention der Apply-Funktion sehr ähnlich.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })
Shane
quelle
Shane, danke. Ich bin nicht sicher, wie ich eine vektorisierte getWellID schreiben soll. Was ich jetzt tun muss, ist in eine vorhandene Liste von Listen zu graben, um sie nachzuschlagen oder aus einer Datenbank zu ziehen.
Carl Coryell-Martin
Fühlen Sie sich frei, die getWellID-Frage (dh kann diese Funktion vektorisiert werden?) Separat zu posten, und ich bin sicher, dass ich (oder jemand anderes) sie beantworten werde.
Shane
2
Auch wenn getWellID nicht vektorisiert ist, sollten Sie diese Lösung verwenden und getWellId durch ersetzen mapply(getWellId, well$name, well$plate).
Jonathan Chang
Selbst wenn Sie es aus einer Datenbank abrufen, können Sie alle auf einmal abrufen und dann das Ergebnis in R filtern. das ist schneller als eine iterative Funktion.
Shane
+1 für foreach- Ich werde die Hölle daraus machen.
Josh Bode
20

Ich denke, der beste Weg, dies mit Basic R zu tun, ist:

for( i in rownames(df) )
   print(df[i, "column1"])

Der Vorteil gegenüber dem for( i in 1:nrow(df))Ansatz ist, dass Sie keine Probleme bekommen, wenn dfleer und nrow(df)=0.

Funkwecker
quelle
17

Ich benutze diese einfache Utility-Funktion:

rows = function(tab) lapply(
  seq_len(nrow(tab)),
  function(i) unclass(tab[i,,drop=F])
)

Oder eine schnellere, weniger klare Form:

rows = function(x) lapply(seq_len(nrow(x)), function(i) lapply(x,"[",i))

Diese Funktion teilt nur einen data.frame in eine Liste von Zeilen auf. Dann können Sie ein normales "für" über diese Liste setzen:

tab = data.frame(x = 1:3, y=2:4, z=3:5)
for (A in rows(tab)) {
    print(A$x + A$y * A$z)
}        

Ihr Code aus der Frage funktioniert mit einer minimalen Änderung:

for (well in rows(dataFrame)) {
  wellName <- well$name    # string like "H1"
  plateName <- well$plate  # string like "plate67"
  wellID <- getWellID(wellName, plateName)
  cat(paste(wellID, well$value1, well$value2, sep=","), file=outputFile)
}
Ł Łaniewski-Wołłk
quelle
Der Zugriff auf eine gerade Liste ist schneller als auf einen data.frame.
Ł Łaniewski-Wołłk
1
Ich habe gerade festgestellt, dass es noch schneller ist, dasselbe mit Double Lapply zu machen: Zeilen = Funktion (x) Lapply (seq_len (nrow (x)), Funktion (i) Lapply (x, Funktion (c) c [i]))
Ł Łaniewski-Wołłk
Das Innere lapplyiteriert also über die Spalten des gesamten Datensatzes x, gibt jeder Spalte den Namen cund extrahiert dann den idritten Eintrag aus diesem Spaltenvektor. Ist das richtig?
Aaron McDaid
Sehr schön! In meinem Fall musste ich von "Faktor" -Werten in den zugrunde liegenden Wert umrechnen : wellName <- as.character(well$name).
Steve Pitchers
9

Ich war neugierig auf die zeitliche Leistung der nicht vektorisierten Optionen. Zu diesem Zweck habe ich die von knguyen definierte Funktion f verwendet

f <- function(x, output) {
  wellName <- x[1]
  plateName <- x[2]
  wellID <- 1
  print(paste(wellID, x[3], x[4], sep=","))
  cat(paste(wellID, x[3], x[4], sep=","), file= output, append = T, fill = T)
}

und ein Datenrahmen wie der in seinem Beispiel:

n = 100; #number of rows for the data frame
d <- data.frame( name = LETTERS[ sample.int( 25, n, replace=T ) ],
                  plate = paste0( "P", 1:n ),
                  value1 = 1:n,
                  value2 = (1:n)*10 )

Ich habe zwei vektorisierte Funktionen eingefügt (sicher schneller als die anderen), um den cat () -Ansatz mit einem write.table () zu vergleichen ...

library("ggplot2")
library( "microbenchmark" )
library( foreach )
library( iterators )

tm <- microbenchmark(S1 =
                       apply(d, 1, f, output = 'outputfile1'),
                     S2 = 
                       for(i in 1:nrow(d)) {
                         row <- d[i,]
                         # do stuff with row
                         f(row, 'outputfile2')
                       },
                     S3 = 
                       foreach(d1=iter(d, by='row'), .combine=rbind) %dopar% f(d1,"outputfile3"),
                     S4= {
                       print( paste(wellID=rep(1,n), d[,3], d[,4], sep=",") )
                       cat( paste(wellID=rep(1,n), d[,3], d[,4], sep=","), file= 'outputfile4', sep='\n',append=T, fill = F)                           
                     },
                     S5 = {
                       print( (paste(wellID=rep(1,n), d[,3], d[,4], sep=",")) )
                       write.table(data.frame(rep(1,n), d[,3], d[,4]), file='outputfile5', row.names=F, col.names=F, sep=",", append=T )
                     },
                     times=100L)
autoplot(tm)

Das resultierende Bild zeigt, dass apply die beste Leistung für eine nicht vektorisierte Version bietet, während write.table () cat () zu übertreffen scheint. ForEachRunningTime

Ferran E.
quelle
6

Sie können die by_rowFunktion aus dem Paket purrrlyrdafür verwenden:

myfn <- function(row) {
  #row is a tibble with one row, and the same 
  #number of columns as the original df
  #If you'd rather it be a list, you can use as.list(row)
}

purrrlyr::by_row(df, myfn)

Standardmäßig wird der zurückgegebene Wert von myfnin eine neue Listenspalte in der aufgerufenen df eingefügt .out.

Wenn dies die einzige Ausgabe ist, die Sie wünschen, können Sie schreiben purrrlyr::by_row(df, myfn)$.out

RobinL
quelle
2

Nun, da Sie nach R gefragt haben, das anderen Sprachen entspricht, habe ich versucht, dies zu tun. Scheint zu funktionieren, obwohl ich nicht wirklich untersucht habe, welche Technik in R effizienter ist.

> myDf <- head(iris)
> myDf
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
> nRowsDf <- nrow(myDf)
> for(i in 1:nRowsDf){
+ print(myDf[i,4])
+ }
[1] 0.2
[1] 0.2
[1] 0.2
[1] 0.2
[1] 0.2
[1] 0.4

Für die kategorialen Spalten wird jedoch ein Datenrahmen abgerufen, den Sie bei Bedarf mit as.character () typisieren können.

Amogh Borkar
quelle