对于 R 数据框中的每一行

Posted

技术标签:

【中文标题】对于 R 数据框中的每一行【英文标题】:For each row in an R dataframe 【发布时间】:2010-12-14 12:25:42 【问题描述】:

我有一个数据框,对于该数据框中的每一行,我必须进行一些复杂的查找并将一些数据附加到文件中。

dataFrame 包含生物研究中使用的 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 是一个二维对象,遍历行是一种非常正常的做事方式,因为行通常是每列中“变量”的“观察”集。 我最终做的是: for (index in 1:nrow(dataFrame)) row = dataFrame[index, ]; # 用行 做一些对我来说从来都不是很漂亮的事情。 getWellID 是调用数据库还是什么?否则,乔纳森可能是对的,您可以将其矢量化。 【参考方案1】:

你可以使用by()函数:

by(dataFrame, seq_len(nrow(dataFrame)), function(row) dostuff)

但是像这样直接遍历行很少是您想要的;你应该尝试矢量化。请问循环中的实际工作是做什么的?

【讨论】:

如果数据框有 0 行,这将无法正常工作,因为 1:0 不为空 0 行情况的简单修复是使用seq_len(),插入seq_len(nrow(dataFrame)) 代替1:nrow(dataFrame) 你如何实际实现(行)?是数据框$列吗?数据框[somevariableNamehere]?你怎么说它是一排。伪代码“function(row) dostuff”实际上看起来如何? @Mike,将此答案中的dostuff 更改为str(row) 您将在控制台中看到以 " 'data.frame': 1 obs of x variables 开头的多行打印。 " 但请注意,将dostuff 更改为row 不会为整个外部函数返回data.frame 对象。相反,它返回一行数据帧的列表。 并非所有内容都应矢量化。但在这种情况下,我想这是有道理的。【参考方案2】:

你可以试试这个,使用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')

【讨论】:

小心,因为数据帧被转换为矩阵,而你最终得到的 (x) 是一个向量。这就是为什么上面的例子必须使用数字索引的原因; by() 方法为您提供了一个 data.frame,这使您的代码更加健壮。 对我不起作用。 apply 函数将赋予 f 的每个 x 视为一个字符值,而不是一行。 请注意,您可以按名称引用列。所以:wellName &lt;- x[1] 也可以是 wellName &lt;- x["name"] 当 Darren 提到健壮时,他的意思是改变列的顺序。这个答案不起作用,而带有 by() 的答案仍然有效。【参考方案3】:

首先,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 )

【讨论】:

谢恩,谢谢。我不确定如何编写矢量化的 getWellID。我现在需要做的是挖掘现有的列表列表以查找或将其从数据库中提取出来。 欢迎单独发布 getWellID 问题(即这个函数可以向量化吗?),我相信我(或其他人)会回答。 即使getWellID没有向量化,我认为你应该使用这个解决方案,并将getWellId替换为mapply(getWellId, well$name, well$plate) 即使从数据库中拉取,也可以一次全部拉取,然后在R中过滤结果;这将比迭代函数更快。 +1 为foreach - 我将使用那个地狱。【参考方案4】:

我认为使用基本 R 的最佳方法是:

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

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

【讨论】:

【参考方案5】:

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

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

这个函数只是将一个 data.frame 拆分为一个行列表。然后你可以在这个列表上做一个普通的“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)

【讨论】:

访问直接列表比访问 data.frame 更快。 刚刚意识到用双 lapply 做同样的事情会更快: rows = function(x) lapply(seq_len(nrow(x)), function(i) lapply(x,function(c) c[i])) 因此内部lapply 遍历整个数据集x 的列,为每列命名c,然后从该列向量中提取ith 条目。这是正确的吗? 非常好!就我而言,我必须将“因子”值转换为基础值:wellName &lt;- as.character(well$name)【参考方案6】:

我对非矢量化选项的时间性能很好奇。 为此,我使用了 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()。

【讨论】:

【参考方案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 的返回值被放入名为.out 的df 中的新list column 中。

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

【讨论】:

【参考方案8】:

好吧,既然您要求 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() 进行类型转换。

【讨论】:

【参考方案9】:

你可以为列表对象做点什么,

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 

【讨论】:

以上是关于对于 R 数据框中的每一行的主要内容,如果未能解决你的问题,请参考以下文章

为数据框中的每一行应用一个函数,用于另一个数据框中的每一行

R中的高效方法是将新列添加到具有大数据集的数据框中

如何在数据框中的每一行的末尾添加 ****?

对 Pandas 数据框中的每一行只运行一次函数

如何通过遍历行来预测数据框中的每一行?

将数据框中的每一行除以 Python 中的向量