Per ogni riga in un frame di dati R.


173

Ho un frame di dati e per ogni riga in quel frame di dati devo fare alcune ricerche complicate e aggiungere alcuni dati a un file.

DataFrame contiene risultati scientifici per pozzetti selezionati da 96 piastre utilizzate nella ricerca biologica, quindi voglio fare qualcosa del tipo:

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

Nel mio mondo procedurale, farei qualcosa del tipo:

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

Qual è il "modo R" per fare questo?


Qual è la tua domanda qui? Un data.frame è un oggetto bidimensionale e il looping sopra le righe è un modo perfettamente normale di fare le cose poiché le righe sono comunemente insiemi di "osservazioni" delle "variabili" in ogni colonna.
Dirk Eddelbuettel,

16
quello che finisco per fare è: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # fai cose con la riga} che non mi sono mai sembrate molto carine.
Carl Coryell-Martin,

1
GetWellID chiama un database o altro? Altrimenti, probabilmente Jonathan ha ragione e potresti vettorializzare questo.
Shane,

Risposte:


103

Puoi provare questo, usando la apply()funzione

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

76
Fai attenzione, poiché il frame di dati viene convertito in una matrice e ciò che ottieni ( x) è un vettore. Questo è il motivo per cui l'esempio sopra deve usare indici numerici; l'approccio by () ti dà un data.frame, che rende il tuo codice più robusto.
Darren Cook,

Non ha funzionato per me. La funzione apply ha trattato ogni x dato a f come un valore di carattere e non una riga.
Zahy,

3
Nota anche che puoi fare riferimento alle colonne per nome. Quindi: wellName <- x[1]potrebbe anche essere wellName <- x["name"].
founddrama,

1
Quando Darren parlava di robustezza, intendeva dire qualcosa come spostare gli ordini delle colonne. Questa risposta non funzionerebbe mentre quella con by () funzionerebbe comunque.
HelloWorld

120

Puoi usare la by()funzione:

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

Ma scorrere le righe direttamente in questo modo è raramente ciò che si desidera; dovresti provare a vettorializzare invece. Posso chiedere cosa sta facendo il lavoro reale nel loop?


5
questo non funzionerà bene se il frame di dati ha 0 righe perché 1:0non è vuoto
sds

10
Una soluzione semplice per il caso della riga 0 consiste nell'utilizzare seq_len () , inserire seq_len(nrow(dataFrame))al posto di 1:nrow(dataFrame).
Jim,

13
Come si implementa effettivamente (riga)? È la colonna $ del frame di dati? dataframe [somevariableNamehere]? Come si dice in realtà è una riga. La pseudocodice "funzione (riga) dostuff" come sarebbe effettivamente?
uh_big_mike_boi

1
@ Mike, modifica dostuffquesta risposta in str(row) Vedrai più righe stampate nella console che iniziano con "'data.frame': 1 obs di x variabili". Ma fai attenzione, il passaggio dostuffa rownon restituisce un oggetto data.frame per la funzione esterna nel suo insieme. Restituisce invece un elenco di frame di dati di una riga.
pwilcox,

91

Innanzitutto, il punto di Jonathan sul vettorializzare è corretto. Se la tua funzione getWellID () è vettorializzata, puoi saltare il ciclo e usare semplicemente cat o write.csv:

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

Se getWellID () non è vettorializzato, allora la raccomandazione di Jonathan di usare byo il suggerimento di knguyen applydovrebbe funzionare.

Altrimenti, se vuoi davvero usarlo for, puoi fare qualcosa del genere:

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

Puoi anche provare a usare il foreachpacchetto, anche se ti richiede di familiarizzare con quella sintassi. Ecco un semplice esempio:

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

Un'ultima opzione è usare una funzione fuori dal plyrpacchetto, nel qual caso la convenzione sarà molto simile alla funzione apply.

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

Shane, grazie. Non sono sicuro di come scrivere un getWellID vettoriale. Quello che devo fare ora è scavare in un elenco esistente di elenchi per cercarlo o estrarlo da un database.
Carl Coryell-Martin,

Sentiti libero di pubblicare la domanda getWellID (cioè questa funzione può essere vettorializzata?) Separatamente, e sono sicuro che io (o qualcun altro) risponderò.
Shane,

2
Anche se getWellID non è vettorializzato, penso che dovresti scegliere questa soluzione e sostituire getWellId con mapply(getWellId, well$name, well$plate).
Jonathan Chang,

Anche se lo si estrae da un database, è possibile estrarli tutti in una volta e quindi filtrare il risultato in R; sarà più veloce di una funzione iterativa.
Shane,

+1 per foreach- Ho intenzione di usare l'inferno di quello.
Josh Bode,

20

Penso che il modo migliore per farlo con la R di base sia:

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

Il vantaggio rispetto for( i in 1:nrow(df))all'approccio è che non ci si mette nei guai se dfè vuoto e nrow(df)=0.


17

Uso questa semplice funzione di utilità:

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

O una forma più veloce, meno chiara:

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

Questa funzione divide semplicemente un data.frame in un elenco di righe. Quindi puoi creare un normale "per" su questo elenco:

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

Il codice della domanda funzionerà con una modifica minima:

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

È più veloce accedere a un elenco semplice di data.frame.
Ł Łaniewski-Wołłk,

1
Ho appena realizzato che è ancora più veloce realizzare la stessa cosa con doppio lapply: righe = funzione (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Łaniewski-Wołłk,

Quindi l'interno lapplyscorre sopra le colonne dell'intero set di dati x, dando ad ogni colonna il nome ce quindi estraendo la ivoce th dal vettore di quella colonna. È corretto?
Aaron McDaid,

Molto bella! Nel mio caso, ho dovuto convertire da valori "Fattore" per il valore sottostante: wellName <- as.character(well$name).
Steve Pitchers,

9

Ero curioso della prestazione temporale delle opzioni non vettorializzate. A tale scopo, ho usato la funzione f definita da 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 un frame di dati come quello nel suo esempio:

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 )

Ho incluso due funzioni vettorializzate (sicuramente più veloci delle altre) per confrontare l'approccio cat () con uno 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)

L'immagine risultante mostra che applicare offre le migliori prestazioni per una versione non vettorializzata, mentre write.table () sembra sovraperformare cat (). ForEachRunningTime


6

È possibile utilizzare la by_rowfunzione dal pacchetto purrrlyrper questo:

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)

Per impostazione predefinita, il valore restituito da myfnviene inserito in una nuova colonna dell'elenco nel df chiamato .out.

Se questo è l'unico risultato che desideri, puoi scrivere purrrlyr::by_row(df, myfn)$.out


2

Bene, dal momento che hai chiesto la R equivalente ad altre lingue, ho provato a farlo. Sembra funzionare anche se non ho davvero visto quale tecnica sia più efficiente in 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

Per le colonne categoriche, tuttavia, verrebbe a prenderti un Data Frame che potresti digitare a macchina usando as.character () se necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.