Para cada linha em um dataframe R

173

Eu tenho um quadro de dados e, para cada linha desse quadro, preciso fazer algumas pesquisas complicadas e anexar alguns dados a um arquivo.

O dataFrame contém resultados científicos para poços selecionados de placas de 96 poços usados ​​em pesquisas biológicas, então eu quero fazer 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)
}

No meu mundo processual, eu faria algo como:

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

Qual é a "maneira R" de fazer isso?

Carl Coryell-Martin
fonte
Qual é a sua pergunta aqui? Um data.frame é um objeto bidimensional e o loop pelas linhas é uma maneira perfeitamente normal de fazer as coisas, pois as linhas são geralmente conjuntos de 'observações' das 'variáveis' em cada coluna.
Dirk Eddelbuettel 9/11/2009
16
o que acabo fazendo é: for (índice em 1: nrow (dataFrame)) {row = dataFrame [index,]; # faça coisas com a linha} que nunca me pareceram muito bonitas.
9139 Carl Coryell-Martin
1
GetWellID chama um banco de dados ou algo assim? Caso contrário, Jonathan provavelmente está certo e você pode vetorizar isso.
217 Shane

Respostas:

103

Você pode tentar isso usando a apply()função

> 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
fonte
76
Tenha cuidado, pois o quadro de dados é convertido em uma matriz e o resultado final ( x) é um vetor. É por isso que o exemplo acima deve usar índices numéricos; a abordagem by () fornece um data.frame, que torna seu código mais robusto.
Darren Cozinhe
não funcionou para mim. A função de aplicação tratou cada x dado a f como um valor de caractere e não uma linha.
Zahy
3
Observe também que você pode consultar as colunas por nome. Então: wellName <- x[1]também poderia ser wellName <- x["name"].
Founddrama
1
Quando Darren mencionou robusto, ele quis dizer algo como mudar as ordens das colunas. Essa resposta não funcionaria, enquanto a que estava com () ainda funcionaria.
HelloWorld
120

Você pode usar a by()função:

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

Mas iterar sobre as linhas diretamente assim raramente é o que você deseja; você deve tentar vetorizar. Posso perguntar o que o trabalho real no loop está fazendo?

Jonathan Chang
fonte
5
isso não vai funcionar bem se o quadro de dados tem 0 linhas, porque 1:0não está vazio
sds
10
A correção fácil para o caso de 0 linhas é usar seq_len () , inserir seq_len(nrow(dataFrame))no lugar de 1:nrow(dataFrame).
Jim
13
Como você implementa (linha)? É a coluna $ dataframe? dataframe [somevariableNamehere]? Como você realmente diz que é uma briga? O pseudocódigo "função (linha) dostuff" como isso seria realmente?
Uh_big_mike_boi
1
@ Mike, altere dostuffesta resposta para str(row) Você verá várias linhas impressas no console, começando com "'data.frame': 1 obs de x variáveis". Mas tenha cuidado, alterar dostuffpara rownão retorna um objeto data.frame para a função externa como um todo. Em vez disso, ele retorna uma lista de quadros de dados de uma linha.
Pwilcox
91

Primeiro, o argumento de Jonathan sobre vetorização está correto. Se sua função getWellID () for vetorizada, você poderá pular o loop e usar cat ou write.csv:

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

Se getWellID () não for vetorizado, a recomendação de Jonathan de usar byou a sugestão de knguyen de applydevem funcionar.

Caso contrário, se você realmente quiser usar for, poderá fazer algo assim:

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

Você também pode tentar usar o foreachpacote, embora exija que você se familiarize com essa sintaxe. Aqui está um exemplo simples:

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

Uma opção final é usar uma função fora do plyrpacote; nesse caso, a convenção será muito semelhante à função aplicar.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })
Shane
fonte
Shane, obrigado. Não sei como escrever um getWellID vetorizado. O que preciso fazer agora é cavar uma lista existente de listas para procurá-la ou retirá-la de um banco de dados.
Carl Coryell-Martin
Sinta-se à vontade para postar a pergunta getWellID (ou seja, essa função pode ser vetorizada?) Separadamente e tenho certeza de que eu (ou alguém) responderá.
Shane
2
Mesmo que o getWellID não seja vetorizado, acho que você deve usar esta solução e substituir o getWellId por mapply(getWellId, well$name, well$plate).
Jonathan Chang
Mesmo se você o extrair de um banco de dados, poderá extraí-los todos de uma vez e filtrar o resultado em R; isso será mais rápido que uma função iterativa.
2111 Shane
+1 para foreach- eu vou usar esse inferno.
precisa saber é o seguinte
20

Eu acho que a melhor maneira de fazer isso com o R básico é:

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

A vantagem sobre a for( i in 1:nrow(df))abordagem é que você não terá problemas se dfestiver vazio e nrow(df)=0.

Funkwecker
fonte
17

Eu uso esta função utilitário simples:

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

Ou uma forma mais rápida e menos clara:

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

Esta função apenas divide um data.frame em uma lista de linhas. Então você pode criar um "for" normal nesta 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)
}        

Seu código da pergunta funcionará com uma modificação 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
fonte
É mais rápido acessar uma lista direta do que um data.frame.
Ł Łaniewski-Wołłk
1
Acabei de perceber que é ainda mais rápido fazer a mesma coisa com dupla dobragem: linhas = função (x) lapply (seq_len (nrow (x)), função (i) lapply (x, função (c) c [i]))
Ł Iewaniewski-Wołłk
Portanto, o interno lapplyitera sobre as colunas de todo o conjunto de dados x, fornecendo o nome a cada coluna ce, em seguida, extraindo a iquinta entrada desse vetor de coluna. Isso está correto?
Aaron McDaid
Muito agradável! No meu caso, eu tinha que converter de valores "factor" para o valor subjacente: wellName <- as.character(well$name).
Steve Pitchers
9

Fiquei curioso sobre o desempenho temporal das opções não vetorizadas. Para esse propósito, usei a função 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)
}

e um quadro de dados como o do seu exemplo:

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 )

Eu incluí duas funções vetorizadas (com certeza, mais rápidas que as outras), a fim de comparar a abordagem cat () com uma abordagem 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)

A imagem resultante mostra que apply fornece o melhor desempenho para uma versão não vetorizada, enquanto write.table () parece ter um desempenho superior a cat (). ForEachRunningTime

Ferran E
fonte
6

Você pode usar a by_rowfunção do pacote purrrlyrpara isso:

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 padrão, o valor retornado de myfné colocado em uma nova coluna da lista no df chamado .out.

Se esta é a única saída que você deseja, você pode escrever purrrlyr::by_row(df, myfn)$.out

RobinL
fonte
2

Bem, desde que você pediu R equivalente a outros idiomas, tentei fazer isso. Parece funcionar, embora eu realmente não tenha examinado qual técnica é mais eficiente em 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

Porém, para as colunas categóricas, você buscaria um Quadro de Dados que você poderia criar usando as.character (), se necessário.

Amogh Borkar
fonte