Skip to content

Commit 53d0064

Browse files
authoredMar 9, 2025··
feat: Implement slice_head() (#640)
1 parent 2b69718 commit 53d0064

12 files changed

+206
-14
lines changed
 

‎R/head.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ head.duckplyr_df <- function(x, n = 6L, ...) {
99
#' - with a negative `n`.
1010
#'
1111
#' These features fall back to [head()], see `vignette("fallback")` for details.
12-
"Can't process negative n" = (n < 0),
12+
"{.code slice_head(n = ...)} with negative values not supported" = (n < 0),
1313
{
1414
rel <- duckdb_rel_from_df(x)
1515
out_rel <- rel_limit(rel, n)

‎R/relational.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ rel_try <- function(call, rel, ...) {
2727

2828
if (Sys.getenv("DUCKPLYR_FALLBACK_FORCE") == "TRUE") {
2929
stats$fallback <- stats$fallback + 1L
30-
return()
30+
return("Fallback enforced")
3131
}
3232

3333
dots <- list(...)

‎R/slice_head-rd.R

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#' @title Subset rows using their positions
2+
#'
3+
#' @description This is a method for the [dplyr::slice_head()] generic.
4+
#' `slice_head()` selects the first rows.
5+
#'
6+
#' @inheritParams dplyr::slice_head
7+
#' @examples
8+
#' library(duckplyr)
9+
#' df <- data.frame(x = 1:3)
10+
#' df <- slice_head(df, n = 2)
11+
#' df
12+
#' @seealso [dplyr::slice_head()]
13+
#' @rdname slice_head.duckplyr_df
14+
#' @name slice_head.duckplyr_df
15+
NULL

‎R/slice_head.R

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
# Generated by 02-duckplyr_df-methods.R
2+
#' @rdname slice_head.duckplyr_df
23
#' @export
34
slice_head.duckplyr_df <- function(.data, ..., n, prop, by = NULL) {
5+
if (!missing(n)) {
6+
n_valid <- (n >= 0)
7+
} else {
8+
n_valid <- TRUE
9+
}
10+
411
# Our implementation
512
duckplyr_error <- rel_try(NULL,
6-
"No relational implementation for {.code slice_head()}" = TRUE,
13+
#' @section Fallbacks:
14+
#' There is no DuckDB translation in `slice_head.duckplyr_df()`
15+
#' - if `by` or `prop` is provided,
16+
#' - with a negative `n`.
17+
#'
18+
#' These features fall back to [slice_head()], see `vignette("fallback")` for details.
19+
"{.code slice_head(by = ...)} not supported" = !missing(by),
20+
"{.code slice_head(prop = ...)} not supported" = !missing(prop),
21+
"{.code slice_head(n = ...)} with negative values not supported" = !n_valid,
722
{
23+
rel <- duckdb_rel_from_df(.data)
24+
out_rel <- rel_limit(rel, n)
25+
out <- duckplyr_reconstruct(out_rel, .data)
826
return(out)
927
}
1028
)

‎R/tpch.R

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ tpch_02 <- function() {
7777
s_address, s_phone, s_comment
7878
) %>%
7979
arrange(desc(s_acctbal), n_name, s_name, p_partkey) %>%
80-
head(100)
80+
slice_head(n = 100)
8181

8282
res
8383
}
@@ -109,7 +109,7 @@ tpch_03 <- function() {
109109
summarise(revenue = sum(na.rm = TRUE, volume), .by = c(l_orderkey, o_orderdate, o_shippriority)) %>%
110110
select(l_orderkey, revenue, o_orderdate, o_shippriority) %>%
111111
arrange(desc(revenue), o_orderdate) %>%
112-
head(10)
112+
slice_head(n = 10)
113113
aggr
114114
}
115115

@@ -450,7 +450,7 @@ tpch_10 <- function() {
450450
c_address, c_phone, c_comment
451451
) %>%
452452
arrange(desc(revenue)) %>%
453-
head(20)
453+
slice_head(n = 20)
454454

455455
res
456456
}

‎R/tpch2.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ tpch_18 <- function() {
186186
o_orderdate, o_totalprice, sum
187187
) %>%
188188
arrange(desc(o_totalprice), o_orderdate) %>%
189-
head(100)
189+
slice_head(n = 100)
190190
}
191191

192192
#' @autoglobal
@@ -305,7 +305,7 @@ tpch_21 <- function() {
305305
filter(n_name == "SAUDI ARABIA") %>%
306306
summarise(numwait = n(), .by = s_name) %>%
307307
arrange(desc(numwait), s_name) %>%
308-
head(100)
308+
slice_head(n = 100)
309309
}
310310

311311
#' @autoglobal

‎_pkgdown.yml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ reference:
4949
- arrange.duckplyr_df
5050
- distinct.duckplyr_df
5151
- filter.duckplyr_df
52+
- slice_head.duckplyr_df
5253
- head.duckplyr_df
5354

5455
- subtitle: Verbs that affect columns

‎man/slice_head.duckplyr_df.Rd

+63
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎patch/slice_head.patch

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
diff --git b/R/slice_head.R a/R/slice_head.R
2+
--- b/R/slice_head.R
3+
+++ a/R/slice_head.R
4+
@@ -1,10 +1,28 @@
5+
# Generated by 02-duckplyr_df-methods.R
6+
+#' @rdname slice_head.duckplyr_df
7+
#' @export
8+
slice_head.duckplyr_df <- function(.data, ..., n, prop, by = NULL) {
9+
+ if (!missing(n)) {
10+
+ n_valid <- (n >= 0)
11+
+ } else {
12+
+ n_valid <- TRUE
13+
+ }
14+
+
15+
# Our implementation
16+
duckplyr_error <- rel_try(NULL,
17+
- "No relational implementation for {.code slice_head()}" = TRUE,
18+
+ #' @section Fallbacks:
19+
+ #' There is no DuckDB translation in `slice_head.duckplyr_df()`
20+
+ #' - if `by` or `prop` is provided,
21+
+ #' - with a negative `n`.
22+
+ #'
23+
+ #' These features fall back to [slice_head()], see `vignette("fallback")` for details.
24+
+ "{.code slice_head(by = ...)} not supported" = !is.null(by),
25+
+ "{.code slice_head(prop = ...)} not supported" = !missing(prop),
26+
+ "{.code slice_head(n = ...)} with negative values not supported" = !n_valid,
27+
{
28+
+ rel <- duckdb_rel_from_df(.data)
29+
+ out_rel <- rel_limit(rel, n)
30+
+ out <- duckplyr_reconstruct(out_rel, .data)
31+
return(out)
32+
}
33+
)

‎tests/testthat/test-as_duckplyr_df.R

+16-5
Original file line numberDiff line numberDiff line change
@@ -2346,17 +2346,28 @@ test_that("as_duckplyr_df_impl() and slice()", {
23462346
expect_identical(pre, post)
23472347
})
23482348

2349-
test_that("as_duckplyr_df_impl() and slice_head()", {
2350-
withr::local_envvar(DUCKPLYR_FORCE = "FALSE")
2349+
test_that("as_duckplyr_df_impl() and slice_head(n = 2)", {
2350+
withr::local_envvar(DUCKPLYR_FALLBACK_FORCE = "TRUE")
2351+
2352+
# Data
2353+
test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3))
2354+
2355+
# Run
2356+
pre <- test_df %>% as_duckplyr_df_impl() %>% slice_head(n = 2)
2357+
post <- test_df %>% slice_head(n = 2) %>% as_duckplyr_df_impl()
2358+
2359+
# Compare
2360+
expect_identical(pre, post)
2361+
})
23512362

2352-
skip("External vector?")
23532363

2364+
test_that("as_duckplyr_df_impl() and slice_head(n = 2)", {
23542365
# Data
23552366
test_df <- data.frame(a = 1:6 + 0, b = 2, g = rep(1:3, 1:3))
23562367

23572368
# Run
2358-
pre <- test_df %>% as_duckplyr_df_impl() %>% slice_head()
2359-
post <- test_df %>% slice_head() %>% as_duckplyr_df_impl()
2369+
pre <- test_df %>% as_duckplyr_df_impl() %>% slice_head(n = 2)
2370+
post <- test_df %>% slice_head(n = 2) %>% as_duckplyr_df_impl()
23602371

23612372
# Compare
23622373
expect_identical(pre, post)

‎tests/testthat/test-rel_api.R

+51
Original file line numberDiff line numberDiff line change
@@ -15007,6 +15007,57 @@ test_that("relational setdiff() order-enforcing", {
1500715007
DBI::dbDisconnect(con, shutdown = TRUE)
1500815008
})
1500915009

15010+
# slice_head order-preserving ----------------------------------------------------------
15011+
15012+
test_that("relational slice_head(n = 2) order-preserving", {
15013+
# Autogenerated
15014+
duckdb <- asNamespace("duckdb")
15015+
drv <- duckdb::duckdb()
15016+
con <- DBI::dbConnect(drv)
15017+
experimental <- FALSE
15018+
df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L))
15019+
15020+
"slice_head"
15021+
rel1 <- duckdb$rel_from_df(con, df1, experimental = experimental)
15022+
"slice_head"
15023+
rel2 <- duckdb$rel_limit(rel1, 2)
15024+
rel2
15025+
out <- duckdb$rel_to_altrep(rel2)
15026+
expect_identical(
15027+
out,
15028+
data.frame(a = c(1, 2), b = 2, g = 1:2)
15029+
)
15030+
DBI::dbDisconnect(con, shutdown = TRUE)
15031+
})
15032+
15033+
# slice_head order-enforcing -----------------------------------------------------------
15034+
15035+
test_that("relational slice_head(n = 2) order-enforcing", {
15036+
# Autogenerated
15037+
duckdb <- asNamespace("duckdb")
15038+
drv <- duckdb::duckdb()
15039+
con <- DBI::dbConnect(drv)
15040+
experimental <- FALSE
15041+
df1 <- data.frame(a = seq(1, 6, by = 1), b = 2, g = c(1L, 2L, 2L, 3L, 3L, 3L))
15042+
15043+
"slice_head"
15044+
rel1 <- duckdb$rel_from_df(con, df1, experimental = experimental)
15045+
"slice_head"
15046+
rel2 <- duckdb$rel_limit(rel1, 2)
15047+
"arrange"
15048+
rel3 <- duckdb$rel_order(
15049+
rel2,
15050+
list(duckdb$expr_reference("a"), duckdb$expr_reference("b"), duckdb$expr_reference("g"))
15051+
)
15052+
rel3
15053+
out <- duckdb$rel_to_altrep(rel3)
15054+
expect_identical(
15055+
out,
15056+
data.frame(a = c(1, 2), b = 2, g = 1:2)
15057+
)
15058+
DBI::dbDisconnect(con, shutdown = TRUE)
15059+
})
15060+
1501015061
# summarise order-preserving -----------------------------------------------------------
1501115062

1501215063
test_that("relational summarise(c = mean(a)) order-preserving", {

‎tools/00-funs.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ test_extra_arg_map <- list(
881881
NULL
882882
),
883883
semi_join = "join_by(a)",
884+
slice_head = 'n = 2',
884885
slice_max = 'a',
885886
slice_min = 'a',
886887
summarise = c(
@@ -934,7 +935,6 @@ test_skip_map <- c(
934935
rowwise = "Stack overflow",
935936
sample_frac = "Random seed",
936937
sample_n = "Random seed",
937-
slice_head = "External vector?",
938938
slice_max = "External vector?",
939939
slice_min = "External vector?",
940940
slice_sample = "External vector?",

0 commit comments

Comments
 (0)
Please sign in to comment.