2009-11-09 23 views
129

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

La trama de datos contiene los resultados científicos para los pozos seleccionados de placas de 96 pocillos utilizados en la 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, que 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" de hacer esto?

+0

¿Cuál es su pregunta aquí? Un data.frame es un objeto bidimensional y el bucle sobre las filas es una forma perfectamente normal de hacer las cosas, ya que las filas son comúnmente conjuntos de 'observaciones' de las 'variables' en cada columna. –

+12

lo que termino haciendo es: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # hacer cosas con la fila} que nunca me pareció muy bonita. –

+1

¿GetWellID llama a una base de datos o algo? De lo contrario, Jonathan probablemente tenga razón y podrías vectorizar esto. – Shane

Respuesta

91

Usted puede probar esto, usando la función apply()

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

Tenga cuidado, ya que el dataframe es convertido en una matriz, y con lo que termina ('x') es un vector. Es por eso que el ejemplo anterior tiene que usar índices numéricos, el método by() le da un data.frame, que hace que su código sea más robusto –

+0

@Darren + 10 para indicarlo. – 2sb

+0

no funcionó para mí. La función de aplicar trató cada x dada a f como un valor de carácter y no como una fila. – Zahy

90

Usted puede utilizar la función de by():

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

Pero iteración en las filas directamente como esto rara vez es lo que quiere; deberías tratar de vectorizar en su lugar. ¿Puedo preguntar qué está haciendo el trabajo real en el ciclo?

+0

pregunta actualizada con más información. ¡Gracias! –

+2

esto no funcionará bien si el marco de datos tiene 0 filas porque '1: 0' no está vacío – sds

+3

Solución fácil para el caso de 0 filas es usar [seq_len()] (http://stat.ethz.ch/R -manual/R-devel/library/base/html/seq.html), inserte 'seq_len (nrow (dataFrame))' en lugar de '1: nrow (dataFrame)'. – Jim

69

En primer lugar, el punto de Jonathan sobre la vectorización es correcta. Si se vectorizado su función getWellID(), entonces puede saltarse el bucle y sólo tiene que utilizar gato o write.csv:

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

Si getWellID() no se vectorizado, entonces la recomendación de Jonathan de utilizar by o de knguyen sugerencia de apply debería funcionar.

De lo contrario, si realmente desea utilizar for, se puede hacer algo como esto:

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

También puede tratar de usar el paquete foreach, aunque requiere que se familiarice con la sintaxis. Aquí está un ejemplo sencillo:

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

Una última opción es utilizar una función fuera del paquete plyr, 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 }) 
+0

Shane, gracias. Yo ' No estoy seguro de cómo escribir un getWellID vectorizado. Lo que necesito hacer ahora mismo es profundizar en una lista existente de listas para buscarla o sacarla de una base de datos. –

+0

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

3

Bueno, ya que pidió R equivalente a otros idiomas, traté de hacer esto. Parece que funciona aunque no he mirado realmente cuál es la técnica 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 

Para las columnas categóricas sin embargo, sería que buscar una trama de datos que se podía usar encasillado as.character() si necesario.

8

Tenía curiosidad sobre el rendimiento en el tiempo de las opciones no vectorizadas. Para ello, he utilizado 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 una trama de datos como la 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) 

que incluye dos funciones vectorizados (con seguridad más rápido que los demás) con el fin de comparar el enfoque de gato() con un write.table() ... uno

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) 

los espectáculos de imágenes resultantes que se aplican ofrece el mejor rendimiento para un no-VEC versión torizada, mientras que write.table() parece superar a cat(). ForEachRunningTime

13

puedo utilizar 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, menos claro:

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

Esta función simplemente divide un hoja.de.datos a una lista de filas. A continuación, puede hacer una normal "para" más de 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 cuestión trabajará 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) 
} 
+1

¿Por qué 'unclass'? –

+0

Es más rápido acceder a una lista directa que a un data.frame. –

+1

Acabo de darme cuenta de que es aún más rápido hacer lo mismo con lapply doble: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i])) –

2

Usted puede utilizar la función by_row del paquete purrrlyr de 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 por myfn se pone en una nueva list column en el df llamados .out.

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

3

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

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

La ventaja sobre el de (i en 1 : nrow (df)) - el enfoque es que no te metes en problemas si df está vacío y nrow (df) = 0.

Cuestiones relacionadas