Bir R veri çerçevesindeki her satır için


173

Bir dataframe var ve bu dataframe her satır için bazı karmaşık aramalar yapmak ve bir dosyaya bazı veri eklemek zorunda.

DataFrame biyolojik araştırmada kullanılan 96 oyuklu plakalardan seçilen kuyular için bilimsel sonuçlar içerir, bu yüzden şöyle bir şey yapmak istiyorum:

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

Prosedür dünyamda şöyle bir şey yapardım:

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

Bunu yapmanın "R yolu" nedir?


Sorunuz burada mı? Bir data.frame iki boyutlu bir nesnedir ve satırlar üzerinde döngü yapmak, normalde normal bir yöntemdir çünkü satırlar genellikle her sütundaki 'değişkenlerin' gözlemleri 'kümesidir.
Dirk Eddelbuettel

16
ne yapıyorum sonunda: için (dizin 1: nrow (dataFrame)) {row = dataFrame [index,]; # Satırla bir şeyler yap} bana hiç hoş gelmedi.
Carl Coryell-Martin

1
GetWellID bir veritabanını veya herhangi bir şeyi çağırıyor mu? Aksi takdirde, Jonathan muhtemelen haklıdır ve bunu vectorize edebilirsiniz.
Shane

Yanıtlar:


103

apply()Fonksiyonu kullanarak bunu deneyebilirsiniz

> 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
Dikkat edin, çünkü veri çerçevesi bir matrise dönüştürülür ve ( x) ile sonuçladığınız şey bir vektördür. Bu nedenle yukarıdaki örnek sayısal dizinler kullanmak zorundadır; by () yaklaşımı size kodunuzu daha sağlam hale getiren bir data.frame sağlar.
Darren Cook

benim için çalışmadı. Uygula işlevi, f'ye verilen her x'i satır değeri değil karakter değeri olarak kabul eder.
Zahy

3
Sütunlara adıyla başvurabileceğinizi de unutmayın. Yani: wellName <- x[1]olabilir de wellName <- x["name"].
kurmakdrama

1
Darren sağlamdan bahsettiğinde, sütunların sırasını değiştirmek gibi bir şey demekti. Bu cevap işe yaramazken by () ile olan cevap hala işe yarayacaktır.
HelloWorld

120

by()İşlevi kullanabilirsiniz :

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

Ancak doğrudan bu şekilde satırlar üzerinde yineleme yapmak istediğiniz şeydir; bunun yerine vektörleştirmeye çalışmalısınız. Döngüdeki gerçek çalışmanın ne yaptığını sorabilir miyim?


5
veri çerçevesinin 1:0boş olmadığı için 0 satırı varsa bu iyi çalışmaz
sds

10
0 satır durumu için kolay düzeltme kullanımı etmektir seq_len () , ekin seq_len(nrow(dataFrame))yerine 1:nrow(dataFrame).
Jim

13
Aslında nasıl uygularsınız (sıra)? Dataframe $ sütunu mu? dataframe [somevariableNamehere]? Aslında nasıl olduğunu söyle. Pseudocode "function (row) dostuff" bu gerçekte nasıl görünürdü?
uh_big_mike_boi

1
@Mike, dostuffbu cevapta değişiklik yapın Konsolda "data.frame "str(row) ile başlayan birden fazla satır göreceksiniz : x değişkeninin 1 obs. " Ama değişen, dikkatli olun dostuffüzere rowbir bütün olarak dış fonksiyon için bir data.frame nesnesi döndürmez. Bunun yerine bir satır veri çerçevelerinin listesini döndürür.
pwilcox

91

İlk olarak, Jonathan'ın vektörleştirme konusundaki anlamı doğrudur. GetWellID () işleviniz vektörleştirilmişse, döngüyü atlayabilir ve sadece cat veya write.csv kullanabilirsiniz:

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

GetWellID () vektörlenmemişse, Jonathan'ın byknguyen önerisini kullanma önerisi applyişe yarayacaktır.

Aksi takdirde, gerçekten kullanmak foristiyorsanız, böyle bir şey yapabilirsiniz:

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

Bu foreachsözdizimine aşina olmanızı gerektirse de paketi de kullanmayı deneyebilirsiniz . İşte basit bir örnek:

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

Son seçenek, plyrpaketin dışında bir işlev kullanmaktır ; bu durumda, kongre uygula işlevine çok benzer olacaktır.

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

Shane, teşekkür ederim. Bir vectorized getWellID nasıl yazılacağından emin değilim. Şu anda yapmam gereken şey, listeye bakmak veya bir veritabanından çıkarmak için mevcut bir liste listesine girmektir.
Carl Coryell-Martin

GetWellID sorusunu (yani bu işlev vektörleştirilebilir mi?) Ayrı olarak yayınlamaktan çekinmeyin ve eminim ben (veya başka biri) buna cevap verecektir.
Shane

2
GetWellID vektörlenmemiş olsa bile, bu çözümle gitmeli ve getWellId ile değiştirmelisiniz mapply(getWellId, well$name, well$plate).
Jonathan Chang

Bir veritabanından çekseniz bile, hepsini bir kerede çekebilir ve ardından sonucu R olarak filtreleyebilirsiniz; yinelemeli bir işlevden daha hızlı olacaktır.
Shane

+1 için foreach- bu cehennemi kullanacağım.
Josh Bode

20

Temel R ile bunu yapmanın en iyi yolu olduğunu düşünüyorum:

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

for( i in 1:nrow(df))-Uygulamaya göre avantajı dfboş ve başınız belaya girmez nrow(df)=0.


17

Bu basit yardımcı program işlevini kullanıyorum:

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

Veya daha hızlı, daha az net bir form:

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

Bu işlev bir data.frame öğesini bir satır listesine böler. Sonra bu liste üzerinden normal bir "for" yapabilirsiniz:

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

Sorudaki kodunuz minimum bir değişiklikle çalışacaktır:

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

Düz bir listeye ve bir data.frame'e erişmek daha hızlıdır.
iew Łaniewski-Wołłk

1
Çift lapply ile aynı şeyi yapmanın daha hızlı olduğunu fark ettim: satırlar = işlev (x) lapply (seq_len (nrow (x)), işlev (i) lapply (x, işlev (c) c [i]))
Ł İewaniewski-Wołłk

Böylece iç lapply, tüm veri kümesinin sütunları üzerinde yinelenir x, her sütuna ad verir cve daha sonra ibu sütunu vektöründen inci girdisini çıkarır. Bu doğru mu?
Aaron McDaid

Çok hoş! Benim durumumda, ben temel değere "faktörü" değerlerinden dönüştürmek zorunda kaldı: wellName <- as.character(well$name).
Steve Pitchers

9

Vektörleştirilmemiş seçeneklerin zaman performansını merak ettim. Bu amaçla, knguyen tarafından tanımlanan f fonksiyonunu kullandım.

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

ve örneğindeki gibi bir veri çerçevesi:

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 )

Cat () yaklaşımını bir write.table () ile karşılaştırmak için iki vektörize fonksiyon (diğerlerinden daha hızlı) dahil ettim.

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)

Ortaya çıkan görüntü, uygulamanın vektörleştirilmemiş bir sürüm için en iyi performansı verdiğini gösterirken, write.table (), cat () 'dan daha iyi performans gösterir. ForEachRunningTime


6

Bunun by_rowiçin paketteki işlevi kullanabilirsiniz purrrlyr:

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)

Varsayılan olarak, döndürülen değer df'de myfnyeni bir liste sütununa.out .

İstediğiniz tek çıktı bu ise, purrrlyr::by_row(df, myfn)$.out


2

Peki, diğer dillere R eşdeğeri istediğinden, bunu yapmaya çalıştım. R'de hangi tekniğin daha verimli olduğuna gerçekten bakmadım ama işe yarıyor gibi görünüyor.

> 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

Kategorik sütunlar için, gerekirse as.character () kullanarak tahmin edebileceğiniz bir Veri Çerçevesi getirecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.