data.frames的非标准子集

时间:2017-11-10 22:08:35

标签: r evaluation

对数据框进行子集化的一个怪癖是,在提及列时必须重复键入该数据框的名称。例如,数据框cars在此处提到3次:

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

data.table包解决了这个问题。

library(data.table)
dt_cars <- as.data.table(cars)
dt_cars[speed == 4 & dist < 10]

dplyr一样。

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

我想知道标准问题data.frames是否存在解决方案(即,不使用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

5 个答案:

答案 0 :(得分:7)

1)子集这只需要提及一次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管道上查看这篇文章: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

答案 1 :(得分:3)

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

例如,这里我们在两个地方定义变量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)

这是因为parent.frame()中使用的subset()lapply()正文中的环境,而不是本地块。由于所有环境最终都是从全局环境继承的,因此变量var的值为40

通过quasiquotation进行的卫生变量扩展(在rlang package中实现)解决了这个问题。我们可以使用在所有上下文中正常工作的整洁评估来定义子集的变体。该代码源自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] } 的代码大致相同。

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()

但是30不会遇到子集的范围问题。在前面的示例中,值var用于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 ,正如我们期望的词汇范围规则。

<form>
<div class="col-xs-12">
<a class="btn btn-block btn-default btn-primary">Highlighted</a>
<a class="btn btn-block btn-default">Link One</a>
<a class="btn btn-block btn-default">Link Two</a>
</div>
</form>

这允许在所有环境中稳健地使用非标准评估,而不仅仅是在以前方法中的顶级环境中。

这使得使用非标准评估的函数更有用。在他们很好地进行交互式使用之前,您需要在编写函数和包时使用更详细的标准评估函数。现在可以在所有上下文中使用相同的函数,而无需修改代码!

有关非标准评估的更多详情,请参阅Lionel Henry的Tidy evaluation (hygienic fexprs) presentationrlang vignette on tidy evaluationprogramming with dplyr插图。

答案 2 :(得分: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

答案 3 :(得分:2)

使用覆盖数据框架[方法的解决方案。在新方法中,我们检查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

答案 4 :(得分:0)

使用attach()

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

我很早就开始学习使用attach()的R学习,但只要你小心不引入名称冲突,我认为应该没问题。