我发布了a question yesterday,得到了专家的精彩回应。但是,我现在面临另一个问题,我发现我的实际数据无法完成作业,因为我的起始文件(df1)太大了。我想知道是否有更快的方法来做同样的工作而不使用adply或for循环?
我原来的问题如下:
第1步:我有一个简化的数据框:
df1 = data.frame (B=c(1,0,1), C=c(1,1,0)
, D=c(1,0,1), E=c(1,1,0), F=c(0,0,1)
, G=c(0,1,0), H=c(0,0,1), I=c(0,1,0))
B C D E F G H I
1 1 1 1 1 0 0 0 0
2 0 1 0 1 0 1 0 1
3 1 0 1 0 1 0 1 0
第2步:我想做行减法,即(row1 - row2),(row1-row3)和(row2-row3)
row1-row2 1 0 1 0 0 -1 0 -1
row1-row3 0 1 0 1 -1 0 -1 0
row2-row3 -1 1 -1 1 -1 1 -1 1
步骤3:将所有-1替换为0
row1-row2 1 0 1 0 0 0 0 0
row1-row3 0 1 0 1 0 0 0 0
row2-row3 0 1 0 1 0 1 0 1
你能不能教我如何以更少记忆要求的方式这样做?
答案 0 :(得分:4)
我知道做第2步的最快方法是将df1
中的索引用于您想要进行的各种成对比较。 combn()
函数可用于生成所需的逐行比较集。 (使用它将是 big 数据集的速率限制步骤。)
对于我们想要形成的逐行操作的组合:
> cmb <- combn(as.numeric(rownames(df1)), 2)
> cmb
[,1] [,2] [,3]
[1,] 1 1 2
[2,] 2 3 3
cmb
行表示形成所请求输出的三行所需的df1
所需的两组索引。 (列3表示预期结果中的3行。)
下一步是使用两行cmb
来索引df1
,并通过-
在R中使用标准的矢量化操作,例如:
> (out <- df1[cmb[1,], ] - df1[cmb[2,], ])
B C D E F G H I
1 1 0 1 0 0 -1 0 -1
1.1 0 1 0 1 -1 0 -1 0
2 -1 1 -1 1 -1 1 -1 1
现在可以完成第3步,但我假设结果输出中只能有1
,0
和-1
值:
> out[out < 0] <- 0
> out
B C D E F G H I
1 1 0 1 0 0 0 0 0
1.1 0 1 0 1 0 0 0 0
2 0 1 0 1 0 1 0 1
这与您请求的输出一致。
对于大型操作,使用矩阵执行此操作可能会更快。所以我们可以这样做:
> mat <- data.matrix(df1)
> cmb <- combn(seq_len(NROW(mat)), 2)
> cmb
[,1] [,2] [,3]
[1,] 1 1 2
[2,] 2 3 3
> out2 <- mat[cmb[1,], ] - mat[cmb[2,], ]
> out2[out2 < 0] <- 0
> out2
B C D E F G H I
[1,] 1 0 1 0 0 0 0 0
[2,] 0 1 0 1 0 0 0 0
[3,] 0 1 0 1 0 1 0 1
如果您需要显示的rownames,那么您可以在最后轻松生成这些:
> apply(cmb, 2, function(x) paste("row", x[1], "-row", x[2], sep = ""))
[1] "row1-row2" "row1-row3" "row2-row3"
可以用作:
> rownames(out) <- apply(cmb, 2, function(x) paste("row", x[1], "-row", x[2], sep = ""))
> out
B C D E F G H I
row1-row2 1 0 1 0 0 0 0 0
row1-row3 0 1 0 1 0 0 0 0
row2-row3 0 1 0 1 0 1 0 1
答案 1 :(得分:3)
直接使用sqldf软件包或RSQLite可以在R外部完成所有计算,这样就不需要中间存储。我们用sqldf来说明。有关详细信息,请参阅sqldf home page。
备选方案1 在此方法中请注意,我们使用dbname = tempfile()
,以便它在外部数据库中执行所有计算(它会动态创建并自动删除),而不是在存储器中。
library(sqldf)
gc()
DF <- sqldf("select x.rowid x, y.rowid y,
max(x.B - y.B, 0) B, max(x.C - y.C, 0) C,
max(x.D - y.D, 0) D, max(x.E - y.E, 0) E,
max(x.F - y.F, 0) F, max(x.G - y.G, 0) G,
max(x.H - y.H, 0) H, max(x.I - y.I, 0) I
from df1 x, df1 y
where x.rowid > y.rowid", dbname = tempfile())
这只需要我们能够在工作区中存储df1
和DF
。
备选方案2 。如果即使溢出,我们也可以写出df1
,删除它,执行下面的计算,然后我们只需要足够的存储来存储结果DF
。
read.csv.sql
默认使用dbname = tempfile()
,所以在这种情况下我们不需要指定它。
write.table(df1, "data.txt", sep = ",", quote = FALSE)
rm(df1)
gc()
DF <- read.csv.sql("data.txt", sql = "select
x.rowid x, y.rowid y,
max(x.B - y.B, 0) B, max(x.C - y.C, 0) C,
max(x.D - y.D, 0) D, max(x.E - y.E, 0) E,
max(x.F - y.F, 0) F, max(x.G - y.G, 0) G,
max(x.H - y.H, 0) H, max(x.I - y.I, 0) I
from file x, file y
where x.rowid > y.rowid")
(当然,如果真的那么大,那么你也可能无法对其进行任何后续计算。)
<强>输出即可。无论如何,两种替代方案都给出了如下所示的相同结果。 x和y显示减去了哪些输入行。
> DF
x y B C D E F G H I
1 2 1 0 0 0 0 0 1 0 1
2 3 1 0 0 0 0 1 0 1 0
3 3 2 1 0 1 0 1 0 1 0
注意即可。虽然问题是要求优化内存而不是速度如果速度是一个问题,可以添加索引。
答案 2 :(得分:3)
由于数据是同质的,因此请使用矩阵表示。组织它,以便'行'是列,如
m <- t(as.matrix(df1))
mode(m) <- "integer" # maybe already true?
为答案预先分配空间
n <- ncol(m) - 1
ans <- matrix(0L, nrow(m), (n+1) * n / 2)
我们希望将列1
与列1:n + 1L
进行比较(1L
将第一个值视为整数值,而不是实数)。这是m[,1] - m[, 1:n + 1L]
,使用R的回收。迭代列,idx
和off
有助于跟踪我们要比较的列的索引,以及答案中的展示位列
off <- 0
for (i in 1:n) {
idx <- i:n + 1L
ans[, off + seq_along(idx)] <- m[, i] - m[, idx]
off <- off + length(idx)
}
最后一步是
ans[ans<0L] <- 0L
除非m[,1] == 1 & m[, 1:n + 1L] == 0
,否则认识到原始操作下的真值表为0可能会有额外的效率。同样,如果空间是一个严重的问题,那么数据可能表示为mode(m) <- "raw"
,并且算术运算被刚刚建议的比较所取代,如下所示:
m <- t(as.matrix(df1))
mode(m) <- "raw"
off <- 0
x0 <- as.raw(0); x1 <- as.raw(1)
ans <- matrix(raw(), nrow(m), (n+1) * n / 2)
for (i in 1:n) {
idx <- i:n + 1L
updt <- which((m[, i] == x1) & (m[, idx] == x0))
ans[off + updt] <- x1
off <- off + length(idx) * nrow(ans)
}