四、基本数据管理(二)
前言
- 上一节 学习之旅13-R基本数据管理(一)中,我们梳理了关于变量,因子的内容,并就如何创建变量以及变量的重编码,重命名做了简单介绍,这一节我们将进一步对数据的管理做详细介绍,比如缺失值如何查询、剔除,如何对数据做进一步转化,数据集的合并,查询子集等等。
4、数据基本管理
4.2 缺失值
- 缺失值在很对场景都会出现,即数据可能由于某种原因导致的不存在或者不完整,在R中缺失值的符号为NA(not variable,不可用),不管是字符型还是数值型,缺失值都表示为NA。
4.2.1 缺失值的判断
- is.na()函数来进行缺失值的检测。该函数可以作用于任何一个对象上,返回一个相同大小的对象,若某个元素是缺失值,则对应位置返回TRUE。
> na01 <- c(1, 2, 3, NA)
> is.na(na01)
[1] FALSE FALSE FALSE TRUE
> id <- c(1, 2, 3, 4)
> sex <- c("男", "男", NA, NA)
> age <- c(18, 21, 19, 20)
> data01 <- data_frame(id, sex, age)
> data01
# A tibble: 4 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
2 2 男 21
3 3 NA 19
4 4 NA 20
> is.na(data01)
id sex age
[1,] FALSE FALSE FALSE
[2,] FALSE FALSE FALSE
[3,] FALSE TRUE FALSE
[4,] FALSE TRUE FALSE
注意:
(1)缺失值是不可比较的,故无法用比较运算符来检测缺失值是否存在,即na01 == NA 的结果永远不会是TRUE。
(2)对于极限值(Inf正无穷,-Inf负无穷),R在处理这些值是并不会把他们标记为缺失值,因为这是值是存在的,只不过不是一个确实的数(not a number),对于这类数只能用is.infinite()和is.nan()来检测。
NaN:无定义数用NaN表示,即“Not a Number(非数)”。不过在R中,R实际上是把NaN视作一个数的,当其参与运算时,返回结果总是NaN。is.nan()函数来检测计算结果有无定义。
NULL:R语言中,NA代表位置上的值为空(即有一个位置,只是这个值因为某些原因缺失了),NULL代表连位置都没有,变量为空,其长度为0,表明“空无一物”
> na01 == NA #无法做比较,返回的永远不会是TRUE
[1] NA NA NA NA
> t1 <- 1/0 #t1 为一个正无穷大的数,此时只有用is.infinite()判断后返回的值才是TRUE,其他返回均为FALSE
> t1
[1] Inf
> is.na(t1)
[1] FALSE
> is.nan(t1)
> is.finite(t1) #有限大时返回TRUE
[1] FALSE
> t2 <- NULL
> t3 <- NA
> length(t2)
[1] 0
> length(t3)
[1] 1
4.2.1 重编码为缺失值
- 对于数据中的某些值,我们希望在特定的情况下重新编码为缺失值,此时我们通过指定哪些值需要重编码即可。具体可以参考上一节4.1.2 变量重编码。
> data01
# A tibble: 4 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
2 2 男 21
3 3 NA 19
4 4 NA 20
> data01$age[data01$age == 21] <- NA
> data01
# A tibble: 4 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
2 2 男 NA
3 3 NA 19
4 4 NA 20
4.2.1 排除缺失值
- 对于某些缺失值,我们在进行计算时必须将其剔除,不然计算出来的结果也是缺失值。当然大多数函数拥有na.rm=TRUE选项,可以在进行计算之前将缺失值先排除,然后在计算剩余的值。
> x <- c(1, 3, 5, NA)
> sum(x)
[1] NA
> x <- c(1, 3, 5, NA)
> sum(x)
[1] NA
- na.omit()可以移除所有缺失值的观测,对于数据框来说,使用这个行数则剔除含有缺失值的行(行删除)。当然这只是其中一种,对于复杂的缺失值处理,我们会在后面的内容中讲解其他方法。
> data01
# A tibble: 4 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
2 2 男 NA
3 3 NA 19
4 4 NA 20
> na.omit(data01)
# A tibble: 1 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
4.3 日期值
- R语言中我们通常以字符串的形录入,我们需要转化成数值形式存储日期变量时就需要as.Date(data, format = “format”),data表示需要转化成数值型的日期数据(字符型),format表示我们永远要转化成的日期格式,常见日期格式如下:只有转化成数值型后我们才能进行日期的计算。
- 默认输出格式为yyyy-mm-dd
> data01
# A tibble: 4 x 3
id sex age
<dbl> <chr> <dbl>
1 1 男 18
2 2 男 NA
3 3 NA 19
4 4 NA 20
> birdate <- c("1993-09-02","1992-01-02","1992-03-02","1995-04-20") # 我们以字符串形式保存一个日期变量birdate
> str(birdate) # 可以看到此时类型为chr
chr [1:4] "1993-09-02" "1992-01-02" "1992-03-02" "1995-04-20"
> day <- birdate[1]-birdate[2] #此时计算birdate中第一个日期和第二个日期间的时间间隔就会报错。
Error in birdate[1] - birdate[2] : 二进列运算符中有非数值参数
> birdate <- as.Date(birdate) # 通过函数转化为数值形式,不加参数默认yyyy-mm-dd
> str(birdate) #此时转化成数值形式的日期格式
Date[1:4], format: "1993-09-02" "1992-01-02" "1992-03-02" "1995-04-20"
> day <- birdate[1]-birdate[2] #可以进行计算,相隔609天
> day
Time difference of 609 days
> > mydate <- c("03/02/1993", "03/05/1992","03/12/1991","28/09/1995")
> mydate
[1] "03/02/1993" "03/05/1992" "03/12/1991" "28/09/1995"
> str(mydate)
chr [1:4] "03/02/1993" "03/05/1992" "03/12/1991" "28/09/1995"
> mformat <- "%d/%m/%Y" # 指定需要读入的日期格式,务必与之一一对应
> mydate1 <- as.Date(mydate, mformat)
> mydate1
[1] "1993-02-03" "1992-05-03" "1991-12-03" "1995-09-28"
以下代码中我的mydate存储的为dd-mm-yyyy,因此我需要指定读入的格式也必须是dd-mm-yyyy,即"%d/%m/%Y",若我们使用"%m/%d/%Y",对于"28/09/1995",没有28这个月份,因此as.Date()转化出来的这个值就是NA。
> mydate <- c("03/02/1993", "03/05/1992","03/12/1991","28/09/1995")
> mydate
[1] "03/02/1993" "03/05/1992" "03/12/1991" "28/09/1995"
> str(mydate)
chr [1:4] "03/02/1993" "03/05/1992" "03/12/1991" "28/09/1995"
> mformat <- "%m/%d/%Y"
> mydate1 <- as.Date(mydate, mformat)
> mydate1
[1] "1993-03-02" "1992-03-05" "1991-03-12" NA
- Sys.Date()查看当天的日期,date()返回当前日期和具体时间。数字和字符串可以使用 format()函数指定参数按某种格式输出。
> Sys.Date()
[1] "2021-03-01"
> date()
[1] "Mon Mar 01 22:02:55 2021"
> today <- Sys.Date()
> today
[1] "2021-03-01"
> format(today, format="%Y %m %B")
[1] "2021 03 三月"
> format(today, format="%Y/%m/%B")
[1] "2021/03/三月"
R的内部在存储日期时,是使用自1970年1月1日以来的天数表示的,更早的日期则表示为负数。故可以进行计算时间间隔:difftime(time1, time2, tz,units = c(“auto”, “secs”, “mins”, “hours”,“days”, “weeks”))函数可以用于计算(time1-time2)。tz:用于转换的可选时区规范,主要用于“POSIXlt”对象。units :计算相关的秒数、分钟数、小时数、天数、周数。
> day1 <- as.Date("1993-08-08")
> day2 <- Sys.Date()
> day2
[1] "2021-03-01"
> > day2-day1
Time difference of 10067 days
> difftime(day2, day1,units = "days") #day2 - day1
Time difference of 10067 days
4.3.1 日期值转化
- 有时我们为了对日期做一些处理,此时转化成字符型变量后,我们能更好的进行数据处理(取子集,替换,连接等):通过函数as.character()转化为字符类型。
4.4 类型转化
- 上面我们谈到了日期格式的相互转化,在R中我们经常需要在各种数值,字符之间进行转化,R提供了一系列类型判断和转化的函数:
> x <- c(1, 3, 5)
> y <- c(20, "张三", "李四")
> data01
key Pa Pb
1 5 12 10
2 10 17 15
3 15 20 21
4 20 23 22
5 25 30 27
> # 我们定义了以上数据,x为一个向量,里面的元素都是数值型,y也是一个向量,但是里面数据都是字符型(虽然有20 ,但是也是字符),data01为一个数据框
> > is.numeric(x) #判断是否为数值,输出TURE和FALSE
[1] TRUE
> is.numeric(y)
[1] FALSE
> is.numeric(data01)
[1] FALSE
> is.character(x) #判断是否为字符
[1] FALSE
> is.character(y)
[1] TRUE
> is.character(data01)
[1] FALSE
> is.vector(x) # 判断是否为向量
[1] TRUE
> is.vector(y)
[1] TRUE
> is.vector(data01)
[1] FALSE
> # 向量x都是数值,可以做运算
> sum(x)
[1] 9
> x <- as.character(x) # 将向量x转化为字符,此时不能再做运算。
> sum(x)
Error in sum(x) : 'type'(character)参数不对
str()函数也可查看具体的一些信息
> str(x)
chr [1:3] "1" "3" "5"
> str(data01)
'data.frame': 5 obs. of 3 variables:
$ key: num 5 10 15 20 25
$ Pa : num 12 17 20 23 30
$ Pb : num 10 15 21 22 27
4.5 简单数据处理
4.5.1 数据排序
- 在进行数据分析时,我们有时会根据不同的情况进行排序,R中通过order()函数来对一个数据框进行排序,默认为升序,在排序字段前加一个 “-”号则表示降序。这里只针对数据框排序进行讲解,对于向量的排序返回的值并不是我们实际的排序值,而是返回升序排序后,在原有向量中元素的位置。对于数据框排序order(x, na.last = NA),主要这两个参数,x是需要排序的向量或数据, na.last如果为TRUE,则将数据中缺少的值放在最后;如果为FALSE,则将其放在第一位;如果为NA,则将其删除。
> data01
key Pa Pb
1 5 12 10
2 10 17 15
3 15 20 21
4 20 23 22
5 25 30 27
> data01[order(-data01$Pa),] #以数据框中Pa进行降序排序
key Pa Pb
5 25 30 27
4 20 23 22
3 15 20 21
2 10 17 15
1 5 12 10
> data01[order(data01$Pa),1] # 这里以数据框data01的Pa排序,但是只取第一列的数据
[1] 5 10 15 20 25
> data01[3,1] <- NA # 将第3行,1列的数(15)替换为NA
> data01[order(data01$Pa),c("key","Pb")] # 以Pa升序排序,取key 和Pb这两列数据
key Pb
1 5 10
2 10 15
3 NA 21
4 20 22
5 25 27
> data01[order(data01$key, na.last = TRUE),] #key中有NA,指定na.last = TRUE 则有NA的放在最后
key Pa Pb
1 5 12 10
2 10 17 15
4 20 23 22
5 25 30 27
3 NA 20 21
> data01[order(data01$key, na.last = NA),] # na.last = NA则删除这一行数据进行排序
key Pa Pb
1 5 12 10
2 10 17 15
4 20 23 22
5 25 30 27
4.5.2 数据合并,查询,增删
- 对于有些数据,我们需要通过两张表来获取,因此有时需要将这两张表通过某个共有的变量进行合并,或者有时我们需要新增行数据或者列数据,我们会通过merge()、rbind()、cbind()函数来进行操作:
library(readxl) #载入readxl,来导入我们需要的Excel数据
xl1 <- read_excel("E:\\学习\\text.xls",sheet=1,col_names=TRUE) #读取第一个sheet ,col_names表示含表头
xl2 <- read_excel("E:\\学习\\text.xls",sheet=2,col_names=TRUE)
- 数据合并查询:
# 合并xl1 xl2 通过学号关联合并,all.xl1=all 表示已xl1为主表链接
> xl3 <- merge(xl1,xl2,by ="学号", all.xl1 = all)
> xl3
学号 姓名 性别 班级 面试 笔试
1 001 张三 男 一班 86 80
2 002 李四 男 二班 78 87
3 003 王五 女 二班 90 90
4 004 赵六 女 一班 68 78
5 005 韩七 男 二班 90 90
# 新增一列总成绩 笔试 0.7 面试0.3,由于excel导入的数据可能不是数值型,这里需要通过as.numeric()函数强制转化成数值型
> xl3$总成绩 <- as.numeric(xl3$面试)*0.7+as.numeric(xl3$笔试)*0.3
> xl3
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90.0
4 004 赵六 女 一班 68 78 71.0
5 005 韩七 男 二班 90 90 90.0
>
# 取指行,列数据
> xl4 <- xl3[1,] # 通过下标进行取值[x,y] x表示取指定行的数据,y表示取指定列的数据
> xl4
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
> xl5 <- xl3[1:3,] # 取1-3行所有数据
> xl5
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90.0
> xl6 <- xl3[c("姓名","面试","笔试","总成绩")] # 通过c() 来获取向量中对应的数据
> xl6
姓名 面试 笔试 总成绩
1 张三 86 80 84.2
2 李四 78 87 80.7
3 王五 90 90 90.0
4 赵六 68 78 71.0
5 韩七 90 90 90.0
>
> xl7 <- xl3[which(xl3$班级=="一班" &xl3$性别=="男"),c("姓名","性别","总成绩")] # 通过which 函数指定哪一个进行筛选
> xl7
姓名 性别 总成绩
1 张三 男 84.2
> library(dplyr) #利用sql的方式操作数据框
> xl8 <- select(xl3,"姓名","总成绩"); #通过 select 也可以取数据框中指定的 列数据
> xl8
姓名 总成绩
1 张三 84.2
2 李四 80.7
3 王五 90.0
4 赵六 71.0
5 韩七 90.0
>#但是对于select 来选取数据有一定的局限性,我们无法进一步选出总成绩≥90的数据,因为对于select 来说必须使用有效的下标向量来子集列
> select(xl3, 1)
学号
1 001
2 002
3 003
4 004
5 005
> select(xl3, 学号:总成绩) # 查询xl3中学号 到总成绩这一列所有数据
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90.0
4 004 赵六 女 一班 68 78 71.0
5 005 韩七 男 二班 90 90 90.0
> #subset()可以进一步操作
> newdata <- subset(xl3, xl3$面试>85 & xl3$笔试 >= 90, select = c(学号,姓名,笔试)) #select = c(学号,姓名,笔试) 不取连续列
> newdata
学号 姓名 笔试
3 003 王五 90
5 005 韩七 90
> newdata <- subset(xl3, xl3$面试>85 & xl3$笔试 >= 90, select = 学号:总成绩) #select = 学号:总成绩 取连续列
> newdata
学号 姓名 性别 班级 面试 笔试 总成绩
3 003 王五 女 二班 90 90 90
5 005 韩七 男 二班 90 90 90
- 数据删除:
# 原始数据
> xl3
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90.0
4 004 赵六 女 一班 68 78 71.0
5 005 韩七 男 二班 90 90 90.0
# 删除行列数据
> xl9 <- xl3[,-1] # 删除数据框xl3 中第一列数据 或者xl8 <- xl3[-1]
> xl9
姓名 性别 班级 面试 笔试 总成绩
1 张三 男 一班 86 80 84.2
2 李四 男 二班 78 87 80.7
3 王五 女 二班 90 90 90.0
4 赵六 女 一班 68 78 71.0
5 韩七 男 二班 90 90 90.0
> xl01 <- xl3[-1,] # 删除数据库xl3 中第一行数据
> xl01
学号 姓名 性别 班级 面试 笔试 总成绩
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90.0
4 004 赵六 女 一班 68 78 71.0
5 005 韩七 男 二班 90 90 90.0
> xl02 <- select(xl3 ,-"面试",-"总成绩") # 通过select 函数可以取指定列数据,同时用-“指定列” 则可以去掉对应列数据
> xl02
学号 姓名 性别 班级 笔试
1 001 张三 男 一班 80
2 002 李四 男 二班 87
3 003 王五 女 二班 90
4 004 赵六 女 一班 78
5 005 韩七 男 二班 90
# rm(xl9) # 删除对应数据
# ls(xl5) # 列出对应标题
# ncol(xl3) #查询xl3数据框的列数
# nrow(xl3) #查询xl3数据框的行数
# ls_li <- ls() # 函数ls的功能是显示所有在内存中的对象:只会列出对象名
# ls.str(xl3) #ls.str()将会展示内存中所有对象的详细信息:
#rm(list = ls()) # 将对象列出后传给列表list ,即清空所有已经存在的数据对象
- 数据新增: 新增行rbind()、新增列cbind() 数据
#新增行数据:rbind()
> adrow <- c("006","刘八","男","一班",77,88,77*0.7+88*0.3); # 先将新增数据定义好,方便后面使用
> xl3_1 <- rbind(xl3, adrow) ## 在xl3数据框中加入一行adrow,加在最后
> xl3_1
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 003 王五 女 二班 90 90 90
4 004 赵六 女 一班 68 78 71
5 005 韩七 男 二班 90 90 90
6 006 刘八 男 一班 77 88 80.3
> xl3_2 <- rbind(xl3[1:2,], adrow ,xl3[3:nrow(xl3),]); # 在xl3数据框 第3行 加入adrow 这一行数据
> xl3_2
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 006 刘八 男 一班 77 88 80.3
31 003 王五 女 二班 90 90 90
4 004 赵六 女 一班 68 78 71
5 005 韩七 男 二班 90 90 90
> xl3_2 <- rbind(xl3[1:2,], adrow ,xl3[3:nrow(xl3),]); # 在xl3数据框 第3行 加入adrow 这一行数据
> xl3_2
学号 姓名 性别 班级 面试 笔试 总成绩
1 001 张三 男 一班 86 80 84.2
2 002 李四 男 二班 78 87 80.7
3 006 刘八 男 一班 77 88 80.3
31 003 王五 女 二班 90 90 90
4 004 赵六 女 一班 68 78 71
5 005 韩七 男 二班 90 90 90
> xl3_4 <- rbind(adrow,xl3[1:nrow(xl3),]); # 在xl3数据框第1行 加入adrow 这一行数据,注意后面的数据必须都列出来(即原来第一行的数据到最后一行的数据 1:nrow())
> xl3_4
学号 姓名 性别 班级 面试 笔试 总成绩
1 006 刘八 男 一班 77 88 80.3
2 001 张三 男 一班 86 80 84.2
3 002 李四 男 二班 78 87 80.7
4 003 王五 女 二班 90 90 90
5 004 赵六 女 一班 68 78 71
6 005 韩七 男 二班 90 90 90
# 新增列数据:cbind()
> bz <- c("合格","合格","优秀","合格","优秀")
> xl3_5 <- cbind(xl3,bz); # 在xl3 数据框中加入一行,加在最后一列
> xl3_5
学号 姓名 性别 班级 面试 笔试 总成绩 bz
1 001 张三 男 一班 86 80 84.2 合格
2 002 李四 男 二班 78 87 80.7 合格
3 003 王五 女 二班 90 90 90.0 优秀
4 004 赵六 女 一班 68 78 71.0 合格
5 005 韩七 男 二班 90 90 90.0 优秀
> xl3_6 <- cbind(xl3_5[,1:4], c("java","python","R","mysql","oracle"),xl3_5[,5:ncol(xl3_5)]); # 在xl3数据框的第3列增加一列,注意命名,由于没有提前给新增数据命名,那对于数据框来说就会使用所有向量来构成一个列名
> xl3_6
学号 姓名 性别 班级 c("java", "python", "R", "mysql", "oracle") 面试 笔试
1 001 张三 男 一班 java 86 80
2 002 李四 男 二班 python 78 87
3 003 王五 女 二班 R 90 90
4 004 赵六 女 一班 mysql 68 78
5 005 韩七 男 二班 oracle 90 90
总成绩 bz
1 84.2 合格
2 80.7 合格
3 90.0 优秀
4 71.0 合格
5 90.0 优秀
> names(xl3_6) <- c("学号","姓名","性别","班级","专业","笔试","面试","总成绩","备注") # names()函数来指定列名
> xl3_6
学号 姓名 性别 班级 专业 笔试 面试 总成绩 备注
1 001 张三 男 一班 java 86 80 84.2 合格
2 002 李四 男 二班 python 78 87 80.7 合格
3 003 王五 女 二班 R 90 90 90.0 优秀
4 004 赵六 女 一班 mysql 68 78 71.0 合格
5 005 韩七 男 二班 oracle 90 90 90.0 优秀