- 大数据分析与应用实战:统计机器学习之数据导向编程
- 邹庆士编著
- 1244字
- 2022-07-28 20:15:40
1.5 向量化与隐式循环
数据分析语言的有趣特征之一是函数可以应用许多不同的数据对象,如向量、矩阵、数组与数据集等,而非仅仅标量而已,此即称为向量化(vectorization),请看下面范例(Kabacoff,2015)。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P85_20096.jpg?sign=1739061145-90mNb5b76ifNI0eUCeshsOc9h2e2mPNJ-0-c9db3efabf2e17bf178df7e77d9eee8e)
上例中a为一常数(标量),而函数sqrt()如同计算机(calculator)一般执行于单值标量上。如果将函数round()与log()分别应用到一维向量或二维的矩阵,其结果如下:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_19907.jpg?sign=1739061145-8H3rTzn9YpWt9z6QfEuAgOUakfEi4NPm-0-67b69ffc91dd81eea3fc1846e78d60aa)
从上面的结果读者不难发现,sqrt()、round()与log()等函数是施加在数据中的每一个元素上,但是有些函数就并非如此执行了!例如下面常用的平均值计算函数mean():
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20097.jpg?sign=1739061145-wZ0GSJTl0oDNiaATCJGUZ8G0exGeqrct-0-157cccb0b1e8faea142391958b10666a)
mean()函数计算矩阵m的12个元素的算术平均数,因此读者须经常注意输入的数据对象(此处m为3×4矩阵),经函数处理后产生的输出对象(上例传回单值),其维度是否改变?数据结构是否改变?类型是否改变?这是掌握数据驱动程序设计的重要概念(参见1.9节程序调试与效率监测)。
上例中如果要计算矩阵m三行的平均值或四列的平均值,可以运用apply()函数:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P86_20098.jpg?sign=1739061145-sLFGYjyn4eqfz7CmuoUXySosJyF8NBJR-0-00a72e2fc4398bd04f237099481df763)
其语法为:
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=1739061145-Hzd53SvMjXh4victvjnuoVjpjqUcmjVl-0-126ff9e05cd7fa33951e38286c9b91de)
若为一维的向量或列表对象,可以使用lapply()或sapply()函数,两者执行方式相同,其中"s"意指简化(simplify),此函数在必要时将简化lapply()函数返回的数据对象。以下用简例说明两者的用法:
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_20100.jpg?sign=1739061145-h9J4weHc56E5UluvxvpQRSVjn6bvEaRE-0-2ec465300988ece76e38938b50e80be7)
R语言apply系列函数众多,mapply()可施加一个函数于多个列表或向量的对应元素上,下例中firstList与secondList均是长度为3的列表对象,mapply()将identical()函数施加在上述两列表的对应元素上,判断其是否完全相同。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P88_19909.jpg?sign=1739061145-kVqsM9AA8hPRXMCvDereF2BSPgUdcjeu-0-533818dba38be9f29007655631d49ab5)
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19910.jpg?sign=1739061145-f8xvl4qyljhHyK0hBvAnr0XuGRsShbPd-0-12ce52972c8a2f813528cb17b299a19c)
mapply()函数语法中的FUN也可以是如下自定义的匿名函数(anonymous function),计算firstList与secondList对应元素的列数和,其他apply系列函数也可以调用自定义的匿名函数。
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P89_19911.jpg?sign=1739061145-JHLI9vwywSUcJBDhlSnKNflR9x13DWTY-0-600fdb43e97ef01305f6baa693aded8a)
活用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=1739061145-5eihUauvcm2l4H9aa3KoT7dUnu0iAJ4N-0-8ca84f7e68f3e93989028592f74f184c)
图1.9 鸢尾花花萼宽度与花瓣长度分布情形及分组回归直线图
![](https://epubservercos.yuewen.com/027AFF/23721555909466506/epubprivate/OEBPS/Images/Figure-P91_19914.jpg?sign=1739061145-JItq04Py581JW9ErOSie6DZDKaHIZhRL-0-c3d750b99663fdec20d7d31c8abfc29b)
其实数据驱动程序设计中输入输出的变量符号(symbol)大多是数据对象,它们可能是一维、二维或更高维的结构。因此,数据分析语言多采用向量化数据处理与计算的方式,以避免额外循环执行,提升工作效率。许多运算符(如乘方运算符^、比较运算符>、加号+)及函数(如apply(),lapply(),sapply(),scale(),rowMeans(),colMeans())都是向量化函数,也就是说函数中隐藏着循环(implicit looping)的处理方式。所以再次提醒读者注意思考下面问题:输入的数据对象经函数处理后产生的输出对象,其维度是否改变?数据结构是否改变?类型是否改变?(参见1.9节程序调试与效率监测)反复思索上述问题可以掌握数据驱动程序设计背后的运行逻辑。