写在前面
前面的初级数据管理只能让你了解一下R在处理各种不同类型数据时发挥的便捷性,平时的科研工作中遇到最多的还是高级数据管理。包括各种需求的组合以及控制流的使用等。
5.1 一个数据处理难题
要讨论数值和字符处理函数,让我们首先考虑一个数据处理问题。一组学生参加了数学、科 学和英语考试。为了给所有学生确定一个单一的成绩衡量指标,需要将这些科目的成绩组合起来。 另外,你还想将前20%的学生评定为A,接下来20%的学生评定为B,依次类推。后,你希望按 字母顺序对学生排序。数据如表5-1所示。
5.2 数值和字符处理函数
本小节介绍数据处理的基石函数,包括数值(数学,统计和概率)函数和字符处理函数。
5.2.1 数字函数
5.2.2 统计函数
数据的标准化
默认情况下,函数scale()
对矩阵或数据框的指定列进行均值为0,标准差为1的标准化。
> newdata <- scale(mydata)
5.2.3 概率函数
正态分布函数
1.设定随机数种子
在每次生成伪随机数的时候,函数都会使用一个不同的种子,因此也会产生不同的结果。你可以通过函数set.seed()显式指定这个种子,让结果可以重现(reproducible),也就是别人可以重复你的代码。下面的代码给出了一个示例。这里的函数runif()
用来生成0到1区间上服从均匀分布的伪随机数。
> runif(5)
[1] 0.8725344 0.3962501 0.6826534 0.3667821 0.9255909
> runif(5)
[1] 0.4273903 0.2641101 0.3550058 0.3233044 0.6584988
> set.seed(1234)
> runif(5)
[1] 0.1137034 0.6222994 0.6092747 0.6233794 0.8609154
> set.seed(1234)
> runif(5)
[1] 0.1137034 0.6222994 0.6092747 0.6233794 0.8609154
2. 生成多元正态数据
在模拟研究和蒙特卡洛方法中,你经常需要获取来自给定均值向量和协方差阵的多元正态分 布的数据。MASS包中的mvrnorm()函数可以让这个问题变得很容易。其调用格式为:
mvrnorm(n, mean, sigma)
其中n是你想要的样本大小,mean为均值向量,而sigma是方差-协方差矩阵(或相关矩阵)。
5.2.4 字符处理函数
函数grep()、sub()和strsplit()能够搜索某个文本字符串(fixed=TRUE)或 某个正则表达式(fixed=FALSE,默认值为FALSE)。
正则表达式举例:
^[hc]?at
可以匹配任意以0个或1个h或c开头,后接at的字符串。
5.2.5 其他实用函数
5.2.6 将函数应用于矩阵和数据框
> a <- 5 > sqrt(a)
[1] 2.236068
> b <- c(1.243, 5.654, 2.99)
> round(b) #将b舍入为指定位的小数
[1] 1 6 3
> c <- matrix(runif(12), nrow=3)
> c
[,1] [,2] [,3] [,4]
[1,] 0.4205 0.355 0.699 0.323
[2,] 0.0270 0.601 0.181 0.926
[3,] 0.6682 0.319 0.599 0.215
> log(c)
[,1] [,2] [,3] [,4]
[1,] -0.866 -1.036 -0.358 -1.130
[2,] -3.614 -0.508 -1.711 -0.077
[3,] -0.403 -1.144 -0.513 -1.538
> mean(c)
[1] 0.444
上述代码框的最后一行代码,函数mean()
求的是矩阵中全部12个元素的均值,如果想求的是各行或各列的均值,应该用apply()
函数,她可以将将一个任意函数“应用”到矩阵、数组、数据框的任何维 度上。apply()函数的使用格式为:
apply(x, MARGIN, FUN, ...)
x为数据对象,MARGIN是维度的下标,FUN是由你指定的函数,而...则包括了任何想传 递给FUN的参数。在矩阵或数据框中,MARGIN=1表示行,MARGIN=2表示列。
apply()
可把函数应用到数组的某个维度上,而lapply()
和sapply()
则可将函数 应用到列表(list)上.
5.3 数据处理难题的一套解决方案
5.1节中提出的问题是:将学生的各科考试成绩组合为单一的成绩衡量指标,基于相对名次 (前20%、下20%、等等)给出从A到F的评分,根据学生姓氏和名字的首字母对花名册进行排序。
步骤1:
options(digits=2)
限定了输出小数点后数字的 位数,并且让输出更容易阅读。
> options(digits=2)
> 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)
步骤2 由于数学、科学和英语考试的分值不同(均值和标准差相去甚远),在组合之前需 要先让它们变得可以比较。一种方法是将变量进行标准化,这样每科考试的成绩就都是用单位 标准差来表示,而不是以原始的尺度来表示了。这个过程可以使用scale()函数来实现:
> z <- scale(roster[,2:4])
步骤3 然后,可以通过函数mean()来计算各行的均值以获得综合得分,并使用函数 cbind()将其添加到花名册中:
> score <- apply(z, 1, mean)
> roster <- cbind(roster, score)
步骤4 函数quantile()给出了学生综合得分的百分位数。可以看到,成绩为A的分界点为 0.74,B的分界点为0.44,等等。
> y <- quantile(score, c(.8,.6,.4,.2))
> y
80% 60% 40% 20%
0.74 0.44 -0.36 -0.89
步骤5 通过使用逻辑运算符,你可以将学生的百分位数排名重编码为一个新的类别型成绩 变量。下面在数据框roster中创建了变量grade。
> 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"
步骤6 你将使用函数strsplit()以空格为界把学生姓名拆分为姓氏和名字。把 strsplit()应用到一个字符串组成的向量上会返回一个列表:
> name <- strsplit((roster$Student), " ")
步骤7 你可以使用函数sapply()提取列表中每个成分的第一个元素,放入一个储存名字 的向量Firstname,并提取每个成分的第二个元素,放入一个储存姓氏的向量Lastname。"[" 是一个可以提取某个对象的一部分的函数——在这里它是用来提取列表name各成分中的第一 个或第二个元素的。你将使用cbind()把它们添加到花名册中。由于已经不再需要student变 量,可以将其丢弃(在下标中使用–1)。
> Lastname <- sapply(name, "[", 2)
> Firstname <- sapply(name, "[", 1)
> roster <- cbind(Firstname,Lastname, roster[,-1])
步骤8 后,可以使用函数order()依姓氏和名字对数据集进行排序:
> roster <- roster[order(Lastname,Firstname),]
> roster
Firstname Lastname Math Science English score grade
6 Cheryl Cushing 512 85 28 0.35 C
1 John Davis 502 95 25 0.56 B
9 Joel England 573 89 27 0.70 B
4 David Jones 358 82 15 -1.16 F
8 Greg Knox 625 95 30 1.34 A
5 Janice Markhammer 495 75 20 -0.63 D
3 Bullwinkle Moose 412 80 18 -0.86 D
10 Mary Rayburn 522 86 18 -0.18 C
2 Angela Williams 600 99 22 0.92 A
7 Reuven Ytzrhak 410 80 15 -1.05 F
5.4 控制流
在正常情况下,R程序中的语句是从上至下顺序执行的。但有时你可能希望重复执行某些语 句,仅在满足特定条件的情况下执行另外的语句。这就是控制流结构发挥作用的地方了。
R拥有一般现代编程语言中都有的标准控制结构。首先你将看到用于条件执行的结构,接下 来是用于循环执行的结构。
为了理解贯穿本节的语法示例,请牢记以下概念:
语句(statement)是一条单独的R语句或一组复合语句(包含在花括号{ }中的一组R 语句,使用分号分隔);
条件(cond)是一条终被解析为真(TRUE)或假(FALSE)的表达式;
表达式(expr)是一条数值或字符串的求值语句;
序列(seq)是一个数值或字符串序列。
在讨论过控制流的构造后,我们将学习如何编写函数。
5.4.1 重复和循环
循环结构重复地执行一个或一系列语句,直到某个条件不为真为止。循环结构包括for和 while结构。
1. for结构
for循环重复地执行一个语句,直到某个变量的值不再包含在序列seq中为止。语法为:
for (var in seq) statement
在下例中:
for (i in 1:10) print("Hello")
单词Hello被输出了10次。
2. while结构
while循环重复地执行一个语句,直到条件不为真为止。语法为:
while (cond) statement
作为第二个例子,代码:
i <- 10 while (i > 0) {print("Hello"); i <- i - 1}
又将单词Hello输出了10次。请确保括号内while的条件语句能够改变,即让它在某个时刻不再为 真——否则循环将永不停止!
在上例中,语句:
i <- i – 1 在每步循环中为对象i减去1,这样在十次循环过后,它就不再大于0了。反之,如果在每步循环 都加1的话,R将不停地打招呼。这也是while循环可能较其他循环结构更危险的原因。 在处理大数据集中的行和列时,R中的循环可能比较低效费时。只要可能,好联用R中的 内建数值/字符处理函数和apply族函数。
5.4.2 条件执行
在条件执行结构中,一条或一组语句仅在满足一个指定条件时执行。条件执行结构包括 if-else、ifelse和switch。
1. if-else结构
控制结构if-else在某个给定条件为真时执行语句。也可以同时在条件为假时执行另外的语 句。语法为:
if (cond) statement if (cond) statement1 else statement2
示例如下:
if (is.character(grade)) grade <- as.factor(grade)
if (!is.factor(grade)) grade <- as.factor(grade) else print("Grade already is a factor")
在第一个实例中,如果grade是一个字符向量,它就会被转换为一个因子。在第二个实例中, 两个语句择其一执行。如果grade不是一个因子(注意符号!),它就会被转换为一个因子。如果 它是一个因子,就会输出一段信息。
2. ifelse结构
ifelse结构是if-else结构比较紧凑的向量化版本,其语法为:
ifelse(cond, statement1, statement2)
若cond为TRUE,则执行第一个语句;若cond为FALSE,则执行第二个语句。示例如下:
ifelse(score > 0.5, print("Passed"), print("Failed"))
outcome <- ifelse (score > 0.5, "Passed", "Failed")
在程序的行为是二元时,或者希望结构的输入和输出均为向量时,请使用ifelse。