感谢Robert I.Kabacoff 著作本书,同时感谢高涛、肖楠、陈钢编译此书。
最近在学习《R语言实战》,特将学习过程记录下来,供各位朋友参考,虽说是笔记,但是90%是书中内容,另外10%是自己偶尔冒出的一点点想法的记录和一些疑问,希望互相探讨。末尾有本章的代码清单下载地址,与各位交流,还是提倡按照书中内容把代码一个个敲出来。
第五章 高级数据管理
本章内容
数学和统计函数
字符处理函数
循环和条件执行
自编函数
数据整合与重塑
5.1 一个数据处理难题
一个难题:
- 三科成绩是无法比较的。
- 需要一种方法来确定某个学生在前述得分上百分比排名。
- 表示姓名的字段只有一个,需要将姓和名拆开。
5.2 数值和字符处理函数
本节将学习R中作为数据处理基石的函数,它们可分为数值(数学、统计、概率)函数和字符处理函数。
5.2.1 数学函数
书中表5-2列出了常用的数学函数和简短的用例。(P83页)
值得一提的是round(x, digits = n) 表示将x舍入为指定位的小数。
signif(x, digits = n)表示将x舍入为指定的有效数字位数。
对比如下
round(3.475, digits = 2)
返回值为3.48。(保留两位小数)
signif(3.475, n= = 2)
返回值为3.5。(保留两位有效数字)
表5-2中的示例将数学函数应用到了标量(单独的数值)上。当这些函数被应用于数值向量、矩阵或者数据框时,它们会作用于其中每一个独立的值,然后重新生成一个向量、矩阵或数据框。
5.2.2 统计函数
方差:表示点的离散程度。方差越小,离散程度越低,越接近平均值。公式表达为:
S=[ (x1-x)^2+(x2-x)^2+(x3-x)^2+……+(xn-x)^2]
标准差是方差开根号。
什么是绝对中位差、分位数、值域、滞后差分、进化中心化、标准化?
代码清单5-1 均值和标准差的计算
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))
meanx
sdx
第二种方式中修正平方和(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) summ((x - meanx)^2)对((x - meanx)^2)的所有元素求和,结果为42。
数据的标准化
将一组数据,投射到很小区间内的方法,便于分析。标准化之后的数据有这样的特征:均值为0,标准差为1。
函数scale()对矩阵或数据框的制定了进行均值为0,标准差为1的标准化。
newdata <- scale(mydata
要对每一列进行任意均值和标准差的标准化,可以使用如下代码:
newdata <- scale(mydata) * SD + M
其中SD表示想要的标准差,M表示想要的均值。注意,在非数值型的列上用scale()函数会报错。如果要对指定列而不是整个数据框或矩阵进行标准化,可以使用如下代码:
newdata <- transform(mydata, myvar = scale(myvar) * 10 + 50)
此句将变量myvar标准化为均值50、标准差为10的变量。
问:是否有能将数据落到指定区间的方法?这种方法与书中的“指定均值和标准差”的方法有何异同?
5.2.3 概率函数
概率函数通常用来生成特征已知的模拟数据,以及在用户编写的统计函数中计算概率值。在R中,概率函数形如:
[dpqr]distribution_abbreviation()
其中第一个字母表示其所指分布的某一方面:
d = 密度函数(density)
p = 分布函数(distribution function)
q = 分位数函数(quantile function)
r = 生成随机数(随机偏差)
1. 设定随机数种子
每次生成伪随机数的时候,函数都会使用一个不同的种子,可以通过函数set.seed()显式指定这个种子,让结果可以重现(reproducible)。代码清单5-2给出了一个示例,这里的函数runif()用来生成0到1区间上服从均匀分布的伪随机数。
设置种子之后,如果种子是同一个数,那么每次产生的随机数,求和、均值、标准差都相同。这几个参数与种子数之间有什么关系?
注意:如果需要设定种子,那么在每一次生成随机数之前都要设定
2. 生成多元正态数据
蒙特卡洛方法:也称统计模拟方法,是以概率统计理论为指导的一类非常重要的数值计算方法。是指使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。与它对应的是确定性算法。蒙特卡洛方法百度百科解释
MASS包可以让获取来自给定均值向量和协方差阵的多元正态分布的数据变得更容易。调用格式为:
mvrnorm(n, mean, sigma)
其中n是你想要的样本大小,mean为均值向量,而sigma是方差-协方差矩阵(或相关矩阵)。
5.2.4 字符处理函数
书中P89-P90页,表5-6 给出了字符处理函数表。
请注意,函数grep()、sub()和strsplit()能够搜索某个文本字符串(fixed = TRUE)或者某个正则表达式(fixed = FALSE,默认值为FALSE)。正则表达式为文本模式的匹配提供了一套清晰而简练的语法。正则表达式 - 语法或者正则表达式语法。
例如^[hc]?at
可以匹配任意0个或1个以h或c开头、后接at的字符串。因此,此表达式可以匹配hat、cat和at,但不会匹配bat。
5.2.5 其他实用函数
转义符“\”,\n表示新行,\t为制表符,'为单引号,\b为退格。
5.2.6 将函数应用于矩阵和数据框
R函数的诸多有趣特性之一就是他们可以应用到一系列的数据对象上,包括向量、标量、矩阵、数组和数据框。
R中提供了一个apply()函数,可将一个任意函数“应用”到矩阵、数组、数据框的任何维度上。其格式为:
apply(x, MARDIN, FUN, …)
其中,x为数据对象,MARGIN是维度的下标,FUN是由你指定的函数,而…则包含了想传递给FUN的参数。在矩阵或数据框中,MARGIN=1表示行,MARGIN=2表示列。
mydata <- matrix(rnorm(30), nrow = 6)
mydata
apply(mydata, 1, mean)
apply(mydata, 2, mean)
apply(mydata, 2, mean, trim =0.2)
最后一行表示计算每一列的截尾均值,忽略了最高和最低的20%。
apply()可把函数应用到数组的某个维度上,而lapply()和aspply()则可将函数应用到列表(list)上。
5.3 数据处理难题的一套解决方案
针对5.1节提出的问题,本节给出了一个解决方案。
-
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, 37, 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(0.8, 0.6, 0.4, 0.2))
y
-
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), " ")
-
name
-
lastname <- sapply(name, "[", 2)
-
firstname <- sapply(name, "[", 1)
-
roster <- cbind(firstname, lastname, roster[, -1])
-
roster <- roster[order(lastname, firstname),]
-
roster
以下是解读过程:
限定了输出小数点后数字的位数,并且让输出更容易阅读。
输入学生姓名。
输入数学成绩。
输入科学成绩。
输入英语成绩。
创建一个数据框roster,其中的变量分别是:姓名、数学成绩、科学成绩、英语成绩。
将数据框roster的第二到四列标准化,并保存到z中。标准化的原因是:由于数学、科学、和英语考试得到分值不同(均值和标准差相去甚远),在组合之前需要让他们变得可以比较。标准化就是让每科的成绩都用单位标准差来表示,而不是以原始的尺度来表示了。
求z中所有行的均值,并保存到score中。
将roster和score按列合成为一个矩阵,保存到roster中。
分别求score的20%分位数,40%分位数,60%分位数,80%分位数。
查看score的20%分位数,40%分位数,60%分位数,80%分位数。分别是
80% : 0.91
60% : 0.32
40% : -0.36
20% : -0.88
将大于等于80%,即大于等于0.91的成绩评为A。
将60%(包含)至80%(不包含),即位于0.32(包含)和0.91(不包含)之间的成绩,评为B。
将40%(包含)至60%(不包含),即位于-0.36(包含)和0.32(不包含)之间的成绩,评为C。
将20%(包含)至40%(不包含),即位于-0.88(包含)和-0.36(不包含)之间的成绩,评为D。
将小于20%,即小于-0.88的成绩评为F。
使用strsplit()函数,将原数据框中的姓名,按照空格分成两列,并保存到新的列表name中。
查看新列表name。
选取列表中第二列,保存到一个新变量lastname中。
选取列表中第一列,保存到一个新变量firstname中。
将firstname和lastname添加到花名册roster中,并将原本第一个变量Student删除,(roster[, -1])。
使用order()函数依照firstname和lastname对数据框进行排序。
查看数据框roster。
5.4 控制流
为了理解贯穿本节的语法示例,请牢记以下概念:
语句(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) {pirnt ("Hello"); i <- i - 1}
请确保括号内while的条件语句能够改变,即让它在某个时刻不再为真——否则循环将永不停止!
5.4.2 条件执行
在条件执行结构中,一条或一组语句仅在满足一个指定条件时执行。条件执行结构包括if-else、ifelse和switch。
1. if-else结构
控制结构if-else在某个给定条件为真时执行语句。也可以同时在条件为假时执行另外的语句,语法为:
if (cond) statement
if (cond) statement1 else statement2
示例如下:
if (is.character(grade)) grede <- as.factor(grade)
当grade为字符型向量时,将其转化为因子。
if (!is.factor(grade)) grade <- as.factor else print ("Grade already is a factor")
当grade不是因子时,将其转化为因子,当其是因子是,输出一段信息。
2. ifelse结构
ifelse是if-else比较紧凑的向量化结构。语法为:
ifelse (cond, statement1, statement2)
当条件为真时,执行第一条语句,当条件为假时,执行第二条语句。
示例如下:
ifelse (score > 0.5, print("Passed"), print("Failed"))
outcome <- ifels (score > 0.5, "Passed", "Failed")
3. switch结构
switch根据一个表达式的值选择语句执行。语法如下:
switch(expr, …)
其中的…表示与expr的各种可能输出值绑定的语句。
5.5 用户自编函数
R最大的优点之一就是用户可以自行添加函数。R中许多函数都是由已有函数构成的。一个函数的结构看起来大致如此:
myfunction <- function(arg1, arg2, …){
statements
return(object)
}
在第二个自编函数的示例中,函数cat()仅会在输入的日期格式类型不匹配“long”或“short”时执行。使用一个表达式来捕获错误输入的参数值通常是一个好主意
有若干函数可以用来为函数添加错误捕获和纠正功能。warning()可以生成一条错误提示信息。message()可以生成一条诊断信息。stop()可以停止当前表达式的执行并提示错误。
小提示
一旦开始编写无论任何长度和复杂度的函数,优秀调试工具的重要性都会凸显出来。R中有许多实用的内建调试函数,也有许多用户贡献包提供了额外的功能。
关于这个话题,一份优秀的参考资料是Duncan Murdoch整理的“Debugging in R”。
5.6 整合与重构
在整合数据时,往往将多组观测替换为根据这些观测计算的描述性统计量。在重塑数据时,则会通过修改数据的结构(行和列)来决定数据的组织方式。
5.6.1 转置
转置(反转行和列)也许是重塑数据集的众多方法种最简单的一个了。使用函数t()即可对一个矩阵或者数据框进行转置。对于后者,行名将成为变量(列)名。
5.6.2 整合数据
在R中使用一个或多个by变量和一个预先定义好的函数来折叠(collapse)数据时比较容易得多。其调用格式为:
aggregate(x, by, FUN)
其中x是待折叠的数据对象,by是一个变量名组成的列表,这些变量将被去掉以形成新的观测,而FUN则是用来计算描述性统计量的标量函数,它将用来计算新观测中的值。
5.6.3 reshape包
首先将数据“融合”(melt),以使每一行都是一个唯一的标识符-变量组合。然后将数据“重铸”为你想要的任何形状。在重铸过程中你可以使用任何函数对数据进行整合。
在这个数据集中,测量(measurement)是指最后两列中的值(5、6、3、5、6、1、2、4)。每个测量都能够被标识符(在本例中,标识符是指ID、Time以及观测属于X1还是X2)唯一地确定。
1. 融合
使用以下代码:
install.package("reshape")
library(reshape)
md <- melt(mydata, id = (c("id", "time")))
2. 重铸
cast()函数读取已融合的数据,并使用你提供的公式和一个(可选的)用于整合数据的函数将其重塑。调用格式为:
newdata <- cast(md, formula, FUN)
其中的md为已融合的数据,formula描述了想要的最后结果,而FUN是(可选的)数据整合函数。其接受公式形如:
rowvar1 + rowvar2 + … + ~ colvar1 + colvar2 + …
在这一公式中,rowvar1 + rowvar2 + … 定义了要划掉的变量集合,以确定各行的内容,而colvar1 + colvar2 + … 则定义了要划掉的、确定各列内容的变量集合。
<font color=gray size=3 face="微软雅黑">作者注:本节(5.6.3)由于第一个示例代码就无法得到书中的结果,所以,本节学习笔记实际上只摘取了书中的内容,未理解其中的意义。主要是由于示例的表5-8 不知道何种形式的数据集,书中未给出,在尝试了矩阵、数据框、列表创建相同内容的数据集之后,均失败。</font>