相对于基本数据管理,此处我们将接触到R中多种数学、统计和字符处理函数,学习如何自己编写函数,包括循环和条件执行语句,以及了解数据的整合和概述方法、重塑和重构方法。
5.1 一个数据处理难题
要讨论数值和字符处理函数,不妨先考虑一个数据问题。一组学生参加了数学、科学和英语考试,需要按照某种成绩衡量指标将三门科目的成绩组合起来并排序,将前20%的学生评定为A,接下来20%的学生评定为B,依次类推。最后,将所有学生按照字母顺序进行排序并输出。
需要考虑的问题包括以下几点:
- 三科的均值和标准差相去甚远,因此求平均值显然没有意义。在组合多门成绩之前,必须将其变换为可比较的单元;
- 将三科成绩组合之后,需要确定一种标准来评定学生的排名;
- 表示姓名的字段只有一个,使得排序任务复杂化。为了正确地将其排序,需要将姓和名拆开。
5.2 数值和字符处理函数
R中数据处理最为重要的函数包括数值(数学、统计、概率)函数和字符处理函数。
数学函数
常用的数学函数包括:
- abs(x):绝对值;
- sqrt(x):平方根;
- ceiling(x):不小于x的最小整数;
- floor(x):不大于x的最大整数;
- trunc(x):向0的方向截取x的整数部分;
- round(x, digits=n):将x舍入为指定位的小数;
- signif(x,digits=n):将x舍入为指定的有效数字位数;
- cos(x)、sin(x)、tan(x)、acos(x)、asin(x)、atan(x)、cosh(x)、sinh(x)、tanh(x)、acosh(x)、asinh(x)、atanh(x):三角函数;
- log(x,base=n):对x取以n为底的对数;
- log(x):对x取以e为底的对数;
- log10(x):对x取以10为底的对数;
- exp(x):e的指数函数。
统计函数
常用的统计函数包括:
- mean(x):平均数;
- median():中位数;
- sd(x):标准差;
- var(x):方差;
- mad(x):绝对中位差;
- quantile(x,probs):求分位数,其中x为待求分位数的数值型向量,probs为一个由[0,1]之间的概率值组成的数值向量;
- range(x):求值域;
- sum(x):求和;
- diff(x,lag=n):滞后差分;
- min(x):求最小值;
- max(x):求最大值;
- scale(x,center=TRUE,scale=TRUE):为数据对象x按列进行中心化或标准化。
其中许多函数都提供了丰富的可选参数,可以进一步影响输出结果。例如以下截尾平均数,丢弃了最大5%和最小5%的数据和所有缺失值后得到算数平均值。
z < - mean ( x , trim = 0.05 , na . rm = TRUE )
以下代码演示了计算某个数值向量均值和标准差的两种方式:
x < - c ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 )
mean ( x )
sd ( x )
n < - length ( x )
meanx < - sum ( x ) / n
css < - sum ( ( x - meanx ) ^ 2 )
sdx < - sqrt ( css / ( n - 1 ) )
不难发现,R中公式的写法和类似Matlab的矩阵运算语言有着许多共同之处。
使用以下代码对矩阵或数据框的数值列进行均值为1、标准差为0的标准化:
newdata < - scale ( mydata )
或者任意均值和标准差:
newdata < - scale ( mydata ) * SD + M
如果仅对指定列处理,则使用transform()函数:
newdata < - transform ( mydata , myvar = scale ( myvar ) * SD + M )
概率函数
概览函数和统计函数类似,但是通常用来生成特征已知的模拟数据,以及在用户编写的统计函数中计算概率值。
R中的每个概览函数都对应四个具体函数:d(密度函数)、p(分布函数)、q(分位数函数)和r(生成随机数)。
以正态分布为例,如果不指定均值和标准差,将会生成标准正态分布(均值为0,标准差为1),相应的密度函数(dnorm)、分布函数(pnorm)、分位数函数(qnorm)和随机生成函数(rnorm)分别如下。
x < - pretty ( c ( - 3 , 3 ) , 30 )
y < - dnorm ( x )
plot ( x , y , type = "l" , xlab = "NormalDeviate" , ylab = "Density" , yaxs = "i" )
#位于x=1.96左侧标准正态曲线下方的面积
pnorm ( 1.96 )
#均值为500,标准差为100正态分布的0.9分位点的值
qnorm ( . 9 , mean = 500 , sd = 100 )
#生成50个均值为50,标准差为10的正态随机数
#rnorm(50,mean=50,sd=10)
在每次生成伪随机数的时候,函数都会使用一个不同的种子,因此也会产生不同的结果。可以通过函数set.seed()显式指定种子,使得之前的结果可以重现(reproducible)。重现数据有助于创建会在未来取用的,以及可与他人分享的随机示例数据。
使用MASS包中的mvrnorm()函数可以生成来自给定均值向量和协方差矩阵的多元正态分布,以下是一个生成满足指定三元正态分布的例子。
library ( MASS )
options ( digits = 3 )
set . seed ( 1234 )
mean < - c ( 230.7 , 146.7 , 3.6 )
sigma < - matrix ( c ( 15360.8 , 6721.2 , - 47.1 , 6721.2 , 4700.9 , - 16.5 , - 47.1 , - 16.5 , 0.3 ) , nrow = 3 , ncol = 3 )
mydata < - mvrnorm ( 500 , mean , sigma )
mydata < - as . data . frame ( mydata )
names ( c ( "y" , "x1" , "x2" ) )
dim ( mydata )
head ( mydata , n = 10 )
字符处理函数
数学和统计函数用于处理数值型数据,而字符处理函数用于从文本型数据中抽取信息。
- nchar(x):计算x中的字符数量;
- substr(x, start, stop):提取或替换子串;
- grep(pattern, x, ignore.case=FALSE, fixed=FALSE):在x中搜索某种模式,fixed=FALSE则pattern为一个正则表达式,否则pattern为一个文本字符串,返回值为匹配的下标;
- sub(pattern, replacement, x, ignore.case=FALSE, fixed=FALSE):在x中搜索某种模式并替换;
- strsplit(x, split, fixed=FALSE):在split处分隔字符向量x中的元素;
- paste(…, sep=””):连接字符串,分隔符为sep。paste(“x”, 1:3, sep=””)返回值为c(“x1”, “x2”, “x3”),paste(“x”, 1:3, sep=”M”)返回值为c(“xM1”, “xM2”, “xM3”);
- toupper(x):大写转换;
- tolower():小写转换。
其他实用函数
- length(x):x的长度;
- seq(from, to, by):生成一个序列,by为步长;
- rep(x, n):将x重复n次;
- cut(x ,n):将连续型变量x分割为n个水平的因子;
- pretty(x, n):通过选取n+1个等间距的取整值,将一个连续型变量分割为n个区间;
- cat(…, file=”mayflies”, append=TRUE):连接…中的对象,并将其输出到屏幕上或文件中。
在R中,函数可以应用到一系列数据对象上,包括标量、向量、矩阵、数组和数据框(和Matlab类似)。如果希望函数应用于矩阵的各行或者各列,可以考虑apply()函数。
apply ( x , MARGIN , FUN , . . . )
MARGIN是维度的下标,1表示行、2表示列,FUN可以是内置函数或者你自己编写的函数,…为可选参数。
mydata < - matrix ( rnorm ( 30 ) , nrow = 6 )
apply ( mydata , 1 , mean )
apply ( mydata , 2 , mean )
apply ( mydata , 2 , mean , trim = 0.2 )
和apply()应用于矩阵一样,lapply()和sapply()则将函数应用于列表上。
5.3 数据处理难题的一套解决方案
回到我们之前的问题,组合三门成绩、按衡量指标排名、按区间分段打分、按姓名排序。
options ( digits = 3 )
Student < - c ( "John Davis" , "Angela Williams" , "Bullwinkle Moose" , "David Jones" , "Janice Markhammer" , "Cheryl Cushing" , "Reuven Ytzrhak" , "Greg Knox" , "Joel England" , "Mary Rayburn" )
Math < - c ( 502 , 600 , 412 , 358 , 495 , 512 , 410 , 625 , 573 , 522 )
Science < - c ( 95 , 99 , 80 , 82 , 75 , 85 , 80 , 95 , 89 , 86 )
English < - c ( 25 , 22 , 18 , 15 , 20 , 28 , 15 , 30 , 27 , 18 )
roster < - data . frame ( Student , Math , Science , English , stringsAsFactors = FALSE )
z < - scale ( roster [ , 2 : 4 ] )
score < - apply ( z , 1 , mean )
roster < - cbind ( roster , score )
y < - quantile ( score , c ( . 8 , . 6 , . 4 , . 2 ) )
roster $ grade [ score >= y [ 1 ] ] < - "A"
roster $ grade [ score < y [ 1 ] & score >= y [ 2 ] ] < - "B"
roster $ grade [ score < y [ 2 ] & score >= y [ 3 ] ] < - "C"
roster $ grade [ score < y [ 3 ] & score >= y [ 4 ] ] < - "D"
roster $ grade [ score < y [ 4 ] ] < - "F"
name < - strsplit ( ( roster $ Student ) , " " )
Lastname < - sapply ( name , "[" , 2 )
Firstname < - sapply ( name , "[" , 1 )
roster < - cbind ( Firstname , Lastname , roster [ , - 1 ] )
roster < - roster [ order ( Lastname , Firstname ) , ]
最后查看roster,你应当得到如下结果。
5.4 控制流
在正常情况下,R程序中的语句是从上至下执行的。当然有时候你需要控制程序的执行流,即使用条件和循环。
为了理解贯穿接下来内容的语法示例,请牢记以下概念:
- 语句(statement)是一条单独的R语句或一组复合语句(包含在{}中的一组R语句,使用分号分割);
- 条件(cond)是一条最终被解析为逻辑值的表达式;
- 表达式(expr)是一条数值或字符串的求值语句;
- 序列(seq)是一个数值或字符串序列。
重复和循环
循环结构重复地执行一个或一系列语句,直到某个条件不再为真,循环结构包括for循环和while循环。
for ( var in seq ) statement
while ( cond ) statement
在以下的例子中,单词Hello被输出了10次。
for ( i in 1 : 10 ) print ( "Hello" )
i < - 10
while ( i > 0 ) { print ( "Hello" ) ; i < - i - 1 }
使用循环的时候,记得在循环里修改标记量,避免导致死循环。
在处理大数据集中的行和列时,R中的循环可能比较低效耗时,应该尽可能使用R中内建的数值/字符处理函数和apply()族函数。
条件执行
在条件执行结构中,一条或一组语句仅在满足指定条件时执行。条件执行结构包括if-else、ifelse和switch。
if ( cond ) statement
if ( cond ) statement1 else statement2
ifelse ( cond , statement1 , statement2 )
switch ( expre , . . . )
再给出一个使用switch的例子,虽然简单但清晰说明了switch的使用方法。
feelings < - c ( "sad" , "afraid" )
for ( i in feelings )
print (
switch ( i ,
happy = "I'm glad you are happy" ,
afraid = "There is nothing to fear" ,
sad = "Cheer up" ,
angry = "Calm down now"
)
)
5.5 用户自编函数
R最大的优点之一就是支持用户自行添加函数,R中许多函数也是基于已由函数构成的,一个函数的结构大概如下:
myfunction < - function ( arg1 , arg2 , . . . ) {
statements
return ( object )
}
函数中的对象只在函数内部使用(记得{}的作用吗?),返回对象的数据类型是任意的,从标量到列表皆可。
mystats < - function ( x , parametric = TRUE , print = FALSE ) {
if ( parametric ) {
center < - mean ( x ) ; spread < - sd ( x )
} else {
center < - median ( x ) ; spread < - mad ( x )
}
if ( print & parametric ) {
cat ( "Mean=" , center , "\n" , "SD=" , spread , "\n" )
} else if ( print & ! parametric ) {
cat ( "Median=" , center , "\n" , "MAD=" , spread , "\n" )
}
result < - list ( center = center , spread = spread )
return ( result )
}
要查看此函数的运行情况,则需要生成一些测试数据并调用。
set . seed ( 1234 )
x < - rnorm ( 500 )
y < - mystats ( x )
z < - mystats ( x , parametric = FALSE , print = TRUE )
在所得结果中,y$center为均值(0.00184),y$spread为标准差(1.03),并且没有输出结果;z$center为中位数(-0.0207),z$spread为绝对中位差(1.001),并且还会在屏幕上打印信息。
再来看一个使用了switch的用户自编函数,该函数可以让用户选择输出当天日期的格式。
mydate < - function ( type = "long" ) {
switch ( type ,
long = format ( Sys . time ( ) , "%A %B %d %Y" ) ,
short = format ( Sys . time ( ) , "%m-%d-%y" ) ,
cat ( type , "is not a recognized type\n" )
)
}
mydate ( "long" )
mydate ( "short" )
mydate ( )
mydate ( "medium" )
switch中的最后一条语句给出了如何处理错误(或其他意料之外)的输入。除此之外,还有一些函数可以用来为函数添加错误捕获和纠正功能,如使用warning()生成一条错误提示信息,用message()生成一条诊断信息,用stop()停止当前表达式的执行并提示错误。如果希望了解更多关于调试程序的内容,请阅读Duncan Murdoch整理的“Debugging in R”。
5.6 整合与重构
R中提供了许多用于整合(aggregate)和重塑(reshape)数据的强大方法,整合数据是指将多组观测替换为根据这些观测计算的描述性统计量,重塑数据是指通过修改数据的结构(行和列)来决定数据的组织方式。
以下例子中,将会使用已包含在R基本安装中的数据框mtcars。该数据集从Motor Trend杂志(1974)提取,描述了34种车型的设计和性能特点(汽缸数、排量、马力、每加仑汽油行驶的英里数)。
转置
使用函数t()即可对一个矩阵或数据框进行转置,对于后者,行名将成为列名。
cars < - mtcars [ 1 : 5 , 1 : 4 ]
cars
t ( cars )
整合数据
在R中使用一个或多个by变量和一个预先定义好的函数来整合(collapse)数据十分容易。
aggregate ( x , by , FUN )
其中x为待整合的数据对象,by是一个变量名组成的列表,这些变量将被去掉以形成新的观测,FUN是用来计算描述性统计量的标量函数,它将被用来计算新观测中的值。以下代码根据汽缸数和档位数整合mtcars数据,并返回各个数值型变量的均值。
options ( digits = 3 )
attach ( mtcars )
aggdata < - aggregate ( mtcars , by = list ( cyl , gear ) , FUN = mean , na . rm = TRUE )
aggdata
将会得到以下结果。如何理解呢?例如第一行,拥有4个气缸和3个档位的车型,每加仑汽油行驶英里数(mpg)均值为21.5。需要注意的是,by中的参数必须写在一个列表中(即使只有一个参数)。
Reshape包
reshape包是一套重构和整合数据集的万能工具。由于reshape包并未内置在R的标准安装中,所以有必要通过install.packages(“reshape”)进行安装。
我们的操作大概包括两部分:融合(melt),使得每一行都是一个唯一的标识符和变量的组合;重铸(cast),将数据集变成任何需要的形状。接下来代码中,将处理以下样例数据。
融合
融合使得每个测量变量独占一行,行中必须带有唯一确定该测量的标识符变量。
library ( reshape )
md < - melt ( mydata , id = c ( "id" , "time" ) )
注意,必须指定要唯一确定每个测量所需的变量(ID和Time),而表示测量变量名的变量(X1和X2)将由程序自动创建。
既然已经拥有了融合后的数据,现在便可以使用cast()函数将其重铸为任意形状了。
重铸
cast()函数读取已融合的数据,并使用提供的公式和一个(可选的)用于整合数据的函数将其重铸。
newdata < - cast ( md , formula , FUN )
接受的公式形如:
rowvar1 + rowvar2 + . . . + ~ colvar1 + colvar2 + . . .
rowvar1+rowvar2+…定义了要去掉的变量集合,以确定各行的内容;colvar1+colvar2+…定义了要去掉的变量集合,以确定各列的内容。下图给出了使用cast()函数处理样例数据的例子。