Voor elke rij in een R-dataframe

Ik heb een dataframe en voor elke rij in dat dataframe moet ik een aantal ingewikkelde opzoekingen doen en wat gegevens aan een bestand toevoegen.

Het dataFrame bevat wetenschappelijke resultaten voor geselecteerde putjes van platen met 96 putjes die worden gebruikt in biologisch onderzoek, dus ik wil iets doen als:

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 mijn procedurele wereld zou ik zoiets doen als:

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

Wat is de “R-manier” om dit te doen?


Antwoord 1, autoriteit 100%

U kunt de by()functie:

by(dataFrame, seq_len(nrow(dataFrame)), function(row) dostuff)

Maar op deze manier rechtstreeks over de rijen herhalen is zelden wat u wilt; je zou in plaats daarvan moeten proberen te vectoriseren. Mag ik vragen wat het eigenlijke werk in de lus doet?


Antwoord 2, autoriteit 86%

Je kunt dit proberen met apply()functie

> 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')

Antwoord 3, autoriteit 82%

Ten eerste is Jonathans punt over vectoriseren correct. Als uw getWellID()-functie gevectoriseerd is, kunt u de lus overslaan en gewoon cat of write.csv gebruiken:

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

Als getWellID() niet gevectoriseerd is, zou Jonathans aanbeveling om byte gebruiken of knguyens suggestie van applymoeten werken.

Anders, als je echt forwilt gebruiken, kun je zoiets als dit doen:

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

U kunt ook proberen het pakket foreachte gebruiken, hoewel u hiervoor vertrouwd moet raken met die syntaxis. Hier is een eenvoudig voorbeeld:

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

Een laatste optie is om een ​​functie uit het pakket plyrte gebruiken, in welk geval de conventie erg lijkt op de functie Apply.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })

Antwoord 4, autoriteit 22%

Ik denk dat de beste manier om dit te doen met basis R is:

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

Het voordeel ten opzichte van de for( i in 1:nrow(df))-aanpak is dat je niet in de problemen komt als dfleeg is en nrow(df)=0.


Antwoord 5, autoriteit 16%

Ik gebruik deze eenvoudige hulpprogramma-functie:

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

Of een snellere, minder duidelijke vorm:

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

Deze functie splitst gewoon een gegevens. Van een lijst met rijen. Dan kunt u een normaal “voor” maken over deze lijst:

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

Uw code uit de vraag zal werken met een minimale wijziging:

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)
}

6, Autoriteit 9%

Ik was nieuwsgierig naar de tijdprestaties van de niet-vectoriseerde opties.
Voor dit doel heb ik de functie F gedefinieerd door Knguyen

gebruikt

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)
}

en een dataframe zoals die in zijn voorbeeld:

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 )

Ik heb twee vectorised-functies (zeker sneller dan de anderen) opgenomen om de Cat () aanpak met een schrijf-tabel () één …

te vergelijken

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)

De resulterende beeld toont aan die van toepassing zijn, geeft de beste prestaties voor een niet-vectoriseerde versie, terwijl schrijf. Table () lijkt te overtreffen in de kat ().


7, Autoriteit 2%

Nou, omdat je om r equivalent bent aan andere talen, probeerde ik dit te doen. Lijkt te werken, hoewel ik niet echt heb gekeken naar welke techniek efficiënter is in r.

> 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

Voor de categorische kolommen zou het u een gegevensframe ophalen dat u kunt typeren met behulp van as.character () indien nodig.


8

U kunt iets doen voor een lijstobject,

data("mtcars")
rownames(mtcars)
data <- list(mtcars ,mtcars, mtcars, mtcars);data
out1 <- NULL 
for(i in seq_along(data)) { 
  out1[[i]] <- data[[i]][rownames(data[[i]]) != "Volvo 142E", ] } 
out1

of een gegevensframe,

data("mtcars")
df <- mtcars
out1 <- NULL 
for(i in 1:nrow(df)) {
  row <- rownames(df[i,])
  # do stuff with row
  out1 <- df[rownames(df) != "Volvo 142E",]
}
out1 

Other episodes