• 机器学习
  • 将两个向量按一定条件变成数据框的问题

我现有个数字型向量,长度不一定相等:

start=c(7,9,12,14)

end=c(11,12,24,32)

我希望得到一个数据框,满足以下几个条件:

1. start<end

2. (end-start) >= 5

3. (end-start) <= 20

然后得到以下数据框,

start end

7 12

7 24

9 24

12 24

12 32

14 24

14 32

请问怎么用code实现呢。R刚入门,提示告诉用什么函数和大致思路就可以,谢谢!

我想到可以用两个嵌套的循环的可以解决这个问题,但是担心效率太低,因为我的原始数据的两个向量长度都超过10亿,不知道有没有高效一点的解决方案呢?

第一个条件是多余的,满足第二个条件的必然满足第一个

ls <- lapply(start, function(x) expand.grid(x, end[end >= (x+5) & end <= (x+20)]))

df <- do.call(rbind, ls)

你这个计算很可能需要放在集群上运行 或者用其他更高效的语言;耗时太长。。。。

C / Rcpp 写循环。

这里直接做向量运算太占内存。

即使有内存,用 R 算也太慢。

3 楼 expand.grid lapply do.call 这些只是表面上避免了循环,但是嵌套速度损失很多。虽然可能比纯一维向量操作节约内存,但是我想不会比后者直接算速度快。

回复 第5楼 的 肖楠:可否展开说说怎么直接算?是指用for循环一个个算吗?

简单比较了一下用我的方法跟用for 循环的耗时;for循环的耗时是用lapply的30倍:

start <- sample(10^5, 10^4)

end <- sample(10^5, 10^4)

ptm <- proc.time()

ls <- lapply(start, function(x) expand.grid(x, end[end >= (x+5) & end <= (x+20)]))

df <- do.call(rbind, ls)

t1 <- proc.time() - ptm

ptm <- proc.time()

df2 <- matrix(nrow=0, ncol = 2)

for (i in start) {

for (j in end) {

if(j>=i+5 & j <=i+20)

df2 <- rbind(df2, c(i,j))

}

}

t2 <- proc.time() - ptm

t1

t2

> t1

user system elapsed

5.43 0.59 6.02

> t2

user system elapsed

181.07 0.02 181.34

回复 第6楼 的 Robert_Hoo:

<br />
x = sample(10^5, 10^4)<br />
y = sample(10^5, 10^4)</p>
<p>m = rep(1:length(x), each  = length(y))<br />
n = rep(1:length(y), times = length(x))<br />
z = y[n] - x[m]<br />
i = which(z >= 5 & z <= 20)<br />
df = data.frame(start = x[m[i]], end = y[n[i]])<br />
</p>

回复 第7楼 的 肖楠:

嗯 学习了;这种算法以前还真没有怎么见过。。。。时间跟用lapply 几乎一样了。。。。

这种方法也确实不大适合做大的运算,m,n,z三个矩阵占的内存太吓人了。。。。

> object.size(n)

400000040 bytes

> object.size(m)

400000040 bytes

> object.size(z)

400000040 bytes

用lapply的话只产生了一个中间变量,占的内存不到n,m,z总和的2%

> object.size(ls)

18257976 bytes

回复 第8楼 的 Robert_Hoo:

嗯,你的方法挺不错的,其实比我的要好。学习了!

学习了!我都试一下,试过后再把结果放上来。谢谢各位了![s:13]

回复 第10楼 的 xshang:

你这个数据量用这些方法都是行不通的。。。。

按照10^4的数据耗时5秒来算,10^9的数据耗时将会是 5*10^(9*2)/10^(4*2) = 5* 10^10秒=1585年

即使你转到C,如果用遍历的方法的话,你的耗时也不会短;

你得观察你的数据,减少start在end里的搜索范围,不用每次都遍历10亿次。。。。

回复 第11楼 的 Robert_Hoo:

谢谢!有道理。确实可以加限制条件的。我的两个向量都是从1到10e9中间的序号,但是两个向量长度是不一样的。我可以在1:10e9之间按照值大小把两个向量分成若干份,因为我要比较的条件就是 (end-start) >= 100, (end-start) <= 350,这样是可以把向量分成很多份然后再合并的。这样应该会快的多。

回复 第4楼 的 肖楠:搭车问 如果循环里面有readLines的操作 能用Rcpp吗

回复 第13楼 的 ypchen:

理论上是可以的,不过一般都会选择直接用 C++ 读 。。。

回复 第11楼 的 Robert_Hoo:

如果将两个向量都排序呢,然后在end里面搜索到start + 300的位置就停止搜索,然后走到start的下一个,这样应该会很快吧。怎么实现呢?

不知道我理解的是否正确, 你是这样的意思吗:

假设start里面有一个数999, 排在100位;那么在end里面搜索的范围是排在100位到400位之间的数, 也就是end[100:400]里面搜索符合 (end-999) >= 5 & (end-999) <= 20条件的:

start <- sample(10^6, 10^4)

end <- sample(10^6, 10^5)

start <- sort(start)

end <- sort(end)

start <- cbind(data = start, ind = 1:length(start))

ln.end <- length(end)

ptm <- proc.time()

ls <- apply(start,1,function(x) {end.sr= end[x[2]:min((x[2]+300),ln.end)]

expand.grid(x[1], end.sr[end.sr >= (x[1]+5) & end.sr <= (x[1]+20)])})

df <- do.call(rbind, ls)

t1 <- proc.time() - ptm

t1

应该几天时间就可以得结果,前提是你的电脑内存得够大,毕竟你两个变量都是10亿长度的,产生的结果还有ls跟df也很大。。。。。

这个其实用sql做挺简单的, 比如用sqldf包

s <- data.frame(start)

e <- data.frame(end)

sqldf("select * from s, e where end >= start + 5 and end <= start + 20")

start end

1 7 12

2 7 24

3 9 24

4 12 24

5 12 32

6 14 24

7 14 32

回复 第16楼 的 Robert_Hoo:

嗯,就是这意思,这样快很多。我把整个数据分成几部分了,降了一个数量级,这样内存耗的小点。

回复 第17楼 的 ntsean:

不熟悉SQL,不过值得学习。我查sqldf的说明,倒是说它的特点就是快,试试。

23 天 后

楼上全是高手,小菜鸟望洋兴叹啊。