R语言系列2:基本数据管理
此文内容为《R语言实战》的笔记,人民邮电出版社出版。
从高中电脑课学VB开始,大一课内开始学习C++,到后来大二为了数模学习Matlab,到大三为了搞深度学习自学Python,到研究生之初学习Stata——选择一门语言对我来说就像是小时候玩冒险岛,到10级的时候是转战士好还是弓箭手好一般的纠结。我查阅了很多B乎的文章,最后觉得可能R比较合适现在的我。
作为从Python转进来R的新手,我把可能会用经常需要用到或经常需要查阅的代码贴上来,主要是为了日后方便查找,就像“字典”一样。推文的顺序与教材不同,为了简洁,我还会删除一些我个人认为不太重要的章节。我还会按照自己的学习进度发布文章,请读者见谅。
本文章仅供学习参考,请勿转载,侵删!
目录
- 4 基本数据管理
- 4.1 一个示例
- 4.2 创建新变量
- 4.3 变量的重编码
- 4.4 变量重命名
- 4.5 缺失值
- 4.5.1 在分析中排除缺失值
- 4.6 日期值
- 4.7 类型转换
- 4.8 数据排序
- 4.9 数据集的合并
- 4.9.1 横向合并数据集
- 4.9.2 纵向合并数据集
- 4.10 数据集的子集
- 4.10.1 选取变量
- 4.10.2 删除变量
- 4.10.3 选择观测值
- 4.10.4 subset()函数
- 4.10.5 随机抽样
第4章 基本数据管理
4.1 一个示例
创建一个数据集,是各经理人的上司对其打分,列分别有经理人的ID、打分的日期、经理人的国别、经理人的性别、经理人的年龄、还有5个上司分别的评分:
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)
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
现在我们把需要进行的操作清理一下:
- 五个评分(q1-q5)需要平均组合起来
- 需要处理数据缺失的问题,而且对99岁这种年龄编码为缺失值
- 我们只对其中某些变量感兴趣
- 领导行为可能随经理人的年龄而变,二者存在函数关系。需要检验这种观点,希望将当前年龄重新编码为年龄组
- 希望将研究范围限定在某个日期内
4.2 创建新变量
可以通过以下的语句:
变量名 <- 表达式
完成赋值。其中表达式
部分可以包含多种运算符和函数,基本的运算符有:
+ # 加法
- # 减法
* # 乘法
/ # 除法
^或** # 指数
x%%y # 求余(x mod y)
x%/%y # 求整数
创建新变量的方法有多种,例如
mydata <- data.frame(x1=c(2,2,6,4),
x2=c(3,4,2,8))
mydata$sumx <- mydata$x1+mydata$x2
mydata$meanx <- mydata$sum/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
每次都使用mydata$
很麻烦,可以使用 attach()-detach()
或者 with()
方法。
也可以使用transform()
函数:
mydata <- data.frame(x1=c(2,2,6,4),
x2=c(3,4,2,8))
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
4.3 变量的重编码
重编码设计根据同一个变量或其他变量的现有值创建新值的过程,可能会使用逻辑运算符:
< # 小于
<= # 小于等于
> # 大于
>= # 大于等于
== # 严格等于
!= # 不等于
!x # 非x
x | y # x或y
x & y # x和y
isTRUE(x) # 测试x是否为TRUE
不妨假设我希望将leadership中经理人的连续年龄重编码为类别型变量agencat(Young, Middle Age, Elder)
。首先,必须将99岁年龄值重编码为缺失值:
leadership$age == 99
## [1] FALSE FALSE FALSE FALSE TRUE
leadership$age[leadership$age==99] <- NA
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 NA 2 2 1 2 1
其中,variable[condition] <- expression
表示仅对condition的值为TRUE时进行赋值。然后可以使用以下代码创建agecat变量:
leadership <- within(leadership,{
agecat <- NA
agecat[age>75] <- "Elder"
agecat[age>=55 & age<=75] <- "Middle Age"
agecat[age<55] <- "Young"})
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
其中,within()
的用法和with()
类似,不同的是它允许修改数据框。如果用with()
并赋值,那么赋值的变量会单独保存;如果用within
并赋值,那么赋值的变量会加入数据框中,见:
df <- data.frame(a=c(2:7), b=c(7:12))
with(df, {c <- a + b ; df;})
## a b
## 1 2 7
## 2 3 8
## 3 4 9
## 4 5 10
## 5 6 11
## 6 7 12
within(df, {c<-a+b; df;})
## a b c
## 1 2 7 9
## 2 3 8 11
## 3 4 9 13
## 4 5 10 15
## 5 6 11 17
## 6 7 12 19
注意,现在agecat
变量是字符型变量,分析的时候需要把它转为因子变量为好。
4.4 变量重命名
如果对现有的变量名不满意,可以使用names()
函数来重命名变量。首先查看leadership
里的变量名:
names(leadership)
## [1] "manager" "date" "country" "gender" "age" "q1" "q2"
## [8] "q3" "q4" "q5" "agecat"
然后我把date
重命名为testDate
,那么:
names(leadership)[2] <- "testDate"
leadership
## manager testDate 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
类似地,还可以执行:
names(leadership)[6:10] <- c("Item1", "Item2", "Item3", "Item4", "Item5")
leadership
## manager 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
4.5 缺失值
R提供了一些函数用于识别包含缺失值的观察。函数is.na()
允许你检测缺失值是否存在,比如:
y <- c(1, 2, 3, NA)
is.na(y)
## [1] FALSE FALSE FALSE TRUE
使用在leadership数据库中,比如检查是否存在没有打分的情况(6-10列是否缺失):
is.na(leadership[, 6:10])
## Item1 Item2 Item3 Item4 Item5
## [1,] FALSE FALSE FALSE FALSE FALSE
## [2,] FALSE FALSE FALSE FALSE FALSE
## [3,] FALSE FALSE FALSE FALSE FALSE
## [4,] FALSE FALSE FALSE TRUE TRUE
## [5,] FALSE FALSE FALSE FALSE FALSE
需要注意的是:
- 缺失值在R中是不可比较的,不能使用逻辑运算符检测缺失值
- 正无穷和负无穷都不是缺失值,他们分别用
Inf
和-Inf
来标记
4.5.2 在分析中排除缺失值
在R中,必须排除缺失值进行计算,否则包含缺失值的计算的结果也是缺失值,比如:
x <- c(1, 2, NA, 3)
y <- x[1] + x[2] + x[4]
y
## [1] 6
y <- x[1] + x[2] + x[3] + x[4]
y
## [1] NA
好在多数函数都拥有一个na.rm=TRUE
选项来帮你排除缺失值,比如:
x <- c(1, 2, NA, 3)
y <- sum(x)
y
## [1] NA
y <- sum(x, na.rm = TRUE) # na.rm默认是FALSE
y
## [1] 6
使用函数处理不完整的数据时,请检查他们的帮助文档。
你还可以通过na.omit()
移除所有包含缺失值的观测。na.omit()
可以删除所有包含缺失值的行。
newdata <- na.omit(leadership)
newdata
## manager 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
leadership
## manager 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
4.6 日期值
日期通常以字符串的形式输入到R中,然后转化为以数值形式保存的日期变量。
函数as.Date()
用于执行这种转化,其语法为
as.Date(x, "input_format")
其中,x是字符型数据,input_format
则给出了用于读入日期的适当格式:
==符号== == 说明 == == 示例 ==
%d # 数字表示的日期 # 0-31
%a # 缩写的星期名 # Mon
%A # 非缩写的星期名 # Monday
%m # 月份 # 0-12
%b # 缩写的月份 # Jan
%B # 非缩写的月份 # January
%y # 两位数的年份 # 20
%Y # 四位数的年份 # 2020
日期的默认输入格式为yyyy-mm-dd
,比如:
mydates <- as.Date(c("2020-8-23", "2020-8-24"))
mydates
## [1] "2020-08-23" "2020-08-24"
如果不是默认的输入格式,那么:
mydates <- as.Date(c("23/08/2020", "24/08/2020"), "%d/%m/%Y")
mydates
## [1] "2020-08-23" "2020-08-24"
比如,我想对leadership数据集的日期字符串变为日期变量,那么:
leadership$testDate <- as.Date(leadership$testDate, "%m/%d/%y")
leadership
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 1 1 2008-10-24 US M 32 5 4 5 5 5 Young
## 2 2 2008-10-28 US F 45 3 5 2 5 5 Young
## 3 3 2008-10-01 UK F 25 3 5 5 5 2 Young
## 4 4 2008-10-12 UK M 39 3 3 4 NA NA Young
## 5 5 2009-05-01 UK F NA 2 2 1 2 1
有趣的是,可以用 Sys.Date()
和 date()
分别获取当前的日期以及日期和时间,比如:
Sys.Date()
## [1] "2020-08-23"
date()
## [1] "Sun Aug 23 20:11:18 2020"
R的内部在存储日期时,是以1970年1月1日以来的天数来处理的,更早的日期用负数表示。这意味着日期是可以计算的:
nowdate <- Sys.Date()
borndate <- as.Date("1996-11-28")
days <- nowdate - borndate
days
## Time difference of 8669 days
最后,也可以使用difftime()
来计算时间间隔,并以星期、天、时、分、秒来表示:
nowdate <- Sys.Date()
borndate <- as.Date("1996-11-28")
difftime(nowdate, borndate, units = 'auto')
## Time difference of 8669 days
如何将日期变成字符串变量呢,非常简单:
strDates <- as.character(dates)
即可。进行转换后,可以使用一系列字符串处理函数处理数据。
4.7 类型转换
R提供了一系列用来判断对象数据类型和将其转换为其他类型的函数:
==判断== ==转换==
is.numeric() as.numeric()
is.character() as.character()
is.vector() as.vector()
is.matrix() as.matrix()
is.data.frame() as.data.frame()
is.factor() as.factor()
is.logical() as.logical()
例如:
a <- c(1:3)
a
## [1] 1 2 3
is.numeric(a)
## [1] TRUE
is.vector(a)
## [1] TRUE
a <- as.character(a)
a
## [1] "1" "2" "3"
is.numeric(a)
## [1] FALSE
is.vector(a)
## [1] TRUE
is.character(a)
## [1] TRUE
在结合第5张谈到的控制流结合使用时,is.type()
这样的函数将成为重要的工具,因为可以允许根据具体的数据类型以不同的方式处理数据。
4.8 数据排序
可以使用order()
对数据进行排序,默认是升序。在排序的变量前加一个符号可以得到降序的结果。例如:
order(leadership$age)
## [1] 3 1 4 2 5
newdata <- leadership[order(leadership$age),]
newdata
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 3 3 2008-10-01 UK F 25 3 5 5 5 2 Young
## 1 1 2008-10-24 US M 32 5 4 5 5 5 Young
## 4 4 2008-10-12 UK M 39 3 3 4 NA NA Young
## 2 2 2008-10-28 US F 45 3 5 2 5 5 Young
## 5 5 2009-05-01 UK F NA 2 2 1 2 1
如果需要对多个变量依次排序(性别-年龄),那么:
with(leadership,{
newdata <<- leadership[order(gender, age),]
})
newdata
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 3 3 2008-10-01 UK F 25 3 5 5 5 2 Young
## 2 2 2008-10-28 US F 45 3 5 2 5 5 Young
## 5 5 2009-05-01 UK F NA 2 2 1 2 1
## 1 1 2008-10-24 US M 32 5 4 5 5 5 Young
## 4 4 2008-10-12 UK M 39 3 3 4 NA NA Young
4.9 数据集的合并
如果数据分散在多个地方,则需要合并。
4.9.1 横向合并数据框
可以使用merge()
函数横向合并数据框。多数情况下,两个数据框是通过一个或多个共有的变量进行联结的,例如:
total <- merge(dfA, dfB. by="ID")
4.9.2 纵向合并数据框
可以使用 rbind()
函数纵向合并数据框。两个数据框必须有相同的变量,但顺序不必相同:
total <- rbind(dfA, dfB)
如果dfA有dfB中没有的变量,请
- 删除dfA多余的变量
- 往dfB增加变量,并赋值NA
4.10 数据集子集
R有强大的索引特性,可以用于访问对象的元素,也可以用于选取观测值或删除观测值。
4.10.1 选取变量
从一个大数据集中选取有限数量的变量来创建一个新的数据集是常有的事。前面已经提到使用
dataframe[rows, columns]
来选取数据集。还有其他方法如下所示:
newdata1 <- leadership[, c(6:10)]
newdata1
## Item1 Item2 Item3 Item4 Item5
## 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
myvar = c("Item1", "Item2", "Item3", "Item4", "Item5")
newdata2 <- leadership[myvar]
newdata2
## Item1 Item2 Item3 Item4 Item5
## 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
myvar = paste("Item", 1:5, sep = "")
myvar
## [1] "Item1" "Item2" "Item3" "Item4" "Item5"
newdata3 <- leadership[myvar]
这里的paste
创建了与myvar相同的字符串向量,将在后面具体讲解。
4.10.2 剔除变量
你可以使用下面的方法来剔除不要的变量:
myvars <- names(leadership) %in% c("Item3", "Item4")
newdata <- leadership[!myvars]
newdata
## manager testDate country gender age Item1 Item2 Item5 agecat
## 1 1 2008-10-24 US M 32 5 4 5 Young
## 2 2 2008-10-28 US F 45 3 5 5 Young
## 3 3 2008-10-01 UK F 25 3 5 2 Young
## 4 4 2008-10-12 UK M 39 3 3 NA Young
## 5 5 2009-05-01 UK F NA 2 2 1
其中,names(leadership)
产生一个包含所有变量名的字符型向量:
names(leadership)
## [1] "manager" "testDate" "country" "gender" "age" "Item1"
## [7] "Item2" "Item3" "Item4" "Item5" "agecat"
%in%
可以返回一个逻辑向量:
names(leadership) %in% c("Item3", "Item4")
## [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
于是!x
就能得到非x
也就是:
!names(leadership) %in% c("Item3", "Item4")
## [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE TRUE TRUE
所以就可以得到剔除变量后的数据框:
leadership[!names(leadership) %in% c("Item3", "Item4")]
## manager testDate country gender age Item1 Item2 Item5 agecat
## 1 1 2008-10-24 US M 32 5 4 5 Young
## 2 2 2008-10-28 US F 45 3 5 5 Young
## 3 3 2008-10-01 UK F 25 3 5 2 Young
## 4 4 2008-10-12 UK M 39 3 3 NA Young
## 5 5 2009-05-01 UK F NA 2 2 1
4.10.3 选择观测值
选入和剔除观测值是成功准备数据的关键。下面给出一些例子:
newdata <- leadership[1:3,]
newdata
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 1 1 2008-10-24 US M 32 5 4 5 5 5 Young
## 2 2 2008-10-28 US F 45 3 5 2 5 5 Young
## 3 3 2008-10-01 UK F 25 3 5 5 5 2 Young
newdata <- leadership[leadership$gender=="M" & leadership$age>30, ]
newdata
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 1 1 2008-10-24 US M 32 5 4 5 5 5 Young
## 4 4 2008-10-12 UK M 39 3 3 4 NA NA Young
4.10.4 subset()函数
使用subset()
方法是选择变量观测的最简单的方法了。参考:
subset(leadership, age>=35 | age<24, select=c(Item1, Item2, Item3, Item4))
## Item1 Item2 Item3 Item4
## 2 3 5 2 5
## 4 3 3 4 NA
subset(leadership, gender=="M" & age>25, select=c(gender:Item4))
## gender age Item1 Item2 Item3 Item4
## 1 M 32 5 4 5 5
## 4 M 39 3 3 4 NA
subset(leadership, gender=="F" & age>25)
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 2 2 2008-10-28 US F 45 3 5 2 5 5 Young
4.10.5 随机抽样
在数据挖掘和机器学习领域,从更大的数据集中抽样是很常见的做法。sample()
函数可以让你从数据集中(又放回或无放回地)抽取样本容量为n的样本。
比如下面的代码从leadership中随机抽取一个样本大小为3的样本:
mysample <- leadership[ sample(1:nrow(leadership), 3, replace = FALSE), ]
mysample
## manager testDate country gender age Item1 Item2 Item3 Item4 Item5 agecat
## 4 4 2008-10-12 UK M 39 3 3 4 NA NA Young
## 3 3 2008-10-01 UK F 25 3 5 5 5 2 Young
## 5 5 2009-05-01 UK F NA 2 2 1 2 1
具体而言,sample()
函数的工作方式如下:
sample(vector, size, replace=FALSE, prob=NULL)
其中,sample会从vector里随机选取size大小的样本,是否放回用replace控制,prob控制的是样本抽取的概率权重。
sample可以有很多妙用,一个具体的例子是:
sample(100:110, 5, replace=FALSE)
## [1] 100 108 109 107 103
所以当我们执行 leadership[sample(...),]
的时候,实质上是按顺序从中选取sample()
返回的行索引。