对于R数据框中的每一行


173

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

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是一个二维对象,在行上循环是一种完全正常的处理方式,因为行通常是每列中“变量”的“观察”集。
Dirk Eddelbuettel,2009年

16
我最终要做的是:for(index in 1:nrow(dataFrame)){row = dataFrame [index,]; #用行做}在我看来从来都不是很漂亮。
卡尔·科雷尔·马丁

1
getWellID会调用数据库还是其他东西?否则,乔纳森(Jonathan)可能是正确的,您可以将其向量化。
Shane

Answers:


103

您可以尝试使用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')

75
请注意,由于数据帧已转换为矩阵,所以最后得到(x)的是向量。这就是上面的示例必须使用数字索引的原因;by()方法为您提供了一个data.frame,这使您的代码更加健壮。
达伦·库克

没有为我工作。apply函数将给f的每个x视为一个字符值而不是一行。
Zahy 2014年

3
还要注意,您可以按名称引用列。因此:wellName <- x[1]也可能是wellName <- x["name"]
founddrama

1
当达伦(Darren)提到稳健时,他的意思是改变列的顺序。此答案不起作用,而使用by()的答案仍然有效。
HelloWorld

119

您可以使用以下by()功能:

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

但是像这样直接在行上进行迭代很少是您想要的。您应该尝试向量化。我可以问一下循环中的实际工作吗?


5
如果数据框有0行,因为1:0它不是空的,那么这将无法正常工作
sds 2013年

10
对于0行的情况,简单的解决方法是使用seq_len()seq_len(nrow(dataFrame))代替1:nrow(dataFrame)
吉姆

13
您如何实际实现(行)?是dataframe $ column吗?dataframe [somevariableNamehere]?您实际上怎么说连续。伪代码“ function(row)dostuff”实际上看起来如何?
uh_big_mike_boi '16

1
@Mike,请更改dostuff此答案,str(row) 您将在控制台中看到以“'data.frame':1 obs x变量”开头的多行打印内容 但请注意,更改dostuffrow不会为外部函数整体返回data.frame对象。而是返回一排数据帧的列表。
pwilcox

91

首先,乔纳森关于向量化的观点是正确的。如果您的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问题(例如,该函数可以向量化吗?),我确定我(或其他人)将回答它。
Shane

2
即使没有对向量getWellID进行矢量化,我认为您也应该使用此解决方案,并将getWellId替换为mapply(getWellId, well$name, well$plate)
乔纳森·张

即使从数据库中将其提取,也可以一次将其全部提取,然后将结果过滤到R中。这将比迭代函数更快。
Shane

+1,代表foreach-我要用那个地狱。
乔什·波德

20

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

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

相对于for( i in 1:nrow(df))-approach 的优点是,如果df为空和,则不会遇到麻烦nrow(df)=0


17

我使用这个简单的实用程序功能:

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更快。
ŁŁaniewski-Wołłk

1
才意识到它的速度更快,使双lapply同样的事情:行数=功能(X)lapply(seq_len(nrow(X)),功能(I)lapply(X,函数(三)C [1]))
Ł Łaniewski-Wołłk'16

因此,内部lapply迭代整个数据集的列x,为每个列命名c,然后i从该列向量中提取第th个条目。这样对吗?
亚伦·麦克戴德

非常好!在我的情况下,我必须将“因子”值转换为基础值:wellName <- as.character(well$name)
史蒂夫·皮尔斯

9

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


6

您可以为此使用by_row软件包中的函数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)

默认情况下,来自的返回值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()进行类型转换。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.