This vignette is an attempt to provide a comprehensive overview over all subassignment operations, highlighting where the tibble implementation differs from the data frame implementation.

Results of the same code for data frames and tibbles are presented side by side:

In the following, if the results are identical (after converting to a data frame if necessary), only the tibble result is shown, as in the example below. This allows to spot differences easier.

For subassignment, we need a fresh copy of the data for each test. The with_*() functions allow for a more concise notation (with_tbl() omitted here for brevity):

This function takes an assignment expression and executes it on a fresh copy of the data. The first example prints what’s really executed, further examples omit this output.

$<-

Recycling

Tibbles allow recycling only for vectors of length 1 or of the same length as the data.

#> Vector of length 2 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Error in `$<-.data.frame`(`*tmp*`,
#> a, value = 1:3): replacement has 3
#> rows, data has 4
#> Vector of length 3 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Error in `$<-.data.frame`(`*tmp*`,
#> a, value = 1:5): replacement has 5
#> rows, data has 4
#> Vector of length 5 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Vector of length 2 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.

Subset assignment

Updating parts of a column extracted by $ is the responsibility of the column vector. Tibbles can’t control what happens after $ has returned.

#> Warning: Unknown or uninitialised
#> column: `c`.
#> Vector of length 2 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Warning in df$a[1:3] <- 4:3: number of
#> items to replace is not a multiple of
#> replacement length
#>   a b         cd
#> 1 4 e          9
#> 2 3 f     10, 11
#> 3 4 g 12, 13, 14
#> 4 4 h       text
#> Warning in tbl$a[1:3] <- 4:3: number of
#> items to replace is not a multiple of
#> replacement length
#> # A tibble: 4 x 3
#>       a b     cd       
#>   <int> <chr> <list>   
#> 1     4 e     <dbl [1]>
#> 2     3 f     <int [2]>
#> 3     4 g     <int [3]>
#> 4     4 h     <chr [1]>

For columns of the stricter "vctrs_vctr" class, this class implements the check, which then works identically for data frames and tibbles:

#> No common type for `x` <vctrs_vctr> and `value` <vctrs_vctr>.

[[<-

Scalars and full length

As with $ subsetting, columns are consistently overwritten, and partial matching doesn’t occur. Numeric indexing is supported, but tibbles don’t support creation of new numbered columns for a good reason.

#> Warning in format.data.frame(if (omit)
#> x[seq_len(n0), , drop = FALSE] else
#> x, : corrupt data frame: columns will be
#> truncated or padded with NAs
#>   a b         cd      V5
#> 1 1 e          9 NULL  x
#> 2 2 f     10, 11 <NA>  x
#> 3 3 g 12, 13, 14 <NA>  x
#> 4 4 h       text <NA>  x
#> Error in
#> error_new_columns_at_end_only():
#> could not find function
#> "error_new_columns_at_end_only"

Cells

Tibbles are stricter when updating single cells, the value must be coercible to the existing contents. Updating a list column requires the contents to be wrapped in a list, consistently with [[ subsetting which returns a list if a cell in a list column is accessed:

#> Lossy cast from `value` <double> to
#> `x` <integer>.
#> Locations: 1
#> No common type for `value`
#> <character>
#> and `x` <integer>.
#> No common type for `value`
#> <character>
#> and `x` <integer>.
#> No common type for `value`
#> <character>
#> and `x` <list>.
#> Error in `[[<-.data.frame`(`*tmp*`,
#> 2, "c", value = "x"): replacing
#> element in non-existent column: c
#> Error: Can't index beyond the end of
#> a vector.
#> The vector has length 3 and you've
#> tried to subset element 4.
#> Error in `[[<-.data.frame`(`*tmp*`,
#> 1:2, "cd", value = "x"): only a
#> single element should be replaced
#> Error in error_need_scalar(): could
#> not find function
#> "error_need_scalar"
#> Error in `[[<-.data.frame`(`*tmp*`,
#> 1:2, "c", value = "x"): replacing
#> element in non-existent column: c
#> Error in error_need_scalar(): could
#> not find function
#> "error_need_scalar"
with_df(df[[2, c("cd", "d")]] <- "x")
#> Error in `[[<-.data.frame`(`*tmp*`,
#> 2, c("cd", "d"), value = "x"):
#> replacing element in non-existent
#> column: d
with_tbl(tbl[[2, c("cd", "d")]] <- "x")
#> Error in error_need_scalar(): could
#> not find function
#> "error_need_scalar"

[<-

Scalars

#> Lossy cast from `value` <double> to
#> `x` <integer>.
#> Locations: 1
#> No common type for `value`
#> <character>
#> and `x` <integer>.
#> No common type for `value`
#> <character>
#> and `x` <list>.
#> No common type for `value`
#> <character>
#> and `x` <list>.
#> Error: Can't index non-existing
#> elements.
#> No common type for `value`
#> <character>
#> and `x` <integer>.
#> No common type for `value`
#> <character>
#> and `x` <list>.
#> Error: Can't index beyond the end of
#> a vector.
#> The vector has length 3 and you've
#> tried to subset element 4.

Full length columns

Multiple full length columns

with_df(df[, c("a", "b")] <- data.frame(a = 4:1, b = letters[4:1], c = 1:4))
#> Warning in `[<-.data.frame`(`*tmp*`, ,
#> c("a", "b"), value = structure(list(:
#> provided 3 variables to replace 2
#> variables
#>   a b         cd
#> 1 4 d          9
#> 2 3 c     10, 11
#> 3 2 b 12, 13, 14
#> 4 1 a       text
with_tbl(tbl[, c("a", "b")] <- data.frame(a = 4:1, b = letters[4:1], c = 1:4))
#> Vector of length 3 cannot be
#> recycled to
#> length 2. Only vectors of length one
#> or
#> of the same length can be recycled.
with_tbl(tbl[, c("a", "b", "c")] <- data.frame(4:1, letters[4:1]))
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
with_tbl(tbl[, c("a", "b", "cd")] <- data.frame(4:1, letters[4:1]))
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.

Full length rows

#> No common type for `value` <double>
#> and
#> `x` <character>.
#> No common type for `value`
#> <character>
#> and `x` <integer>.
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
with_df(df[2, ] <- tibble(a = 1, b = "x", c = list("y"), d = "z"))
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 2, , value = structure(list(a = 1, :
#> provided 4 variables to replace 3
#> variables
#>   a b         cd
#> 1 1 e          9
#> 2 1 x          y
#> 3 3 g 12, 13, 14
#> 4 4 h       text
with_tbl(tbl[2, ] <- tibble(a = 1, b = "x", c = list("y"), d = "z"))
#> Vector of length 4 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.

Multiple full length rows

#> No common type for `value` <double>
#> and
#> `x` <character>.
#> No common type for `value` <integer>
#> and
#> `x` <character>.
#> No common type for `value`
#> <character>
#> and `x` <integer>.
with_tbl(tbl[2:3, ] <- tibble(a = 1:2, b = c("x", "y")))
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
with_df(df[2:3, ] <- tibble(a = 1, b = "x", c = list("y"), d = "z"))
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 2:3, , value = structure(list(a =
#> 1, : provided 4 variables to replace 3
#> variables
#>   a b   cd
#> 1 1 e    9
#> 2 1 x    y
#> 3 1 x    y
#> 4 4 h text
with_tbl(tbl[2:3, ] <- tibble(a = 1, b = "x", c = list("y"), d = "z"))
#> Vector of length 4 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
with_df(df[0:1, ] <- tibble(a = 1:2, b = "x", c = list("y")))
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 0:1, , value = structure(list(a = 1:2, :
#> replacement element 1 has 2 rows to
#> replace 1 rows
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 0:1, , value = structure(list(a = 1:2, :
#> replacement element 2 has 2 rows to
#> replace 1 rows
#> Warning in `[<-.data.frame`(`*tmp*`,
#> 0:1, , value = structure(list(a = 1:2, :
#> replacement element 3 has 2 rows to
#> replace 1 rows
#>   a b         cd
#> 1 1 x          y
#> 2 2 f     10, 11
#> 3 3 g 12, 13, 14
#> 4 4 h       text
with_tbl(tbl[0:1, ] <- tibble(a = 1:2, b = "x", c = list("y")))
#> Vector of length 2 cannot be
#> recycled to
#> length 1. Only vectors of length one
#> or
#> of the same length can be recycled.

Unspecified

#> Vector of length 3 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Error in `[<-.data.frame`(`*tmp*`, ,
#> value = 5:1): replacement has 5
#> items, need 12
#> Vector of length 5 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Warning in `[<-.data.frame`(`*tmp*`, ,
#> value = structure(list(X1 = 1, X.x.
#> = structure(1L, .Label = "x", class =
#> "factor"), : provided 4 variables to
#> replace 3 variables
#>   a b cd
#> 1 1 x  2
#> 2 1 x  2
#> 3 1 x  2
#> 4 1 x  2
#> Vector of length 4 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Vector of length 3 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Error in `[<-.data.frame`(`*tmp*`, ,
#> , value = 5:1): replacement has 5
#> items, need 12
#> Vector of length 5 cannot be
#> recycled to
#> length 4. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Vector of length 2 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.
#> Warning in `[<-.data.frame`(`*tmp*`, , ,
#> value = structure(list(X1 = 1, :
#> provided 4 variables to replace 3
#> variables
#>   a b cd
#> 1 1 x  2
#> 2 1 x  2
#> 3 1 x  2
#> 4 1 x  2
#> Vector of length 4 cannot be
#> recycled to
#> length 3. Only vectors of length one
#> or
#> of the same length can be recycled.

Subset assignment

Due to tibble’s default of drop = FALSE, updating a portion of a [ subset is still safe, because tibble is still in control. Only one example is given here.

#> No common type for `value`
#> <character>
#> and `x` <integer>.