使用 data.table 语法 disk.frame objects 中的 non-standard 评估问题

Problem with non-standard evaluation in disk.frame objects using data.table syntax

问题

我目前正在尝试编写一个函数,使用正则表达式过滤 disk.frame object 的某些行。不幸的是,我 运行 在过滤器函数中评估我的搜索字符串时遇到了一些问题。我的想法是将正则表达式作为字符串传递到函数参数(例如 storm_name),然后将该参数传递到我的过滤调用中。我使用 {data.table} 中包含的 %like% 函数来过滤行。

我的问题是 storm_name object 在 disk.frame 中被求值。但是,由于 storm_name 仅包含在函数环境中,而不包含在 disk.frame object 中,我得到以下错误:

Error in .checkTypos(e, names_x) : 
  Object 'storm_name' not found amongst name, year, month, day, hour and 8 more

我已经尝试使用 eval(sotm_name, env = parent.env()) 计算 parent 框架中的 storm_nameobject,但这也无济于事。有趣的是,这个问题只发生在 {disk.frame} objects 而不是 {data.table} objects.

现在我找到了一个使用 {dplyr} 的解决方案。但是,对于如何使用 {data.table}.

解决此问题的任何想法,我将不胜感激

可重现的例子

# Load packages
library(data.table)
library(disk.frame)

# Create data table and diskframe object of storm data
storms_df <- as.disk.frame(storms)
storms_dt <- as.data.table(storms)

# Create search function
grep_storm_name <- function(dfr, storm_name){
  
  dfr[name %like% storm_name]
  
}

# Check function with data.table object
grep_storm_name(storms_dt, "^A")

# Check function with diskframe object
grep_storm_name(storms_df, "^A")

Session 信息

R version 4.1.0 (2021-05-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)

Matrix products: default

locale:
[1] LC_COLLATE=English_Sweden.1252  LC_CTYPE=English_Sweden.1252    LC_MONETARY=English_Sweden.1252
[4] LC_NUMERIC=C                    LC_TIME=English_Sweden.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] disk.frame_0.5.0  purrr_0.3.4       dplyr_1.0.7       data.table_1.14.0

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.7            benchmarkmeData_1.0.4 pryr_0.1.4            pillar_1.6.4         
 [5] compiler_4.1.0        iterators_1.0.13      tools_4.1.0           digest_0.6.27        
 [9] bit_4.0.4             jsonlite_1.7.2        lifecycle_1.0.1       tibble_3.1.6         
[13] lattice_0.20-44       pkgconfig_2.0.3       rlang_0.4.12          Matrix_1.3-3         
[17] foreach_1.5.1         rstudioapi_0.13       DBI_1.1.1             parallel_4.1.0       
[21] bigassertr_0.1.4      bigreadr_0.2.4        httr_1.4.2            stringr_1.4.0        
[25] globals_0.14.0        generics_0.1.1        fs_1.5.0              vctrs_0.3.8          
[29] bit64_4.0.5           grid_4.1.0            tidyselect_1.1.1      glue_1.6.0           
[33] listenv_0.8.0         R6_2.5.1              future.apply_1.7.0    parallelly_1.25.0    
[37] fansi_1.0.0           magrittr_2.0.1        codetools_0.2-18      ellipsis_0.3.2       
[41] fst_0.9.4             assertthat_0.2.1      future_1.21.0         benchmarkme_1.0.7    
[45] utf8_1.2.2            stringi_1.7.6         doParallel_1.0.16     crayon_1.4.2 

虽然我不知道具体原因,但它与环境、搜索路径等有关。例如,这些工作:

storms_df[name %like% "^A"]

nm <- "^A"
storms_df[name %like% nm]

grep1 <- function(dfr, storm_name) { dfr[name %like% "^A"]; }
grep1(storms_df)

但这不是:

grep2 <- function(dfr, storm_name) { dfr[name %like% storm_name]; }
grep2(storms_df, "^A")
# Error in .checkTypos(e, names_x) : 
#   Object 'storm_name' not found amongst name, year, month, day, hour and 8 more

我们可以通过 eval(substitute(..)) 解决这个问题。

grep3 <- function(dfr, storm_name) { 
  eval(substitute(dfr[name %like% storm_name], list(storm_name = storm_name)))
}
grep3(storms_df, "^A")
#        name  year month   day  hour   lat  long              status category  wind pressure ts_diameter hu_diameter
#      <char> <num> <num> <int> <num> <num> <num>              <char>    <ord> <int>    <int>       <num>       <num>
#   1:    Amy  1975     6    27     0  27.5 -79.0 tropical depression       -1    25     1013          NA          NA
#   2:    Amy  1975     6    27     6  28.5 -79.0 tropical depression       -1    25     1013          NA          NA
#   3:    Amy  1975     6    27    12  29.5 -79.0 tropical depression       -1    25     1013          NA          NA
# ...

grep3(storms_dt, "^A") 也有效)

这通过将 [ 表达式中 storm_name 符号 storm_name 更改为文字字符串来实现。由于这是在未评估的表达式上完成的,因此还没有查找,也没有搜索这个和继承的环境来找到 storm_name.

如果手动检查:

debug(grep3)
grep3(storms_df, "^A")
# debugging in: grep3(storms_df, "^A")
# debug at #1: {
#     eval(substitute(dfr[name %like% storm_name], list(storm_name = storm_name)))
# }
# Browse[2]> 
substitute(dfr[name %like% storm_name], list(storm_name = storm_name))
# dfr[name %like% "^A"]

我认为这与 disk.frame 如何影响 [ 和 calling/parent 环境中的环境有关。 有趣的是(对我来说),你可以看到变量的搜索路径不是空的,这不是我们所期望的:

grep2 <- function(dfr, storm_name) { dfr[name %like% storm_name]; }
grep2(storms_df, "^A")
# Error in .checkTypos(e, names_x) : 
#   Object 'storm_name' not found amongst name, year, month, day, hour and 8 more

### but let's pre-define `storm_name` outside of the function,
### then re-define the function (no change)
storm_name <- "^A"
grep2 <- function(dfr, storm_name) { dfr[name %like% storm_name]; }
head(grep2(storms_df, "^A"), 2)
#      name  year month   day  hour   lat  long              status category  wind pressure ts_diameter hu_diameter
#    <char> <num> <num> <int> <num> <num> <num>              <char>    <ord> <int>    <int>       <num>       <num>
# 1:    Amy  1975     6    27     0  27.5   -79 tropical depression       -1    25     1013          NA          NA
# 2:    Amy  1975     6    27     6  28.5   -79 tropical depression       -1    25     1013          NA          NA

这似乎可行,但我们可以看到它使用的是 storm_name 的外部版本,而不是参数版本,看到 name 仍然以 A 开头,尽管发生了变化至 "^B".

head(grep2(storms_df, "^B"), 2)
#      name  year month   day  hour   lat  long              status category  wind pressure ts_diameter hu_diameter
#    <char> <num> <num> <int> <num> <num> <num>              <char>    <ord> <int>    <int>       <num>       <num>
# 1:    Amy  1975     6    27     0  27.5   -79 tropical depression       -1    25     1013          NA          NA
# 2:    Amy  1975     6    27     6  28.5   -79 tropical depression       -1    25     1013          NA          NA

坦率地说,我对 disk.frame 的内部结构了解不够,无法判断这是一个 bug 还是由于它必须为非-标准 data.table 对不完全在内存中的数据集的评估。


如果您关心性能(公平问题),eval(substitute(..)) 方法似乎不会受到太大影响:

bench::mark(
  raw = dfr[name %like% "^A"],
  subst = eval(substitute(dfr[name %like% storm_name], list(storm_name = storm_name))),
  iterations = 1000
)
# # A tibble: 2 x 13
#   expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result                  memory               time               gc                  
#   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list>                  <list>               <list>             <list>              
# 1 raw          12.9ms   16.8ms      55.2    1.69MB     3.97   933    67      16.9s <data.table [990 x 13]> <Rprofmem [669 x 3]> <bench_tm [1,000]> <tibble [1,000 x 3]>
# 2 subst        12.8ms   15.8ms      60.5    1.69MB     3.25   949    51      15.7s <data.table [990 x 13]> <Rprofmem [669 x 3]> <bench_tm [1,000]> <tibble [1,000 x 3]>

在重复的基准测试中,我实际上看到 subst 稍快 ,这表明部分性能差异与添加 eval(substitute(..)).这种差异(55.2 到 60.5 `itr/sec`)是我见过的最差的...刚才重复了 57.1 和 57.5,所以我建议不要担心性能下降。

自 disk.frame v0.6

起现在有效