Advanced R之构造子集

转发请声明出处:http://www.cnblogs.com/lizichao/p/4794733.html

构造子集

  R构造子集的操作功能强大而且速度快。精通构造子集者可以用简洁的方式表达复杂的操作,很少有其他语言能做到这一点。构造子集学习起来比较困难,因为需要掌握一系列相互关联的概念:

  • 3种构造子集操作符。
  • 6类子集。
  • 不同对象(比如向量、列表、因子、矩阵、数据框)行为上的重要不同。
  • 联合使用构造子集与赋值。

  本章将帮助掌握构造子集,让我们从最简单的构造子集开始:用[构造原子向量的子集。然后逐渐扩展你的知识,首先学习更加复杂的数据类型(比如数组和列表),然后是其他构造子集操作符,[[和$。接着你将学习如何组合使用构造子集和赋值来修改对象的一部分,最后,你将看到很多有用的应用。

  构造子集与str()相辅相成。str()展示任意对象的结构,构造子集让你选取你所感兴趣的部分。

测试

  做下下面的测试,以确定你是否需要阅读本章。如果可以很快想到答案,你可以跳过本章。可以在answers中检查你的答案是否正确。

  1. 构造向量的子集时,使用正整数、负整数、一个逻辑向量或者一个字符向量,结果会如何?
  2. 当作用于列表时,[,[[,$有何不同?
  3. 什么时候使用drop = FALSE
  4. 如果x是一个矩阵,那么x[] <- 0会发生什么?这与x <- 0有何不同?
  5. 如何使用命名向量来重新标记分类变量?
概述
  • 数据类型部分以学习[开始。首先学习可以用来构造原子向量子集的6种数据类型,然后学习这6种数据类型如何构造列表、矩阵、数据框和S3对象的子集。
  • 构造子集操作符部分扩展关于构造子集操作符的知识,包括[[和$,关注点在于结构简化与保留的重要规律。
  • 构造子集与赋值部分你将学习局部赋值的技巧,即联合使用构造子集和赋值来改变对象的一部分。
  • 应用部分介绍8种重要但是不明显的构造子集应用,这些应用在数据分析中经常用到。

数据类型

  构造原子向量子集是最简单的,然后归纳到高维和其他更加复杂的对象。我们从[开始,[是最常见的构造子集操作符。构造子集操作符部分将介绍[[和$,它们是另外2种主要的构造子集操作符。

原子向量

  让我们看下对于一个简单的向量x,用不同数据类型来构造其子集会有何不同。

x <- c(2.1, 4.2, 3.3, 5.4)

  注意,向量中每个元素小数点后的数字,表明该元素在向量中的位置。

  可以用5样东西来构造向量的子集:

  •  正整数,这时将得到对应位置的元素:

    x[c(3, 1)]
    #> [1] 3.3 2.1
    x[order(x)]
    #> [1] 2.1 3.3 4.2 5.4
    
    # Duplicated indices yield duplicated values
    x[c(1, 1)]
    #> [1] 2.1 2.1
    
    # Real numbers are silently truncated to integers
    x[c(2.1, 2.9)]
    #> [1] 4.2 4.2
  • 负整数,这时会忽略对应位置的元素:

    x[-c(3, 1)]
    #> [1] 4.2 5.4

  不能混合使用正整数和负整数: 

x[c(-1, 2)]
#> Error in x[c(-1, 2)]: only 0's may be mixed with negative subscripts
  • 逻辑向量,这时会选择对应的逻辑值为真的元素。这可能是最有用的构造子集方式,因为你可以利用创建逻辑向量的表达式:

    x[c(TRUE, TRUE, FALSE, FALSE)]
    #> [1] 2.1 4.2
    x[x > 3]
    #> [1] 4.2 3.3 5.4

    如果逻辑向量的长度,比子集所在向量的长度短,逻辑向量将被循环复制直到达到相同的长度。

    x[c(TRUE, FALSE)]
    #> [1] 2.1 3.3
    # Equivalent to
    x[c(TRUE, FALSE, TRUE, FALSE)]
    #> [1] 2.1 3.3

    缺失值所在的下标,一般对应的也会得到一个缺失值:

    x[c(TRUE, TRUE, NA, FALSE)]
    #> [1] 2.1 4.2  NA
  • ,此时返回原向量。这对于向量来说没有用,但是对于矩阵、数据框和数组非常有用。当联合使用构造子集与赋值时也比较有用。

    x[]
    #> [1] 2.1 4.2 3.3 5.4
  • 0,此时返回一个长度为0的向量。这通常不是你的意图,但是对于产生测试数据是有用的。

    x[0]
    #> numeric(0)
    如果向量是命了名的,你也可以使用:
  • 字符向量,此时返回匹配的元素。

    (y <- setNames(x, letters[1:4]))
    #>   a   b   c   d 
    #> 2.1 4.2 3.3 5.4
    y[c("d", "c", "a")]
    #>   d   c   a 
    #> 5.4 3.3 2.1
    
    # Like integer indices, you can repeat indices
    y[c("a", "a", "a")]
    #>   a   a   a 
    #> 2.1 2.1 2.1
    
    # When subsetting with [ names are always matched exactly
    z <- c(abc = 1, def = 2)
    z[c("a", "d")]
    #> <NA> <NA> 
    #>   NA   NA

列表

  构造列表的子集与构造原子向量子集的方式相同。对列表使用[构造子集总是得到一个列表;[[和$得到列表的内容,如下所述。

矩阵和数组

  对于高维数据结构,有3种方式构造子集:

  • 使用多个向量。
  • 使用单个向量。
  • 使用矩阵。

  构造矩阵(2维)和数组(>2维)子集最常见的方式,是对1维情况下构造子集的简单拓展:对于每个维度提供一个1维的下标,用逗号分隔。用空格构造子集此时比较有用,因为它允许你保留所有的行或者列。

a <- matrix(1:9, nrow = 3)
colnames(a) <- c("A", "B", "C")
a[1:2, ]
#>      A B C
#> [1,] 1 4 7
#> [2,] 2 5 8
a[c(T, F, T), c("B", "A")]
#>      B A
#> [1,] 4 1
#> [2,] 6 3
a[0, -2]
#>      A C
  默认情况下,[会简化结果,即将结果的维度降到可能的最低维度。参见结构简化与保留部分介绍的如何避免这种情况。

  因为矩阵和数组是通过向量实现的,即具有特殊属性的向量,所以可以用一个向量来对它们构造子集。这种情况下,矩阵和数组会表现的像向量。R中的数组以列为首要存储顺序。

(vals <- outer(1:5, 1:5, FUN = "paste", sep = ","))
#>      [,1]  [,2]  [,3]  [,4]  [,5] 
#> [1,] "1,1" "1,2" "1,3" "1,4" "1,5"
#> [2,] "2,1" "2,2" "2,3" "2,4" "2,5"
#> [3,] "3,1" "3,2" "3,3" "3,4" "3,5"
#> [4,] "4,1" "4,2" "4,3" "4,4" "4,5"
#> [5,] "5,1" "5,2" "5,3" "5,4" "5,5"
vals[c(4, 15)]
#> [1] "4,1" "5,3"
  也可以利用整形矩阵来对更高维度的数据结构构造子集(或者,如果是命名的数据结构,可以用字符矩阵)。对于子集所在的数组,矩阵的每一行确定了值的位置,每一列对应一个维度。也就是说,用2列的矩阵来构造矩阵的子集,用3列的矩阵构造3维数组的矩阵。得到的结果是向量:
vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")
select <- matrix(ncol = 2, byrow = TRUE, c(
  1, 1,
  3, 1,
  2, 4
))
vals[select]
#> [1] "1,1" "3,1" "2,4"

数据框

  数据框具有列表和矩阵的特征:如果用一个向量对数据框构造子集,数据框表现的像列表;如果使用2个向量,它表现的像矩阵。

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])

df[df$x == 2, ]
#>   x y z
#> 2 2 2 b
df[c(1, 3), ]
#>   x y z
#> 1 1 3 a
#> 3 3 1 c

# There are two ways to select columns from a data frame
# Like a list:
df[c("x", "z")]
#>   x z
#> 1 1 a
#> 2 2 b
#> 3 3 c
# Like a matrix
df[, c("x", "z")]
#>   x z
#> 1 1 a
#> 2 2 b
#> 3 3 c

# There's an important difference if you select a single 
# column: matrix subsetting simplifies by default, list 
# subsetting does not.
str(df["x"])
#> 'data.frame':    3 obs. of  1 variable:
#>  $ x: int  1 2 3
str(df[, "x"])
#>  int [1:3] 1 2 3

S3对象

  S3对象由原子向量、数组、列表组成,所以你可以使用上述方法以及str()得到的信息,来构造S3对象的子集。

S4对象

  对于S4对象,还有另外2种构造子集的运算符:@(等同于$),slot()(等同于[[)。@比$有更加严格的限制,即如果槽不存在会返回错误。这些内容在the OO field guide介绍。

练习

  1. 下列代码试图构造数据框子集,请修复其中的错误:

    mtcars[mtcars$cyl = 4, ]
    mtcars[-1:4, ]
    mtcars[mtcars$cyl <= 5]
    mtcars[mtcars$cyl == 4 | 6, ]
  2. 对于x <- 1:5;为何x[NA]得到5个缺失值?(思路:这与x[NA_real_]为何不同?)

  3. upper.tri()返回什么?为何可以使用它对矩阵构造子集?对于这种情况,我们是否需要额外的构造子集规则来描述它?

    x <- outer(1:5, 1:5, FUN = "*")
    x[upper.tri(x)]
  4. 为何mtcars[1:20]返回错误?它与mtcars[1:20, ]为何不同?

  5. 自己动手写一个函数,取矩阵对角线上的元素(该函数应该类似于diag(x),其中x是矩阵)。

  6. df[is.na(df)] <- 0什么意思?为何可以这样使用?

构造子集操作符

  另外2种构造子集操作符是[[和$。[[类似于[,只不过[[只返回一个单独的值,而且允许构造列表的子集。$是[[的简化,它利用字符构造子集,很有用。

  当构造列表子集时需要使用[[。因为[应用到列表时总是得到一个列表:而不是列表的内容。为了得到列表内容,需要使用[[:

“如果x是拉货的火车,x[[5]]是5号车厢中的货物;x[4:6]是4到6号车厢。”

@RLangTip

  因为[[只返回一个值,必须使用正整数或者字符串来使用[[:

a <- list(a = 1, b = 2)
a[[1]]
#> [1] 1
a[["a"]]
#> [1] 1

# If you do supply a vector it indexes recursively
b <- list(a = list(b = list(c = list(d = 1))))
b[[c("a", "b", "c", "d")]]
#> [1] 1
# Same as
b[["a"]][["b"]][["c"]][["d"]]
#> [1] 1

  因为数据框是列组成的列表,所以要使用[[来提取数据框的一列:mtcars[[1]]mtcars[["cyl"]]

  S3对象和S4对象可以覆盖[和[[操作符的标准行为,所以对于不同的对象会有不同的表现。关键的不同点往往在于如何选择结构简化或者保留,以及默认情况。

结构简化和保留

  理解子集结构简化和保留的区别非常重要。结构简化是指返回的子集的数据结构尽可能简单,只要能表示输出即可,对于交互来说这很有用因为这样往往可以得到想要的结果。结构保留是指输入与输出的结构相同,在编程中这往往更好些,因为结果一直是同类型的。构造矩阵和数据框子集时,忽略drop = FALSE是个最常见的编程错误。(在测试时可以正常工作,但是对于只有一列的数据框,会以不可预测且不清楚的方式失败。)

  不幸的是,对不同数据类型,如何在简化与保留之间切换那,这里总结了下表。

  简化 保留
向量 x[[1]] x[1]
列表 x[[1]] x[1]
因子 x[1:4, drop = T] x[1:4]
数组 x[1, ] or x[, 1] x[1, , drop = F] or x[, 1, drop = F]
数据框
x[, 1] or x[[1]] x[, 1, drop = F] or x[1]

  结构保留对于所有的数据类型都相同:输出与输入类型相同。不同数据类型的结构简化行为略有不同,如下:

  • 原子向量:移除名称。

    x <- c(a = 1, b = 2)
    x[1]
    #> a 
    #> 1
    x[[1]]
    #> [1] 1
  • 列表:返回列表中的对象,而不是单个元素的列表。

    y <- list(a = 1, b = 2)
    str(y[1])
    #> List of 1
    #>  $ a: num 1
    str(y[[1]])
    #>  num 1
  • 因子:丢掉所有没有用到的因子水平。

    z <- factor(c("a", "b"))
    z[1]
    #> [1] a
    #> Levels: a b
    z[1, drop = TRUE]
    #> [1] a
    #> Levels: a
  • 矩阵和数组:如果某个维度的长度为1,丢掉该维度。

    a <- matrix(1:4, nrow = 2)
    a[1, , drop = FALSE]
    #>      [,1] [,2]
    #> [1,]    1    3
    a[1, ]
    #> [1] 1 3
  • 数据框:如果输出是单个列,返回一个向量而非数据框。

    df <- data.frame(a = 1:2, b = 1:2)
    str(df[1])
    #> 'data.frame':    2 obs. of  1 variable:
    #>  $ a: int  1 2
    str(df[[1]])
    #>  int [1:2] 1 2
    str(df[, "a", drop = FALSE])
    #> 'data.frame':    2 obs. of  1 variable:
    #>  $ a: int  1 2
    str(df[, "a"])
    #>  int [1:2] 1 2

$

  $是个简化符号,x$y等同于x[["y", exact = FALSE]]。它常被用在数据框中访问变量,比如mtcars$cyl or diamonds$carat

  对于$常见的一个错误是,当将列名存储到一个变量,尝试并使用$和这个变量来访问数据:

var <- "cyl"
# Doesn't work - mtcars$var translated to mtcars[["var"]]
mtcars$var
#> NULL

# Instead use [[
mtcars[[var]]
#>  [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4

  $与[[之间有一个重要的不同,即$支持部分匹配:

x <- list(abc = 1)
x$a
#> [1] 1
x[["a"]]
#> NULL

  如果要禁止这种情况,可以使用全局选项warnPartialMatchDollarTRUE。使用时要小心:这可能影响你加载的其他代码(比如从包中加载的代码)。

缺失值/下标越界

   当下标越界时(OOB),[和[[的行为略有不同,比如,对于一个长度为4的向量,试图取第5个元素,或者使用NA或NULL构造子集:

x <- 1:4
str(x[5])
#>  int NA
str(x[NA_real_])
#>  int NA
str(x[NULL])
#>  int(0)

  下表总结了利用[和[[,在不同类型下标、下标越界情况下,构造原子向量和列表子集的结果。

操作符 下标 原子向量 列表
[ OOB NA list(NULL)
[ NA_real_ NA list(NULL)
[ NULL x[0] list(NULL)
[[ OOB Error Error
[[ NA_real_ Error NULL
[[ NULL Error Error

  如果输入向量是命名的,那么下标越界、缺失值或者NULL,对应结果的名称为"<NA>"

测试

  1. 给定一个线性模型,比如mod <- lm(mpg ~ wt, data = mtcars),提取残差的自由度。从模型摘要(summary(mod))中提取R squared。

构造子集和赋值

  所有子集构造符都可以与赋值操作联合使用,来修改输入变量指定的值。

x <- 1:5
x[c(1, 2)] <- 2:3
x
#> [1] 2 3 3 4 5

# The length of the LHS needs to match the RHS
x[-1] <- 4:1
x
#> [1] 2 4 3 2 1

# Note that there's no checking for duplicate indices
x[c(1, 1)] <- 2:3
x
#> [1] 3 4 3 2 1

# You can't combine integer indices with NA
x[c(1, NA)] <- c(1, 2)
#> Error in x[c(1, NA)] <- c(1, 2): NAs are not allowed in subscripted assignments
# But you can combine logical indices with NA
# (where they're treated as false).
x[c(T, F, NA)] <- 1
x
#> [1] 1 4 3 1 1

# This is mostly useful when conditionally modifying vectors
df <- data.frame(a = c(1, 10, NA))
df$a[df$a < 5] <- 0
df$a
#> [1]  0 10 NA

  利用空来构造子集,并联合使用赋值操作比较有用,因为这会保留原始对象的类和结构。比较下列2个表达式。第一个,mtcars仍然是一个数据框。第二个,mtcars变成了一个列表。

mtcars[] <- lapply(mtcars, as.integer)
mtcars <- lapply(mtcars, as.integer)

  对于列表,可以联合使用构造子集、赋值、NULL来移除列表的一部分。利用[和list(NULL)添加一个字符NULL到列表中:

x <- list(a = 1, b = 2)
x[["b"]] <- NULL
str(x)
#> List of 1
#>  $ a: num 1

y <- list(a = 1)
y["b"] <- list(NULL)
str(y)
#> List of 2
#>  $ a: num 1
#>  $ b: NULL

应用

  利用上述基本规则,可以创建很多有用的应用。下列描述的是一些最重要的应用。其中一些基本的技巧已经包装到了更加准确的函数中(比如subset(),merge()plyr::arrange()),但是理解其中的构造子集原理是很有用的。这样当你遇到新的情况而现有函数无法解决时,你知道该怎么做。

查阅表(字符构造子集)

   字符匹配是创建查询表的一种强大方式。比如你要转换缩略词:

x <- c("m", "f", "u", "f", "f", "m", "m")
lookup <- c(m = "Male", f = "Female", u = NA)
lookup[x]
#>        m        f        u        f        f        m        m 
#>   "Male" "Female"       NA "Female" "Female"   "Male"   "Male"
unname(lookup[x])
#> [1] "Male"   "Female" NA       "Female" "Female" "Male"   "Male"

# Or with fewer output values
c(m = "Known", f = "Known", u = "Unknown")[x]
#>         m         f         u         f         f         m         m 
#>   "Known"   "Known" "Unknown"   "Known"   "Known"   "Known"   "Known"

  如果不想要结果中的名称,使用unname()将其移出。

手动匹配和合并(整形构造子集)

  你可能有一个更加复杂的查询表,它包含多个列。假设我们有一个成绩向量,以及一个描述成绩属性的数据框:

grades <- c(1, 2, 2, 3, 1)

info <- data.frame(
  grade = 3:1,
  desc = c("Excellent", "Good", "Poor"),
  fail = c(F, F, T)
)

  我们想复制信息表,需要对于grades中每个值创建一行。有2种方式实现,一是使用match()和整形构造子集,或者使用rownames和字符构造子集:

grades
#> [1] 1 2 2 3 1

# Using match
id <- match(grades, info$grade)
info[id, ]
#>     grade      desc  fail
#> 3       1      Poor  TRUE
#> 2       2      Good FALSE
#> 2.1     2      Good FALSE
#> 1       3 Excellent FALSE
#> 3.1     1      Poor  TRUE

# Using rownames
rownames(info) <- info$grade
info[as.character(grades), ]
#>     grade      desc  fail
#> 1       1      Poor  TRUE
#> 2       2      Good FALSE
#> 2.1     2      Good FALSE
#> 3       3 Excellent FALSE
#> 1.1     1      Poor  TRUE

  如果有多个列需要匹配,你需要将它们折叠到一个单独的列(使用interaction()paste()或者 plyr::id())。也可以用merge()或者plyr::join(),作用是一样的——要了解如何实现的可以查阅源代码。

随机取样/放回取样(整数构造子集)

  对向量和数据框,可以使用整数下标实现随机取样或者有放回取样。sample()生成一个下标向量,然后构造子集来访问相应值:

df <- data.frame(x = rep(1:3, each = 2), y = 6:1, z = letters[1:6])

# Set seed for reproducibility
set.seed(10)

# Randomly reorder
df[sample(nrow(df)), ]
#>   x y z
#> 4 2 3 d
#> 2 1 5 b
#> 5 3 2 e
#> 3 2 4 c
#> 1 1 6 a
#> 6 3 1 f
# Select 3 random rows
df[sample(nrow(df), 3), ]
#>   x y z
#> 2 1 5 b
#> 6 3 1 f
#> 3 2 4 c
# Select 6 bootstrap replicates
df[sample(nrow(df), 6, rep = T), ]
#>     x y z
#> 3   2 4 c
#> 4   2 3 d
#> 4.1 2 3 d
#> 1   1 6 a
#> 4.2 2 3 d
#> 3.1 2 4 c

  sample()的参数控制取样的数量,以及是否是有放回取样。

排序(整数构造子集)

  order()以向量为输入,返回一个整形向量,该整形向量描述了该向量应该如何排序:

x <- c("b", "c", "a")
order(x)
#> [1] 3 1 2
x[order(x)]
#> [1] "a" "b" "c"

  为断绝关系,你可以在order()中使用额外的变量,还可以使用decreasing = TRUE将升序修改为降序。缺省情况下,所有缺省值会被放在向量最后;然而可以使用na.last = NA移除缺省值,或者使用na.last = FALSE将缺省值放在前面。

  对于2维和高维数据结构,利用order()和整形构造子集,可以方便的按照对象的行或者列排序:

# Randomly reorder df
df2 <- df[sample(nrow(df)), 3:1]
df2
#>   z y x
#> 3 c 4 2
#> 1 a 6 1
#> 2 b 5 1
#> 4 d 3 2
#> 6 f 1 3
#> 5 e 2 3

df2[order(df2$x), ]
#>   z y x
#> 1 a 6 1
#> 2 b 5 1
#> 3 c 4 2
#> 4 d 3 2
#> 6 f 1 3
#> 5 e 2 3
df2[, order(names(df2))]
#>   x y z
#> 3 2 4 c
#> 1 1 6 a
#> 2 1 5 b
#> 4 2 3 d
#> 6 3 1 f
#> 5 3 2 e

  sort()可以更准确对向量排序,但是不够灵活,同样plyr::arrange()可以对数据框进行更准确的排序,但是同样不够灵活。

扩展合计数量(整形构造子集)

  有时你会得到这样一个数据框,它的相同的行被折叠到了一行,并且有一列表明行的数量。rep()和整形构造子集可以很容易将折叠的数据分开,这通过一个重复的行索引实现:

df <- data.frame(x = c(2, 4, 1), y = c(9, 11, 6), n = c(3, 5, 1))
rep(1:nrow(df), df$n)
#> [1] 1 1 1 2 2 2 2 2 3
df[rep(1:nrow(df), df$n), ]
#>     x  y n
#> 1   2  9 3
#> 1.1 2  9 3
#> 1.2 2  9 3
#> 2   4 11 5
#> 2.1 4 11 5
#> 2.2 4 11 5
#> 2.3 4 11 5
#> 2.4 4 11 5
#> 3   1  6 1

移除数据框的列(字符构造子集)

  有2种方式从数据框中移除列。可以通过将某一列设置为NULL来实现:

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df$z <- NULL

  或者可以通过构造子集来获取想要的列:

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df[c("x", "y")]
#>   x y
#> 1 1 3
#> 2 2 2
#> 3 3 1

  如果你知道哪些列是不需要的,可以使用设置操作来得到想要的列:

df[setdiff(names(df), "z")]
#>   x y
#> 1 1 3
#> 2 2 2
#> 3 3 1

根据条件选择行(逻辑构造子集)

  因为可以方便的合并多个列的条件,所以逻辑构造子集可能是提取数据框行的最常用方式。

mtcars[mtcars$gear == 5, ]
#>     mpg cyl  disp  hp drat    wt qsec vs am gear carb
#> 27 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
#> 28 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2
#> 29 15.8   8 351.0 264 4.22 3.170 14.5  0  1    5    4
#> 30 19.7   6 145.0 175 3.62 2.770 15.5  0  1    5    6
#> 31 15.0   8 301.0 335 3.54 3.570 14.6  0  1    5    8
mtcars[mtcars$gear == 5 & mtcars$cyl == 4, ]
#>     mpg cyl  disp  hp drat    wt qsec vs am gear carb
#> 27 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
#> 28 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2

  记得使用&和|,而不是&&和||,&&和||在条件语句中更加有用。不要忘记摩根定律(De Morgan’s laws),它可以用来简化否定:

  • !(X & Y)等同于!X | !Y
  • !(X | Y)等同于!X & !Y

  例如,!(X & !(Y | Z))可以简化为!X | !!(Y|Z),进一步简化为!X | Y | Z

  subset()是一个特殊的简化了的构造数据框子集的函数,用它可以少敲几下键盘,因为不需要重复数据框的名字。你将在非标准计算(non-standard evaluation)中看到它是如何工作的。

subset(mtcars, gear == 5)
#>     mpg cyl  disp  hp drat    wt qsec vs am gear carb
#> 27 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
#> 28 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2
#> 29 15.8   8 351.0 264 4.22 3.170 14.5  0  1    5    4
#> 30 19.7   6 145.0 175 3.62 2.770 15.5  0  1    5    6
#> 31 15.0   8 301.0 335 3.54 3.570 14.6  0  1    5    8
subset(mtcars, gear == 5 & cyl == 4)
#>     mpg cyl  disp  hp drat    wt qsec vs am gear carb
#> 27 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
#> 28 30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2

布尔代数与设置操作(逻辑和整形构造子集)

  需要注意设置操作(整形构造子集)与布尔代数(布尔构造子集)之间的等效性。在某些情况下使用设置操作更加有效:

  • 试图找出第一个(或者最后一个)TRUE

  • 当有比较少的TRUEs和非常多的FALSEs;设置操作可能更快而且需要更少的内存。

  which()可以将一个逻辑表示转换为整形表示。基础R中没有反转操作但是可以很容易创建一个:

x <- sample(10) < 4
which(x)
#> [1]  3  7 10

unwhich <- function(x, n) {
  out <- rep_len(FALSE, n)
  out[x] <- TRUE
  out
}
unwhich(which(x), 10)
#>  [1] FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE

  让我们来创建2个逻辑向量和对等的整形向量,然后看下布尔值与设置操作的关系。

(x1 <- 1:10 %% 2 == 0)
#>  [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE
(x2 <- which(x1))
#> [1]  2  4  6  8 10
(y1 <- 1:10 %% 5 == 0)
#>  [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE
(y2 <- which(y1))
#> [1]  5 10

# X & Y <-> intersect(x, y)
x1 & y1
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
intersect(x2, y2)
#> [1] 10

# X | Y <-> union(x, y)
x1 | y1
#>  [1] FALSE  TRUE FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE
union(x2, y2)
#> [1]  2  4  6  8 10  5

# X & !Y <-> setdiff(x, y)
x1 & !y1
#>  [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE
setdiff(x2, y2)
#> [1] 2 4 6 8

# xor(X, Y) <-> setdiff(union(x, y), intersect(x, y))
xor(x1, y1)
#>  [1] FALSE  TRUE FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE
setdiff(union(x2, y2), intersect(x2, y2))
#> [1] 2 4 6 8 5

  开始学习构造子集时的一个常见错误是使用x[which(y)]替代x[y]。这儿which()什么都没干:它将逻辑构造子集转换成整形构造子集,但是结果完全一样。同样要注意x[-which(y)]并不等同于x[!y]:如果y全是FALSE,which(y)会得到integer(0),而integer(0)还是integer(0),所以你没有获取任何值,而不是所有值。通常应该避免将逻辑构造子集转换成整形构造子集,除非你想这么做,比如获取第一个或者最后一个TRUE。

练习

  1. 如何随机排列数据框中的列?(在随机预测中这很重要。)你能否在一步内同时随机排列行和列?
  2. 如何随机选取数据框中的m行?如果要求这m行是连续的那(比如,一个首行,一个结束行,以及其中所有的行)?
  3. 如何将数据框的列按照字母表排序?

答案

  1. 正整数选取对应位置的元素,负整数去除对应位置的元素;逻辑向量保留TRUE对应位置的元素;字符向量按照名称匹配选取元素。
  2. [选取子列表。它总是得到一个列表;如果使用[和一个正整数来构造子集,会得到一个长度为1的列表。[[选取列表中的元素。$是为了方便起见的缩写符号:x$y等同于x[["y"]]
  3. 如果构造矩阵、数据或者数据框的子集时,想保留数据结构,使用drop = FALSE。在函数内容构造子集时,你应该总是这么做。
  4. 如果x是矩阵,x[] <- 0将替换每个元素为0,行和列的数量不变,x <- 0会将矩阵替换为0。
  5. 一个命名的向量可以作为一个简单的查询表:c(x = 1, y = 2, z = 3)[c("y", "z", "x")]
posted @ 2015-09-11 09:27  超仔  阅读(626)  评论(0编辑  收藏  举报