Untuk setiap baris dalam bingkai data R

173

Saya memiliki dataframe, dan untuk setiap baris dalam dataframe saya harus melakukan beberapa pencarian rumit dan menambahkan beberapa data ke file.

DataFrame berisi hasil-hasil ilmiah untuk sumur-sumur terpilih dari 96 pelat sumur yang digunakan dalam penelitian biologi, jadi saya ingin melakukan sesuatu seperti:

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

Di dunia prosedural saya, saya akan melakukan sesuatu seperti:

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

Apa "cara R" untuk melakukan ini?

Carl Coryell-Martin
sumber
Apa pertanyaan anda di sini Sebuah data.frame adalah objek dua dimensi dan perulangan di atas baris adalah cara yang sangat normal untuk melakukan sesuatu karena baris biasanya merupakan set 'pengamatan' dari 'variabel' di setiap kolom.
Dirk Eddelbuettel
16
apa yang akhirnya saya lakukan adalah: untuk (indeks dalam 1: nrow (dataFrame)) {row = dataFrame [index,]; # lakukan hal-hal dengan baris} yang menurutku tidak pernah cantik.
Carl Coryell-Martin
1
Apakah getWellID memanggil basis data atau apa pun? Kalau tidak, Jonathan mungkin benar dan Anda bisa membuat vektor ini.
Shane

Jawaban:

103

Anda dapat mencoba ini, menggunakan apply()fungsi

> 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
sumber
76
Hati-hati, karena bingkai data dikonversi ke matriks, dan apa yang Anda akhirnya ( x) adalah vektor. Inilah sebabnya mengapa contoh di atas harus menggunakan indeks numerik; pendekatan by () memberi Anda data.frame, yang membuat kode Anda lebih kuat.
Darren Cook
tidak bekerja untuk saya. Fungsi yang berlaku memperlakukan setiap x yang diberikan kepada f sebagai nilai karakter dan bukan baris.
Zahy
3
Perhatikan juga bahwa Anda dapat merujuk ke kolom dengan nama. Jadi: wellName <- x[1]bisa juga bisa wellName <- x["name"].
founddrama
1
Ketika Darren disebut kuat, dia berarti sesuatu seperti menggeser urutan kolom. Jawaban ini tidak akan berfungsi sedangkan yang dengan () akan tetap berfungsi.
HelloWorld
120

Anda dapat menggunakan by()fungsi ini:

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

Tapi iterasi pada baris langsung seperti ini jarang yang Anda inginkan; Anda harus mencoba membuat vektor sebagai gantinya. Bisakah saya bertanya apa yang sebenarnya dilakukan dalam loop?

Jonathan Chang
sumber
5
ini tidak akan berfungsi dengan baik jika bingkai data memiliki 0 baris karena 1:0tidak kosong
sds
10
Perbaikan mudah untuk kasus 0 baris adalah dengan menggunakan seq_len () , masukkan seq_len(nrow(dataFrame))di tempat 1:nrow(dataFrame).
Jim
13
Bagaimana Anda benar-benar menerapkan (baris)? Apakah dataframe $ kolom? dataframe [somevariableNamehere]? Bagaimana Anda mengatakannya secara berurutan? Kode "fungsi (baris) kodesemu" bagaimana tampilan sebenarnya?
uh_big_mike_boi
1
@ Mike, ubah dostuffjawaban ini untuk str(row) Anda akan melihat beberapa baris dicetak di konsol dimulai dengan "'data.frame': 1 obs of x variable." Tapi hati-hati, mengubah dostuffuntuk rowtidak mengembalikan objek data.frame untuk fungsi luar secara keseluruhan. Alih-alih itu mengembalikan daftar satu frame data-baris.
pwilcox
91

Pertama, poin Jonathan tentang vektorisasi adalah benar. Jika fungsi getWellID () Anda adalah vektor, maka Anda dapat melewati loop dan cukup menggunakan cat atau write.csv:

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

Jika getWellID () tidak di-vectorized, maka rekomendasi Jonathan untuk menggunakan byatau saran knguyen tentang applyharus bekerja.

Jika tidak, jika Anda benar-benar ingin menggunakannya for, Anda dapat melakukan sesuatu seperti ini:

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

Anda juga dapat mencoba menggunakan foreachpaket, meskipun mengharuskan Anda untuk terbiasa dengan sintaks itu. Berikut ini contoh sederhana:

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

Opsi terakhir adalah menggunakan fungsi di luar plyrpaket, dalam hal ini konvensi akan sangat mirip dengan fungsi yang berlaku.

library(plyr)
ddply(dataFrame, .(x), function(x) { # do stuff })
Shane
sumber
Shane, terima kasih. Saya tidak yakin bagaimana menulis getWellID yang di-vektor. Yang perlu saya lakukan sekarang adalah menggali daftar daftar yang ada untuk mencarinya atau menariknya keluar dari database.
Carl Coryell-Martin
Jangan ragu untuk memposting pertanyaan getWellID (mis. Bisakah fungsi ini di-vectorized?) Secara terpisah, dan saya yakin saya (atau orang lain) akan menjawabnya.
Shane
2
Bahkan jika getWellID tidak vektor, saya pikir Anda harus pergi dengan solusi ini, dan ganti getWellId dengan mapply(getWellId, well$name, well$plate).
Jonathan Chang
Bahkan jika Anda menariknya dari database, Anda dapat menarik semuanya sekaligus dan memfilter hasilnya dalam R; yang akan lebih cepat dari fungsi yang berulang.
Shane
+1 untuk foreach- Saya akan menggunakan yang itu.
Josh Bode
20

Saya pikir cara terbaik untuk melakukan ini dengan R dasar adalah:

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

Keuntungan daripada for( i in 1:nrow(df))pendekatan-adalah bahwa Anda tidak mendapat masalah jika dfkosong dan nrow(df)=0.

Funkwecker
sumber
17

Saya menggunakan fungsi utilitas sederhana ini:

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

Atau bentuk yang lebih cepat, kurang jelas:

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

Fungsi ini hanya membagi data.frame menjadi daftar baris. Kemudian Anda dapat membuat "untuk" yang normal pada daftar ini:

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

Kode Anda dari pertanyaan akan berfungsi dengan modifikasi minimal:

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
sumber
Lebih cepat mengakses daftar langsung daripada data.frame.
Ł Łaniewski-Wołłk
1
Baru menyadari bahwa ini lebih cepat untuk membuat hal yang sama dengan double lapply: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Łaniewski-Wołłk
Jadi bagian dalam lapplyiterates atas kolom seluruh datasetx , memberikan nama masing-masing kolom c, dan kemudian mengekstraksi ientri th dari vektor kolom itu. Apakah ini benar?
Aaron McDaid
Sangat bagus! Dalam kasus saya, saya harus mengkonversi dari nilai-nilai "faktor" dengan nilai yang mendasari: wellName <- as.character(well$name).
Steve Pitchers
9

Saya ingin tahu tentang kinerja waktu dari opsi non-vektor. Untuk tujuan ini, saya telah menggunakan fungsi f yang didefinisikan oleh 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)
}

dan bingkai data seperti yang ada dalam contohnya:

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 )

Saya menyertakan dua fungsi vektor (pasti lebih cepat dari yang lain) untuk membandingkan pendekatan cat () dengan metode 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)

Gambar yang dihasilkan menunjukkan bahwa penerapan memberikan kinerja terbaik untuk versi non-vektor, sedangkan write.table () tampaknya mengungguli cat (). ForEachRunningTime

Ferran E
sumber
6

Anda dapat menggunakan by_rowfungsi dari paket purrrlyruntuk ini:

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)

Secara default, nilai yang dikembalikan dari myfndimasukkan ke dalam kolom daftar baru di df yang dipanggil .out.

Jika ini adalah satu-satunya hasil yang Anda inginkan, Anda dapat menulis purrrlyr::by_row(df, myfn)$.out

RobinL
sumber
2

Yah, karena Anda meminta R yang setara dengan bahasa lain, saya mencoba melakukan ini. Tampaknya bekerja meskipun saya belum benar-benar melihat teknik mana yang lebih efisien dalam 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

Untuk kolom kategorikal, ia akan mengambil Anda sebuah Frame Data yang bisa Anda ketik menggunakan as.character () jika diperlukan.

Amogh Borkar
sumber