web-dev-qa-db-fra.com

Pour chaque ligne dans un cadre de données R

J'ai une base de données et, pour chaque ligne de cette base de données, je dois effectuer des recherches compliquées et ajouter des données à un fichier.

Le dataFrame contient des résultats scientifiques pour des puits sélectionnés à partir de plaques de 96 puits utilisées en recherche biologique. Je souhaite donc faire quelque chose comme:

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

Dans mon monde procédural, je ferais quelque chose comme:

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

Quelle est la "manière R" de faire cela?

160

Vous pouvez essayer ceci en utilisant apply() function

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

Vous pouvez utiliser la fonction by() :

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

Mais itérer directement sur les lignes comme ceci est rarement ce que vous voulez; vous devriez plutôt essayer de vectoriser. Puis-je demander ce que le travail réel dans la boucle fait?

111
Jonathan Chang

Premièrement, l'argument de Jonathan sur la vectorisation est correct. Si votre fonction getWellID () est vectorisée, vous pouvez ignorer la boucle et utiliser simplement cat ou write.csv:

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

Si getWellID () n'est pas vectorisé, la recommandation de Jonathan d'utiliser by ou la suggestion de knguyen de apply devrait fonctionner.

Sinon, si vous voulez vraiment utiliser for, vous pouvez faire quelque chose comme ceci:

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

Vous pouvez également essayer d'utiliser le paquetage foreach, bien qu'il vous oblige à vous familiariser avec cette syntaxe. Voici un exemple simple:

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

Une dernière option consiste à utiliser une fonction hors du package plyr, auquel cas la convention sera très similaire à la fonction apply.

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

J'utilise cette fonction utilitaire simple:

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

Ou une forme plus rapide et moins claire:

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

Cette fonction divise simplement un data.frame en une liste de lignes. Ensuite, vous pouvez créer un "pour" normal sur cette liste:

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

Votre code de la question fonctionnera avec une modification minimale:

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

Je pense que la meilleure façon de faire cela avec Basic R est:

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

L'avantage par rapport à l'approche for( i in 1:nrow(df))- est que vous ne rencontrez aucun problème si df est vide et nrow(df)=0.

13
Julian

J'étais curieux de la performance temporelle des options non vectorisées. Pour cela, j'ai utilisé la fonction f définie par knguyen

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

et un dataframe comme celui de son exemple:

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 )

J'ai inclus deux fonctions vectorisées (sûrement plus rapides que les autres) afin de comparer l'approche cat () à une write.table () one ...

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)

L'image résultante montre que apply donne les meilleures performances pour une version non vectorisée, alors que write.table () semble dépasser les performances de cat (). ForEachRunningTime

8
Ferran E

Vous pouvez utiliser la fonction by_row du package purrrlyr pour cela:

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)

Par défaut, la valeur renvoyée par myfn est placée dans une nouvelle colonne de liste dans le df appelée .out.

Si c’est la seule sortie que vous désirez, vous pouvez écrire purrrlyr::by_row(df, myfn)$.out

6
RobinL

Eh bien, puisque vous avez demandé R équivalent à d’autres langues, j’ai essayé de le faire. Cela semble fonctionner même si je n'ai pas vraiment cherché quelle technique est la plus efficace dans 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

Cependant, pour les colonnes catégorielles, il vous chercherait un cadre de données que vous pourriez transtyper en utilisant as.character () si nécessaire.

3
Amogh Borkar