这里是佳奥!让我们继续R的数据管理学习!
本篇分为三个基本部分。
在第一部分中,我们将快速浏览R中的多种数学、统计和字符处理函数。
接下来,我们将讲解如何自己编写函数来完成数据处理和分析任务。包括探索控制程序流程的多种方式,循环和条件执行语句,以及研究用户自编函数的结构,在编写完成如何调用它们。
最后,我们将了解数据的整合和概述方法,以及数据集的重塑和重构方法。
让我们开始吧!
1 一个数据处理难题
让我们首先考虑一个数据处理问题:
一组学生参加了数学、科学和英语考试。为了给所有学生确定一个单一的成绩衡量指标,需要将这些科目的成绩组合起来。
另外,我们还想将前20%的学生评定为A,接下来20%的学生评定为B,依次类推。
最后,我们希望按字母顺序对学生排序。数据如下:
观察此数据集,马上可以发现一些明显的障碍。
首先,三科考试的成绩是无法比较的。由于它们的均值和标准差相去甚远,所以对它们求平均值是没有意义的。我们在组合这些考试成绩之前,必须将其变换为可比较的单元。
其次,为了评定等级,我们需要一种方法来确定某个学生在前述得分上百分比排名。再次,表示姓名的字段只有一个,这让排序任务复杂化了。为了正确地将其排序,需要将姓和名拆开。
以上每一个任务都可以巧妙地利用R中的数值和字符处理函数完成。在学习完下一节中的各种函数之后,我们将考虑一套可行的解决方,以解决这项数据处理难题。
2 数值和字符处理函数
本节我们将综述R中作为数据处理基石的函数,它们可分为数值(数学、统计、概率)函数和字符处理函数。并展示如何将函数应用到矩阵和数据框的行列上。
2.1 数学函数
对数据做变换是这些函数的一个主要用途。
上表中的示例将数学函数应用到了标量(单独的数值)上。
当这些函数被应用于数值向量、矩阵或数据框时,它们会作用于每一个独立的值。
例如:sqrt(c(4, 16, 25))的返回值为c(2,4, 5)。
2.2 统计函数
常用的统计函数如下表所示,其中许多函数都拥有可以影响输出结果的可选参数。举例来说:
提供了对象x中元素的算术平均数,而:
则提供了截尾平均数,即丢弃了最大5%和最小5%的数据和所有缺失值后的算术平均数。
更多实用方法参见help( )。
下面这段代码演示了计算某个数值向量的均值和标准差的两种方式:
第二种方式中修正平方和(css)的计算过程是很有启发性的:
1、 x等于c(1, 2, 3, 4, 5, 6, 7, 8),x的平均值等于4.5(length(x)返回了x中元素的数量)。
2、(x – meanx) 从x的每个元素中减去了4.5,结果为c(-3.5, -2.5, -1.5, -0.5, 0.5,1.5, 2.5, 3.5)。
3、 (x – meanx)^2 将 (x - meanx )的每个元素求平方,结果为c(12.25, 6.25, 2.25,0.25, 0.25, 2.25, 6.25, 12.25)。
4、sum((x - meanx)^2) 对 (x - meanx)^2) 的所有元素求和,结果为42。
数据的标准化
默认情况下,函数scale( )对矩阵或数据框的指定列进行均值为0、标准差为1的标准化:
要对每一列进行任意均值和标准差的标准化,可以使用如下的代码:
其中的M是想要的均值,SD为想要的标准差。
在非数值型的列上使用scale( )函数将会报错。要对指定列而不是整个矩阵或数据框进行标准化,我们可以使用这样的代码:
此句将变量myvar标准化为均值50、标准差为10的变量。
我们将在后续的数据处理问题的解决方法章节中用到scale( )函数。
2.3 概率函数
概率函数通常用来生成特征已知的模拟数据,以及在用户编写的统计函数中计算概率值。
在R中,概率函数形如:
其中第一个字母表示其所指分布的某一方面:
d = 密度函数(density)p = 分布函数(distribution function)q = 分位数函数(quantile function)r = 生成随机数(随机偏差)
常用的概率函数如下表:
我们先来了解正态分布有关函数:
如果不指定一个均值和一个标准差,则函数将假定其为标准正态分布(均值为0,标准差为1)。
密度函数(dnorm)、分布函数(pnorm)、分位数函数(qnorm)和随机数生成函数(rnorm)的使用示例见下图:
1、在区间[-3,3]绘制标准正态分布曲线:
2、位于 z=1.96 左侧的标准正态曲线下方面积是多少?
3、均值为500,标准差为100的正态分布的0.9分位点值为多少?
4、生成50个均值为50,标准差为10的正态随机数
1 设定随机数种子
在每次生成伪随机数的时候,函数都会使用一个不同的种子,因此也会产生不同的结果。
我们可以通过函数set.seed( )显式指定这个种子,让结果可以重现(reproducible)。
下面的函数runif( )用来生成0到1区间上服从均匀分布的伪随机数:
通过手动设定种子,就可以重现我们的结果了。
2 生成多元正态数据
在模拟研究和蒙特卡洛方法中,经常需要获取来自给定均值向量和协方差阵的多元正态分布的数据。
MASS包中的mvrnorm( )函数可以让这个问题变得很容易:
其中n是想要的样本大小,mean为均值向量,而sigma是方差-协方差矩阵(或相关矩阵)。
下列代码将从一个参数如下所示的三元正态分布中抽取500个观测:
生成服从多元正态分布的数据:
上述代码中设定了一个随机数种子,这样就可以在之后重现结果。
首先,指定了想要的均值向量和方差—协方差阵,并生成了500个伪随机观测。
为了方便,结果从矩阵转换为数据框,并为变量指定了名称。
最后,我们确认了拥有500个观测和3个变量,并输出了前10个观测。
注意的是,由于相关矩阵同时也是协方差阵,所以其实可以直接指定相关关系的结构。
2.4 字符处理函数
数学和统计函数是用来处理数值型数据的,而字符处理函数可以从文本型数据中抽取信息,或者为打印输出和生成报告重设文本的格式。
举例来说,我们可能希望将某人的姓和名连接在一起,并保证姓和名的首字母大写。下表是一些字符处理函数:
函数grep( )、sub( )和strsplit( )能够搜索某个文本字符串(fixed=TRUE)或某个正则表达式(fixed=FALSE,默认值为FALSE)。
正则表达式为文本模式的匹配提供了一套清晰而简练的语法。例如,正则表达式:
可匹配任意以0个或1个h或c开头、后接at的字符串。因此,此表达式可以匹配hat、cat和at,但不会匹配bat。了解更多请参考维基百科的regular expression(正则表达式)条目。
2.5 其他实用函数
表中的最后一个例子演示了在输出时转义字符的使用方法。
\n表示新行,\t为制表符,'为单引号,\b为退格,等等。(键入?Quotes以了解更多。)
例如,代码可生成:
请注意第二行缩进了一个空格。当cat输出连接后的对象时,它会将每一个对象都用空格分开。这就是在句号之前使用退格转义字符(\b)的原因。不然,生成的结果将是“Hello Bob .”。
在数值、字符串和向量上使用我们最近学习的函数是直观而明确的,具体运用于矩阵和数据框就是下一节的内容:
2.6 将函数应用于矩阵和数据框
R函数的诸多有趣特性之一,就是它们可以应用到一系列的数据对象上,包括标量、向量、矩阵、数组和数据框。
下面代码演示了将函数应用于数据对象:
上述代码中对矩阵c求均值的结果为一个标量(0.465)。
函数mean( )求得的是矩阵中全部12个元素的均值。但如果希望求的是各行的均值或各列的均值呢?
R中提供了一个apply( )函数,可将一个任意函数“应用”到矩阵、数组、数据框的任何维度上。apply函数的使用格式为:
其中,x为数据对象,MARGIN是维度的下标,FUN是由你指定的函数,而...则包括了任何想传递给FUN的参数。
在矩阵或数据框中,MARGIN=1表示行,MARGIN=2表示列。下面的例子是将一个函数应用到矩阵的所有行(列):
首先,生成了一个包含正态随机数的6×5矩阵。
然后你计算了6行的均值,以及5列的均值。
最后,计算了每列的截尾均值(在本例中,截尾均值基于中间60%的数据,最高和最低20%的值均被忽略)。
FUN可为任意R函数,这也包括我们自行编写的函数,所以apply( )是一种很强大的机制。
apply( )可把函数应用到数组的某个维度上,而lapply( )和sapply( )则可将函数应用到列表(list)上。
我们将在下一节中看到sapply(它是lapply的更好用的版本)的一个示例。
现在,让我们小试身手。
3 数据处理难题的一套解决方案
第1节中提出的问题是:
将学生的各科考试成绩组合为单一的成绩衡量指标、基于相对名次(前20%,下20%,等等)给出从A到F的评分、根据学生姓氏和名字的首字母对花名册进行排序。
下列代码给出了一种解决方案:
在第一次运行的时候,最后结果:
有些信息不完整,是名字录入的时候出了问题,后面排查发现是有的名字" "与名字之间有一个空格导致了识别的错误,修改后正常。
那么我们继续:
以上代码写得比较紧凑,逐步分解如下:
步骤1、原始的学生花名册已经给出了。options(digits=2) 限定了输出小数点后数字的位数,并且让输出更容易阅读。
步骤2、由于数学、科学和英语考试的分值不同(均值和标准差相去甚远),在组合之前需要先让它们变得可以比较。
一种方法是将变量进行标准化,这样每科考试的成绩就都是用单位标准差来表示,而不是以原始的尺度来表示了。这个过程可以使用scale( )函数来实现:
这里打断一下,关于这和函数我觉得有需要补充的:
1、数据的中心化
所谓数据的中心化是指数据集中的各项数据减去数据集的均值。例如有数据集1, 2,3, 6, 3,其均值为3,那么中心化之后的数据集为1-3,2-3,3-3,6-3,3-3,即: -2,-1,0,3,0
2、数据的标准化
所谓数据的标准化是指中心化之后的数据在除以数据集的标准差,即数据集中的各项数据减去数据集的均值再除以数据集的标准差。例如有数据集1,2,3, 6, 3,其均值为3,其标准差为1.87,那么标准化之后的数据集为(1-3)/1.87.(2-3)/1.87,(3-3)/1 .87,(6-3)/1.87,(3-3)/1.87,即: -1.069,-0.535,0,1 .604,0
我们上述函数scale( )实现的就是数据的标准化。
那么我们继续吧:
步骤3、然后,可以通过函数mean( )来计算各行的均值以获得综合得分,并使用函数cbind( )将其添加到花名册中:
步骤4、函数quantile( )给出了学生综合得分的百分位数。
可以看到,成绩为A的分界点为0.74,B的分界点为0.44,等等。
步骤5、通过使用逻辑运算符,你可以将学生的百分位数排名重编码为一个新的类别型成绩变量。下面在数据框roster中创建了变量grade。
步骤6、你将使用函数strsplit( )以空格为界把学生姓名拆分为姓氏和名字。
把strsplit( )应用到一个字符串组成的向量上会返回一个列表:
步骤7、我们可以使用函数sapply( )提取列表中每个成分的第一个元素,放入一个储存名字的向量,并提取每个成分的第二个元素,放入一个储存姓氏的向量。
"["是一个可以提取某个对象的一部分的函数——在这里它是用来提取列表name各成分中的第一个或第二个元素的。
我们将使用cbind( )把它们添加到花名册中。由于已经不再需要student变量,可以将其丢弃(在下标中使用-1)。
步骤8、最后,可以使用函数order( )依姓氏和名字对数据集进行排序:
先按照lastname排序,再按照firstname排序。
瞧!小事一桩!
完成这些任务的方式有许多,只是以上代码体现了相应函数的设计初衷。现在到了学习控制结构和自己编写函数的时候了。
4 控制流
在正常情况下,R程序中的语句是从上至下顺序执行的。但有时我们可能希望重复执行某些语句,仅在满足特定条件的情况下执行另外的语句。
这就是控制流结构发挥作用的地方了。
首先我们将看到用于条件执行的结构,接下来是用于循环执行的结构。
牢记以下概念:语句(statement):是一条单独的R语句或一组复合语句(包含在花括号{ } 中的一组R语句,使用分号分隔)。条件(cond):是一条最终被解析为真(TRUE)或假(FALSE)的表达式。表达式(expr):是一条数值或字符串的求值语句。序列(seq):是一个数值或字符串序列。
在讨论过控制流的构造后,我们将学习如何编写函数。
4.1 重复和循环
循环结构重复地执行一个或一系列语句,直到某个条件不为真为止。循环结构包括for和while结构。
1 for结构
for循环重复地执行一个语句,直到某个变量的值不再包含在序列seq中为止。语法为:
在下例中:
单词Hello被输出了10次。
2 while结构
while循环重复地执行一个语句,直到条件不为真为止。语法为:
作为第二个例子,代码:
又将单词Hello输出了10次。请确保括号内while的条件语句能够改变,即让它在某个时刻不再为真——否则循环将永不停止!
在上例中,语句:
在每步循环中为对象i减去1,这样在十次循环过后,它就不再大于0了。反之,如果在每步循环都加1的话,R将不停地输出Hello。这也是while循环可能较其他循环结构更危险的原因。
在处理大数据集中的行和列时,R中的循环可能比较低效费时。只要可能,最好联用R中的内建数值/字符处理函数和apply族函数。
4.2 条件执行
在条件执行结构中,一条或一组语句仅在满足一个指定条件时执行。条件执行结构包括if-else、ifelse和switch。
1 if-else结构
控制结构if-else在某个给定条件为真时执行语句。也可以同时在条件为假时执行另外的语句。语法为:
示例如下:
在第一个实例中,如果grade是一个字符向量,它就会被转换为一个因子。
在第二个实例中,两个语句择其一执行。如果grade不是一个因子(注意符号),它就会被转换为一个因子。如果它是一个因子,就会输出一段信息。
2 ifelse结构
ifelse结构是if-else结构比较紧凑的向量化版本,其语法为:
若cond为TRUE,则执行第一个语句;若cond为FALSE,则执行第二个语句。示例如下:
在程序的行为是二元时,或者希望结构的输入和输出均为向量时,请使用ifelse。
3 switch结构
switch根据一个表达式的值选择语句执行。语法为:
其中的...表示与expr的各种可能输出值绑定的语句。
通过观察下列代码,可以轻松地理解switch的工作原理:
它展示了switch的主要功能。
我们将在下一节学习如何使用switch编写自己的函数。
5 用户自编函数
R的最大优点之一就是用户可以自行添加函数。事实上,R中的许多函数都是由已有函数构成的。
一个函数的结构看起来大致如此:
函数中的对象只在函数内部使用。返回对象的数据类型是任意的,从标量到列表皆可。
假设我们想编写一个函数,用来计算数据对象的集中趋势和散布情况。
此函数应当可以选择性地给出参数统计量(均值和标准差)和非参数统计量(中位数和绝对中位差)。
结果应当以一个含名称列表的形式给出。
另外,用户应当可以选择是否自动输出结果。除非另外指定,否则此函数的默认行为应当是计算参数统计量并且不输出结果。代码如下:
mystats( ):一个由用户编写的描述性统计量计算函数:
要看此函数的实战情况,首先需要生成一些数据(服从正态分布的,大小为500的随机样本):
在执行语句:
之后,y$center将包含均值(0.00184),y$spread将包含标准差(1.03),并且没有输出结果。
如果执行语句:
y$center将包含中位数(–0.0207),y$spread将包含绝对中位差(1.001)。另外,还会输出以下结果:
下一个例子:
下面让我们看一个使用了switch结构的用户自编函数,此函数可让用户选择输出当天日期的格式。在函数声明中为参数指定的值将作为其默认值。
在函数mydate( )中,如果未指定type,则long将为默认的日期格式:
实战中的函数如下:
请注意,函数cat( )仅会在输入的日期格式类型不匹配"long"或"short"时执行。使用一个表达式来捕获错误输入的参数值通常来说是一个好主意。
有若干函数可以用来为函数添加错误捕获和纠正功能。你可以使用函数warning( )来生成一条错误提示信息,用message( )来生成一条诊断信息,或用stop( )停止当前表达式的执行并提示错误。
6 整合与重构
R中提供了许多用来整合(aggregate)和重塑(reshape)数据的强大方法。
在整合数据时,往往将多组观测替换为根据这些观测计算的描述性统计量。
在重塑数据时,则会通过修改数据的结构(行和列)来决定数据的组织方式。
本节描述了用来完成这些任务的多种方式,在接下来的两个小节中,我们将使用已包含在R基本安装中的数据框mtcars。这个数据集是从Motor Trend 杂志(1974)提取的,它描述了34种车型的设计和性能特点(汽缸数、排量、马力、每加仑汽油行驶的英里数,等等)。
6.1 转置
转置(反转行和列)也许是重塑数据集的众多方法中最简单的一个了。
使用函数t( )即可对一个矩阵或数据框进行转置。对于后者,行名将成为变量(列)名。
6.2 整合数据
在R中使用一个或多个by变量和一个预先定义好的函数来折叠(collapse)数据是比较容易的。
调用格式为:
其中x是待折叠的数据对象,
by是一个变量名组成的列表,这些变量将被去掉以形成新的观测,
FUN则是用来计算描述性统计量的标量函数,它将被用来计算新观测中的值。
作为一个示例,我们将根据汽缸数和挡位数整合mtcars数据,并返回各个数值型变量的均值:
在结果中,Group.1表示汽缸数量(4、6或8),Group.2代表挡位数(3、4或5)。
举例来说,拥有4个汽缸和3个挡位车型的每加仑汽油行驶英里数(mpg)均值为21.5。
在使用aggregate( )函数的时候,by中的变量必须在一个列表中(即使只有一个变量)。我们可以在列表中为各组声明自定义的名称, 例如 by=list(Group.cyl=cyl, Group.gears=gear)。
指定的函数可为任意的内建或自编函数,这就为整合命令赋予了强大的力量。
6.3 reshape包
reshape包是一套重构和整合数据集的万能工具。
我们将慢慢地梳理整个过程,并使用一个小型数据集作为示例,这样每一步发生了什么就很清晰了。
大致说来,我们需要首先将数据“融合”(melt),以使每一行都是一个唯一的标识符—变量组合。
然后将数据“重铸”(cast)为你想要的任何形状。在重铸过程中,你可以使用任何函数对数据进行整合。
将使用的数据集如下表所示:
在这个数据集中,测量(measurement)是指最后两列中的值(5、6、3、5、6、1、2、4)。
每个测量都能够被标识符变量(在本例中,标识符是指ID、Time以及观测属于X1还是X2)唯一地确定。
举例来说,在知道ID为1、Time为1,以及属于变量X1之后,即可确定测量值为第一行中的5。
1 融合
数据集的融合是将它重构为这样一种格式:每个测量变量独占一行,行中带有要唯一确定这个测量所需的标识符变量。
要融合表中的数据,可使用以下代码:
将得到如下表所示的结构:
注意,必须指定要唯一确定每个测量所需的变量(ID和Time),而表示测量变量名的变量(X1或X2)将由程序为你自动创建。
既然已经拥有了融合后的数据,现在就可以使用cast( )函数将它重铸为任意形状了。
2 重铸
cast( )函数读取已融合的数据,并使用你提供的公式和一个(可选的)用于整合数据的函数将其重塑。
调用格式为:
其中的md为已融合的数据,formula描述了想要的最后结果,而FUN是(可选的)数据整合函数。
其接受的公式形如:
在这一公式中,rowvar1 + rowvar2 + ...定义了要划掉的变量集合,以确定各行的内容。
colvar1 + colvar2 + ...则定义了要划掉的、确定各列内容的变量集合。参见下图中的示例:
由于右侧(d、e和f)的公式中并未包括某个函数,所以数据仅被重塑了。
反之,左侧的示例(a、b和c)中指定了mean作为整合函数,从而就对数据同时进行了重塑与整合。
例如,(a)中给出了每个观测所有时刻中在X1和X2上的均值;示例(b)则给出了X1和X2在时刻1和时刻2的均值,对不同的观测进行了平均;在(c)中则是每个观测在时刻1和时刻2的均值,对不同的X1和X2进行了平均。
如你所见,函数melt( )和cast( )提供了很多灵活性。很多时候,我们不得不在进行分析之前重塑或整合数据。举例来说,在分析重复测量数据(为每个观测记录了多个测量的数据)时,通常需要将数据转化为类似于上表中所谓的“长格式”。
7 小结
本章总结了用于处理数据的数学、统计和概率函数。
我们看到了如何将这些函数应用到范围广泛的数据对象上,其中包括向量、矩阵和数据框。我们学习了控制流结构的使用方法:
用循环重复执行某些语句,或用分支在满足某些特定条件时执行另外的语句。
然后编写了自己的函数,并将它们应用到了数据上。
最后,我们探索了折叠、整合以及重构数据的多种方法。
我们准备好告别第一部分,并进入激动人心的数据分析世界了!在接下来的几篇中,我们将探索多种将数据转化为信息的统计方法和图形方法。
那么我们,下篇再见!