![大数据分析与应用实战:统计机器学习之数据导向编程](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=1739694899-k2UbNvdKS583ZIP2unaf7D09u46sUqOC-0-a481aa098efee74aa397f7e8d52d6966)
上例中a为一常数(标量),而函数sqrt()如同计算机(calculator)一般执行于单值标量上。如果将函数round()与log()分别应用到一维向量或二维的矩阵,其结果如下:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_19907.jpg?sign=1739694899-hkplzR5BK9xi4dqrlgilBrw4oXssOmgB-0-a81dbdcaffd81893cd0559e9104c58bc)
从上面的结果读者不难发现,sqrt()、round()与log()等函数是施加在数据中的每一个元素上,但是有些函数就并非如此执行了!例如下面常用的平均值计算函数mean():
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20097.jpg?sign=1739694899-65z3QMsdNTQnI0nuD6JHT77RdMWRSx3p-0-3c3990e4f9c9240cd0a43537d5bd5d8d)
mean()函数计算矩阵m的12个元素的算术平均数,因此读者须经常注意输入的数据对象(此处m为3×4矩阵),经函数处理后产生的输出对象(上例传回单值),其维度是否改变?数据结构是否改变?类型是否改变?这是掌握数据驱动程序设计的重要概念(参见1.9节程序调试与效率监测)。
上例中如果要计算矩阵m三行的平均值或四列的平均值,可以运用apply()函数:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20098.jpg?sign=1739694899-Y2zXX0dfpMxthg31YDA87b3DjGtKT9SF-0-ad2e8286e91bc9d9d8f4cd7036158d84)
其语法为:
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=1739694899-AdhIAoaw1OL79XsuN8BbJMjfF779wFkL-0-66985c14231ac32c24e5910fa31a8eb0)
若为一维的向量或列表对象,可以使用lapply()或sapply()函数,两者执行方式相同,其中"s"意指简化(simplify),此函数在必要时将简化lapply()函数返回的数据对象。以下用简例说明两者的用法:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_20100.jpg?sign=1739694899-691BNKkmauURX2qKYSQi3dtOGR9sW7pq-0-f165776eee04fe5164cbcc80d3349616)
R语言apply系列函数众多,mapply()可施加一个函数于多个列表或向量的对应元素上,下例中firstList与secondList均是长度为3的列表对象,mapply()将identical()函数施加在上述两列表的对应元素上,判断其是否完全相同。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_19909.jpg?sign=1739694899-pN5f1woTWEaWsu8KtO4HAUZe2szn1AUD-0-3717405be1114cbea8e96f798c08e446)
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19910.jpg?sign=1739694899-I2JvQqNIh6BWVrg00j5bi21b9hTj761h-0-abb425612069156cfd4c3714cfeff9c9)
mapply()函数语法中的FUN也可以是如下自定义的匿名函数(anonymous function),计算firstList与secondList对应元素的列数和,其他apply系列函数也可以调用自定义的匿名函数。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19911.jpg?sign=1739694899-pfHguG8S6qW14pmTSMremsSo0VKhF61y-0-f22136c015982449f38e4e362a3e608d)
活用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=1739694899-dpND3lKO7RtFmGRfDopoVgC6UKLWmqe8-0-70b170024687be9376a90ccf1ea2ef12)
图1.9 鸢尾花花萼宽度与花瓣长度分布情形及分组回归直线图
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P91_19914.jpg?sign=1739694899-GasyyEdN2RVD9Ht11GwwWqa0BSqZjok1-0-5756710fed07f4f826420161e5abde1b)
其实数据驱动程序设计中输入输出的变量符号(symbol)大多是数据对象,它们可能是一维、二维或更高维的结构。因此,数据分析语言多采用向量化数据处理与计算的方式,以避免额外循环执行,提升工作效率。许多运算符(如乘方运算符^、比较运算符>、加号+)及函数(如apply(),lapply(),sapply(),scale(),rowMeans(),colMeans())都是向量化函数,也就是说函数中隐藏着循环(implicit looping)的处理方式。所以再次提醒读者注意思考下面问题:输入的数据对象经函数处理后产生的输出对象,其维度是否改变?数据结构是否改变?类型是否改变?(参见1.9节程序调试与效率监测)反复思索上述问题可以掌握数据驱动程序设计背后的运行逻辑。