数据框的非标准子集提取

4

在对数据框进行子集化的过程中,一个怪癖就是在提到列名时必须反复输入数据框的名称。例如,在此处提到数据框 cars 3 次:

cars[cars$speed == 4 & cars$dist < 10, ]
##   speed dist
## 1     4    2
< p > data.table 包可以解决这个问题。 < /p >
library(data.table)
dt_cars <- as.data.table(cars)
dt_cars[speed == 4 & dist < 10]
< p>与 dplyr 一样。

library(dplyr)
cars %>% filter(speed == 4, dist < 10)

我想知道是否存在一种解决标准数据框(即不使用data.tabledplyr)的方法。

我认为我正在寻找类似于以下内容:

cars[MAGIC(speed == 4 & dist < 10), ]

或者
MAGIC(cars[speed == 4 & dist < 10, ])

需要确定MAGIC的位置。

我尝试了以下方法,但出现了错误。

library(rlang)
cars[locally(speed == 4 & dist < 10), ]
# Error in locally(speed == 4 & dist < 10) : object 'speed' not found

7
with(cars, cars[speed == 4 & dist < 10, ]) 的翻译是:使用 with() 函数,筛选出速度为 4 且距离小于 10 的汽车。 - G5W
1
@G5W with()需要两次输入数据框名称,就像cars[eval(substitute(speed == 4 & dist < 10, cars)), ]一样。 - Richie Cotton
dplyr适用于标准数据框。 - Hugh
@BrodieG 是的,subset() 可以解决这个问题。我更想知道是否有一种使用 [ 的方法来解决它。 - Richie Cotton
1
@BrodieG 最近似乎在思考整洁语法代码的评估方面花费了很多精力,我想知道您是否可以将评估部分与整洁语法部分分开。 - Richie Cotton
显示剩余3条评论
5个回答

7

1) subset 这只需要提及 cars 一次,不需要使用任何包。

subset(cars, speed == 4 & dist < 10)
##   speed dist
## 1     4    2

2) sqldf 这个方法使用了一个包,但并没有使用在问题中被排除的dplyr或data.table这两个包:

library(sqldf)

sqldf("select * from cars where speed = 4 and dist < 10")
##   speed dist
## 1     4    2

3) 赋值 不确定是否计入,但您可以将 cars 分配给其他变量名称,例如 . 然后使用该变量名称。在这种情况下, cars 仅提及一次。这不使用任何软件包。

. <- cars
.[.$speed == 4 & .$dist < 10, ]
##   speed dist
## 1     4    2

或者

. <- cars
with(., .[speed == 4 & dist < 10, ])
##   speed dist
## 1     4    2

关于这两个解决方案,你可能想查看Bizarro Pipe的相关文章:http://www.win-vector.com/blog/2017/01/using-the-bizarro-pipe-to-debug-magrittr-pipelines-in-r/ 4) magrittr 这也可以用magrittr来表达,该包并没有被问题所排除。请注意我们使用magrittr的%$%运算符。
library(magrittr)

cars %$% .[speed == 4 & dist < 10, ]
##   speed dist
## 1     4    2

3

subset是解决此问题的基本函数。然而,像所有使用非标准评估的基本R函数一样,subset不执行完全卫生的代码扩展。因此,在非全局范围(例如在lapply循环中)使用subset()时,它会评估错误的变量。

例如,在这里我们在两个地方定义变量var,首先在全局范围内定义值为40,然后在本地范围内定义值为30。这里使用local()是为了简单起见,但在函数内部也具有相同的行为。直观上,我们希望subset在评估时使用值30。然而,在执行以下代码时,我们看到使用的是值40(因此没有返回任何行)。

var <- 40

local({
  var <- 30
  dfs <- list(mtcars, mtcars)
  lapply(dfs, subset, mpg > var)
})

#> [[1]]
#>  [1] mpg  cyl  disp hp   drat wt   qsec vs   am   gear carb
#> <0 rows> (or 0-length row.names)
#> 
#> [[2]]
#>  [1] mpg  cyl  disp hp   drat wt   qsec vs   am   gear carb
#> <0 rows> (or 0-length row.names)

这是因为在subset()中使用的parent.frame()lapply()内部环境,而不是局部块。由于所有环境最终都继承自全局环境,变量var在那里被找到并具有值40
通过引用展开进行卫生变量(如 rlang 包 中实现的那样)可以解决此问题。我们可以使用整洁评估定义一个subset的变体,它可以在所有上下文中正常工作。该代码源自base::subset.data.frame()的代码,并且大部分相同。
subset2 <- function (x, subset, select, drop = FALSE, ...) {
  r <- if (missing(subset))
    rep_len(TRUE, nrow(x))
  else {
    r <- rlang::eval_tidy(rlang::enquo(subset), x)
    if (!is.logical(r))
      stop("'subset' must be logical")
    r & !is.na(r)
  }
  vars <- if (missing(select))
    TRUE
  else {
    nl <- as.list(seq_along(x))
    names(nl) <- names(x)
    rlang::eval_tidy(rlang::enquo(select), nl)
  }
  x[r, vars, drop = drop]
}

这个版本的subset与base::subset.data.frame()完全一样。

subset2(mtcars, gear > 4, disp:wt)
#>                 disp  hp drat    wt
#> Porsche 914-2  120.3  91 4.43 2.140
#> Lotus Europa    95.1 113 3.77 1.513
#> Ford Pantera L 351.0 264 4.22 3.170
#> Ferrari Dino   145.0 175 3.62 2.770
#> Maserati Bora  301.0 335 3.54 3.570

然而,subset2()没有遭受 subset 的作用域问题。在我们之前的示例中,变量 var 使用值 30,就像我们从词法作用域规则中所期望的那样。

local({
  var <- 30
  dfs <- list(mtcars, mtcars)
  lapply(dfs, subset2, mpg > var)
})

#> [[1]]
#>                 mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> Fiat 128       32.4   4 78.7  66 4.08 2.200 19.47  1  1    4    1
#> Honda Civic    30.4   4 75.7  52 4.93 1.615 18.52  1  1    4    2
#> Toyota Corolla 33.9   4 71.1  65 4.22 1.835 19.90  1  1    4    1
#> Lotus Europa   30.4   4 95.1 113 3.77 1.513 16.90  1  1    5    2
#> 
#> [[2]]
#>                 mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> Fiat 128       32.4   4 78.7  66 4.08 2.200 19.47  1  1    4    1
#> Honda Civic    30.4   4 75.7  52 4.93 1.615 18.52  1  1    4    2
#> Toyota Corolla 33.9   4 71.1  65 4.22 1.835 19.90  1  1    4    1
#> Lotus Europa   30.4   4 95.1 113 3.77 1.513 16.90  1  1    5    2

这使得非标准计算可以在所有上下文中稳健地使用,而不仅仅是像以前的方法一样只能在顶级上下文中使用。

这使得使用非标准计算的函数更加有用。以前,虽然它们在交互式使用时很好用,但在编写函数和包时需要使用更冗长的标准计算函数。现在,同一个函数可以在所有上下文中使用,无需修改代码!

关于非标准计算的更多细节,请参见Lionel Henry的Tidy evaluation (hygienic fexprs) presentationtidy evaluation的rlang说明文档以及使用dplyr进行编程说明文档。


2

我知道我完全在作弊,但从技术上讲,它是有效的:):

with(cars, data.frame(speed=speed,dist=dist)[speed == 4 & dist < 10,])
#   speed dist
# 1     4    2

更多恐怖:
`[` <- function(x,i,j){
  rm(`[`,envir = parent.frame())
  eval(parse(text=paste0("with(x,x[",deparse(substitute(i)),",])")))
  }
cars[speed == 4 & dist < 10, ]

#   speed dist
# 1     4    2

2

通过覆盖data.frame的[方法来解决问题。在新方法中,我们检查i参数的类别,如果它是表达式或公式,则在data.frame上下文中对其进行评估。

##### override subsetting method
`[.data.frame` = function (x, i, j, ...) {
    if(!missing(i) && (is.language(i) || is.symbol(i) || inherits(i, "formula"))) {
        if(inherits(i, "formula")) i = as.list(i)[[2]] 
        i = eval(i, x, enclos = baseenv())
    } 
    base::`[.data.frame`(x, i, j, ...)
}

#####

data(cars)
cars[cars$speed == 4 & cars$dist < 10, ]
#     speed dist
# 1     4    2

# cars[speed == 4 & dist < 10, ] # error

cars[quote(speed == 4 & dist < 10),] 
#     speed dist
# 1     4    2


# ,or
cars[~ speed == 4 & dist < 10,]
#     speed dist
# 1     4    2

另一种更神奇的解决方案。请重新启动R会话以避免与先前的解决方案干扰:

locally = function(expr){
    curr_call = as.list(sys.call(1))
    if(as.character(curr_call[[1]])=="["){
        possibly_df = eval(curr_call[[2]], parent.frame())
        if(is.data.frame(possibly_df)){
            expr = substitute(expr)
            expr = eval(expr, possibly_df, enclos = baseenv())
        }
    }
    expr
}

cars[locally(speed == 4 & dist < 10), ]
#     speed dist
# 1     4    2

0
使用 attach()
attach(cars)
cars[speed == 4 & dist < 10,]
#   speed dist
# 1     4    2

在我学习 R 语言的早期,我就被劝阻使用 attach(),但只要你小心不引入名称冲突,我认为这应该是可以的。


当然,您是绝对正确的,但是赞同一个建议使用“attach”的答案感觉有些不妥。 - Richie Cotton
是的,这仍然不是我认可的方法。一想到它就让我有点不舒服。 - AkselA
1
无论如何,它仍然被提及两次,所以最好使用 with(cars, cars[speed == 4 & dist < 10, ])。更不用说你仍然需要调用 detach。 - moodymudskipper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接