【问题标题】:dbplyr, dplyr, and functions with no SQL equivalents [eg `slice()`]dbplyr、dplyr 和没有 SQL 等效项的函数 [例如 `slice()`]
【发布时间】:2020-03-31 17:08:24
【问题描述】:
library(tidyverse)
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
copy_to(con, mtcars)
mtcars2 <- tbl(con, "mtcars")

我可以在上面创建这个模拟 SQL 数据库。我可以在这个“数据库”上执行标准的 dplyr 函数,这非常酷:

mtcars2 %>% 
  group_by(cyl) %>% 
  summarise(mpg = mean(mpg, na.rm = TRUE)) %>% 
  arrange(desc(mpg))
#> # Source:     lazy query [?? x 2]
#> # Database:   sqlite 3.29.0 [:memory:]
#> # Ordered by: desc(mpg)
#>     cyl   mpg
#>   <dbl> <dbl>
#> 1     4  26.7
#> 2     6  19.7
#> 3     8  15.1

看来我无法使用没有直接 SQL 等效项的 dplyr 函数(例如 dplyr::slice())。对于slice(),我可以使用filter()row_number() 的替代组合来获得与仅使用slice() 相同的结果。但是如果没有这么简单的解决方法会发生什么?

mtcars2 %>% slice(1:5)
#>Error in UseMethod("slice_") : 
#>  no applicable method for 'slice_' applied to an object of class 
#>  "c('tbl_SQLiteConnection', 'tbl_dbi', 'tbl_sql', 'tbl_lazy', 'tbl')"

当 dplyr 函数没有直接的 SQL 等效项时,我可以强制它们与 dbplyr 一起使用,或者是唯一的选择来使用具有 SQL 等效项的 dplyr 动词来获得创意,或者直接编写 SQL(这不是 my 首选解决方案)?

【问题讨论】:

  • 我不知道这是否是首选,但您可以将更大的数据帧拉入内存,然后在 r 对象而不是 sql 表上使用没有等效项的 dplyr 动词吗?
  • 您只需要在slice(或其他不可翻译的乐趣)之前添加一个collect() %&gt;% 行,以便首先将其带入内存,如果您想这样做的话
  • 我认为我的建议会是类似的:mt4 &lt;- RSQLite::dbReadTable(conn = con, "mtcars") %&gt;% slice(1:5)。如果内存是一个问题,我会考虑采用混合方法,在 SQL 中选择和过滤行,然后使用 collect 拉入较小的子集
  • 除了 for slice 之外还有什么功能会导致您出现问题?
  • 知道了。如果您尝试获取数据的View,这对我有用:View(mtcars2 %&gt;% do(head(.,5)))

标签: sql r dplyr r-dbi dbplyr


【解决方案1】:

我理解了这个问题:如何让slice() 为 SQL 数据库工作?这与“强制使用它们”不同,但仍可能适用于您的情况。

下面的例子展示了如何实现一个在数据库上工作的slice() 的“穷人”变体。我们仍然需要做一些跑腿工作并使用在数据库上工作的动词来实现它,但是我们可以将它与数据框类似地使用。

http://adv-r.had.co.nz/OO-essentials.html#s3 中阅读有关 S3 课程的更多信息。

library(tidyverse)
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
copy_to(con, mtcars)
mtcars2 <- tbl(con, "mtcars")

# mtcars2 has a class attribute
class(mtcars2)
#> [1] "tbl_SQLiteConnection" "tbl_dbi"              "tbl_sql"             
#> [4] "tbl_lazy"             "tbl"

# slice() is an S3 method
slice
#> function(.data, ..., .preserve = FALSE) {
#>   UseMethod("slice")
#> }
#> <bytecode: 0x560a03460548>
#> <environment: namespace:dplyr>

# we can implement a "poor man's" variant of slice()
# for the particular class. (It doesn't work quite the same
# in all cases.)
#' @export
slice.tbl_sql <- function(.data, ...) {
  rows <- c(...)

  .data %>%
    mutate(...row_id = row_number()) %>%
    filter(...row_id %in% !!rows) %>%
    select(-...row_id)
}

mtcars2 %>%
  slice(1:5)
#> # Source:   lazy query [?? x 11]
#> # Database: sqlite 3.29.0 [:memory:]
#>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1  21       6   160   110  3.9   2.62  16.5     0     1     4     4
#> 2  21       6   160   110  3.9   2.88  17.0     0     1     4     4
#> 3  22.8     4   108    93  3.85  2.32  18.6     1     1     4     1
#> 4  21.4     6   258   110  3.08  3.22  19.4     1     0     3     1
#> 5  18.7     8   360   175  3.15  3.44  17.0     0     0     3     2

reprex package (v0.3.0) 于 2019 年 12 月 7 日创建

【讨论】:

  • 很酷的解决方案。问题是它在后台创建了一个新的 row_id 列,这可能很昂贵并且会导致开销。有解决办法吗?
  • 你能量化开销吗?另一种方法是收集max(rows) 行并在本地进行切片,在某些情况下可能会更快。
  • 我没有量化开销,但实际上它似乎不是很多,在大多数情况下仍然比在本地做更好。感谢您的反馈
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-09
  • 2021-03-14
相关资源
最近更新 更多