R: 坏循环代码,提高速度?
R: Bad loop code, improve the speed?
我创建了一些基本代码来实现我的需要,但它是糟糕的代码,速度非常慢。目的是从 SE 列中取出一行,如果它与 SC 列匹配,则为它所属的每 5 分钟括号将计数器加 1。
我写的代码是
for (i in 1:(nrow(SC)))
for(j in 1:(nrow(SE)))
for (k in 0:5)
if ( (SE[j,3]==SC[i,1]) &
(as.POSIXlt(SE[j,1])>as.POSIXlt(SC[i,4]) +k*5*60)&
(as.POSIXlt(SE[j,1])<=as.POSIXlt(SC[i,4])+ (k+1)*5*60 ) &
(SE[j,2]==1) )
{ SC[i,6+k]=SC[i,6+k]+1 }
也就是说检查 SC 的每个单元格以查看条件是否相同(房间号相同,时间范围内的时间等)。
这是非常低效的,因为三重循环在 R 中需要永远。正在寻找替换循环的方法,也许是向量或应用?
> SE
UTC pin Room
1 2014-12-22 10:14:34 1 Alpha
4 2014-12-22 10:15:27 1 Alpha
5 2014-12-22 10:16:00 1 Alpha
8 2014-12-22 10:18:10 1 Alpha
12 2014-12-22 10:19:06 1 Alpha
13 2014-12-22 10:20:00 1 Alpha
14 2014-12-22 10:08:34 1 Beta
17 2014-12-22 10:15:29 1 Beta
18 2014-12-22 10:16:00 1 Beta
19 2014-12-22 10:17:00 1 Beta
22 2014-12-22 10:18:10 1 Beta
24 2014-12-22 10:19:00 1 Beta
26 2014-12-22 10:19:11 1 Beta
28 2014-12-22 10:09:34 1 Gamma
29 2014-12-22 10:39:11 1 Gamma
> SC
Room Capacity Video.Conference ST ET
1 Alpha 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00
2 Alpha 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00
3 Beta 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00
4 Beta 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00
5 Gamma 10 0 2014-12-22 10:00:00 2014-12-22 10:30:00
6 Gamma 10 0 2014-12-22 10:30:00 2014-12-22 11:00:00
>Desired #This is the intended output
X Room Capacity Vid ST ET X0.to.5.min X5.to.10.min X10.to.15.min X15.to.20.min X20.to.25.min X25.to.30.min
1 Alpha 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00 0 0 1 5 0 0
2 Alpha 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00 0 0 0 0 0 0
3 Beta 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00 0 1 0 6 0 0
4 Beta 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00 0 0 0 0 0 0
5 Gamma 10 0 2014-12-22 10:00:00 2014-12-22 10:30:00 0 1 0 0 0 0
6 Gamma 10 0 2014-12-22 10:30:00 2014-12-22 11:00:00 0 1 0 0 0 0
您可以减少 for 循环中的函数调用次数(尤其是 as.POSIXlt
调用),这应该会有所帮助。此外,&&
运算符可能运行得更快,因为在第一个条件为 false 之后不会对后续比较进行评估。
posix.SE <- as.POSIXlt(SE[,1])
posix.SC <- as.POSIXlt(SC[,4])
for (i in 1:(nrow(SC)))
for(j in 1:(nrow(SE)))
for (k in (0:5))
if ( (SE[j,3]==SC[i,1]) &&
(posix.SE[j]>posix.SC[i] + k*300)&&
(posix.SE[j]<=posix.SC[i]+ (k+1)*300 ) &&
(SE[j,2]==1) ) {
SC[i,6+k]=SC[i,6+k]+1
}
您还可以通过以下方式减少 if
子句中第一个条件的计算次数:
for(val in unique(SE[,3]))
for(i in which(SC[,1] == val))
for(j in which(SE[,3] == val))
for (k in (0:5))
if ((posix.SE[j]>posix.SC[i] + k*300)&&
(posix.SE[j]<=posix.SC[i]+ (k+1)*300 ) &&
(SE[j,2]==1) ) {
SC[i,6+k]=SC[i,6+k]+1
}
使用 'outer' 可能比
更有效
for(val in unique(SC[,1])){
# index the relevent rows for each value in SC[,1]
index.SC <- which(SC[,1] == val)
index.SE <- which(SE[,3] == val & SE[,2]==1)
MX <- outer(posix.SE[index.SE], posix.SC[index.SC],`-`)
for (k in (0:5))
SC[indxe.SC,6+k] <- apply((MX > k*300)& (MX <= (k+1)*300 ),2,sum)
}
[如果 SC[1] 是您要在 for 循环中使用 'levels(SC[1]) 而不是 unique(SC[1]) 的一个因素...]
你的算法目前是 运行 O(n^2) 如果你对 SE 和 SC 列的属性一无所知,这是你能做的最好的。
如果任一列中的数据具有某些特定属性,那么您可以进行一些优化。例如
SE 中的条目是否唯一?如果是这样,那么您可以删除匹配项
SC 所以他们不会再次检查。
SE 或 SC 中的条目是否已排序?如果是这样,那么你可以使用
比较短路 SC 中的搜索(例如:如果 SC 被排序
按递增顺序,然后在检查匹配项时是否是我所在的行
比较大于我正在寻找的然后我保释
因为不会再有匹配项了)
进一步采纳@jthorpe 的建议,在可能的情况下进行矢量化并提取通用计算
step <- 5 * 60
se <- as.POSIXlt(SE[,1]) / step
sc <- as.POSIXlt(SC[,4]) / step
k <- 0:5
更新 data.frame 成本很高,因此创建一个矩阵来包含答案
ans <- as.matrix(SC[, 6 + 0:5])
重新安排循环,以便可以向量化第一个和最后一个测试标准的计算
for (j in seq_along(se)[SE[,2] == 1])
for (i in seq_along(sc)[SE[j, 3] == SC[,1]])
并对最内层循环进行矢量化
{
d <- se[j] - sc[i]
idx <- k[(d > k) & (d <= (k + 1))] + 1
ans[i, idx] <- ans[i, idx] + 1
}
正如@hhafez 指出的那样,这仍然是一个二次时间算法,使用您的数据的属性可能还有很大的改进空间。
我创建了一些基本代码来实现我的需要,但它是糟糕的代码,速度非常慢。目的是从 SE 列中取出一行,如果它与 SC 列匹配,则为它所属的每 5 分钟括号将计数器加 1。
我写的代码是
for (i in 1:(nrow(SC)))
for(j in 1:(nrow(SE)))
for (k in 0:5)
if ( (SE[j,3]==SC[i,1]) &
(as.POSIXlt(SE[j,1])>as.POSIXlt(SC[i,4]) +k*5*60)&
(as.POSIXlt(SE[j,1])<=as.POSIXlt(SC[i,4])+ (k+1)*5*60 ) &
(SE[j,2]==1) )
{ SC[i,6+k]=SC[i,6+k]+1 }
也就是说检查 SC 的每个单元格以查看条件是否相同(房间号相同,时间范围内的时间等)。
这是非常低效的,因为三重循环在 R 中需要永远。正在寻找替换循环的方法,也许是向量或应用?
> SE
UTC pin Room
1 2014-12-22 10:14:34 1 Alpha
4 2014-12-22 10:15:27 1 Alpha
5 2014-12-22 10:16:00 1 Alpha
8 2014-12-22 10:18:10 1 Alpha
12 2014-12-22 10:19:06 1 Alpha
13 2014-12-22 10:20:00 1 Alpha
14 2014-12-22 10:08:34 1 Beta
17 2014-12-22 10:15:29 1 Beta
18 2014-12-22 10:16:00 1 Beta
19 2014-12-22 10:17:00 1 Beta
22 2014-12-22 10:18:10 1 Beta
24 2014-12-22 10:19:00 1 Beta
26 2014-12-22 10:19:11 1 Beta
28 2014-12-22 10:09:34 1 Gamma
29 2014-12-22 10:39:11 1 Gamma
> SC
Room Capacity Video.Conference ST ET
1 Alpha 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00
2 Alpha 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00
3 Beta 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00
4 Beta 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00
5 Gamma 10 0 2014-12-22 10:00:00 2014-12-22 10:30:00
6 Gamma 10 0 2014-12-22 10:30:00 2014-12-22 11:00:00
>Desired #This is the intended output
X Room Capacity Vid ST ET X0.to.5.min X5.to.10.min X10.to.15.min X15.to.20.min X20.to.25.min X25.to.30.min
1 Alpha 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00 0 0 1 5 0 0
2 Alpha 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00 0 0 0 0 0 0
3 Beta 16 1 2014-12-22 10:00:00 2014-12-22 10:30:00 0 1 0 6 0 0
4 Beta 16 1 2014-12-22 10:30:00 2014-12-22 11:00:00 0 0 0 0 0 0
5 Gamma 10 0 2014-12-22 10:00:00 2014-12-22 10:30:00 0 1 0 0 0 0
6 Gamma 10 0 2014-12-22 10:30:00 2014-12-22 11:00:00 0 1 0 0 0 0
您可以减少 for 循环中的函数调用次数(尤其是 as.POSIXlt
调用),这应该会有所帮助。此外,&&
运算符可能运行得更快,因为在第一个条件为 false 之后不会对后续比较进行评估。
posix.SE <- as.POSIXlt(SE[,1])
posix.SC <- as.POSIXlt(SC[,4])
for (i in 1:(nrow(SC)))
for(j in 1:(nrow(SE)))
for (k in (0:5))
if ( (SE[j,3]==SC[i,1]) &&
(posix.SE[j]>posix.SC[i] + k*300)&&
(posix.SE[j]<=posix.SC[i]+ (k+1)*300 ) &&
(SE[j,2]==1) ) {
SC[i,6+k]=SC[i,6+k]+1
}
您还可以通过以下方式减少 if
子句中第一个条件的计算次数:
for(val in unique(SE[,3]))
for(i in which(SC[,1] == val))
for(j in which(SE[,3] == val))
for (k in (0:5))
if ((posix.SE[j]>posix.SC[i] + k*300)&&
(posix.SE[j]<=posix.SC[i]+ (k+1)*300 ) &&
(SE[j,2]==1) ) {
SC[i,6+k]=SC[i,6+k]+1
}
使用 'outer' 可能比
更有效for(val in unique(SC[,1])){
# index the relevent rows for each value in SC[,1]
index.SC <- which(SC[,1] == val)
index.SE <- which(SE[,3] == val & SE[,2]==1)
MX <- outer(posix.SE[index.SE], posix.SC[index.SC],`-`)
for (k in (0:5))
SC[indxe.SC,6+k] <- apply((MX > k*300)& (MX <= (k+1)*300 ),2,sum)
}
[如果 SC[1] 是您要在 for 循环中使用 'levels(SC[1]) 而不是 unique(SC[1]) 的一个因素...]
你的算法目前是 运行 O(n^2) 如果你对 SE 和 SC 列的属性一无所知,这是你能做的最好的。 如果任一列中的数据具有某些特定属性,那么您可以进行一些优化。例如
SE 中的条目是否唯一?如果是这样,那么您可以删除匹配项 SC 所以他们不会再次检查。
SE 或 SC 中的条目是否已排序?如果是这样,那么你可以使用 比较短路 SC 中的搜索(例如:如果 SC 被排序 按递增顺序,然后在检查匹配项时是否是我所在的行 比较大于我正在寻找的然后我保释 因为不会再有匹配项了)
进一步采纳@jthorpe 的建议,在可能的情况下进行矢量化并提取通用计算
step <- 5 * 60
se <- as.POSIXlt(SE[,1]) / step
sc <- as.POSIXlt(SC[,4]) / step
k <- 0:5
更新 data.frame 成本很高,因此创建一个矩阵来包含答案
ans <- as.matrix(SC[, 6 + 0:5])
重新安排循环,以便可以向量化第一个和最后一个测试标准的计算
for (j in seq_along(se)[SE[,2] == 1])
for (i in seq_along(sc)[SE[j, 3] == SC[,1]])
并对最内层循环进行矢量化
{
d <- se[j] - sc[i]
idx <- k[(d > k) & (d <= (k + 1))] + 1
ans[i, idx] <- ans[i, idx] + 1
}
正如@hhafez 指出的那样,这仍然是一个二次时间算法,使用您的数据的属性可能还有很大的改进空间。