![大数据分析与应用实战:统计机器学习之数据导向编程](https://wfqqreader-1252317822.image.myqcloud.com/cover/943/44509943/b_44509943.jpg)
1.5 向量化与隐式循环
数据分析语言的有趣特征之一是函数可以应用许多不同的数据对象,如向量、矩阵、数组与数据集等,而非仅仅标量而已,此即称为向量化(vectorization),请看下面范例(Kabacoff,2015)。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P85_20096.jpg?sign=1739086880-iOChMt8WvZMJ03jBv8ZuDxzxH5Akqk4X-0-6616325450b5105b936bf884a5ab9232)
上例中a为一常数(标量),而函数sqrt()如同计算机(calculator)一般执行于单值标量上。如果将函数round()与log()分别应用到一维向量或二维的矩阵,其结果如下:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_19907.jpg?sign=1739086880-mSfVLnzPawxoJEqppCkY1Be7EDo0v7md-0-71b72c451754babb3864bb7d6af23307)
从上面的结果读者不难发现,sqrt()、round()与log()等函数是施加在数据中的每一个元素上,但是有些函数就并非如此执行了!例如下面常用的平均值计算函数mean():
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20097.jpg?sign=1739086880-koI0ZohC4TPSsl6ZdEWSEveSvp6sscGy-0-73629fbfee41399b869b25627a8994e6)
mean()函数计算矩阵m的12个元素的算术平均数,因此读者须经常注意输入的数据对象(此处m为3×4矩阵),经函数处理后产生的输出对象(上例传回单值),其维度是否改变?数据结构是否改变?类型是否改变?这是掌握数据驱动程序设计的重要概念(参见1.9节程序调试与效率监测)。
上例中如果要计算矩阵m三行的平均值或四列的平均值,可以运用apply()函数:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20098.jpg?sign=1739086880-s1tIhlTTcpBlQWqzj1IbS9Wvmbik6HCl-0-e02f5a7e7708402713a12b13c55f235d)
其语法为:
apply (m, MARGIN, FUN,...)
apply()函数是将FUN运算施加于矩阵或数组对象的某一维度上,其中m是数组(包括二维矩阵),MARGIN是给定运作维度的数值向量或字符串向量,FUN是欲应用的函数,而...是额外要传入FUN的参数值。m为二维的矩阵或数据集时,MARGIN=1表示逐行套用函数,MARGIN=2表示逐列套用函数。在数据驱动的程序语言中,R或Python的numpy与pandas等模块,建议避免写显式循环(explicit looping,即for循环),而以隐式循环(implicit looping)的apply()系列函数取代之,不过上例中apply()还是比rowMeans()或colMeans()等更直接的向量化函数慢。Python语言apply()方法的编程应用,请参见1.6节编程范式与面向对象概念、2.2.3节Python语言群组与摘要,以及4.2.2节在线音乐城关联规则分析等各节范例。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P87_19908.jpg?sign=1739086880-ZRMB7SOHQTP6rUx9LKHjORsoInyR0H0p-0-96d715d8c946ae88a09c0e67805b740e)
若为一维的向量或列表对象,可以使用lapply()或sapply()函数,两者执行方式相同,其中"s"意指简化(simplify),此函数在必要时将简化lapply()函数返回的数据对象。以下用简例说明两者的用法:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_20100.jpg?sign=1739086880-2ccGTv5xGJAGMLBJoNa5aAuq0InfnA55-0-cf1b0c0e9d732597ea46280cae49cabe)
R语言apply系列函数众多,mapply()可施加一个函数于多个列表或向量的对应元素上,下例中firstList与secondList均是长度为3的列表对象,mapply()将identical()函数施加在上述两列表的对应元素上,判断其是否完全相同。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_19909.jpg?sign=1739086880-gzEgAcLbb5Y3OkSXOdVCyjnxX3AE2e4P-0-3c5193aaabf57d2071240a4853204853)
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19910.jpg?sign=1739086880-mxHeQoYdE58hkawPYZb4ymtoXflSZyRn-0-fb47f28422dd18f9f22598c5695c13d8)
mapply()函数语法中的FUN也可以是如下自定义的匿名函数(anonymous function),计算firstList与secondList对应元素的列数和,其他apply系列函数也可以调用自定义的匿名函数。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19911.jpg?sign=1739086880-AA1oAtKyaGQzelOCxOabiwrtUyOAeDfq-0-379e594770c0a1d4fdacca661aac2265)
活用mapply()函数有时可以快速完成某些分组处理或可视化的工作,下例在mapply()函数中定义绘制各组回归直线的匿名函数后,将其添加到iris数据集的Sepal.Width对Petal.Length的散点图上,然后在适当位置标出图例(图1.9)(Verzani,2014)。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P90_19912.jpg?sign=1739086880-TEjyiCCDFgjUHGxraA04tmJFgS4nMu0A-0-6e79d2c09030174e22e70e17ac4071c7)
图1.9 鸢尾花花萼宽度与花瓣长度分布情形及分组回归直线图
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P91_19914.jpg?sign=1739086880-LPgDZ7bwnimoW0weJvKipgp38nd27CN9-0-99c4645426b1214b9369780813f93a0c)
其实数据驱动程序设计中输入输出的变量符号(symbol)大多是数据对象,它们可能是一维、二维或更高维的结构。因此,数据分析语言多采用向量化数据处理与计算的方式,以避免额外循环执行,提升工作效率。许多运算符(如乘方运算符^、比较运算符>、加号+)及函数(如apply(),lapply(),sapply(),scale(),rowMeans(),colMeans())都是向量化函数,也就是说函数中隐藏着循环(implicit looping)的处理方式。所以再次提醒读者注意思考下面问题:输入的数据对象经函数处理后产生的输出对象,其维度是否改变?数据结构是否改变?类型是否改变?(参见1.9节程序调试与效率监测)反复思索上述问题可以掌握数据驱动程序设计背后的运行逻辑。