在第2章中,我们讨论了多种导入数据到R中的方法。遗憾的是,将我们的数据表示为矩阵或数据框这样的矩形形式仅仅是数据准备的第一步。这里可以演绎Kirk船长在《星际迷航》“末日决战的滋味”一集中的台词(这完全验明了我的极客基因):“数据是一件麻烦事——一件非常非常麻烦的事。”在我的工作中,有多达60%的数据分析时间都花在了实际分析前数据的准备上。我敢大胆地说,多数需要处理现实数据的分析师可能都面临着以某种形式存在的类似问题。让我们先看一个例子。
一个示例
本人当前工作的研究主题之一是男性和女性在领导各自企业方式上的不同。典型的问题如下。
- 处于管理岗位的男性和女性在听从上级的程度上是否有所不同?
- 这种情况是否依国家的不同而有所不同,或者说这些由性别导致的不同是否普遍存在?
解答这些问题的一种方法是让多个国家的 经理人的上司对其服从程度打分,使用的问题类似于:这名经理在做出人事决策之前会询问我的意见。
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
非常不同意 | 不同意 | 既不同意也不反对 | 同意 | 非常同意 |
结果数据可能类似于表4-1。各行数据代表了某个经理人的上司对他的评分。
接下来创建 leadership 数据框:
manager <- c(1, 2, 3, 4, 5)
date <- c("10/24/08", "10/28/08", "10/1/08", "10/12/08", "5/1/09")
country <- c("US", "US", "UK", "UK", "UK")
gender <- c("M", "F", "F", "M", "F")
age <- c(32, 45, 25, 39, 99)
q1 <- c(5, 3, 3, 3, 2)
q2 <- c(4, 5, 5, 3, 2)
q3 <- c(5, 2, 5, 4, 1)
q4 <- c(5, 5, 5, NA, 2)
q5 <- c(5, 5, 2, NA, 1)
leadership <- data.frame(manager, date, country, gender, age, q1, q2, q3, q4, q5, stringsAsFactors = FALSE)
为了解决感兴趣的问题,我们必须首先解决一些数据管理方面的问题。这里列出其中一部分。
- 五个评分(q1到q5)需要组合起来,即为每位经理人生成一个平均服从程度得分。
- 在问卷调查中,被调查者经常会跳过某些问题。例如,为4号经理人打分的上司跳过了问题4和问题5。我们需要一种处理不完整数据的方法,同时也需要将99岁这样的年龄值重编码为缺失值。
- 一个数据集中也许会有数百个变量,但我们可能仅对其中的一些感兴趣。为了简化问题,我们往往希望创建一个只包含那些感兴趣变量的数据集。
- 既往研究表明,领导行为可能随经理人的年龄而改变,二者存在函数关系。要检验这种观点,我们希望将当前的年龄值重编码为类别型的年龄组(例如年轻、中年、年长)。
- 领导行为可能随时间推移而发生改变。我们可能想重点研究最近全球金融危机期间的服从行为。为了做到这一点,我们希望将研究范围限定在某一个特定时间段收集的数据上(比如,2009年1月1日到2009年12月31日)。
我们将在本章中逐个解决这些问题,同时完成如数据集的组合与排序这样的基本数据管理任务。在第5章,我们会讨论一些更为高级的话题。
创建新变量
在典型的研究项目中,你可能需要创建新变量或者对现有的变量进行变换。这可以通过以下形式的语句来完成:
变量名 ← 表达式
以上语句中的“表达式”部分可以包含多种运算符和函数。表4-2列出了R中的算术运算符。
算术运算符可用于构造公式(formula)。
假设你有一个名为 mydata 的数据框,其中的变量为 x1 和 x2 ,现在你想创建一个新变量 sumx存储以上两个变量的加和,并创建一个名为 meanx 的新变量存储这两个变量的均值。你希望将两个新变量整合到原始的数据框中。代码清单4-2提供了三种不同的方式来实现这个目标,具体选择哪一个由你决定,所得结果都是相同的。
mydata <- data.frame(x1 = c(2, 2, 6, 4), x2 = c(3, 4, 2, 8))
# 方法一
mydata$sumx <- mydata$x1 + mydata$x2
mydata$meanx <- mydata$sumx/2
# 方法二
attach(mydata)
mydata$sumx <- x1 + x2
mydata$meanx <- (x1 + x2)/2
detach(mydata)
#方法三
mydata <- transform(mydata, sumx = x1 + x2, meanx = (x1 + x2)/2)
> mydata
x1 x2 sumx meanx
1 2 3 5 2.5
2 2 4 6 3.0
3 6 2 8 4.0
4 4 8 12 6.0
我个人倾向于第三种方式,即 transform() 函数的一个示例。这种方式简化了按需创建新
变量并将其保存到数据框中的过程。
变量的重编码
重编码涉及根据同一个变量和/或其他变量的现有值创建新值的过程。举例来说,你可能想:
- 将一个连续型变量修改为一组类别值;
- 将误编码的值替换为正确值;
- 基于一组分数线创建一个表示及格/不及格的变量。
要重编码数据,可以使用R中的一个或多个逻辑运算符(见表4-3)。逻辑运算符表达式可返回 TRUE 或 FALSE 。
不妨假设你希望将 leadership 数据集中经理人的连续型年龄变量 age 重编码为类别型变量agecat ( Young 、 Middle Aged 、 Elder )。首先,必须将99岁的年龄值重编码为缺失值,使用的代码为:
leadership$age[leadership$age == 99] <- NA
语句 variable[condition] <- expression 将仅在 condition 的值为 TRUE 时执行赋值。在指定好年龄中的缺失值后,你可以接着使用以下代码创建 agecat 变量:
leadership$sgecat[leadership$sge > 75] <- "Elder"
leadership$agecat[leadership$age >= 55 & leadership$age = 75] <- "Middle Aged"
leadership$agecat[leadership$age < 55] <- "Young"
你在 leadership$agecat 中写上了数据框的名称,以确保新变量能够保存到数据框中。你将中年人( Middle Aged )定义为55到75岁,这样不会让我感觉自己是个老古董。请注意,如果你一开始没把99重编码为 age 的缺失值,那么经理人5就将在变量 agecat 中被错误地赋值为“老年人”( Elder )。
> leadership
manager date country gender age q1 q2 q3 q4 q5 agecat
1 1 10/24/08 US M 32 5 4 5 5 5 Young
2 2 10/28/08 US F 45 3 5 2 5 5 Young
3 3 10/1/08 UK F 25 3 5 5 5 2 Young
4 4 10/12/08 UK M 39 3 3 4 NA NA Young
5 5 5/1/09 UK F NA 2 2 1 2 1 <NA>
这段代码可以写成更紧凑的:
leadership <- within(leadership, {
agecat <- NA
agecat[age > 75] <-"Elder"
agecat[age >= 55 & age <= 75] <- "Middle Aged"
agecat[age < 55] <- "Young"
})
函数 within() 与函数 with() 类似(见2.2.4节),不同的是它允许你修改数据框。首先,我们创建了 agecat 变量,并将每一行都设为缺失值。括号中剩下的语句接下来依次执行。请记住agecat 现在只是一个字符型变量,你可能更希望像2.2.5节讲解的那样把它转换成一个有序型因子。
若干程序包都提供了实用的变量重编码函数,特别地, car 包中的 recode() 函数可以十分简便地重编码数值型、字符型向量或因子。而 doBy 包提供了另外一个很受欢迎的函数recodevar() 。最后,R中也自带了 cut() ,可将一个数值型变量按值域切割为多个区间,并返回一个因子。
变量的重命名
如果对现有的变量名称不满意,你可以交互地或者以编程的方式修改它们。假设你希望将变量名 manager 修改为 managerID ,并将 date 修改为 testDate ,那么可以使用语句:
fix(leadership)
来调用一个交互式的编辑器,单击变量名,然后在弹出的对话框中将其重命名(见图4-1)。若以编程方式, reshape 包中有一个 rename() 函数,可用于修改变量名。 rename() 函数的使用格式为:
rename(dataframe, c(oldname1="newname1", oldname2="newname2", ...))
这里是一个示例:
install.packages("reshape")
library(reshape)
leadership <- rename(leadership, c(manager = "managerID", date = "testDate"))
names(leadership)[2] <- "testDate"
names(leadership)[6:10] <- c("item1", "item2", "item3", "item4", "item5")
> leadership
managerID testDate country gender age item1 item2 item3 item4 item5 agecat
1 1 10/24/08 US M 32 5 4 5 5 5 Young
2 2 10/28/08 US F 45 3 5 2 5 5 Young
3 3 10/1/08 UK F 25 3 5 5 5 2 Young
4 4 10/12/08 UK M 39 3 3 4 NA NA Young
5 5 5/1/09 UK F NA 2 2 1 2 1 <NA>
reshape 包未被默认安装,所以在首次使用它之前需要先使用 install.packages("reshape") 命令安装它。reshape 包拥有一系列强大的数据集结构修改函数,我们将在第5章中探究其中的一部分。
rename()可指定要修改的变量名,names()可修改连续的多个变量名。
缺失值
考虑以下代码:
> rm(list=ls())
> x <- c(1, 2, NA, 3)
> y <- x[1] + x[2] + x[3] +x[4]
> z <- sum(x)
> z
[1] NA
> z <- sum(x,na.rm=TRUE)
> z
[1] 6
由于 x 中的第3个元素是缺失值,所以 y 和 z 也都是 NA (缺失值)。好在多数的数值函数都拥有一个na.rm=TRUE 选项,可以在计算之前移除缺失值并使用剩余值进行计算。
这里, y 等于6。在使用函数处理不完整的数据时,请务必查阅它们的帮助文档(例如, help(sum) ),检查这些函数是如何处理缺失数据的。函数 sum() 只是我们将在第5章中讨论的众多函数之一,使用这些函数可以灵活而轻松地转换数据。
你可以通过函数 na.omit() 移除所有含有缺失值的观测。 na.omit() 可以删除所有含有缺失数据的行。在代码清单4-4中,我们将此函数应用到了 leadership 数据集上。
在任何规模的项目中,数据都可能由于未作答问题、设备故障或误编码数据的缘故而不完整。在R中,缺失值以符号 NA (Not Available,不可用)表示。不可能出现的值(例如,被0除的结果)通过符号 NaN (Not a Number,非数值)来表示。与SAS等程序不同,R中字符型和数值型数据使用的缺失值符号是相同的。
R提供了一些函数,用于识别包含缺失值的观测。函数 is.na() 允许你检测缺失值是否存在。假设你有一个向量:
y <- c(1, 2, 3, NA)
,然后使用函数:is.na(y)
,将返回 c(FALSE, FALSE, FALSE, TRUE)
。
请注意 is.na() 函数是如何作用于一个对象上的。它将返回一个相同大小的对象,如果某个元素是缺失值,相应的位置将被改写为 TRUE ,不是缺失值的位置则为 FALSE 。代码清单4-3将此函数应用到了我们的 leadership 数据集上。
manager <- c(1, 2, 3, 4, 5)
date <- c("10/24/08", "10/28/08", "10/1/08", "10/12/08", "5/1/09")
country <- c("US", "US", "UK", "UK", "UK")
gender <- c("M", "F", "F", "M", "F")
age <- c(32, 45, 25, 39, 99)
q1 <- c(5, 3, 3, 3, 2)
q2 <- c(4, 5, 5, 3, 2)
q3 <- c(5, 2, 5, 4, 1)
q4 <- c(5, 5, 5, NA, 2)
q5 <- c(5, 5, 2, NA, 1)
leadership <- data.frame(manager, date, country, gender, age, q1, q2, q3, q4, q5, stringsAsFactors = FALSE)
> leadership
manager date country gender age q1 q2 q3 q4 q5
1 1 10/24/08 US M 32 5 4 5 5 5
2 2 10/28/08 US F 45 3 5 2 5 5
3 3 10/1/08 UK F 25 3 5 5 5 2
4 4 10/12/08 UK M 39 3 3 4 NA NA
5 5 5/1/09 UK F 99 2 2 1 2 1
newdata <- na.omit(leadership)
> newdata
manager date country gender age q1 q2 q3 q4 q5
1 1 10/24/08 US M 32 5 4 5 5 5
2 2 10/28/08 US F 45 3 5 2 5 5
3 3 10/1/08 UK F 25 3 5 5 5 2
5 5 5/1/09 UK F 99 2 2 1 2 1
在结果被保存到 newdata 之前,所有包含缺失数据的行均已从 leadership 中删除。删除所有含有缺失数据的观测(称为行删除,listwise deletion)是处理不完整数据集的若干手段之一。如果只有少数缺失值或者缺失值仅集中于一小部分观测中,行删除不失为解决缺失值问题的一种优秀方法。但如果缺失值遍布于数据之中,或者一小部分变量中包含大量的缺失数据,行删除可能会剔除相当比例的数据。我们将在第15章中探索若干更为复杂精妙的缺失值处理方法。下面,让我们谈谈日期值。
日期值
日期值通常以字符串的形式输入到R中,然后转化为以数值形式存储的日期变量。函数as.Date() 用于执行这种转化。其语法为 as.Date(x, "input_format") ,其中 x 是字符型数据, input_format 则给出了用于读入日期的适当格式(见表4-4)。
类型转换
R中提供了一系列用来判断某个对象的数据类型和将其转换为另一种数据类型的函数。R与其他统计编程语言有着类似的数据类型转换方式。举例来说,向一个数值型向量中添加一个字符串会将此向量中的所有元素转换为字符型。你可以使用表4-5中列出的函数来判断数据的类型或者将其转换为指定类型。
名为 is.datatype() 这样的函数返回 TRUE 或 FALSE ,而 as.datatype() 这样的函数则将其参数转换为对应的类型。代码清单4-5提供了一个示例。
a <- c(1, 2, 3)
a
is.numeric(a)
is.vector(a)
a <- as.character(a)
is.numeric(a)
is.vector(a)
is.character(a)
当和第5章中讨论的控制流(如 if-then )结合使用时, is.datatype() 这样的函数将成为一类强大的工具,即允许根据数据的具体类型以不同的方式处理数据。另外,某些R函数需要接受某个特定类型(字符型或数值型,矩阵或数据框)的数据, as.datatype() 这类函数可以让你在分析之前先行将数据转换为要求的格式。
数据排序
有些情况下,查看排序后的数据集可以获得相当多的信息。例如,哪些经理人最具服从意识?在R中,可以使用 order() 函数对一个数据框进行排序。默认的排序顺序是升序。在排序变量的前边加一个减号即可得到降序的排序结果。以下示例使用 leadership 演示了数据框的排序。
语句:
newdata <- leadership[order(leadership$age),]
> newdata
manager date country gender age q1 q2 q3 q4 q5
3 3 10/1/08 UK F 25 3 5 5 5 2
1 1 10/24/08 US M 32 5 4 5 5 5
4 4 10/12/08 UK M 39 3 3 4 NA NA
2 2 10/28/08 US F 45 3 5 2 5 5
5 5 5/1/09 UK F 99 2 2 1 2 1
创建了一个新的数据集,其中各行依经理人的年龄升序排序。语句:
attach(leadership)
newdata2 <- leadership[order(gender, age), ]
detach(leadership)
newdata2
则将各行依女性到男性、同样性别中按年龄升序排序。
数据集的合并
如果数据分散在多个地方,你就需要在继续下一步之前将其合并。本节展示了向数据框中添加列(变量)和行(观测)的方法。
添加列
要横向合并两个数据框(数据集),请使用 merge() 函数。在多数情况下,两个数据框是通过一个或多个共有变量进行联结的(即一种内联结,inner join)。例如newdatafram
的生成:
ID <- c(1, 2, 3)
name1 <- c("a", "b", "c")
age1 <- c(11, 12, 13)
name2 <- c("d", "e", "f")
age2 <- c(14, 15, 16)
dataframe1 <- data.frame(ID, name1, age1)
> dataframe1
ID name1 age1
1 1 a 11
2 2 b 12
3 3 c 13
dataframe2 <- data.frame(ID, name2, age2)
dataframe2
> dataframe2
ID name2 age2
1 1 d 14
2 2 e 15
3 3 f 16
newdataframe <- merge(dataframe1, dataframe2, by = "ID")
> newdataframe
ID name1 age1 name2 age2
1 1 a 11 d 14
2 2 b 12 e 15
3 3 c 13 f 16
newdataframe2 <- cbind(dataframe1, dataframe2)
> newdataframe2
ID name1 age1 ID name2 age2
1 1 a 11 1 d 14
2 2 b 12 2 e 15
3 3 c 13 3 f 16
如果要直接横向合并两个矩阵或数据框,并且不需要指定一个公共索引,那么可以直接使用 cbind() 函数,如newdataframe2
的生成。这个函数将横向合并对象A和对象B。为了让它正常工作,每个对象必须拥有相同的行数,且要以相同顺序排序。
添加行
要纵向合并两个数据框(数据集),请使用 rbind() 函数:
dataframe3 <- data.frame(ID, age1, name1)
dataframe3
> dataframe3
ID age1 name1
1 1 11 a
2 2 12 b
3 3 13 c
newdataframe3 <- rbind(dataframe1, dataframe3)
> newdataframe3
ID name1 age1
1 1 a 11
2 2 b 12
3 3 c 13
4 1 a 11
5 2 b 12
6 3 c 13
两个数据框必须拥有相同的变量,不过它们的顺序不必一定相同。如果 dataframeA 中拥有dataframeB 中没有的变量,请在合并它们之前做以下某种处理:
- 删除 dataframeA 中的多余变量;
- 在 dataframeB 中创建追加的变量并将其值设为 NA (缺失)。
纵向联结通常用于向数据框中添加观测。
数据集取子集
R拥有强大的索引特性,可以用于访问对象中的元素。也可利用这些特性对变量或观测进行选入和排除。以下几节演示了对变量和观测进行保留或删除的若干方法。
选入(保留)变量
从一个大数据集中选择有限数量的变量来创建一个新的数据集是常有的事。在第2章中,数据框中的元素是通过 dataframe[row indices, column indices]
这样的记号来访问的。你可以沿用这种方法来选择变量。
例如:
newdata1 <- leadership[ , c(6:10)]
> newdata1
q1 q2 q3 q4 q5
1 5 4 5 5 5
2 3 5 2 5 5
3 3 5 5 5 2
4 3 3 4 NA NA
5 2 2 1 2 1
从 leadership 数据框中选择了变量 q1 、 q2 、 q3 、 q4 和 q5 ,并将它们保存到了数据框 newdata中。将行下标留空( , )表示默认选择所有行。
剔除(丢弃)变量
剔除变量的原因有很多。举例来说,如果某个变量中有若干缺失值,你可能就想在进一步分析之前将其丢弃。下面是一些剔除变量的方法。
你可以使用语句:
myvars3 <- names(leadership) %in% c("q3", "q4")
newdata4 <- leadership[!myvars3]
newdata4
剔除变量 q3 和 q4 。为了理解以上语句的原理,你需要把它拆解如下。
(1) names(leadership) 生成了一个包含所有变量名的字符型向量:c("managerID","testDate","country","gender","age","q1", "q2","q3","q4","q5") 。
(2) names(leadership) %in% c("q3", "q4") 返回了一个逻辑型向量, names(leadership)中每个 匹配 q3 或 q4 的元素的值为 TRUE ,反之为 FALSE : c(FALSE, FALSE, FALSE, FALSE,FALSE, FALSE, FALSE, TRUE, TRUE, FALSE) 。
(3) 运算符非( ! )将逻辑值反转: c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE) 。
(4) leadership[c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE,TRUE)] 选择了逻辑值为 TRUE 的列,于是 q3 和 q4 被剔除了。
选入观测
选入或剔除观测(行)通常是成功的数据准备和数据分析的一个关键方面。代码清单4-6给出了一些例子。
newdata <- leadership[which(leadership$gender == "M" & leadership$age > 30), ]
> newdata
manager date country gender age q1 q2 q3 q4 q5
1 1 10/24/08 US M 32 5 4 5 5 5
4 4 10/12/08 UK M 39 3 3 4 NA NA
代码清单4-6 选入观测在以上每个示例中,你只提供了行下标,并将列下标留空(故选入了所有列)。在第一个示
例中,你选择了第1行到第3行(前三个观测)。
在第二个示例中,你选择了所有30岁以上的男性。让我们拆解这行代码以便理解它。
逻辑比较 leadership$gender=="M" 生成了向量 c(TRUE, FALSE, FALSE, TRUE,FALSE) 。
逻辑比较 leadership$age > 30 生成了向量 c(TRUE, TRUE, FALSE, TRUE, TRUE) 。
逻辑比较 c(TRUE, FALSE, FALSE, TRUE, TRUE) & c(TRUE, TRUE, FALSE, TRUE,TRUE) 生成了向量 c(TRUE, FALSE, FALSE, TRUE, FALSE) 。
函数 which() 给出了向量中值为 TRUE 元素的下标。因此, which(c(TRUE, FALSE,FALSE, TRUE, FALSE)) 生成了向量 c(1, 4) 。
leadership[c(1,4),] 从数据框中选择了第一个和第四个观测。这就满足了我们的选取准则(30岁以上的男性)。
在本章开始的时候,我曾经提到,你可能希望将研究范围限定在2009年1月1日到2009年12月31日之间收集的观测上。怎么做呢?这里有一个办法:
leadership$date <- as.Date(leadership$date, "%m/%d/%y")
startdate <- as.Date("2009-01-01")
enddate <- as.Date("2009-12-31")
newdate <- leadership[which(leadership$date >= startdate & leadership$date <= enddate), ]
> newdate
manager date country gender age q1 q2 q3 q4 q5
5 5 2009-05-01 UK F 99 2 2 1 2 1
首先,使用格式mm/dd/yy将开始作为字符值读入的日期转换为日期值。然后,创建开始日期和结束日期。由于 as.Date() 函数的默认格式就是yyyy-mm-dd,所以你无须在这里提供这个参数。最后,像上例一样选取那些满足你期望中准则的个案即可。
subset() 函数
前两节中的示例很重要,因为它们辅助描述了逻辑型向量和比较运算符在R中的解释方式。理解这些例子的工作原理在总体上将有助于你对R代码的解读。既然你已经用笨办法完成了任务,现在不妨来看一种简便方法。
使用 subset 函数大概是选择变量和观测最简单的方法了。两个示例如下:
newdata1 <- subset(leadership, age >= 35 | age < 24, select = c(q1, q2, q3, q4))
newdata1
newdata2 <- subset(leadership, gender == "M" & age > 25, select = gender:q4)
newdata2
在第一个示例中,你选择了所有 age 值大于等于35或 age 值小于24的行,保留了变量 q1 到 q4 。
在第二个示例中,你选择了所有25岁以上的男性,并保留了变量 gender 到 q4 ( gender 、 q4 和其间所有列)。你在第2章中已经看到了冒号运算符 from:to 。在这里,它表示了数据框中变量 from到变量 to 包含的所有变量。
随机抽样
在数据挖掘和机器学习领域,从更大的数据集中抽样是很常见的做法。举例来说,你可能希望选择两份随机样本,使用其中一份样本构建预测模型,使用另一份样本验证模型的有效性。
sample() 函数能够让你从数据集中(有放回或无放回地)抽取大小为n的一个随机样本。你可以使用以下语句从 leadership 数据集中随机抽取一个大小为3的样本:
mysample <- leadership[sample(1:nrow(leadership), 3, replace = TRUE), ]
mysample
sample() 函数中的第一个参数是一个由要从中抽样的元素组成的向量。在这里,这个向量是1到数据框中观测的数量,第二个参数是要抽取的元素数量,第三个参数表示无放回抽样。
sample() 函数会返回随机抽样得到的元素,之后即可用于选择数据框中的行。
更进一步
R中拥有齐全的抽样工具,包括抽取和校正调查样本(参见 sampling 包)以及分析复杂调查数据(参见 survey 包)的工具。其他依赖于抽样的方法,包括自助法和重抽样统计方法,详见第11章。
使用 SQL 语句操作数据框
目前感觉没啥用,不细说了