7 Basic: type conversion (optional)

转换包括两种,element type 转换和 structure type 转换。

为什么需要转换?是因为在使用某个 function 处理某个 object 的时候,这个对象并不是 function 所要求提供的 element type 或 structure type。所以需要转换才能让 function 顺利执行。

7.1 Element type conversion

7.1.1 Coder initialized conversion

当需要转换时,用户可以主动触发转换。不同的 element type 之的转换,可以使用as.fun()族,如下:

function 用途
as.logical() 转换为 logical 类型
as.integer() 转换为 integer 类型
as.double() 转换为 double 类型
as.character() 转换为 character 类型
  1. as.logical()

如果转换的对象是 integer 或 double,那么该对象中的 0 会被转换为FALSE,所有非 0 的元素会被转换为TRUE,除此之外的任何对象都会被转换为NA例如:

var_int
#> [1]  1  6 10
var_dbl
#> [1] 0.0 1.0 2.5 4.5
var_chr
#> [1] "these are"    "some strings"
var_na
#> [1] NA NA NA
var_null
#> NULL
as.logical(var_int)
#> [1] TRUE TRUE TRUE
as.logical(var_dbl)
#> [1] FALSE  TRUE  TRUE  TRUE
as.logical(var_chr)
#> [1] NA NA
as.logical(var_na)
#> [1] NA NA NA
as.logical(var_null)
#> logical(0)
  1. as.integer()
  • 如果被转换的对象是 double,会把所有元素向 0 取整;
  • 如果转换的对象是纯数字 character,会把该字符串先转换为 double,然后再按前一条规则转换;
  • 如果被转换对象是 logical,则会把TRUE转换成1L,把FALSE转换成0L

例如:

var_lgl
#> [1]  TRUE FALSE
var_int
#> [1]  1  6 10
var_chr
#> [1] "these are"    "some strings"
var_na
#> [1] NA NA NA
var_null
#> NULL
as.integer(var_lgl)
#> [1] 1 0
as.integer(var_dbl)
#> [1] 0 1 2 4
as.integer(var_chr)
#> Warning: NAs introduced by coercion
#> [1] NA NA
as.integer(var_na)
#> [1] NA NA NA
as.integer(var_null)
#> integer(0)
as.integer("10.1")
#> [1] 10
  1. as.double()as.integer()类似,如果转换的对象是纯数字 character,会把该字符串转换为 double,例如:
var_lgl
#> [1]  TRUE FALSE
var_int
#> [1]  1  6 10
var_chr
#> [1] "these are"    "some strings"
var_na
#> [1] NA NA NA
var_null
#> NULL
as.double(var_lgl)
#> [1] 1 0
as.double(var_int)
#> [1]  1  6 10
as.double(var_chr)
#> Warning: NAs introduced by coercion
#> [1] NA NA
as.double(var_na)
#> [1] NA NA NA
as.double(var_null)
#> numeric(0)
as.double("3.141592653")
#> [1] 3.141593

注:as.double()as.numeric()等价。

  1. as.character()
var_lgl
#> [1]  TRUE FALSE
var_int
#> [1]  1  6 10
var_dbl
#> [1] 0.0 1.0 2.5 4.5
var_na
#> [1] NA NA NA
var_null
#> NULL
as.character(var_lgl)
#> [1] "TRUE"  "FALSE"
as.character(var_int)
#> [1] "1"  "6"  "10"
as.character(var_dbl)
#> [1] "0"   "1"   "2.5" "4.5"
as.character(var_na)
#> [1] NA NA NA
as.character(var_null)
#> character(0)

几种特殊的 element type 如NANULLNaN中只有NULL有对应的转换function as.null(name),不过较少使用,等价于name <- NULL。余下的特殊 element type 也都可以通过<-直接实现转换,例如:

a <- NA
b <- NaN

7.1.2 Automatic conversion by R: coercion

被动转换,或强制转换(coercion)实际上就是 R 在一些必要的情况,帮我们自动完成了 element type 转换。那什么时候是必要的情况?如果使用c()合并element type 不同的 scalars 来创建一个新的 vector,那么就会一定会发生 coercion,因为在 R 中,vector 内所有 element 必须是相同 type。

coercion 遵循的优先级为 expression > character > double > integer > logical(包括NA)> raw。例如

typeof(c("a", expression(1 + 2)))
#> [1] "expression"
typeof(c(1, expression(1 + 2)))
#> [1] "expression"
typeof(c(1L, expression(1 + 2)))
#> [1] "expression"
typeof(c(NA, expression(1 + 2)))
#> [1] "expression"
typeof(c(raw(2), expression(1 + 2)))
#> [1] "expression"
typeof(c("a", 1))
#> [1] "character"
typeof(c("a", 1L))
#> [1] "character"
typeof(c("a", TRUE))
#> [1] "character"
typeof(c("a", NA))
#> [1] "character"
typeof(c("a", raw(2)))
#> [1] "character"
typeof(c(1, 1L))
#> [1] "double"
typeof(c(1, TRUE))
#> [1] "double"
typeof(c(1, NA))
#> [1] "double"
typeof(c(1, raw(2)))
#> [1] "double"
typeof(c(1L, TRUE))
#> [1] "integer"
typeof(c(1L, NA))
#> [1] "integer"
typeof(c(1L, raw(2)))
#> [1] "integer"
typeof(c(TRUE, raw(2)))
#> [1] "logical"
typeof(c(NA, raw(2)))
#> [1] "logical"

coercion 的规则中不包括NULL,因为NULL本身没有任何内容,NULL在一个向量的任何位置都意味着这个向量没有这个位置。例如:

c(NULL, "a", NA)
#> [1] "a" NA
c("a", NA, NULL)
#> [1] "a" NA
c("a", NULL, NA)
#> [1] "a" NA

coercion 容易在不经意间造成一些问题,例如,假设有两批数据,分别读入到 R 中,需要合并在一起计算均值。但其中一批是 character,导致合并时发生 coercion,求均值出错,代码为:

a <- matrix(1, 3, 3)
a
#>      [,1] [,2] [,3]
#> [1,]    1    1    1
#> [2,]    1    1    1
#> [3,]    1    1    1
b <- matrix(c("1", "1", "1"), 3, 1)
b
#>      [,1]
#> [1,] "1" 
#> [2,] "1" 
#> [3,] "1"
ab <- cbind(a, b)
ab
#>      [,1] [,2] [,3] [,4]
#> [1,] "1"  "1"  "1"  "1" 
#> [2,] "1"  "1"  "1"  "1" 
#> [3,] "1"  "1"  "1"  "1"
mean(ab)
#> Warning in mean.default(ab): argument is not numeric or
#> logical: returning NA
#> [1] NA

以上由 coercion 导致的错误通常都会表现为某个 function 报错,提示给这个 function 提供的某个 argument 的 element type 不符合要求。

coercion 不仅发生在使用c()将不同类型的 scalars 合并成 vector 的情况,还会发生在各种各样的运算中。例如,在需要 numeric object,如果提供的是 logical object,那么 R 就会将 logical object 转换成 integer,其中TRUE会被自动转换为1LFALSE会被自动转换为0L

1*TRUE  # 乘法运算需要 numeric value,但提供的是 TRUE,执行时被自动转换成 1L
#> [1] 1
1 + FALSE  # 同理,FALSE 在执行时会被自动转换成 0L
#> [1] 1

活用 coercion 可以写出一些简便的代码,例如:

score <- sample(100, 60)
score
#>  [1]  9 81  2 12 40 64 52  8 46 69 54  4 90 26 83 44 24 48
#> [19] 16 51 45 78 49 43 42 66 36 22 20 31 92 25 97 27 30 14
#> [37] 85 33 38 87 58 65  1 99 62 68 29 73 32 89 93 28  3 34
#> [55] 79 88 18 55 75  5
sum(score > 90)
#> [1] 4

7.1.3 Frequently made mistake

因为 element type 并不匹配导致的 error 或 warning,多数情况下是由 coercion 导致的,这种问题通常表现为某个 function 弹出 error 或 warning,但问题的根源在使用这个 function 之前,所需 argument 的 element type 就是错的;

cor("1", "2")
#> Error in cor("1", "2"): 'x' must be numeric
mean(c("1", "2"))
#> Warning in mean.default(c("1", "2")): argument is not
#> numeric or logical: returning NA
#> [1] NA

7.2 Structure type conversion

同样,不同的结构类型也可以使用as.fun()族转换,例如:

function 用途
as.vector() 转换为 vector 类型
as.matrix() 转换为 matrix 类型
as.data.frame() 转换为 data.frame 类型
as.list() 转换为 list 类型
as.factor() 转换为 factor 类型

但是,也不建议盲目使用,因为这些 function 的结果会比较难预判,例如:

  • 转换对象是 vector:
var_dbl
#> [1] 0.0 1.0 2.5 4.5
as.matrix(var_dbl)
#>      [,1]
#> [1,]  0.0
#> [2,]  1.0
#> [3,]  2.5
#> [4,]  4.5
as.data.frame(var_dbl)
#>   var_dbl
#> 1     0.0
#> 2     1.0
#> 3     2.5
#> 4     4.5
as.list(var_dbl)
#> [[1]]
#> [1] 0
#> 
#> [[2]]
#> [1] 1
#> 
#> [[3]]
#> [1] 2.5
#> 
#> [[4]]
#> [1] 4.5
as.factor(var_dbl)
#> [1] 0   1   2.5 4.5
#> Levels: 0 1 2.5 4.5
as.array(var_dbl)
#> [1] 0.0 1.0 2.5 4.5
  • 转换对象是 data.frame:
as.matrix(df1)
#>      x   y  
#> [1,] "1" "a"
#> [2,] "2" "b"
#> [3,] "3" "c"
as.vector(df1)
#> $x
#> [1] 1 2 3
#> 
#> $y
#> [1] "a" "b" "c"
as.list(df1)
#> $x
#> [1] 1 2 3
#> 
#> $y
#> [1] "a" "b" "c"
as.factor(df1)
#> Error in xtfrm.data.frame(x): cannot xtfrm data frames

注意,在低于 4.2.0 版本的 R 中,as.vector(x)如果x是 data.frame,直接返回x;在大于 4.2.0 版本的 R 中,返回一个 list,这更符合几种 structure types 间的关系,即 data.frame 本质是 list,而 list 本质是 vector。

df <- data.frame(x = c(1, 2, 3), y = c(4, 5, 6))
as.vector(df)

R<4.2.0

  x y
1 1 4
2 2 5
3 3 6

R>=4.2.0

$x
[1] 1 2 3

$y
[1] "a" "b" "c"
  • 转换的的对象是 list:
str(l1)
#> List of 7
#>  $ :List of 3
#>   ..$ : num 1
#>   ..$ : num 2
#>   ..$ : num 3
#>  $ : int [1:3] 1 2 3
#>  $ : chr "a"
#>  $ : logi [1:3] TRUE FALSE TRUE
#>  $ : num 2.3
#>  $ : logi [1:3] NA NA NA
#>  $ : num [1:2, 1:2] 1 2 3 4
as.vector(l1)
#> [[1]]
#> [[1]][[1]]
#> [1] 1
#> 
#> [[1]][[2]]
#> [1] 2
#> 
#> [[1]][[3]]
#> [1] 3
#> 
#> 
#> [[2]]
#> [1] 1 2 3
#> 
#> [[3]]
#> [1] "a"
#> 
#> [[4]]
#> [1]  TRUE FALSE  TRUE
#> 
#> [[5]]
#> [1] 2.3
#> 
#> [[6]]
#> [1] NA NA NA
#> 
#> [[7]]
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4
as.matrix(l1)
#>      [,1]     
#> [1,] list,3   
#> [2,] integer,3
#> [3,] "a"      
#> [4,] logical,3
#> [5,] 2.3      
#> [6,] logical,3
#> [7,] numeric,4
as.array(l1)
#> [[1]]
#> [[1]][[1]]
#> [1] 1
#> 
#> [[1]][[2]]
#> [1] 2
#> 
#> [[1]][[3]]
#> [1] 3
#> 
#> 
#> [[2]]
#> [1] 1 2 3
#> 
#> [[3]]
#> [1] "a"
#> 
#> [[4]]
#> [1]  TRUE FALSE  TRUE
#> 
#> [[5]]
#> [1] 2.3
#> 
#> [[6]]
#> [1] NA NA NA
#> 
#> [[7]]
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4
  • 转换的对象是 array:
a1
#> , , 1
#> 
#>      [,1] [,2]
#> [1,]    1    2
#> 
#> , , 2
#> 
#>      [,1] [,2]
#> [1,]    3    4
#> 
#> , , 3
#> 
#>      [,1] [,2]
#> [1,]    5    6
as.vector(a1)
#> [1] 1 2 3 4 5 6
as.matrix(a1)
#>      [,1]
#> [1,]    1
#> [2,]    2
#> [3,]    3
#> [4,]    4
#> [5,]    5
#> [6,]    6
as.data.frame(a1)
#>   V1 V2 V3 V4 V5 V6
#> 1  1  2  3  4  5  6
as.list(a1)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3
#> 
#> [[4]]
#> [1] 4
#> 
#> [[5]]
#> [1] 5
#> 
#> [[6]]
#> [1] 6
as.factor(a1)
#> [1] 1 2 3 4 5 6
#> Levels: 1 2 3 4 5 6

实际上,之前提到的创建 matrix 和 array 时使用的matrix()array(),本质上是将 vector 转换为 matrix 和 array,如:

matrix(data = c(1, 2, 3, 4), nrow = 2, ncol = 2)
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4
array(data = c(1, 2, 3, 4, 5, 6), dim = c(1, 2, 3))
#> , , 1
#> 
#>      [,1] [,2]
#> [1,]    1    2
#> 
#> , , 2
#> 
#>      [,1] [,2]
#> [1,]    3    4
#> 
#> , , 3
#> 
#>      [,1] [,2]
#> [1,]    5    6

如果涉及到转换目标是 matrix、array,且转换前后 object 维数不同(如 atomic vector 转 matrix,\(2\times3\) matrix 转\(1\times2\times3\) array,等等)时,请尽量使用上述两个 functions。

7.2.1 Conversion between matrix and data frame

matrix 和 data.frame 因为都是行 + 列的呈现形式,所以这两种 structures 互相转换不会改变原有结构。此外,由于大多数读取数据的 function 会自动讲读取的数据存成一个 data.frame,所以使用较为频繁的场景是 data.frame 转 matrix。

str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
is.data.frame(iris)
#> [1] TRUE
str(as.matrix(iris))
#>  chr [1:150, 1:5] "5.1" "4.9" "4.7" "4.6" "5.0" "5.4" ...
#>  - attr(*, "dimnames")=List of 2
#>   ..$ : NULL
#>   ..$ : chr [1:5] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" ...

7.2.2 By row and recycling rule

按列转换。当转换前后 object 的维数不同时,数据将会按照列转换,例如:

1 维转 2 维:

matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4

2 维转 1 维:

as.vector(matrix(
  c(1, 2, 3, 4), 
  byrow = TRUE, 
  nrow = 2, ncol = 2)
)
#> [1] 1 3 2 4
  1. 回收法则recycling rule)。当转换的 vector 和被转换的 vector 长度不一致时,绝大多数情况下, R 会自动把短的 vector 扩展成和长的 vector 一样长度,相当于把短 vector 回收(recycle)重复利用。例如:
a <- c(1, 2)
matrix(a, nrow = 2, ncol = 2)
#>      [,1] [,2]
#> [1,]    1    1
#> [2,]    2    2

当长 vector 的长度并不是短 vector 的长度的整数倍时, R 依然会执行 recycling rule,但是会给出一个 warning,例如:

a <- c(1, 2, 3)
matrix(a, nrow = 4, ncol = 4)
#> Warning in matrix(a, nrow = 4, ncol = 4): data length [3]
#> is not a sub-multiple or multiple of the number of rows [4]
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    2    3    1
#> [2,]    2    3    1    2
#> [3,]    3    1    2    3
#> [4,]    1    2    3    1

然而,如果是要将不同长度的 vector 作为 data.frame 的元素时,非整数倍的差异会直接导致报错,

data.frame(x = c(1, 2), y = c(1, 2, 3))
#> Error in data.frame(x = c(1, 2), y = c(1, 2, 3)): arguments imply differing number of rows: 2, 3

Recycling rule 不仅限于转换数据的情况,任何涉及到需要操作的多个对象长度必须一致的情况,该法则都会发挥作用,例如:

a <- c(1, 2, 3, 4)
b <- c(2, 4)
a*b
#> [1]  2  8  6 16
a + 1
#> [1] 2 3 4 5

按列转换recycling rule这两个细节是 R 很重要的特点。在编程过程中如果能够灵活运用这两点,可以使用简短的代码达到一样的效果。在后续章节的学习过程中,还会讲到这两点在解决实际问题中的运用。

除此之外,还有很多as.fun(),在使用 Rstudio 时,可以通过输入as.并借助自动补全(auto completion)功能查看。

7.3 Recap

  1. 转换元素类型时,请确认转换的 object 是 atomic vector;
  2. logical 和 integer 之间相互转换时,TRUE对应 1LFALSE对应 0L
  3. double 转 logical 时,非 0 对应TRUE0 对应FALSE
  4. double 转 integer 时,会把所有 elements 向下取整;
  5. 只包含数字的 character 可以直接转为 double;
  6. 只包含数字的 character 转为 integer 时会先转为 double,再由 double 转为 integer;
  7. matrix 和 data.frame 因为呈现方式上的一致性,可以无障碍互相转换,所以as.matrix()as.data.frame()的使用较多;
  8. 记住按列转换recycling rule,尤其是后者。