在该章将会展示这些数据类型是如何相互关联起来的
R的基础数据结构可以根据它们的维度和是否要求类型一致来区分.
image其他的所有对象都是建立在这些类型之上的.要说明的是R是没有0维或者标量类型的.单个的字符串或是数字可以认为是长度为一的向量.
拿到一个对象想了解它的结构最好就是用str()看一下.
Vectors
R中最基础的数据结构就是向量.分为两种.原子向量和列表,有三种共同的属性
-
类型,typeis()
-
长度,length() 都是包含了多少元素
-
属性,attributes(),额外的任意元数据
它们的元素类型不同,原子向量的所有元素都必须是同一个类型,而列表则不是
NOTE:is.vector()并不能测试一个对象是否是向量,只有当一个对象除了名称没有任何属性的时候才会返回TURE.可以使用is.atomic(x) || is.list(x)来测试.
Atomic vectors
image原子型向量用c()来创建,combine的缩写
dbl_var <- c(1, 2.5, 4.5)#其缺省值是NA_real_
# With the L suffix, you get an integer rather than a double
int_var <- c(1L, 6L, 10L)#其缺省值是NA_integer_
# Use TRUE and FALSE (or T and F) to create logical vectors
log_var <- c(TRUE, FALSE, T, F)#其缺省值是NA
chr_var <- c("these are", "some strings") #其缺省值是NA_character_
原子型向量总是同一水平的,即使嵌套了c()也是一样
c(1, c(2, c(3, 4)))
# the same asc(1, 2, 3, 4)
缺失值被指定为NA,是一个长度为一的逻辑向量.NA在c()中将会被强制转换为正确的类型.
Given a vector, you can determine its type with typeof(), or check if it’s a specific type with an “is” function: is.character(), is.double(), is.integer(), is.logical(), or, more generally, is.atomic().
Coercion
当不是所有元素都是同一类型的时候它们将会被强制转换成同一类型.类型灵活度依次增加的是:逻辑型,整数型,浮点数,字符串.
str(c("a", 1))
## chr [1:2] "a" "1"
当逻辑型向量被纠正为浮点或者整数时,TRUE变成1,FALSE变成0,这在sum()和mean()中非常有用
纠正通常是自动进行的,但如果纠正之后会丢失信息会出现提示.如果有时候没有纠正但是又需要纠正,就可以使用as.character(), as.double(), as.integer(), or as.logical().
Lists
列表中的各个元素可以是不同的,甚至也可以是一个列表.应该使用list()来构建一个列表.同时列表可以是递归的,一个列表包含另一个列表.
x <- list(list(1, 2), c(3, 4))
y <- c(list(1, 2), c(3, 4))str(x)
## List of 2
## $ :List of 2
## ..$ : num 1
## ..$ : num 2
## $ : num [1:2] 3 4
str(y)
## List of 4
## $ : num 1
## $ : num 2
## $ : num 3
## $ : num 4
The typeof() a list is list. You can test for a list with is.list() and coerce to a list with as.list(). You can turn a list into an atomic vector with unlist(). If the elements of a list have different types, unlist() uses the same coercion rules as c().
Attributes
所有的对象都可以有任意的额外属性,用于储存关于对象的元数据.属性可以被当成一个有名字的列表.属性可以通过attr()来单独访问,也可以通过attributes()来当做一个列表全部访问
y <- 1:10
attr(y, "my_attribute") <- "This is a vector"
attr(y, "my_attribute")
## [1] "This is a vector"
str(attributes(y))
## List of 1
## $ my_attribute: chr "This is a vector"
structure()函数可以返回一个带有修饰属性的新对象.
structure(1:10, my_attribute = "This is a vector")
## [1] 1 2 3 4 5 6 7 8 9 10
## attr(,"my_attribute")
## [1] "This is a vector"
默认情况下大部分的注释都是空的.只有这三个注释是最重要的
-
名字,一个字符串向量给每个元素一个名字
-
维度,在将向量转换为矩阵和数组时使用
-
类,被使用在S3类中
访问它们的时候应该使用names(),dim(),class()而不是attr(x,"names")这样
Names
可以命名一个向量以下面三种方式
-
创建向量时用 x <- c(a = 1, b = 2, c = 3)
-
通过修改现有的向量 x <- 1:3; names(x) <- c("a", "b", "c")或x <- 1:3; names(x)[[1]] <- c("a")
-
通过创建一个修饰的副本 x <- setNames(1:3, c("a", "b", "c"))
名字是非唯一的,但是通常来说只有是唯一时才可以发挥作用
不是向量中所有的元素都需要有一个名字,如果一些元素没有被设置名字,他们的名字会是一个空字符串.这个时候names()会返回一个NA,如果所有的名字都没有设置,将会返回NULL
Factors
一个属性重要的使用方式就是定义因子.因子是一个仅包含预先定义的值的向量,被用来储存分类资料.因子构建在整数向量之上,使用了两个属性,class--factor,这使得他们的行为不同于整数向量以及levels属性,定义了被允许的值
x <- factor(c("a", "b", "b", "a"))
x
## [1] a b b a
## Levels: a b
class(x)
## [1] "factor"
levels(x)
## [1] "a" "b"
# You can't use values that are not in the levels
x[2] <- "c"
## Warning in `[<-.factor`(`*tmp*`, 2, value = "c"): invalid factor level, NA
## generated
x
## [1] a <NA> b a
## Levels: a b
# NB: you can't combine factors
c(factor("a"), factor("b"))
## [1] 1 1
使用因子可以仅看到想要看到的值,在没有任何有效的观测值的时候可以通过因子很方便地看到结果
sex_char <- c("m", "m", "m")
sex_factor <- factor(sex_char, levels = c("m", "f"))
table(sex_char)
## sex_char
## m
## 3
table(sex_factor)
## sex_factor
## m f
## 3 0
有时当一个数据框从一个文件读取来的时候,某一列本应该是数字的被读取成为了因子,这可能是因为有非数字的值在这一列中,通常是用特殊方式编码的缺失值.或者-
为了纠正,可以将因子变成字符串,然后再变成一个浮点数(但要注意丢失的值).当然最好的方法是找到问题,例如使用read.csv()中的na.strings参数
# Reading in "text" instead of from a file here:
z <- read.csv(text = "value\n12\n1\n.\n9")
typeof(z$value)
## [1] "integer"
as.double(z$value)
## [1] 3 2 1 4
# Oops, that's not right: 3 2 1 4 are the levels of a factor,
# not the values we read in!
class(z$value)
## [1] "factor"
# We can fix it now:
as.double(as.character(z$value))
## Warning: NAs introduced by coercion
## [1] 12 1 NA 9
# Or change how we read it in:
z <- read.csv(text = "value\n12\n1\n.\n9", na.strings=".")
typeof(z$value)
## [1] "integer"
class(z$value)
## [1] "integer"
z$value
## [1] 12 1 NA 9
# Perfect! :)
不幸的是R中大部分读取函数都会自动地把字符串读取为因子.可以使用参数stringsAsFactors = FALSE来拒绝这一行为.一个全局的选项options(stringsAsFactors = FALSE)也可以被用来控制这一行为,但是不推荐这么使用.使用全局设置可能会在和其他代码连用的时候产生不可预料的错误
虽然因子看起来(它的行为也经常这样)像是字符串向量,但是他们实际上是整数.在使用对字符串处理时要小心.有些字符串的方法如gsub()和grepl()将会强制转换因子为字符串,而一些其他的如nchar()则会报错.还有一些如c()将使用底层额整数来处理.鉴于此,如果需要一些字符串的操作最好是将其显式地将其转换.
在R的早期版本使用因子可以节省内存,但现在已经不再是这种情况了.
Dates
时间向量建立在复数向量之上.类型是Date,没有其他的注释
today <- Sys.Date()
typeof(today)
#> [1] "double"
attributes(today)
#> $class
#> [1] "Date"
Date的值表示距离1970/1/1的天数
date <- as.Date("1970-02-01")
unclass(date)
#> [1] 31
Date-times
image
now_ct <- as.POSIXct("2018-08-01 22:00", tz = "UTC")
now_ct
#> [1] "2018-08-01 22:00:00 UTC"
typeof(now_ct)
#> [1] "double"
attributes(now_ct)
#> $class
#> [1] "POSIXct" "POSIXt"
#>
#> $tzone
#> [1] "UTC"
image
Durations
持续时间(两个日期或日期时间之间的时间量)存储在difftimes中.difftime构建在double的基础之上,并具有一个units属性,该属性决定了应该如何解释为一个整数
one_week_1 <- as.difftime(1, units = "weeks")
one_week_1
#> Time difference of 1 weeks
typeof(one_week_1)
#> [1] "double"
attributes(one_week_1)
#> $class
#> [1] "difftime"
#>
#> $units
#> [1] "weeks"
one_week_2 <- as.difftime(7, units = "days")
one_week_2
#> Time difference of 7 days
typeof(one_week_2)
#> [1] "double"
attributes(one_week_2)
#> $class
#> [1] "difftime"
#>
#> $units
#> [1] "days"
Matrices and arrays
将原子向量赋予维度属性后就可以变成多维的数组.但是数组较矩阵来说并不是非常常用
可以通过matrix()和array()来创建或者使用dim()来赋值
# Two scalar arguments to specify rows and columns
a <- matrix(1:6, ncol = 3, nrow = 2)
# One vector argument to describe all dimensions
b <- array(1:12, c(2, 3, 2))
# You can also modify an object in place by setting dim()
c <- 1:6
dim(c) <- c(3, 2)
c
## [,1] [,2]
## [1,] 1 4
## [2,] 2 5
## [3,] 3 6
dim(c) <- c(2, 3)
c
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
length()和names()有高维的推广
image
length(a)
## [1] 6
nrow(a)
## [1] 2
ncol(a)
## [1] 3
rownames(a) <- c("A", "B")
colnames(a) <- c("a", "b", "c")
a
## a b c
## A 1 3 5
## B 2 4 6
length(b)
## [1] 12
dim(b)
## [1] 2 3 2
dimnames(b) <- list(c("one", "two"), c("a", "b", "c"), c("A", "B"))
b
## , , A
##
## a b c
## one 1 3 5
## two 2 4 6
##
## , , B
##
## a b c
## one 7 9 11
## two 8 10 12
c()被概括为cbind()和rbind()对于矩阵来说,abind()(来自于abind包)对于数组来说.
可以转置矩阵使用t(),其相当于数组的aperm()
可以测试一个对象is.matrix()和is.array().或者是用dim()看一下
向量并不是唯一的一维,也有单行或单列的矩阵,和数组,它们打印出来差不多,但有些行为上不也一样,例如tapply().可以用str()来揭示差异
str(1:3) # 1d vector
## int [1:3] 1 2 3
str(matrix(1:3, ncol = 1)) # column vector
## int [1:3, 1] 1 2 3
str(matrix(1:3, nrow = 1)) # row vector
## int [1, 1:3] 1 2 3
str(array(1:3, 3)) # "array" vector
## int [1:3(1d)] 1 2 3
维度也可以被设置在列表中,生成列表矩阵或列表数组
l <- list(1:3, "a", TRUE, 1.0)
dim(l) <- c(2, 2)
l
## [,1] [,2]
## [1,] Integer,3 TRUE
## [2,] "a" 1
Data frames and tibbles
数据框是最常用的结构.数据框实际上是一个具有等长的向量的列表.使其变为一个二维的结构.它同时具有矩阵和列表的性质,这意味着数据帧具有names()、colnames()和rownames(),尽管name()和colnames()是相同的.
数据框的length()为底层列表的长度,与ncol()相同;nrow()给出行数.
创建
可以使用data,frame()来创建一个数据框,以命名过的向量为输入
df <- data.frame(x = 1:3, y = c("a", "b", "c"))
str(df)
## 'data.frame': 3 obs. of 2 variables:
## $ x: int 1 2 3
## $ y: Factor w/ 3 levels "a","b","c": 1 2 3
注意默认转换为了因子
df <- data.frame(
x = 1:3,
y = c("a", "b", "c"),
stringsAsFactors = FALSE)
str(df)
## 'data.frame': 3 obs. of 2 variables:
## $ x: int 1 2 3
## $ y: chr "a" "b" "c"
对于tibble,通过tibble包获得
library(tibble)
df2 <- tibble(x = 1:3, y = letters[1:3])
typeof(df2)
#> [1] "list"
attributes(df2)
#> $names
#> [1] "x" "y"
#>
#> $row.names
#> [1] 1 2 3
#>
#> $class
#> [1] "tbl_df" "tbl" "data.frame"
此外,数据框会自动地把不符合语法的名字转换(除非check.names = FALSE),tibbles则不会(虽然它们会被反引号包起来),同时tibble不会将字符串转换为因子
names(data.frame(`1` = 1))
#> [1] "X1"
names(tibble(`1` = 1))
#> [1] "1"
在创建时,tibble只会循环长度为一的向量,而数据框则是循环长度为整倍数的
data.frame(x = 1:4, y = 1:2)
#> x y
#> 1 1 1
#> 2 2 2
#> 3 3 1
#> 4 4 2
data.frame(x = 1:4, y = 1:3)
#> Error in data.frame(x = 1:4, y = 1:3):
#> arguments imply differing number of rows: 4, 3
tibble(x = 1:4, y = 1)
#> # A tibble: 4 x 2
#> x y
#> <int> <dbl>
#> 1 1 1
#> 2 2 1
#> 3 3 1
#> 4 4 1
tibble(x = 1:4, y = 1:2)
#> Error: Tibble columns must have consistent lengths, only values of length one are recycled:
#> * Length 2: Column `y`
#> * Length 4: Column `x`
#> Backtrace:
#> █
#> 1\. └─tibble::tibble(x = 1:4, y = 1:2)
#> 2. └─tibble:::lst_to_tibble(xlq$output, .rows, .name_repair, lengths = xlq$lengths)
#> 3. └─tibble:::recycle_columns(x, .rows, lengths)
最后的一个差别是tibble允许在创建过程中创建一个变量
tibble(
x = 1:3,
y = x * 2
)
#> # A tibble: 3 x 2
#> x y
#> <int> <dbl>
#> 1 1 2
#> 2 2 4
#> 3 3 6
行名
数据框允许每一行都有一个名字,一个包含唯一值的字符向量
df3 <- data.frame(
age = c(35, 27, 18),
hair = c("blond", "brown", "black"),
row.names = c("Bob", "Susan", "Sam")
)
df3
#> age hair
#> Bob 35 blond
#> Susan 27 brown
#> Sam 18 black
可以通过rownames()函数来获得,也可以使用它们获得行的子集
rownames(df3)
#> [1] "Bob" "Susan" "Sam"
df3["Bob", ]
#> age hair
#> Bob 35 blond
与矩阵不同的是矩阵是可以转置的,行和列是可以交换的,而数据框则不能进行这种操作.数据框的转置不是一个数据框
有三个理由为什么行名不是很重要
-
它只能由一个字符标记,作为一行的摘要来说很糟糕
-
元数据是一种数据,将它以不同的方式储存在数据的其他地方是一个坏主意.这意味着需要学习新的工具去处理行名,不能使用已有的处理列的工具
-
行名必须是唯一的,当复制一些行的时候将必须创建一个新的行名,如果想要匹配新的行和旧的行需要进行复杂的字符串操作
df3[c(1, 1, 1), ]
#> age hair
#> Bob 35 blond
#> Bob.1 35 blond
#> Bob.2 35 blond
出于这些原因,tibble不支持行名.但是tibble包提供了很多工具,可以很方便地将行名转换为可控的列.rownames_to_column()或是as_tibble()中的参数rownames
as_tibble(df3, rownames = "name")
#> # A tibble: 3 x 3
#> name age hair
#> <chr> <dbl> <fct>
#> 1 Bob 35 blond
#> 2 Susan 27 brown
#> 3 Sam 18 black
取子集
当使用[来取子集的时候,如果只选了一列将会得到一个向量,除非使用参数drop = F
当选择df$x时,如果没有x则会选择x开头的列,如果没有则会返回NULL,很容易出现bug
tibble对这些做了调整
df1 <- data.frame(xyz = "a")
df2 <- tibble(xyz = "a")
str(df1$x)
#> Factor w/ 1 level "a": 1
str(df2$x)
#> Warning: Unknown or uninitialised column: 'x'.
#> NULL
如果想要对接之前的代码,强调提取一列,可以使用df[["col"]]
测试和纠正
因为数据框是S3类,所以它的类型被反映为底层的列表.可以使用class()和is.data.frame()来看
typeof(df)
## [1] "list"
class(df)
## [1] "data.frame"
is.data.frame(df)
## [1] TRUE
可以使用as.data.frame()来转换
-
一个向量会创建一个一列的数据框
-
一个列表会为每个元素创建一列,如果长度不一样会报错
-
一个矩阵会创建一个和矩阵一样行列的数字
连接数据框
可以使用cbind()和rbind()
cbind(df, data.frame(z = 3:1))
## x y z
## 1 1 a 3
## 2 2 b 2
## 3 3 c 1
rbind(df, data.frame(x = 10, y = "z"))
## x y
## 1 1 a
## 2 2 b
## 3 3 c
## 4 10 z
当连接以列形式,行数必须相同,但是行名可要忽略,当连接以行形式,列名和长度都必须相等.可以使用plyr::rbin .fill()来组合没有相同列的数据框
尝试用cbind()连接向量来创建数据框,并不会起作用,这只会创建一个矩阵,除非其中一个已经是数据框.
cbind()的转换规则很复杂,最好确保所有输入都是相同类型的,从而避免使用这些规则
特殊的列
df <- data.frame(x = 1:3)
df$y <- list(1:2, 1:3, 1:4)
df
## x y
## 1 1 1, 2
## 2 2 1, 2, 3
## 3 3 1, 2, 3, 4
image
然而当被给予一个列表时,它会尝试把列表中的每一项都放到自己的列中,所以会失败,可以使用I()来将列表视作一个独立的单元
data.frame(x = 1:3, y = list(1:2, 1:3, 1:4))
## Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, : arguments imply differing number of rows: 2, 3, 4
dfl <- data.frame(x = 1:3, y = I(list(1:2, 1:3, 1:4)))
str(dfl)
## 'data.frame': 3 obs. of 2 variables:
## $ x: int 1 2 3
## $ y:List of 3
## ..$ : int 1 2
## ..$ : int 1 2 3
## ..$ : int 1 2 3 4
## ..- attr(*, "class")= chr "AsIs"
dfl[2, "y"]
## [[1]]
## [1] 1 2 3
I()将AsIs类添加到它的输入中,但是通常可以安全地忽略它.
同样,列也可是矩阵或数组,只要行数相匹配
dfm <- data.frame(x = 1:3, y = I(matrix(1:9, nrow = 3)))
str(dfm)
## 'data.frame': 3 obs. of 2 variables:
## $ x: int 1 2 3
## $ y: 'AsIs' int [1:3, 1:3] 1 2 3 4 5 6 7 8 9
dfm[2, "y"]
## [,1] [,2] [,3]
## [1,] 2 5 8
image
当使用tibble时更加方便,数据库必须先创建再添加或者使用I()
tibble(
x = 1:3,
y = list(1:2, 1:3, 1:4)
)
#> # A tibble: 3 x 2
#> x y
#> <int> <list>
#> 1 1 <int [2]>
#> 2 2 <int [3]>
#> 3 3 <int [4]>
NULL
总是长度为0,不能有其他的属性
typeof(NULL)
#> [1] "NULL"
length(NULL)
#> [1] 0
x <- NULL
attr(x, "y") <- 1
#> Error in attr(x, "y") <- 1:
#> attempt to set an attribute on NULL
可以通过is.null()来检验
有两点常见的使用:
-
去代表一个空向量,例如c().
-
来代表一个不存在的向量.NULL经常被用在参数的默认值.NA用于表示元素不存在
网友评论