Pour chaque ligne d'une trame de données R

173

J'ai un dataframe, et pour chaque ligne de ce dataframe, je dois faire 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 dans la recherche biologique, je veux 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?

Carl Coryell-Martin
la source
Quelle est votre question ici? Un data.frame est un objet bidimensionnel et le bouclage sur les lignes est une manière parfaitement normale de faire les choses car les lignes sont généralement des ensembles d '«observations» des «variables» dans chaque colonne.
Dirk Eddelbuettel
16
ce que je finis par faire est: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # faire des trucs avec la ligne} qui ne m'ont jamais paru très jolie.
Carl Coryell-Martin
1
GetWellID appelle-t-il une base de données ou quoi que ce soit? Sinon, Jonathan a probablement raison et vous pourriez vectoriser cela.
Shane

Réponses:

103

Vous pouvez essayer ceci, en utilisant la apply()fonction

> 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
la source
76
Soyez prudent, car le dataframe est converti en matrice et ce que vous obtenez ( x) est un vecteur. C'est pourquoi l'exemple ci-dessus doit utiliser des index numériques; l'approche by () vous donne un data.frame, ce qui rend votre code plus robuste.
Darren Cook
N'a pas travaillé pour moi. La fonction apply traitait chaque x donné à f comme une valeur de caractère et non comme une ligne.
Zahy
3
Notez également que vous pouvez faire référence aux colonnes par leur nom. Donc: wellName <- x[1]pourrait aussi être wellName <- x["name"].
founddrama
1
Quand Darren a mentionné robuste, il voulait dire quelque chose comme changer les ordres des colonnes. Cette réponse ne fonctionnerait pas alors que celle avec by () fonctionnerait toujours.
HelloWorld le
120

Vous pouvez utiliser la by()fonction:

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 essayer de vectoriser à la place. Puis-je demander ce que fait le travail réel de la boucle?

Jonathan Chang
la source
5
cela ne fonctionnera pas bien si le bloc de données a 0 lignes car il 1:0n'est pas vide
sds
10
La solution facile pour le cas de la ligne 0 est d'utiliser seq_len () , insérer seq_len(nrow(dataFrame))à la place de 1:nrow(dataFrame).
Jim
13
Comment implémentez-vous réellement (ligne)? S'agit-il de dataframe $ column? dataframe [somevariableNamehere]? Comment dites-vous réellement que c'est une ligne. Le pseudocode «fonction (ligne) dostuff» à quoi cela ressemblerait-il réellement?
uh_big_mike_boi
1
@Mike, changez dostuffcette réponse en str(row) Vous verrez plusieurs lignes imprimées dans la console commençant par "'data.frame': 1 obs de x variables." Mais attention, changer dostuffen rowne renvoie pas un objet data.frame pour la fonction externe dans son ensemble. Au lieu de cela, il renvoie une liste de blocs de données d'une ligne.
pwilcox
91

Premièrement, l'argument de Jonathan concernant 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 byou la suggestion de knguyen de applydevrait 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 foreachpackage, 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 plyrpackage, auquel cas la convention sera très similaire à la fonction apply.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })
Shane
la source
Shane, merci. Je ne sais pas comment écrire un getWellID vectorisé. Ce que je dois faire maintenant est de fouiller dans une liste existante de listes pour la rechercher ou la sortir d'une base de données.
Carl Coryell-Martin
N'hésitez pas à poster la question getWellID (par exemple, cette fonction peut-elle être vectorisée?) Séparément, et je suis sûr que moi (ou quelqu'un d'autre) y répondrai.
Shane
2
Même si getWellID n'est pas vectorisé, je pense que vous devriez utiliser cette solution et remplacer getWellId par mapply(getWellId, well$name, well$plate).
Jonathan Chang
Même si vous le tirez d'une base de données, vous pouvez les extraire tous en même temps, puis filtrer le résultat dans R; ce sera plus rapide qu'une fonction itérative.
Shane
+1 pour foreach- Je vais utiliser l'enfer de celui-là.
Josh Bode
20

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

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

L'avantage par rapport à l' for( i in 1:nrow(df))approche-est que vous n'avez pas de problème si dfest vide et nrow(df)=0.

Funkwecker
la source
17

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 faire 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)
}
Ł Łaniewski-Wołłk
la source
Il est plus rapide d'accéder à une liste simple qu'à un data.frame.
Ł Łaniewski-Wołłk
1
Je viens de réaliser qu'il est encore plus rapide de faire la même chose avec double lapply: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Łaniewski-Wołłk
Ainsi, l'intérieur lapplyitère sur les colonnes de l'ensemble de données entier x, en donnant le nom à chaque colonne c, puis en extrayant la ie entrée de ce vecteur de colonne. Est-ce correct?
Aaron McDaid
Très agréable! Dans mon cas, je devais convertir des valeurs « facteur » à la valeur sous - jacente: wellName <- as.character(well$name).
Steve Pitchers
9

J'étais curieux de connaître les performances temporelles 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 (bien sûr plus rapides que les autres) afin de comparer l'approche cat () avec une approche write.table () ...

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 surpasser cat (). ForEachRunningTime

Ferran E
la source
6

Vous pouvez utiliser la by_rowfonction du package purrrlyrpour 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 myfnest placée dans une nouvelle colonne de liste dans le df appelé .out.

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

RobinL
la source
2

Eh bien, puisque vous avez demandé un R équivalent à d'autres langues, j'ai essayé de le faire. Cela semble fonctionner même si je n'ai pas vraiment regardé 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

Pour les colonnes catégoriques cependant, cela vous chercherait un Data Frame que vous pourriez taper en utilisant as.character () si nécessaire.

Amogh Borkar
la source