在R数据框中的每一行

208

我有一个数据框,对于每一行数据,我需要进行一些复杂的查找并将一些数据追加到文件中。

这个数据框包含了生物研究中使用的96孔板中选择井的科学结果,所以我想做以下操作:

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)
}
在我的过程式世界中,我会这样做:
for (row in dataFrame) {
    #look up stuff using data from the row
    #write stuff to the file
}

如何用“R语言方式”来完成这个问题?


你在这里有什么问题?data.frame是一个二维对象,循环遍历行是一种完全正常的做法,因为行通常是每列中“变量”的“观察”集合。 - Dirk Eddelbuettel
21
我最终做的是:对于(dataFrame)中的每一行(index in 1:nrow(dataFrame)),执行以下操作{ row = dataFrame[index, ]; # 处理行 },这个方法从来不让我感觉很美观。 - Carl Coryell-Martin
1
getWellID这个函数是否调用了数据库或其他什么?否则,Jonathan可能是对的,你可以将其向量化。 - Shane
9个回答

129
你可以使用 by() 函数:
by(dataFrame, seq_len(nrow(dataFrame)), function(row) dostuff)

但是直接遍历行通常不是你想要的;你应该尝试向量化。我可以问一下循环中实际的工作是什么吗?


6
如果数据框没有任何行,那么这并不会很有效,因为1:0不是空的。 - sds
11
解决0行情况的简单方法是使用seq_len(),将1:nrow(dataFrame)替换为seq_len(nrow(dataFrame))即可。 - Jim
16
你实际上如何实现(行)?是dataframe$column吗?dataframe [somevariableNamehere]?你如何表达它是一行。伪代码“function(row) dostuff”看起来实际上是什么样子? 你怎样实际执行(行)操作?这是通过dataframe$column还是dataframe[somevariableNamehere]实现的?如何明确指出这是一行?“function(row) dostuff”伪代码实际呈现的样子是怎样的? - uh_big_mike_boi
2
@Mike,请将此答案中的dostuff更改为str(row)。您将在控制台中看到多行打印,以“'data.frame':1 obs of x variables.”开头。但要小心,将dostuff更改为row不会将数据框对象作为整个外部函数返回。而是返回一个包含一个行数据框的列表。 - pwilcox
1
不是所有的东西都应该矢量化。但在这种情况下,我想这样做是有意义的。 - stephanmg
我通过编辑解决了sdsJim指出的问题。 - Ken Williams

110
您可以尝试使用apply()函数进行操作。
> 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')

81
注意,由于数据框被转换为矩阵,因此最终得到的 (x) 是一个向量。这就是为什么上面的例子必须使用数字索引;使用 by() 方法可以得到数据框,使您的代码更加健壮。 - Darren Cook
1
对我来说没有用。apply函数将传递给f的每个x都视为字符值而不是行。 - Zahy
4
还有一点需要注意,你可以通过名称来引用列。因此:wellName <- x[1]也可以写作wellName <- x["name"] - founddrama
1
当达伦提到“健壮”,他的意思是像移动列顺序这样的操作。使用这种方式回答不可行,而使用 by() 则仍然有效。 - ABCD

110

首先,Jonathan关于向量化的观点是正确的。如果您的getWellID()函数已经向量化了,那么您可以跳过循环,只需使用cat或write.csv:

write.csv(data.frame(wellid=getWellID(well$name, well$plate), 
         value1=well$value1, value2=well$value2), file=outputFile)
如果 getWellID() 没有向量化,那么 Jonathan 建议的使用 by 或 knguyen 的建议使用 apply 应该可行。
否则,如果你真的想使用 for,你可以像这样做:
for(i in 1:nrow(dataFrame)) {
    row <- dataFrame[i,]
    # do stuff with row
}

你也可以尝试使用foreach包,不过需要你熟悉这种语法。这里是一个简单的例子:

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

最后一个选择是使用plyr包中的函数,此时惯例与apply函数非常相似。

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

Shane,谢谢。我不确定如何编写矢量化的getWellID。现在我需要做的是挖掘现有的列表以查找它或从数据库中提取它。 - Carl Coryell-Martin
请随意单独发布getWellID问题(即此函数是否可以向量化?),我相信我(或其他人)会回答它。 - Shane
2
即使getWellID没有向量化,我认为你应该选择这个解决方案,并用mapply(getWellId, well$name, well$plate)替换getWellId。 - Jonathan Chang
即使您从数据库中提取数据,也可以一次性获取所有数据,然后在 R 中过滤结果;这比迭代函数更快。 - Shane
+1 对于 foreach - 我会大量使用它。 - Josh Bode

31

我认为使用基本的 R 来完成这个任务是最好的方法:

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

for( i in 1:nrow(df))方法相比,优势在于如果df为空且nrow(df)=0,您不会遇到问题。


21

我使用这个简单的实用函数:

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

或者是一种更快、不那么清晰的形式:

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

这个函数仅将数据框拆分为一行的列表。然后您可以对此列表进行普通的“for”循环:

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

你在问题中提供的代码只需要进行微小修改即可正常工作:

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
1
刚刚意识到使用双重 lapply 制作相同的东西更快:rows = function(x) lapply(seq_len(nrow(x)), function(i) lapply(x,function(c) c[i])) - Ł Łaniewski-Wołłk
那么内部的 lapply 迭代整个数据集 x 的列,将每一列命名为 c,然后从该列向量中提取第 i 个条目。这样正确吗? - Aaron McDaid
非常好!在我的情况下,我需要将“因子”值转换为其基础值:wellName <- as.character(well$name) - Steve Pitchers

11

我对非向量化选项的时间性能很好奇。为此,我使用了由knguyen定义的函数f。

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

并且有一个类似于他示例中的数据框:

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()方法和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)

得到的图像显示,对于非向量化版本,apply提供了最佳性能,而write.table()似乎优于cat()。

ForEachRunningTime

7
您可以使用purrrlyr包中的by_row函数来实现此功能:
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)

默认情况下,来自myfn的返回值被放入df中称为.out的新列表列.

如果这是您想要的唯一输出,则可以编写purrrlyr::by_row(df, myfn)$.out


2

好的,既然您要求将R语言与其他语言进行比较,我试着完成了这个任务。似乎可以工作,但我没有仔细研究在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

对于分类列,它将为您提取一个数据框,如果需要,您可以使用as.character()进行类型转换。


-1

你可以为列表对象做一些事情,

data("mtcars")
rownames(mtcars)
data <- list(mtcars ,mtcars, mtcars, mtcars);data

out1 <- NULL 
for(i in seq_along(data)) { 
  out1[[i]] <- data[[i]][rownames(data[[i]]) != "Volvo 142E", ] } 
out1

或者一个数据框架,

data("mtcars")
df <- mtcars
out1 <- NULL 
for(i in 1:nrow(df)) {
  row <- rownames(df[i,])
  # do stuff with row
  out1 <- df[rownames(df) != "Volvo 142E",]
  
}
out1 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接