Para cada fila en un marco de datos R

173

Tengo un marco de datos, y para cada fila en ese marco de datos tengo que hacer algunas búsquedas complicadas y agregar algunos datos a un archivo.

El DataFrame contiene resultados científicos para pozos seleccionados de placas de 96 pocillos utilizados en investigación biológica, por lo que quiero hacer algo como:

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

En mi mundo procesal, haría algo como:

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

¿Cuál es la "forma R" para hacer esto?

Carl Coryell-Martin
fuente
¿Cuál es tu pregunta aquí? Un data.frame es un objeto bidimensional y recorrer las filas es una forma perfectamente normal de hacer las cosas, ya que las filas suelen ser conjuntos de 'observaciones' de las 'variables' en cada columna.
Dirk Eddelbuettel
16
lo que termino haciendo es: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # hacer cosas con la fila} que nunca me parecieron muy bonitas.
Carl Coryell-Martin
1
¿GetWellID llama a una base de datos o algo así? De lo contrario, Jonathan probablemente tenga razón y podría vectorizar esto.
Shane

Respuestas:

103

Puedes probar esto, usando la apply()función

> 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
fuente
76
Tenga cuidado, ya que el marco de datos se convierte en una matriz, y lo que termina con ( x) es un vector. Es por esto que el ejemplo anterior tiene que usar índices numéricos; el enfoque by () le brinda un marco data.frame, que hace que su código sea más robusto.
Darren Cook,
no funciono para mi La función de aplicación trata cada x dada a f como un valor de carácter y no como una fila.
Zahy
3
Tenga en cuenta también que puede referirse a las columnas por su nombre. Entonces: wellName <- x[1]también podría ser wellName <- x["name"].
founddrama
1
Cuando Darren mencionó robusto, se refería a algo como cambiar el orden de las columnas. Esta respuesta no funcionaría, mientras que la que tiene by () aún funcionaría.
HelloWorld
120

Puedes usar la by()función:

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

Pero iterar sobre las filas directamente así rara vez es lo que quieres; deberías tratar de vectorizar en su lugar. ¿Puedo preguntar qué está haciendo el trabajo real en el bucle?

Jonathan Chang
fuente
55
esto no funcionará bien si el marco de datos tiene 0 filas porque 1:0no está vacío
sds
10
La solución fácil para el caso de 0 filas es usar seq_len () , insertar seq_len(nrow(dataFrame))en lugar de 1:nrow(dataFrame).
Jim
13
¿Cómo se implementa realmente (fila)? ¿Es dataframe $ column? marco de datos [somevariableNamehere]? ¿Cómo se dice que es una fila? El pseudocódigo "función (fila) dostuff" ¿cómo se vería realmente?
uh_big_mike_boi
1
@ Mike, cambia dostuffesta respuesta a str(row) Verás varias líneas impresas en la consola que comienzan con "'data.frame': 1 obs de x variables". Pero tenga cuidado, cambiar dostuffa rowno devuelve un objeto data.frame para la función externa en su conjunto. En su lugar, devuelve una lista de marcos de datos de una fila.
pwilcox
91

Primero, el punto de Jonathan sobre la vectorización es correcto. Si su función getWellID () está vectorizada, puede omitir el ciclo y simplemente usar cat o write.csv:

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

Si getWellID () no está vectorizado, entonces la recomendación de Jonathan de usar byo la sugerencia de knguyen applydebería funcionar.

De lo contrario, si realmente quieres usar for, puedes hacer algo como esto:

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

También puede intentar usar el foreachpaquete, aunque requiere que se familiarice con esa sintaxis. Aquí hay un ejemplo simple:

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

Una opción final es usar una función fuera del plyrpaquete, en cuyo caso la convención será muy similar a la función de aplicación.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })
Shane
fuente
Shane, gracias. No estoy seguro de cómo escribir un getWellID vectorizado. Lo que necesito hacer ahora es buscar en una lista existente de listas para buscarla o sacarla de una base de datos.
Carl Coryell-Martin
Siéntase libre de publicar la pregunta getWellID (es decir, ¿se puede vectorizar esta función?) Por separado, y estoy seguro de que yo (u otra persona) la responderé.
Shane
2
Incluso si getWellID no está vectorizado, creo que debería ir con esta solución y reemplazar getWellId con mapply(getWellId, well$name, well$plate).
Jonathan Chang el
Incluso si lo extrae de una base de datos, puede extraerlos todos a la vez y luego filtrar el resultado en R; eso será más rápido que una función iterativa.
Shane
+1 para foreach- Voy a sacar el máximo provecho de eso.
Josh Bode
20

Creo que la mejor manera de hacer esto con R básica es:

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

La ventaja sobre el for( i in 1:nrow(df))enfoque es que no se mete en problemas si dfestá vacío y nrow(df)=0.

Funkwecker
fuente
17

Yo uso esta sencilla función de utilidad:

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

O una forma más rápida y menos clara:

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

Esta función simplemente divide un data.frame en una lista de filas. Entonces puede hacer un "for" normal sobre esta lista:

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

Su código de la pregunta funcionará con una modificación mínima:

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)
}
Ł iewaniewski-Wołłk
fuente
Es más rápido acceder a una lista directa que a un data.frame.
iew iewaniewski-Wołłk
1
Me acabo de dar cuenta de que es aún más rápido hacer lo mismo con doble vuelta: filas = función (x) lapply (seq_len (nrow (x)), función (i) lapply (x, función (c) c [i]))
Ł Iewaniewski-Wołłk
Entonces, el interno lapplyitera sobre las columnas de todo el conjunto de datos x, dando a cada columna el nombre cy luego extrayendo la ientrada th de ese vector de columna. ¿Es esto correcto?
Aaron McDaid
¡Muy agradable! En mi caso, tuve que convertir de valores "factor" en el valor subyacente: wellName <- as.character(well$name).
Steve Pitchers
9

Tenía curiosidad sobre el rendimiento en el tiempo de las opciones no vectorizadas. Para este propósito, he usado la función f definida por 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)
}

y un marco de datos como el de su ejemplo:

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 )

Incluí dos funciones vectorizadas (seguramente más rápido que las demás) para comparar el enfoque cat () con uno 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)

La imagen resultante muestra que apply ofrece el mejor rendimiento para una versión no vectorizada, mientras que write.table () parece superar a cat (). ForEachRunningTime

Ferran E
fuente
6

Puede usar la by_rowfunción del paquete purrrlyrpara esto:

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)

Por defecto, el valor devuelto desde myfnse coloca en una nueva columna de lista en el df llamado .out.

Si esta es la única salida que deseas, podrías escribir purrrlyr::by_row(df, myfn)$.out

RobinL
fuente
2

Bueno, como pediste R equivalente a otros idiomas, traté de hacer esto. Parece funcionar, aunque realmente no he visto qué técnica es más eficiente en 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

Sin embargo, para las columnas categóricas, obtendría un Marco de datos que podría escribir utilizando as.character () si es necesario.

Amogh Borkar
fuente