循环 R 数据集合中的记录

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

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方法是什么?

437897 次浏览

你可以使用by()函数:

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

但是像这样直接遍历各行不是你想要的;你应该尝试向量化。我能问一下循环中的实际工作在做什么吗?

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

首先,乔纳森关于向量化的观点是正确的。如果你的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 })

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

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

我用这个简单的效用函数:

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

你可以使用包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

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

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

与__abc0方法相比的优点是,如果df为空且nrow(df)=0为空,则不会遇到麻烦。

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

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