-
Notifications
You must be signed in to change notification settings - Fork 118
duckplyr 1.0.0 #724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
duckplyr 1.0.0 #724
Changes from 9 commits
96861b7
5b2b472
97739a8
1d80f56
11e21c1
eb09f2b
44b7c66
20b6a6e
4ccfa70
692b390
0df0d53
4251569
e30f959
1cec2cb
b276db4
7c92ddb
d8aca67
480551b
6adf074
c955826
1c5153f
78ee84b
1b55a30
ae87276
ae8c8e6
7661ad3
48eba60
8b6d0bd
e25fb07
a0b9b39
b90e8af
5ac6f6e
14ec2f6
b9a277a
5762c0a
6aaf953
f847736
c78073f
1f898c0
5a1f22c
2b4b421
d97b031
4be5ea9
f344b9f
a13315a
ad9825f
a734638
fc8122d
3211710
eea955a
4a20ca3
f5e4a38
20dff03
f231c68
49b4f8b
6b84b25
452d5f2
21c2b74
a21daa5
9022d7f
f0563c0
88846a3
17cdfc3
9e0e496
a5ac341
36a93cb
4f88bbd
ad8866a
beee540
5fe00ac
a186621
a994038
ac86b3a
4d6fa0d
0582a15
735e9d9
217144a
dc07389
663eda2
dd9d20d
0008ac8
ea3fda0
9128e16
eaee543
931fd45
5bb1a84
78729ac
0ef4ace
0f23a74
236d793
5bc98a6
cf61e47
bfac020
d0ed8f9
51931cb
1ddb0e1
85b98cd
58997b1
29f8d0c
af72705
9fbb466
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| --- | ||
| output: hugodown::hugo_document | ||
|
|
||
| slug: duckplyr-1-0-0 | ||
| title: duckplyr fully joins the tidyverse! | ||
| date: 2025-02-11 | ||
| author: Kirill Müller and Maëlle Salmon | ||
| description: > | ||
| duckplyr 1.0.0 is on CRAN and part of the tidyverse! duckplyr is a drop-in | ||
| replacement for dplyr, powered by DuckDB for speed. | ||
|
|
||
| photo: | ||
| url: https://www.pexels.com/photo/a-mallard-duck-on-water-6918877/ | ||
| author: Kiril Gruev | ||
|
|
||
| # one of: "deep-dive", "learn", "package", "programming", "roundup", or "other" | ||
| categories: [package] | ||
| tags: | ||
| - duckplyr | ||
| - dplyr | ||
| - tidyverse | ||
| --- | ||
|
|
||
| <!-- | ||
| TODO: | ||
| * [x] Look over / edit the post's title in the yaml | ||
| * [x] Edit (or delete) the description; note this appears in the Twitter card | ||
| * [x] Pick category and tags (see existing with `hugodown::tidy_show_meta()`) | ||
| * [x] Find photo & update yaml metadata | ||
| * [ ] Create `thumbnail-sq.jpg`; height and width should be equal | ||
| * [ ] Create `thumbnail-wd.jpg`; width should be >5x height | ||
| * [ ] `hugodown::use_tidy_thumbnails()` | ||
| * [x] Add intro sentence, e.g. the standard tagline for the package | ||
| * [x] `usethis::use_tidy_thanks()` | ||
| --> | ||
|
|
||
| We're very chuffed to announce the release of [duckplyr](https://duckplyr.tidyverse.org) 1.0.0. | ||
| duckplyr is a drop-in replacement for dplyr, powered by [DuckDB](https://duckdb.org/) for speed. | ||
| It joins the rank of dplyr backends together with [dtplyr](https://dtplyr.tidyverse.org) and [dbplyr](https://dbplyr.tidyverse.org). | ||
|
|
||
| You can install it from CRAN with: | ||
|
|
||
| ```{r, eval = FALSE} | ||
| install.packages("duckplyr") | ||
| ``` | ||
|
|
||
| In this article, we'll introduce you to the basic concepts behind duckplyr, show how it can help you handle normal sized but also large data, and explain how you can help improve the package. | ||
|
|
||
| ## A drop-in replacement for dplyr | ||
|
|
||
| The duckplyr package is a _drop-in replacement for dplyr_ that uses _DuckDB for speed_. | ||
|
||
| You can simply _drop_ duckplyr into your pipeline by loading it, then computations will be efficiently carried out by DuckDB. | ||
|
|
||
| ```{r} | ||
| library(conflicted) | ||
| library(duckplyr) | ||
| conflict_prefer("filter", "dplyr", quiet = TRUE) | ||
| library("babynames") | ||
|
|
||
|
|
||
| out <- babynames |> | ||
| filter(n > 1000) |> | ||
| summarize( | ||
| .by = c(sex, year), | ||
| babies_n = sum(n) | ||
| ) |> | ||
| filter(sex == "F") | ||
| class(out) | ||
|
|
||
| ``` | ||
|
|
||
| The very tagline of duckplyr, being a drop-in replacement for dplyr that uses DuckDB for speed, creates a tension: | ||
|
|
||
| - When using dplyr, we are not used to explicitly collect results, we simply access them: the data.frames are "eager" by default. | ||
| Adding a `collect()` step by default would confuse users and make "drop-in replacement" an exaggeration. | ||
| The collection of results, called materialization, has to be automatic by default. | ||
| Therefore, _duckplyr needs eagerness_! | ||
|
|
||
| - The whole advantage of using DuckDB under the hood is letting DuckDB optimize computations, like dtplyr does with data.table. | ||
| _Therefore, duckplyr needs laziness_! | ||
|
|
||
| As a consequence, duckplyr is lazy on the inside for all DuckDB operations but eager on the outside, thanks to [ALTREP](https://duckdb.org/2024/04/02/duckplyr.html#eager-vs-lazy-materialization), a powerful R feature that among other things supports *deferred evaluation*. | ||
|
|
||
| > "ALTREP allows R objects to have different in-memory representations, and for custom code to be executed whenever those objects are accessed." Hannes Mühleisen. | ||
|
|
||
| If the duckplyr data.frame is accessed by... | ||
|
|
||
| - duckplyr, then the operations continue to be lazy (until a call to `collect.duckplyr_df()` for instance). | ||
| - not duckplyr (say, ggplot2, or `nrow()`), then a special callback is executed, allowing materialization of the data frame. | ||
|
|
||
| Therefore, duckplyr can be both *lazy* (within itself) and *not lazy* (for the outside world). | ||
|
|
||
| Now, the default automatic materialization can be problematic if dealing with large data: what if the materialization eats up all memory? | ||
| Therefore, the duckplyr package has a safeguard called `prudence` with three levels. | ||
|
|
||
| - `"lavish"`: automatically materialize _regardless of size_, | ||
|
|
||
| ```{r} | ||
| out <- babynames |> | ||
| duckdb_tibble(prudence = "lavish") |> # default value of prudence :-) | ||
maelle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| filter(n > 1000) |> | ||
| summarize( | ||
| .by = c(sex, year), | ||
| babies_n = sum(n) | ||
| ) |> | ||
| filter(sex == "F") | ||
|
|
||
| class(out) | ||
| nrow(out) | ||
| ``` | ||
|
|
||
| - `"stingy"`: _never_ automatically materialize, | ||
|
|
||
| ```{r, error = TRUE} | ||
| stingy <- babynames |> | ||
| duckdb_tibble(prudence = "stingy") |> # like the famous duck Uncle Scrooge :-) | ||
| filter(n > 1500) |> | ||
| summarize( | ||
| .by = c(sex, year), | ||
| babies_n = sum(n) | ||
| ) |> | ||
| filter(sex == "F") | ||
|
|
||
| class(stingy) | ||
| nrow(stingy) | ||
| ``` | ||
|
|
||
| - `"thrifty"`: automatically materialize _up to a maximum size of 1 million cells_. | ||
|
|
||
| ```{r, error = TRUE} | ||
| thrifty <- babynames |> | ||
| duckdb_tibble(prudence = "stingy") |> | ||
| filter(n > 1000) |> | ||
| summarize( | ||
| .by = c(sex, year), | ||
| babies_n = sum(n) | ||
| ) |> | ||
| filter(sex == "F") | ||
|
|
||
| class(thrifty) | ||
| nrow(thrifty) | ||
| ``` | ||
|
|
||
| By default, duckplyr data frames are _lavish_, but duckplyr data frames created from Parquet data (presumedly large) are _thrifty_. | ||
|
|
||
| ## How to use duckplyr | ||
|
|
||
| To _replace_ dplyr with duckplyr, you can either | ||
|
|
||
| - load duckplyr and then keep your pipeline as is. Calling `library(duckplyr)` overwrites dplyr methods, enabling duckplyr for the entire session no matter how data.frames are created. | ||
|
|
||
| ```{r} | ||
| library(conflicted) | ||
| library(duckplyr) | ||
| conflict_prefer("filter", "dplyr", quiet = TRUE) | ||
| ``` | ||
|
|
||
| - Create individual "duck frames" which allows you to control their automatic materialization parameters. To do so, you can use _conversion functions_ like `duckdb_tibble()` or `as_duckdb_tibble()`, or _ingestion functions_ like `read_csv_duckdb()`. | ||
|
|
||
| Then, the data manipulation pipeline uses the exact same syntax as a dplyr pipeline. | ||
| The duckplyr package performs the computation using DuckDB. | ||
|
|
||
| ```{r} | ||
| library("babynames") | ||
| out <- babynames |> | ||
| filter(n > 1000) |> | ||
| summarize( | ||
| .by = c(sex, year), | ||
| babies_n = sum(n) | ||
| ) |> | ||
| filter(sex == "F") | ||
| ``` | ||
|
|
||
| The result can finally be materialized to memory, or computed temporarily, or computed to a file. | ||
|
|
||
| ```{r} | ||
| # to memory | ||
| out | ||
|
|
||
| # to a file | ||
| csv_file <- withr::local_tempfile() | ||
| file.size(csv_file) | ||
| compute_csv(out, csv_file) | ||
| file.size(csv_file) | ||
| ``` | ||
|
|
||
| When duckplyr itself does not support specific functionality, it falls back to dplyr. | ||
| For instance, row names are not supported yet: | ||
|
|
||
| ```{r} | ||
| mtcars |> | ||
| summarize( | ||
| .by = cyl, | ||
| disp = mean(disp, na.rm = TRUE), | ||
| sd = sd(disp, na.rm = TRUE) | ||
| ) | ||
| ``` | ||
|
|
||
| Current limitations are documented in a vignette. | ||
| You can change the verbosity of fallbacks, refer to [`duckplyr::fallback_sitrep()`](https://duckplyr.tidyverse.org/reference/fallback.html). | ||
|
|
||
| ### For large data | ||
|
|
||
| For large data, duckplyr is a worthy alternative to dtplyr and dbplyr. | ||
|
|
||
| With large datasets, you want: | ||
|
|
||
| - input data in an efficient format, like Parquet files. Therefore you might input data using `read_parquet_duckdb()`. | ||
| - efficient computation, which duckplyr provides via DuckDB's holistic optimization, without your having to use another syntax than dplyr. | ||
| - the output to not clutter all the memory. Therefore you can make use of these features: | ||
| - the `prudence` parameter, to disable automatic materialization completely or to disable automatic materialization up to a certain output size. | ||
| - computation to files using `compute_parquet()` or `compute_csv()`. | ||
|
|
||
| A drawback of analyzing large data with duckplyr is that the limitations of duckplyr won't be compensated by fallbacks since fallbacks to dplyr necessitate putting data into memory. | ||
| Therefore, if your pipeline encounters fallbacks, you might want to workaround them by converting the duck frame into a table through `compute()` then running SQL code through the experimental `read_sql_duckdb()` function. | ||
|
|
||
| ```{r} | ||
| data <- | ||
| duckdb_tibble(a = 2) |> | ||
| mutate(b = 3) | ||
|
|
||
| computed_data <- | ||
| data |> | ||
| compute(name = "computed_data") | ||
|
|
||
| sql_data <- | ||
| read_sql_duckdb("SELECT *, a * b AS c FROM computed_data") | ||
|
|
||
| sql_data | ||
| ``` | ||
|
|
||
| ## Help us improve duckplyr! | ||
|
|
||
| Our goals for future development of duckplyr include: | ||
|
|
||
| - Enabling users to provide [custom translations](https://github.com/tidyverse/duckplyr/issues/158) of dplyr functionality; | ||
| - Making it easier to contribute code to duckplyr. | ||
|
|
||
| You can already help though, in three main ways: | ||
|
|
||
| - Please report any issue especially regarding unknown incompabilities. See [`vignette("limits")`](https://duckplyr.tidyverse.org/articles/limits.html). | ||
| - Contribute to the codebase after reading duckplyr's [contributing guide](https://duckplyr.tidyverse.org/CONTRIBUTING.html). | ||
| - Turn on telemetry to help us hear about the most frequent fallbacks so we can prioritize working on the corresponding missing dplyr translation. See [`vignette("telemetry")`](https://duckplyr.tidyverse.org/articles/telemetry.html) and the [`duckplyr::fallback_sitrep()`](https://duckplyr.tidyverse.org/reference/fallback.html) function. | ||
|
|
||
| ## Acknowledgements | ||
|
|
||
| A big thanks to all 54 folks who filed issues, created PRs and generally helped to improve duckplyr! | ||
|
|
||
| [@adamschwing](https://github.com/adamschwing), [@andreranza](https://github.com/andreranza), [@apalacio9502](https://github.com/apalacio9502), [@apsteinmetz](https://github.com/apsteinmetz), [@barracuda156](https://github.com/barracuda156), [@beniaminogreen](https://github.com/beniaminogreen), [@bob-rietveld](https://github.com/bob-rietveld), [@brichards920](https://github.com/brichards920), [@cboettig](https://github.com/cboettig), [@davidjayjackson](https://github.com/davidjayjackson), [@DavisVaughan](https://github.com/DavisVaughan), [@Ed2uiz](https://github.com/Ed2uiz), [@eitsupi](https://github.com/eitsupi), [@era127](https://github.com/era127), [@etiennebacher](https://github.com/etiennebacher), [@eutwt](https://github.com/eutwt), [@fmichonneau](https://github.com/fmichonneau), [@github-actions[bot]](https://github.com/github-actions[bot]), [@hadley](https://github.com/hadley), [@hannes](https://github.com/hannes), [@hawkfish](https://github.com/hawkfish), [@IndrajeetPatil](https://github.com/IndrajeetPatil), [@JanSulavik](https://github.com/JanSulavik), [@JavOrraca](https://github.com/JavOrraca), [@jeroen](https://github.com/jeroen), [@jhk0530](https://github.com/jhk0530), [@joakimlinde](https://github.com/joakimlinde), [@JosiahParry](https://github.com/JosiahParry), [@krlmlr](https://github.com/krlmlr), [@larry77](https://github.com/larry77), [@lnkuiper](https://github.com/lnkuiper), [@lorenzwalthert](https://github.com/lorenzwalthert), [@luisDVA](https://github.com/luisDVA), [@maelle](https://github.com/maelle), [@math-mcshane](https://github.com/math-mcshane), [@meersel](https://github.com/meersel), [@multimeric](https://github.com/multimeric), [@mytarmail](https://github.com/mytarmail), [@nicki-dese](https://github.com/nicki-dese), [@PMassicotte](https://github.com/PMassicotte), [@prasundutta87](https://github.com/prasundutta87), [@rafapereirabr](https://github.com/rafapereirabr), [@Robinlovelace](https://github.com/Robinlovelace), [@romainfrancois](https://github.com/romainfrancois), [@sparrow925](https://github.com/sparrow925), [@stefanlinner](https://github.com/stefanlinner), [@thomasp85](https://github.com/thomasp85), [@TimTaylor](https://github.com/TimTaylor), [@Tmonster](https://github.com/Tmonster), [@toppyy](https://github.com/toppyy), [@wibeasley](https://github.com/wibeasley), [@yjunechoe](https://github.com/yjunechoe), [@ywhcuhk](https://github.com/ywhcuhk), and [@zhjx19](https://github.com/zhjx19). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With tidyverse/tidyverse#346, we can also
install.packages("tidyverse").There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but won't the post be published before the PR is merged and the tidyverse package is released on CRAN?