Skip to content

Commit 38dcb8f

Browse files
authored
Merge pull request #947 from tidyverse/f-922-auto-recycle
- `x[i, j] <- one_row_value` avoids explicit recycling of the right-hand side, the recycling happens implicitly in `vctrs::vec_assign()` for performance (#922).
2 parents 38db5d9 + 23e04a9 commit 38dcb8f

File tree

3 files changed

+106
-10
lines changed

3 files changed

+106
-10
lines changed

R/subsetting.R

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ tbl_subassign <- function(x, i, j, value, i_arg, j_arg, value_arg) {
425425
j <- vectbl_as_new_col_index(j, x, j_arg, names2(value), value_arg)
426426
}
427427

428-
value <- vectbl_recycle_rhs(value, fast_nrow(x), length(j), i_arg = NULL, value_arg)
428+
value <- vectbl_recycle_rhs_rows(value, fast_nrow(x), i_arg = NULL, value_arg)
429+
value <- vectbl_recycle_rhs_cols(value, length(j))
429430

430431
xo <- tbl_subassign_col(x, j, value)
431432
} else if (is.null(i_arg)) {
@@ -439,8 +440,7 @@ tbl_subassign <- function(x, i, j, value, i_arg, j_arg, value_arg) {
439440
value <- vectbl_wrap_rhs_row(value, value_arg)
440441

441442
if (is.null(j)) {
442-
value <- vectbl_recycle_rhs(value, length(i), length(x), i_arg, value_arg)
443-
xo <- tbl_subassign_row(x, i, value, value_arg)
443+
xo <- tbl_subassign_row(x, i, value, i_arg, value_arg)
444444
} else {
445445
# Optimization: match only once
446446
# (Invariant: x[[j]] is equivalent to x[[vec_as_location(j)]],
@@ -449,7 +449,6 @@ tbl_subassign <- function(x, i, j, value, i_arg, j_arg, value_arg) {
449449
j <- vectbl_as_new_col_index(j, x, j_arg, names2(value), value_arg)
450450
}
451451
new <- which(j > length(x))
452-
value <- vectbl_recycle_rhs(value, length(i), length(j), i_arg, value_arg)
453452

454453
# Fill up columns if necessary
455454
if (has_length(new)) {
@@ -458,7 +457,7 @@ tbl_subassign <- function(x, i, j, value, i_arg, j_arg, value_arg) {
458457
}
459458

460459
xj <- .subset(x, j)
461-
xj <- tbl_subassign_row(xj, i, value, value_arg)
460+
xj <- tbl_subassign_row(xj, i, value, i_arg, value_arg)
462461
xo <- tbl_subassign_col(x, j, unclass(xj))
463462
}
464463
}
@@ -646,17 +645,21 @@ tbl_expand_to_nrow <- function(x, i) {
646645
x
647646
}
648647

649-
tbl_subassign_row <- function(x, i, value, value_arg) {
648+
tbl_subassign_row <- function(x, i, value, i_arg, value_arg) {
650649
nrow <- fast_nrow(x)
651650
x <- unclass(x)
651+
recycled_value <- vectbl_recycle_rhs_cols(value, length(x))
652652

653653
withCallingHandlers(
654654
for (j in seq_along(x)) {
655-
x[[j]] <- vectbl_assign(x[[j]], i, value[[j]])
655+
x[[j]] <- vectbl_assign(x[[j]], i, recycled_value[[j]])
656656
},
657657

658658
vctrs_error = function(cnd) {
659-
cnd_signal(error_assign_incompatible_type(x, value, j, value_arg, cnd_message(cnd)))
659+
# Side effect: check if `value` can be recycled
660+
vectbl_recycle_rhs_rows(value, length(i), i_arg, value_arg)
661+
662+
cnd_signal(error_assign_incompatible_type(x, recycled_value, j, value_arg, cnd_message(cnd)))
660663
}
661664
)
662665

@@ -725,7 +728,12 @@ result_vectbl_wrap_rhs <- function(value) {
725728
}
726729

727730
vectbl_recycle_rhs <- function(value, nrow, ncol, i_arg, value_arg) {
728-
if (length(value) > 0L && (nrow != 1L || vec_size(value[[1L]]) != 1L)) {
731+
value <- vectbl_recycle_rhs_rows(value, nrow, i_arg, value_arg)
732+
vectbl_recycle_rhs_cols(value, ncol)
733+
}
734+
735+
vectbl_recycle_rhs_rows <- function(value, nrow, i_arg, value_arg) {
736+
if (length(value) > 0L) {
729737
withCallingHandlers(
730738
for (j in seq_along(value)) {
731739
if (!is.null(value[[j]])) {
@@ -739,8 +747,12 @@ vectbl_recycle_rhs <- function(value, nrow, ncol, i_arg, value_arg) {
739747
)
740748
}
741749

750+
value
751+
}
752+
753+
vectbl_recycle_rhs_cols <- function(value, ncol) {
742754
if (length(value) != 1L || ncol != 1L) {
743-
# Errors have been caught beforehand in vectbl_recycle_rhs_names()
755+
# Errors have been caught beforehand in vectbl_as_new_col_index()
744756
value <- vec_recycle(value, ncol)
745757
}
746758

tests/testthat/_snaps/subsetting.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,78 @@
525525
x Existing data has 3 rows.
526526
x Assigned data has 2 rows.
527527
i Only vectors of size 1 are recycled.
528+
Code
529+
df[1, ] <- list(a = 1:3, b = 1)
530+
Error <vctrs_error_incompatible_size>
531+
Can't recycle input of size 2 to size 3.
532+
Code
533+
df[1, ] <- list(a = 1, b = 1:3)
534+
Error <vctrs_error_incompatible_size>
535+
Can't recycle input of size 2 to size 3.
536+
Code
537+
df[1:2, ] <- list(a = 1:3, b = 1)
538+
Error <vctrs_error_incompatible_size>
539+
Can't recycle input of size 2 to size 3.
540+
Code
541+
df[1:2, ] <- list(a = 1, b = 1:3)
542+
Error <vctrs_error_incompatible_size>
543+
Can't recycle input of size 2 to size 3.
544+
Code
545+
df[1, 1:2] <- list(a = 1:3, b = 1)
546+
Error <tibble_error_assign_incompatible_size>
547+
Assigned data `list(a = 1:3, b = 1)` must be compatible with row subscript `1`.
548+
x 1 row must be assigned.
549+
x Element 1 of assigned data has 3 rows.
550+
i Row updates require a list value. Do you need `list()` or `as.list()`?
551+
Code
552+
df[1, 1:2] <- list(a = 1, b = 1:3)
553+
Error <tibble_error_assign_incompatible_size>
554+
Assigned data `list(a = 1, b = 1:3)` must be compatible with row subscript `1`.
555+
x 1 row must be assigned.
556+
x Element 2 of assigned data has 3 rows.
557+
i Row updates require a list value. Do you need `list()` or `as.list()`?
558+
Code
559+
df[1:2, 1:2] <- list(a = 1:3, b = 1)
560+
Error <tibble_error_assign_incompatible_size>
561+
Assigned data `list(a = 1:3, b = 1)` must be compatible with row subscript `1:2`.
562+
x 2 rows must be assigned.
563+
x Element 1 of assigned data has 3 rows.
564+
i Only vectors of size 1 are recycled.
565+
Code
566+
df[1:2, 1:2] <- list(a = 1, b = 1:3)
567+
Error <tibble_error_assign_incompatible_size>
568+
Assigned data `list(a = 1, b = 1:3)` must be compatible with row subscript `1:2`.
569+
x 2 rows must be assigned.
570+
x Element 2 of assigned data has 3 rows.
571+
i Only vectors of size 1 are recycled.
572+
Code
573+
df[1, ] <- list(a = 1:3, b = 1, c = 1:3)
574+
Error <tibble_error_assign_incompatible_size>
575+
Assigned data `list(a = 1:3, b = 1, c = 1:3)` must be compatible with row subscript `1`.
576+
x 1 row must be assigned.
577+
x Element 1 of assigned data has 3 rows.
578+
i Row updates require a list value. Do you need `list()` or `as.list()`?
579+
Code
580+
df[1, ] <- list(a = 1, b = 1:3, c = 1:3)
581+
Error <tibble_error_assign_incompatible_size>
582+
Assigned data `list(a = 1, b = 1:3, c = 1:3)` must be compatible with row subscript `1`.
583+
x 1 row must be assigned.
584+
x Element 2 of assigned data has 3 rows.
585+
i Row updates require a list value. Do you need `list()` or `as.list()`?
586+
Code
587+
df[1:2, ] <- list(a = 1:3, b = 1, c = 1:3)
588+
Error <tibble_error_assign_incompatible_size>
589+
Assigned data `list(a = 1:3, b = 1, c = 1:3)` must be compatible with row subscript `1:2`.
590+
x 2 rows must be assigned.
591+
x Element 1 of assigned data has 3 rows.
592+
i Only vectors of size 1 are recycled.
593+
Code
594+
df[1:2, ] <- list(a = 1, b = 1:3, c = 1:3)
595+
Error <tibble_error_assign_incompatible_size>
596+
Assigned data `list(a = 1, b = 1:3, c = 1:3)` must be compatible with row subscript `1:2`.
597+
x 2 rows must be assigned.
598+
x Element 2 of assigned data has 3 rows.
599+
i Only vectors of size 1 are recycled.
528600
Code
529601
# # [<-.tbl_df and coercion
530602
df <- tibble(x = 1:3, y = letters[1:3], z = as.list(1:3))

tests/testthat/test-subsetting.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,18 @@ test_that("output test", {
914914
df[1, ] <- 1:3
915915
df[1:2, ] <- 1:3
916916
df[,] <- 1:2
917+
df[1, ] <- list(a = 1:3, b = 1)
918+
df[1, ] <- list(a = 1, b = 1:3)
919+
df[1:2, ] <- list(a = 1:3, b = 1)
920+
df[1:2, ] <- list(a = 1, b = 1:3)
921+
df[1, 1:2] <- list(a = 1:3, b = 1)
922+
df[1, 1:2] <- list(a = 1, b = 1:3)
923+
df[1:2, 1:2] <- list(a = 1:3, b = 1)
924+
df[1:2, 1:2] <- list(a = 1, b = 1:3)
925+
df[1, ] <- list(a = 1:3, b = 1, c = 1:3)
926+
df[1, ] <- list(a = 1, b = 1:3, c = 1:3)
927+
df[1:2, ] <- list(a = 1:3, b = 1, c = 1:3)
928+
df[1:2, ] <- list(a = 1, b = 1:3, c = 1:3)
917929

918930
"# [<-.tbl_df and coercion"
919931
df <- tibble(x = 1:3, y = letters[1:3], z = as.list(1:3))

0 commit comments

Comments
 (0)