Đối với mỗi hàng trong một khung dữ liệu R


173

Tôi có một khung dữ liệu và với mỗi hàng trong khung dữ liệu đó, tôi phải thực hiện một số tra cứu phức tạp và nối thêm một số dữ liệu vào một tệp.

DataFrame chứa các kết quả khoa học cho các giếng được chọn từ 96 tấm giếng được sử dụng trong nghiên cứu sinh học, vì vậy tôi muốn làm một cái gì đó như:

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

Trong thế giới thủ tục của tôi, tôi sẽ làm một cái gì đó như:

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

"Cách R" để làm điều này là gì?


Câu hỏi của bạn ở đây là gì? Data.frame là một đối tượng hai chiều và lặp qua các hàng là một cách làm hoàn toàn bình thường vì các hàng thường là tập hợp 'quan sát' của 'biến' trong mỗi cột.
Dirk Eddelbuettel

16
những gì tôi kết thúc là: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # làm công việc với hàng} mà dường như không bao giờ rất đẹp đối với tôi.
Carl Coryell-Martin

1
Có getWellID gọi một cơ sở dữ liệu hoặc bất cứ điều gì? Nếu không, Jonathan có lẽ đúng và bạn có thể vector hóa điều này.
Shane

Câu trả lời:


103

Bạn có thể thử điều này, sử dụng apply()chức năng

> 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
Hãy cẩn thận, vì khung dữ liệu được chuyển đổi thành ma trận và những gì bạn kết thúc với ( x) là một vectơ. Đây là lý do tại sao ví dụ trên phải sử dụng các chỉ mục số; Cách tiếp cận by () cung cấp cho bạn một data.frame, làm cho mã của bạn mạnh mẽ hơn.
Darren Cook

đã không làm việc cho tôi. Hàm áp dụng được coi mỗi x được cung cấp cho f dưới dạng giá trị ký tự và không phải là một hàng.
Zahy

3
Lưu ý rằng bạn có thể tham khảo các cột theo tên. Vì vậy: wellName <- x[1]cũng có thể được wellName <- x["name"].
Founddrama

1
Khi Darren đề cập mạnh mẽ, anh ta có ý gì đó như thay đổi thứ tự của các cột. Câu trả lời này sẽ không hoạt động trong khi câu trả lời bằng () vẫn hoạt động.
HelloWorld

120

Bạn có thể sử dụng by()chức năng:

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

Nhưng lặp đi lặp lại qua các hàng như thế này hiếm khi bạn muốn; thay vào đó, bạn nên cố gắng vector hóa. Tôi có thể hỏi công việc thực tế trong vòng lặp đang làm gì không?


5
điều này sẽ không hoạt động tốt nếu khung dữ liệu có 0 hàng vì 1:0không trống
sds

10
Khắc phục dễ dàng cho trường hợp hàng 0 là sử dụng seq_len () , chèn seq_len(nrow(dataFrame))vào vị trí 1:nrow(dataFrame).
Jim

13
Làm thế nào để bạn thực sự thực hiện (hàng)? Có phải cột dữ liệu $ cột không? dataframe [somevariableNamehere]? Làm thế nào để bạn thực sự nói một hàng của nó. Mã giả "hàm (hàng) dothing" trông như thế nào?
uh_big_mike_boi

1
@Mike, thay đổi dostuffcâu trả lời này thành str(row) Bạn sẽ thấy nhiều dòng được in trong bảng điều khiển bắt đầu bằng "'data.frame': 1 obs của x biến." Nhưng hãy cẩn thận, thay đổi dostuffthành rowkhông trả về một đối tượng data.frame cho toàn bộ chức năng bên ngoài. Thay vào đó, nó trả về một danh sách các khung dữ liệu một hàng.
pwilcox

91

Đầu tiên, quan điểm của Jonathan về vector hóa là chính xác. Nếu hàm getWellID () của bạn được vector hóa, thì bạn có thể bỏ qua vòng lặp và chỉ sử dụng cat hoặc write.csv:

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

Nếu getWellID () không được vector hóa, thì đề xuất sử dụng byhoặc đề xuất của knguyen của Jonathan applysẽ hoạt động.

Mặt khác, nếu bạn thực sự muốn sử dụng for, bạn có thể làm một cái gì đó như thế này:

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

Bạn cũng có thể thử sử dụng foreachgói, mặc dù nó yêu cầu bạn phải làm quen với cú pháp đó. Đây là một ví dụ đơn giản:

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

Tùy chọn cuối cùng là sử dụng một hàm ngoài plyrgói, trong trường hợp đó, quy ước sẽ rất giống với hàm áp dụng.

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

Shane, cảm ơn bạn. Tôi không chắc chắn làm thế nào để viết một getWellID được vector hóa. Những gì tôi cần làm ngay bây giờ là đào sâu vào một danh sách các danh sách hiện có để tra cứu hoặc lấy nó ra khỏi cơ sở dữ liệu.
Carl Coryell-Martin

Vui lòng gửi riêng câu hỏi getWellID (nghĩa là chức năng này có thể được vector hóa không?) Và tôi chắc chắn rằng tôi (hoặc ai đó) sẽ trả lời nó.
Shane

2
Ngay cả khi getWellID không được vector hóa, tôi nghĩ bạn nên dùng giải pháp này và thay thế getWellId bằng mapply(getWellId, well$name, well$plate).
Jonathan Chang

Ngay cả khi bạn lấy nó từ cơ sở dữ liệu, bạn có thể kéo tất cả chúng cùng một lúc và sau đó lọc kết quả trong R; nó sẽ nhanh hơn một hàm lặp.
Shane

+1 cho foreach- Tôi sẽ sử dụng cái địa ngục đó.
Josh Bode

20

Tôi nghĩ cách tốt nhất để làm điều này với R cơ bản là:

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

Ưu điểm so với for( i in 1:nrow(df))-approach là bạn không gặp rắc rối nếu dftrống và nrow(df)=0.


17

Tôi sử dụng chức năng tiện ích đơn giản này:

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

Hoặc một hình thức nhanh hơn, ít rõ ràng hơn:

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

Hàm này chỉ phân tách một data.frame thành một danh sách các hàng. Sau đó, bạn có thể tạo một "cho" bình thường trong danh sách này:

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

Mã của bạn từ câu hỏi sẽ hoạt động với một sửa đổi tối thiểu:

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

Nhanh hơn để truy cập một danh sách thẳng sau đó là data.frame.
Ł iewaniewski-Wołłk

1
Chỉ cần nhận ra rằng nó còn nhanh hơn để tạo ra điều tương tự với double lapply: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Łaniewski-Wołłk

Vì vậy, bên trong lapplylặp lại qua các cột của toàn bộ tập dữ liệu x, đặt tên cho mỗi cột cvà sau đó trích xuất imục nhập từ vectơ cột đó. Điều này có đúng không?
Aaron McDaid

Rất đẹp! Trong trường hợp của tôi, tôi đã phải chuyển đổi từ giá trị "yếu tố" sang giá trị cơ bản : wellName <- as.character(well$name).
Steve Pitchers

9

Tôi tò mò về hiệu suất thời gian của các tùy chọn không véc tơ. Với mục đích này, tôi đã sử dụng hàm f được xác định bởi 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)
}

và một khung dữ liệu giống như trong ví dụ của anh ấy:

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 )

Tôi đã bao gồm hai hàm vectơ (chắc chắn nhanh hơn các hàm khác) để so sánh cách tiếp cận cat () với phương thức 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)

Hình ảnh thu được cho thấy rằng ứng dụng mang lại hiệu suất tốt nhất cho phiên bản không được vector hóa, trong khi write.table () dường như vượt trội hơn so với cat (). ForEachRastyTime


6

Bạn có thể sử dụng by_rowchức năng từ gói purrrlyrcho việc này:

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)

Theo mặc định, giá trị được trả về từ myfnđược đưa vào cột danh sách mới trong df được gọi .out.

Nếu đây là đầu ra duy nhất bạn mong muốn, bạn có thể viết purrrlyr::by_row(df, myfn)$.out


2

Chà, vì bạn đã yêu cầu R tương đương với các ngôn ngữ khác, tôi đã cố gắng làm điều này. Có vẻ như hoạt động mặc dù tôi chưa thực sự xem xét kỹ thuật nào hiệu quả hơn trong 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

Đối với các cột phân loại, nó sẽ tìm nạp cho bạn một Khung dữ liệu mà bạn có thể đánh máy bằng cách sử dụng as.character () nếu cần.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.