@@ -9,14 +9,44 @@ generate_slice <- function(.data, dots) {
99 inx
1010}
1111
12- # ' Choose rows by their position
12+ # ' Choose ranges by their position
13+ # '
14+ # ' @description
15+ # ' The `slice` family of functions let you select ranges by position. They
16+ # ' follow the same conventions as their dplyr counterparts for data frames:
17+ # '
18+ # ' * `slice()` selects ranges by explicit integer position.
19+ # ' * `slice_head()` / `slice_tail()` select the first or last ranges.
20+ # ' * `slice_sample()` randomly selects ranges.
21+ # ' * `slice_min()` / `slice_max()` select ranges with the smallest or largest
22+ # ' values of a metadata column.
23+ # '
24+ # ' All functions preserve grouping: when `.data` has been grouped with
25+ # ' [group_by()], selection is performed within each group.
26+ # '
27+ # ' @param .data a `Ranges` object.
28+ # ' @param ... <[`data-masking`][rlang::args_data_masking]> Integer row values
29+ # ' indicating positions to keep. Only used by `slice()`; ignored elsewhere.
30+ # ' @param n Number of ranges to select. Mutually exclusive with `prop`.
31+ # ' For `slice_min()` and `slice_max()`, negative values are not supported.
32+ # ' @param prop Fraction of ranges to select, in `(0, 1]`. Mutually exclusive
33+ # ' with `n`.
34+ # ' @param order_by <[`data-masking`][rlang::args_data_masking]> Unquoted name
35+ # ' of a numeric metadata column to order by. Used by `slice_min()` and
36+ # ' `slice_max()`.
37+ # ' @param with_ties If `TRUE` (the default), all ranges tied at the boundary
38+ # ' rank are kept, which may return more than `n` ranges. Set to `FALSE` to
39+ # ' return exactly `n`. Used by `slice_min()` and `slice_max()`.
40+ # ' @param weight_by <[`data-masking`][rlang::args_data_masking]> Unquoted name
41+ # ' of a non-negative numeric metadata column used as sampling weights. Used
42+ # ' by `slice_sample()`.
43+ # ' @param replace Should sampling be with replacement? Default `FALSE`. Used
44+ # ' by `slice_sample()`.
45+ # ' @param .preserve Ignored; retained for compatibility with the dplyr generic.
46+ # '
47+ # ' @return A `GRanges` (or `IRanges`) object of the same class as `.data`, with
48+ # ' grouping structure re-applied when `.data` was grouped.
1349# '
14- # ' @param .data a `Ranges` object
15- # ' @param ... Integer row values indicating rows to keep. If `.data` has
16- # ' been grouped via [group_by.GenomicRanges()], then the positions are selected within each group.
17- # ' @param .preserve when FALSE (the default) the grouping structure is
18- # ' recomputed, otherwise it is kept as is. Currently ignored.
19- # ' @return a GRanges object
2050# ' @method slice Ranges
2151# ' @importFrom dplyr slice
2252# ' @rdname slice-ranges
@@ -97,5 +127,292 @@ check_length <- function(length, n, replace = FALSE) {
97127 )
98128}
99129
130+ # the following generated by Claude Sonnet 4.6 as part of adding new slice functionality
131+ # 2026-03-18
132+
133+ # ── slice_head() methods ───────────────────────────────────────────────────────
134+
135+ # ' @rdname slice-ranges
136+ # ' @importFrom dplyr slice_head
137+ # ' @method slice_head Ranges
138+ # ' @export
139+ slice_head.Ranges <- function (.data , ... , n = NULL , prop = NULL ,
140+ .preserve = FALSE ) {
141+ inx <- head_tail_index(length(.data ), n = n , prop = prop , from_head = TRUE )
142+ .data [inx , ]
143+ }
144+
145+ # ' @rdname slice-ranges
146+ # ' @method slice_head GroupedGenomicRanges
147+ # ' @export
148+ slice_head.GroupedGenomicRanges <- function (.data , ... , n = NULL , prop = NULL ,
149+ .preserve = FALSE ) {
150+ apply_grouped_slice(
151+ .data ,
152+ function (grp_size ) head_tail_index(grp_size , n = n , prop = prop ,
153+ from_head = TRUE )
154+ )
155+ }
156+
157+ # ' @rdname slice-ranges
158+ # ' @method slice_head GroupedIntegerRanges
159+ # ' @export
160+ slice_head.GroupedIntegerRanges <- slice_head.GroupedGenomicRanges
161+
162+
163+ # ── slice_tail() methods ───────────────────────────────────────────────────────
164+
165+ # ' @rdname slice-ranges
166+ # ' @importFrom dplyr slice_tail
167+ # ' @method slice_tail Ranges
168+ # ' @export
169+ slice_tail.Ranges <- function (.data , ... , n = NULL , prop = NULL ,
170+ .preserve = FALSE ) {
171+ inx <- head_tail_index(length(.data ), n = n , prop = prop , from_head = FALSE )
172+ .data [inx , ]
173+ }
174+
175+ # ' @rdname slice-ranges
176+ # ' @method slice_tail GroupedGenomicRanges
177+ # ' @export
178+ slice_tail.GroupedGenomicRanges <- function (.data , ... , n = NULL , prop = NULL ,
179+ .preserve = FALSE ) {
180+ apply_grouped_slice(
181+ .data ,
182+ function (grp_size ) head_tail_index(grp_size , n = n , prop = prop ,
183+ from_head = FALSE )
184+ )
185+ }
186+
187+ # ' @rdname slice-ranges
188+ # ' @method slice_tail GroupedIntegerRanges
189+ # ' @export
190+ slice_tail.GroupedIntegerRanges <- slice_tail.GroupedGenomicRanges
191+
192+
193+ # ── slice_sample() methods ─────────────────────────────────────────────────────
194+
195+ # ' @rdname slice-ranges
196+ # ' @importFrom dplyr slice_sample
197+ # ' @method slice_sample Ranges
198+ # ' @export
199+ slice_sample.Ranges <- function (.data , ... , n = NULL , prop = NULL ,
200+ weight_by = NULL , replace = FALSE ,
201+ .preserve = FALSE ) {
202+ total <- length(.data )
203+ size <- resolve_n_prop(total , n = n , prop = prop , replace = replace )
204+ weights <- eval_weight_by(.data , rlang :: enquo(weight_by ))
205+ inx <- sample.int(total , size , replace = replace , prob = weights )
206+ .data [inx , ]
207+ }
208+
209+ # ' @rdname slice-ranges
210+ # ' @method slice_sample GroupedGenomicRanges
211+ # ' @export
212+ slice_sample.GroupedGenomicRanges <- function (.data , ... , n = NULL ,
213+ prop = NULL , weight_by = NULL ,
214+ replace = FALSE ,
215+ .preserve = FALSE ) {
216+ weight_quo <- rlang :: enquo(weight_by )
217+ rng <- .data @ delegate
218+ grp_rows <- .group_rows(.data )
100219
220+ selected <- lapply(grp_rows , function (i ) {
221+ sub <- rng [i ]
222+ size <- resolve_n_prop(length(i ), n = n , prop = prop , replace = replace )
223+ w <- eval_weight_by(sub , weight_quo )
224+ local_inx <- sample.int(length(i ), size , replace = replace , prob = w )
225+ i [local_inx ]
226+ })
101227
228+ rng_out <- rng [sort(unlist(selected ))]
229+ group_by(rng_out , !!! groups(.data ))
230+ }
231+
232+ # ' @rdname slice-ranges
233+ # ' @method slice_sample GroupedIntegerRanges
234+ # ' @export
235+ slice_sample.GroupedIntegerRanges <- slice_sample.GroupedGenomicRanges
236+
237+
238+ # ── slice_min() methods ────────────────────────────────────────────────────────
239+
240+ # ' @rdname slice-ranges
241+ # ' @importFrom dplyr slice_min
242+ # ' @method slice_min Ranges
243+ # ' @export
244+ slice_min.Ranges <- function (.data , order_by , n = NULL , prop = NULL ,
245+ with_ties = TRUE , .preserve = FALSE ) {
246+ order_quo <- rlang :: enquo(order_by )
247+ inx <- minmax_index(.data , order_quo , n = n , prop = prop ,
248+ with_ties = with_ties , decreasing = FALSE )
249+ .data [inx , ]
250+ }
251+
252+ # ' @rdname slice-ranges
253+ # ' @method slice_min GroupedGenomicRanges
254+ # ' @export
255+ slice_min.GroupedGenomicRanges <- function (.data , order_by , n = NULL ,
256+ prop = NULL , with_ties = TRUE ,
257+ .preserve = FALSE ) {
258+ order_quo <- rlang :: enquo(order_by )
259+ apply_grouped_minmax(.data , order_quo , n = n , prop = prop ,
260+ with_ties = with_ties , decreasing = FALSE )
261+ }
262+
263+ # ' @rdname slice-ranges
264+ # ' @method slice_min GroupedIntegerRanges
265+ # ' @export
266+ slice_min.GroupedIntegerRanges <- slice_min.GroupedGenomicRanges
267+
268+
269+ # ── slice_max() methods ────────────────────────────────────────────────────────
270+
271+ # ' @rdname slice-ranges
272+ # ' @importFrom dplyr slice_max
273+ # ' @method slice_max Ranges
274+ # ' @export
275+ slice_max.Ranges <- function (.data , order_by , n = NULL , prop = NULL ,
276+ with_ties = TRUE , .preserve = FALSE ) {
277+ order_quo <- rlang :: enquo(order_by )
278+ inx <- minmax_index(.data , order_quo , n = n , prop = prop ,
279+ with_ties = with_ties , decreasing = TRUE )
280+ .data [inx , ]
281+ }
282+
283+ # ' @rdname slice-ranges
284+ # ' @method slice_max GroupedGenomicRanges
285+ # ' @export
286+ slice_max.GroupedGenomicRanges <- function (.data , order_by , n = NULL ,
287+ prop = NULL , with_ties = TRUE ,
288+ .preserve = FALSE ) {
289+ order_quo <- rlang :: enquo(order_by )
290+ apply_grouped_minmax(.data , order_quo , n = n , prop = prop ,
291+ with_ties = with_ties , decreasing = TRUE )
292+ }
293+
294+ # ' @rdname slice-ranges
295+ # ' @method slice_max GroupedIntegerRanges
296+ # ' @export
297+ slice_max.GroupedIntegerRanges <- slice_max.GroupedGenomicRanges
298+
299+
300+ # ── Internal helpers ───────────────────────────────────────────────────────────
301+
302+ generate_slice <- function (.data , dots ) {
303+ os <- overscope_ranges(.data )
304+ inx <- overscope_eval_update(os , dots , bind_envir = FALSE )
305+ check_num <- vapply(inx , is.numeric , logical (1 ))
306+ if (any(! check_num )) {
307+ stop(" slice condition does not evaluate to an integer or numeric vector" )
308+ }
309+ inx
310+ }
311+
312+ # Resolve n / prop to a concrete integer size.
313+ resolve_n_prop <- function (total , n = NULL , prop = NULL , replace = FALSE ) {
314+ if (! is.null(n ) && ! is.null(prop )) {
315+ stop(" Exactly one of `n` or `prop` must be supplied, not both." )
316+ }
317+ if (! is.null(prop )) {
318+ if (prop < = 0 || prop > 1 ) stop(" `prop` must be in (0, 1]." )
319+ n <- max(1L , floor(prop * total ))
320+ }
321+ if (is.null(n )) n <- 1L # default to n = 1
322+ n <- as.integer(n )
323+ if (! replace && n > total ) {
324+ stop(paste(" `n` (" , n , " ) must be <= length of .data (" , total , " )." ,
325+ " Set `replace = TRUE` to use sampling with replacement." ))
326+ }
327+ n
328+ }
329+
330+ # Build a positional index for head / tail selection.
331+ head_tail_index <- function (total , n = NULL , prop = NULL , from_head ) {
332+ size <- resolve_n_prop(total , n = n , prop = prop )
333+ if (from_head ) seq_len(size ) else seq.int(total - size + 1L , total )
334+ }
335+
336+ # Evaluate an optional weight_by quosure against .data's mcols.
337+ eval_weight_by <- function (.data , weight_quo ) {
338+ if (rlang :: quo_is_null(weight_quo )) return (NULL )
339+ col_name <- rlang :: as_name(weight_quo )
340+ w <- mcols(.data )[[col_name ]]
341+ if (is.null(w )) stop(paste0(" Column '" , col_name , " ' not found in .data." ))
342+ if (any(w < 0 , na.rm = TRUE )) stop(" `weight_by` values must be non-negative." )
343+ w
344+ }
345+
346+ # Shared guard used by slice_min / slice_max.
347+ check_column_numeric <- function (x , col ) {
348+ if (! is.numeric(x )) {
349+ stop(paste0(" `" , col , " ` must be a numeric column for slice_min / slice_max" ))
350+ }
351+ }
352+
353+ # Build an ordering index for slice_min / slice_max.
354+ minmax_index <- function (.data , order_quo , n = NULL , prop = NULL , with_ties ,
355+ decreasing ) {
356+ col_name <- rlang :: as_name(order_quo )
357+ vals <- mcols(.data )[[col_name ]]
358+ if (is.null(vals )) stop(paste0(" Column '" , col_name , " ' not found in .data." ))
359+ check_column_numeric(vals , col_name )
360+
361+ total <- length(.data )
362+ size <- resolve_n_prop(total , n = n , prop = prop )
363+ ord <- order(vals , decreasing = decreasing , na.last = NA )
364+
365+ if (with_ties ) {
366+ threshold <- vals [ord [min(size , length(ord ))]]
367+ keep <- if (decreasing ) which(vals > = threshold ) else which(vals < = threshold )
368+ sort(keep )
369+ } else {
370+ sort(ord [seq_len(min(size , length(ord )))])
371+ }
372+ }
373+
374+ # Apply a grouped slice using a per-group index function.
375+ # `index_fn` receives the group size and returns *local* indices.
376+ apply_grouped_slice <- function (.data , index_fn ) {
377+ rng <- .data @ delegate
378+ grp_rows <- .group_rows(.data )
379+
380+ selected <- lapply(grp_rows , function (i ) {
381+ local_inx <- index_fn(length(i ))
382+ local_inx <- local_inx [local_inx > = 1L & local_inx < = length(i )]
383+ i [local_inx ]
384+ })
385+
386+ rng_out <- rng [sort(unlist(selected ))]
387+ group_by(rng_out , !!! groups(.data ))
388+ }
389+
390+ # Grouped min / max helper.
391+ apply_grouped_minmax <- function (.data , order_quo , n = NULL , prop = NULL ,
392+ with_ties , decreasing ) {
393+ col_name <- rlang :: as_name(order_quo )
394+ rng <- .data @ delegate
395+ grp_rows <- .group_rows(.data )
396+
397+ selected <- lapply(grp_rows , function (i ) {
398+ sub <- rng [i ]
399+ vals <- mcols(sub )[[col_name ]]
400+ if (is.null(vals )) stop(paste0(" Column '" , col_name , " ' not found." ))
401+ check_column_numeric(vals , col_name )
402+
403+ size <- resolve_n_prop(length(i ), n = n , prop = prop )
404+ ord <- order(vals , decreasing = decreasing , na.last = NA )
405+
406+ local_inx <- if (with_ties ) {
407+ threshold <- vals [ord [min(size , length(ord ))]]
408+ if (decreasing ) which(vals > = threshold ) else which(vals < = threshold )
409+ } else {
410+ ord [seq_len(min(size , length(ord )))]
411+ }
412+
413+ i [sort(local_inx )]
414+ })
415+
416+ rng_out <- rng [sort(unlist(selected ))]
417+ group_by(rng_out , !!! groups(.data ))
418+ }
0 commit comments