Skip to content

Commit de2abba

Browse files
authored
Merge pull request #903 from tidyverse/b-773-na-subassign
- `tbl[row, col] <- rhs` treats an all-`NA` logical vector as a missing value both for existing data (#773) and for the right-hand side value (#868). This means that a column initialized with `NA` (of type `logical`) will change its type when a row is updated to a value of a different type.
2 parents 8994e1e + f30b5d9 commit de2abba

File tree

10 files changed

+149
-20
lines changed

10 files changed

+149
-20
lines changed

NAMESPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ importFrom(pillar,type_sum)
8787
importFrom(pkgconfig,set_config)
8888
importFrom(utils,head)
8989
importFrom(utils,tail)
90-
importFrom(vctrs,"vec_slice<-")
9190
importFrom(vctrs,num_as_location)
9291
importFrom(vctrs,unspecified)
9392
importFrom(vctrs,vec_as_location)
9493
importFrom(vctrs,vec_as_location2)
9594
importFrom(vctrs,vec_as_names)
9695
importFrom(vctrs,vec_as_names_legacy)
9796
importFrom(vctrs,vec_as_subscript2)
97+
importFrom(vctrs,vec_assign)
9898
importFrom(vctrs,vec_c)
9999
importFrom(vctrs,vec_is)
100100
importFrom(vctrs,vec_names2)

R/subsetting-matrix.R

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ tbl_subassign_matrix <- function(x, j, value, j_arg, value_arg) {
2828

2929
withCallingHandlers(
3030
for (j in col_idx) {
31-
xj <- x[[j]]
32-
vec_slice(xj, cells[[j]]) <- value
33-
x[[j]] <- xj
31+
x[[j]] <- vectbl_assign(x[[j]], cells[[j]], value)
3432
},
3533

3634
vctrs_error_incompatible_type = function(cnd) {

R/subsetting.R

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,7 @@ tbl_subassign_row <- function(x, i, value, value_arg) {
646646

647647
withCallingHandlers(
648648
for (j in seq_along(x)) {
649-
xj <- x[[j]]
650-
vec_slice(xj, i) <- value[[j]]
651-
x[[j]] <- xj
649+
x[[j]] <- vectbl_assign(x[[j]], i, value[[j]])
652650
},
653651

654652
vctrs_error = function(cnd) {
@@ -663,6 +661,24 @@ fast_nrow <- function(x) {
663661
.row_names_info(x, 2L)
664662
}
665663

664+
vectbl_assign <- function(x, i, value) {
665+
if (is.logical(value)) {
666+
if (.Call("tibble_need_coerce", value)) {
667+
value <- vec_slice(x, NA_integer_)
668+
}
669+
} else {
670+
if (.Call("tibble_need_coerce", x)) {
671+
d <- dim(x)
672+
dn <- dimnames(x)
673+
x <- vec_slice(value, rep(NA_integer_, length(x)))
674+
dim(x) <- d
675+
dimnames(x) <- dn
676+
}
677+
}
678+
679+
vec_assign(x, i, value)
680+
}
681+
666682
vectbl_strip_names <- function(x) {
667683
maybe_row_names <- is.data.frame(x) || is.array(x)
668684

R/tibble-package.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#' @import lifecycle
66
#' @import ellipsis
77
#' @importFrom vctrs vec_as_location vec_as_location2 vec_as_names vec_as_names_legacy vec_c
8-
#' @importFrom vctrs vec_is vec_rbind vec_recycle vec_size vec_slice vec_slice<-
8+
#' @importFrom vctrs vec_is vec_rbind vec_recycle vec_size vec_slice vec_assign
99
#' @importFrom vctrs unspecified vec_as_subscript2 num_as_location vec_ptype_abbr
1010
#' @importFrom vctrs vec_names2 vec_set_names
1111
#' @aliases NULL tibble-package

src/coerce.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,19 @@ SEXP tibble_string_to_indices(SEXP x) {
6161
UNPROTECT(1);
6262
return out;
6363
}
64+
65+
SEXP tibble_need_coerce(SEXP x) {
66+
if (TYPEOF(x) != LGLSXP) {
67+
return(Rf_ScalarLogical(0));
68+
}
69+
70+
const R_xlen_t len = Rf_xlength(x);
71+
const int* px = LOGICAL(x);
72+
for (R_xlen_t i = 0; i < len; ++i) {
73+
if (px[i] != NA_LOGICAL) {
74+
return(Rf_ScalarLogical(0));
75+
}
76+
}
77+
78+
return(Rf_ScalarLogical(1));
79+
}

src/init.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ static const R_CallMethodDef CallEntries[] = {
99
{"tibble_string_to_indices", (DL_FUNC) &tibble_string_to_indices, 1},
1010
{"tibble_update_attrs", (DL_FUNC) &tibble_update_attrs, 2},
1111
{"tibble_restore_impl", (DL_FUNC) &tibble_restore_impl, 2},
12+
{"tibble_need_coerce", (DL_FUNC) &tibble_need_coerce, 1},
1213

1314
{NULL, NULL, 0}
1415
};

src/tibble.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
SEXP tibble_matrixToDataFrame(SEXP xSEXP);
88
SEXP tibble_string_to_indices(SEXP x);
9+
SEXP tibble_need_coerce(SEXP x);
910
SEXP tibble_update_attrs(SEXP x, SEXP dots);
1011
SEXP tibble_restore_impl(SEXP xo, SEXP x);
1112

tests/testthat/_snaps/invariants.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,11 +1191,14 @@
11911191
df$x <- NA
11921192
df[2:3, "x"] <- 3:2
11931193
})
1194-
Error <tibble_error_assign_incompatible_type>
1195-
Assigned data `3:2` must be compatible with existing data.
1196-
i Error occurred for column `x`.
1197-
x Can't convert from <integer> to <logical> due to loss of precision.
1198-
* Locations: 1, 2.
1194+
Output
1195+
# A tibble: 4 x 4
1196+
n c li x
1197+
<int> <chr> <list> <int>
1198+
1 1 e <dbl [1]> NA
1199+
2 NA f <int [2]> 3
1200+
3 3 g <int [3]> 2
1201+
4 NA h <chr [1]> NA
11991202
Code
12001203
with_df({
12011204
df$x <- NA_integer_

tests/testthat/_snaps/subsetting.md

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,13 +557,66 @@
557557
`NULL` must be a vector, a bare list, a data frame or a matrix.
558558
Code
559559
# # [<-.tbl_df and overwriting NA
560-
df <- tibble(x = rep(NA, 3))
560+
df <- tibble(x = rep(NA, 3), z = matrix(NA, ncol = 2, dimnames = list(NULL, c(
561+
"a", "b"))))
561562
df[1, "x"] <- 5
562-
Error <tibble_error_assign_incompatible_type>
563-
Assigned data `5` must be compatible with existing data.
564-
i Error occurred for column `x`.
565-
x Can't convert from <double> to <logical> due to loss of precision.
566-
* Locations: 1.
563+
df[1, "z"] <- 5
564+
df
565+
Output
566+
# A tibble: 3 x 2
567+
x z[,"a"] [,"b"]
568+
<dbl> <dbl> <dbl>
569+
1 5 5 5
570+
2 NA NA NA
571+
3 NA NA NA
572+
Code
573+
# # [<-.tbl_df and overwriting with NA
574+
df <- tibble(a = TRUE, b = 1L, c = sqrt(2), d = 0+3i + 1, e = "e", f = raw(1),
575+
g = tibble(x = 1, y = 1), h = matrix(1:3, nrow = 1))
576+
df[FALSE, "a"] <- NA
577+
df[FALSE, "b"] <- NA
578+
df[FALSE, "c"] <- NA
579+
df[FALSE, "d"] <- NA
580+
df[FALSE, "e"] <- NA
581+
df[FALSE, "f"] <- NA
582+
df[FALSE, "g"] <- NA
583+
df[FALSE, "h"] <- NA
584+
df
585+
Output
586+
# A tibble: 1 x 8
587+
a b c d e f g$x $y h[,1] [,2] [,3]
588+
<lgl> <int> <dbl> <cpl> <chr> <raw> <dbl> <dbl> <int> <int> <int>
589+
1 TRUE 1 1.41 1+3i e 00 1 1 1 2 3
590+
Code
591+
df[integer(), "a"] <- NA
592+
df[integer(), "b"] <- NA
593+
df[integer(), "c"] <- NA
594+
df[integer(), "d"] <- NA
595+
df[integer(), "e"] <- NA
596+
df[integer(), "f"] <- NA
597+
df[integer(), "g"] <- NA
598+
df[integer(), "h"] <- NA
599+
df
600+
Output
601+
# A tibble: 1 x 8
602+
a b c d e f g$x $y h[,1] [,2] [,3]
603+
<lgl> <int> <dbl> <cpl> <chr> <raw> <dbl> <dbl> <int> <int> <int>
604+
1 TRUE 1 1.41 1+3i e 00 1 1 1 2 3
605+
Code
606+
df[1, "a"] <- NA
607+
df[1, "b"] <- NA
608+
df[1, "c"] <- NA
609+
df[1, "d"] <- NA
610+
df[1, "e"] <- NA
611+
df[1, "f"] <- NA
612+
df[1, "g"] <- NA
613+
df[1, "h"] <- NA
614+
df
615+
Output
616+
# A tibble: 1 x 8
617+
a b c d e f g$x $y h[,1] [,2] [,3]
618+
<lgl> <int> <dbl> <cpl> <chr> <raw> <dbl> <dbl> <int> <int> <int>
619+
1 NA NA NA NA <NA> 00 NA NA NA NA NA
567620
Code
568621
# # [<-.tbl_df and matrix subsetting
569622
foo <- tibble(a = 1:3, b = letters[1:3])

tests/testthat/test-subsetting.R

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,49 @@ test_that("output test", {
924924
df[1:3, 1:3] <- NULL
925925

926926
"# [<-.tbl_df and overwriting NA"
927-
df <- tibble(x = rep(NA, 3))
927+
df <- tibble(x = rep(NA, 3), z = matrix(NA, ncol = 2, dimnames = list(NULL, c("a", "b"))))
928928
df[1, "x"] <- 5
929+
df[1, "z"] <- 5
930+
df
931+
932+
"# [<-.tbl_df and overwriting with NA"
933+
df <- tibble(
934+
a = TRUE,
935+
b = 1L,
936+
c = sqrt(2),
937+
d = 3i + 1,
938+
e = "e",
939+
f = raw(1),
940+
g = tibble(x = 1, y = 1),
941+
h = matrix(1:3, nrow = 1)
942+
)
943+
df[FALSE, "a"] <- NA
944+
df[FALSE, "b"] <- NA
945+
df[FALSE, "c"] <- NA
946+
df[FALSE, "d"] <- NA
947+
df[FALSE, "e"] <- NA
948+
df[FALSE, "f"] <- NA
949+
df[FALSE, "g"] <- NA
950+
df[FALSE, "h"] <- NA
951+
df
952+
df[integer(), "a"] <- NA
953+
df[integer(), "b"] <- NA
954+
df[integer(), "c"] <- NA
955+
df[integer(), "d"] <- NA
956+
df[integer(), "e"] <- NA
957+
df[integer(), "f"] <- NA
958+
df[integer(), "g"] <- NA
959+
df[integer(), "h"] <- NA
960+
df
961+
df[1, "a"] <- NA
962+
df[1, "b"] <- NA
963+
df[1, "c"] <- NA
964+
df[1, "d"] <- NA
965+
df[1, "e"] <- NA
966+
df[1, "f"] <- NA
967+
df[1, "g"] <- NA
968+
df[1, "h"] <- NA
969+
df
929970

930971
"# [<-.tbl_df and matrix subsetting"
931972
foo <- tibble(a = 1:3, b = letters[1:3])

0 commit comments

Comments
 (0)