《R语言编程艺术》——2.5 使用all()和any()

2.5 使用all()和any()

any() 和all() 函数非常方便快捷,它们分别报告其参数是否至少有一个或全部为TRUE。

《R语言编程艺术》——2.5 使用all()和any()

any()函数判断这些值是否至少一个为TURE。all()函数的功能类似,它判断这些值是否全部为TRUE。
2.5.1 扩展案例:寻找连续出现1 的游程
假设一个向量由若干0 和1 构成,我们想找出其中连续出现1 的游程。例如,对于向量(1,0,0,1,1,1,0,1,1),从它第4索引处开始有长度为3的游程,而长度为2的游程分别始于第4,第5和第8索引的位置。因此,用语句findruns(c(1,0,0,1,1,1,0,1,1),2)调用下面展示的函数,返回结果(4,5,8)。代码如下:
《R语言编程艺术》——2.5 使用all()和any()

第五行,我们需要判断从x[i]开始的连续k个值,即x[i],x[i+1],...,x[i+k-1]的值,是否全部为1。表达式x[i:(i+k-1)]语句给出了上述子向量的值,然后使用all()函数检验它是否是一个游程。
我们对它进行一下测试:
《R语言编程艺术》——2.5 使用all()和any()

尽管前面的代码中使用all()比较好,但建立向量runs的过程并不理想。向量的内存分配过程比较耗时,由于调用c(runs,i)时给新的向量分配了内存空间,每次执行时都会减慢代码的运行速度。(这与新向量赋值给runs无关,我们仍然给向量分配了内存空间。)
《R语言编程艺术》——2.5 使用all()和any()

在较短的循环中,这样做可能没问题,但当应用程序的运行性能受到重点关注时,这里有更好的方法。
一种替代方法是预先分配的内存空间,像这样:
《R语言编程艺术》——2.5 使用all()和any()

在第3行,我们给一个长度为n的向量分配了内存空间。这意味着在执行循环的过程中,可以避免分配新的内存。第8行代码做的只是填充runs。在退出函数之前,我们在第12行重新定义runs,来删除该向量中没用的部分。
这种方法更好,第一版代码可能会有很多次内存分配,而第二版代码将之减少为两次。如果我们确实需要提高速度,可能考虑使用C语言重新编码,这会在第14章中讨论。
2.5.2 扩展案例:预测离散值时间序列
假设我们观察到取值为0或1的数据,每个时刻一个值。为了了解具体应用,假设这是每天的天气数据:1代表有雨,0代表没有雨。假设已经知道最近几天是否下雨,我们希望预测明天是否会下雨。具体而言,对于某个k值,我们会根据最近k天的天气记录来预测明天的天气。我们将使用“过半数规则”(majority rule:):如果在最近k期里1的数量大于等于k/2,那么预测下一个值为1,否则,预测下一个值为0。例如如果k=3,最近三期的数据为1、0、1,则预测下一期值为1。
但是,我们应该如何选择k?显然,如果选择的值太小,则给我们用以预测的样本量太小。如果取值过大,导致我们使用过于早期的数据,而这些数据只有很少或根本没有预测价值。
一个解决方案是针对已知的数据(称为训练集),变换不同的k值,看看预测效果如何。
在天气的例子中,假设我们有500天的数据,假设我们考虑使用k=3。为了评价k值的预测能力,我们基于前三天的数据来“预测”每天的数据,然后将预测值与已知值进行对比。以此类推,对于k=1、k=2、k=4,我们做同样的事情,直到k值足够大。然后,我们使用训练数据中表现最好的k值,用于未来的预测。
那么我们如何编写R代码?这里有一个简单的方法:

《R语言编程艺术》——2.5 使用all()和any()

这段代码的核心在第7行。此处要预测第i+k天的值(预测结果保存在pred[i]),利用的是之前k天的值,也即第i天,……,第i+k-1天的值。因此,我们需要算出这些天中1的个数。由于我们处理的是0-1数据,1的数量可以简单地使用这些天x[j]的总和,它可以很方便地用以下方法获取:

《R语言编程艺术》——2.5 使用all()和any()

使用sum()函数和向量索引使得计算更简捷,避免了循环,因此它更简单更快速。这是R语言典型的用法。
第9行的表达式也是同样的道理:

《R语言编程艺术》——2.5 使用all()和any()

在这里,pred包含预测值,而x[(k+1):n]是这些天的实际值。前者减去后者,得到的值要么为0,要么为1,或-1。在这里,1或-1对应两个方向的预测误差,即当真实值为1时预测值为0,或者真实值为0时预测为1。再用abs()函数求出绝对值,得到0和1的序列,后者表示预测有误差。
这样,我们就能知道哪些天的预测有误差,然后使用mean()来计算错误率,在这里我们应用了这一数学原理:即0-1数据的均值是1的比例。这是R语言的一个常见技巧。
上述preda()的编码是相当直截了当的,它的优点是简单和紧凑。然而,它可能很慢。我们可以尝试用向量化循环来加快速度,正如2.6节所讨论的那样。然而在这里它不能解决加速的主要障碍,即这些代码中所有的重复计算都不能避免。在循环中对于i的相邻两个取值,调用sum()函数求和的向量只相差两个元素。这会减慢速度,除非k值非常小。
所以,我们重写代码,计算过程中利用上一步计算的结果。在循环的每一次迭代中,将更新前一次得到的总和,而不是从头开始计算新的总和。
《R语言编程艺术》——2.5 使用all()和any()

关键在第9行。在这里从总和sm里减去最早的元素x[i-1],再加上新的元素(x[i+k-1]),从而更新sm。
另一种方法是使用R函数cumsum(),它能计算向量的累积和(cumulative sums)。这里是一个例子:
《R语言编程艺术》——2.5 使用all()和any()

在这里,y的累加和是5=5,5+2=7,5 + 2 + (-3) = 4,5 + 2 + (-3) + 8 = 12,这些值由cumsum()返回。
在上面的例子里,建议用cumsum()的差值替代preda()中的表达式sum(x[i:(i+(k-1))。

《R语言编程艺术》——2.5 使用all()和any()

在求x中连续k个元素(称为窗口)之和的时候,没有像下面这样使用sum()函数:

《R语言编程艺术》——2.5 使用all()和any()

这是为了保证在i=1时能计算出正确的值。
predb()函数里每次循环迭代要做两次减法运算,对predc()来说只需要做一次。

上一篇:【LeetCode刷题】744. 寻找比目标字母大的最小字母


下一篇:【Bug记录】Caused by: struts.apache.org - Class: java.net.AbstractPlainSocketImpl