Skip to content

Commit 3c4cd45

Browse files
krlmlrDavisVaughan
andauthored
Implement rows_append() (#6249)
* New rows_append() with data.frame method * Document * Revert documentation update in `ident()` * Tweak example comment * Flesh out the `rows_append()` tests more * Add return value details for `rows_patch()` and `rows_append()` * NEWS bullet Co-authored-by: DavisVaughan <davis@rstudio.com>
1 parent 8c55a20 commit 3c4cd45

File tree

6 files changed

+121
-8
lines changed

6 files changed

+121
-8
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ S3method(rename_,data.frame)
124124
S3method(rename_,grouped_df)
125125
S3method(rename_with,data.frame)
126126
S3method(right_join,data.frame)
127+
S3method(rows_append,data.frame)
127128
S3method(rows_delete,data.frame)
128129
S3method(rows_insert,data.frame)
129130
S3method(rows_patch,data.frame)
@@ -376,6 +377,7 @@ export(rename_vars_)
376377
export(rename_with)
377378
export(right_join)
378379
export(row_number)
380+
export(rows_append)
379381
export(rows_delete)
380382
export(rows_insert)
381383
export(rows_patch)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# dplyr (development version)
22

3+
* New `rows_append()` which works like `rows_insert()` but ignores keys and
4+
allows you to insert arbitrary rows with a guarantee that the type of `x`
5+
won't change (#6249, thanks to @krlmlr for the implementation and @mgirlich
6+
for the idea).
7+
38
* The `rows_*()` functions no longer require that the key values in `x` uniquely
49
identify each row. Additionally, `rows_insert()` and `rows_delete()` no
510
longer require that the key values in `y` uniquely identify each row. Relaxing

R/rows.R

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#'
1212
#' * `rows_insert()` adds new rows (like `INSERT`). By default, key values in
1313
#' `y` must not exist in `x`.
14+
#' * `rows_append()` works like `rows_insert()` but ignores keys.
1415
#' * `rows_update()` modifies existing rows (like `UPDATE`). Key values in `y`
1516
#' must be unique, and, by default, key values in `y` must exist in `x`.
1617
#' * `rows_patch()` works like `rows_update()` but only overwrites `NA` values.
@@ -55,9 +56,10 @@
5556
#' An object of the same type as `x`. The order of the rows and columns of `x`
5657
#' is preserved as much as possible. The output has the following properties:
5758
#'
58-
#' * `rows_update()` preserves rows as is; `rows_insert()` and `rows_upsert()`
59-
#' return all existing rows and potentially new rows; `rows_delete()` returns
60-
#' a subset of the rows.
59+
#' * `rows_update()` and `rows_patch()` preserve the number of rows;
60+
#' `rows_insert()`, `rows_append()`, and `rows_upsert()` return all existing
61+
#' rows and potentially new rows; `rows_delete()` returns a subset of the
62+
#' rows.
6163
#' * Columns are not added, removed, or relocated, though the data may be
6264
#' updated.
6365
#' * Groups are taken from `x`.
@@ -74,9 +76,11 @@
7476
#'
7577
#' # By default, if a key in `y` matches a key in `x`, then it can't be inserted
7678
#' # and will throw an error. Alternatively, you can ignore rows in `y`
77-
#' # containing keys that conflict with keys in `x` with `conflict = "ignore"`.
79+
#' # containing keys that conflict with keys in `x` with `conflict = "ignore"`,
80+
#' # or you can use `rows_append()` to ignore keys entirely.
7881
#' try(rows_insert(data, tibble(a = 3, b = "z")))
7982
#' rows_insert(data, tibble(a = 3, b = "z"), conflict = "ignore")
83+
#' rows_append(data, tibble(a = 3, b = "z"))
8084
#'
8185
#' # Update
8286
#' rows_update(data, tibble(a = 2:3, b = "z"))
@@ -144,6 +148,34 @@ rows_insert.data.frame <- function(x,
144148
rows_bind(x, y)
145149
}
146150

151+
#' @rdname rows
152+
#' @export
153+
rows_append <- function(x,
154+
y,
155+
...,
156+
copy = FALSE,
157+
in_place = FALSE) {
158+
lifecycle::signal_stage("experimental", "rows_append()")
159+
UseMethod("rows_append")
160+
}
161+
162+
#' @export
163+
rows_append.data.frame <- function(x,
164+
y,
165+
...,
166+
copy = FALSE,
167+
in_place = FALSE) {
168+
check_dots_empty()
169+
rows_df_in_place(in_place)
170+
171+
y <- auto_copy(x, y, copy = copy)
172+
173+
rows_check_containment(x, y)
174+
y <- rows_cast_y(y, x)
175+
176+
rows_bind(x, y)
177+
}
178+
147179
#' @rdname rows
148180
#' @export
149181
rows_update <- function(x,

man/rows.Rd

Lines changed: 11 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/rows.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@
5555
Error in `rows_insert()`:
5656
! `conflict` must be a character vector, not a number.
5757

58+
# rows_append() casts to the type of `x`
59+
60+
Code
61+
(expect_error(rows_append(x, y)))
62+
Output
63+
<error/vctrs_error_cast_lossy>
64+
Error in `rows_append()`:
65+
! Can't convert from `y$key` <double> to `x$key` <integer> due to loss of precision.
66+
* Locations: 1
67+
68+
# rows_append() requires that `y` columns be a subset of `x`
69+
70+
Code
71+
(expect_error(rows_append(x, y)))
72+
Output
73+
<error/rlang_error>
74+
Error in `rows_append()`:
75+
! All columns in `y` must exist in `x`.
76+
i The following columns only exist in `y`: `c`.
77+
5878
# rows_update() requires `y` keys to exist in `x` by default
5979

6080
Code

tests/testthat/test-rows.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,53 @@ test_that("`conflict` is validated", {
8989
})
9090
})
9191

92+
# ------------------------------------------------------------------------------
93+
# rows_append()
94+
95+
test_that("rows_append() allows you to insert unconditionally", {
96+
x <- tibble(a = 1, b = 2)
97+
y <- tibble(a = 1, b = 3)
98+
99+
expect_identical(rows_append(x, y), bind_rows(x, y))
100+
101+
y <- tibble(a = c(1, 2, 1), b = c(3, 4, 5))
102+
103+
expect_identical(rows_append(x, y), bind_rows(x, y))
104+
})
105+
106+
test_that("rows_append() casts to the type of `x`", {
107+
x <- vctrs::data_frame(key = 1L, value = 2)
108+
109+
y <- vctrs::data_frame(key = 1.5, value = 1.5)
110+
111+
expect_snapshot({
112+
(expect_error(rows_append(x, y)))
113+
})
114+
115+
y <- vctrs::data_frame(key = 2, value = 3L)
116+
117+
out <- rows_append(x, y)
118+
expect_identical(out$key, c(1L, 2L))
119+
expect_identical(out$value, c(2, 3))
120+
})
121+
122+
test_that("rows_append() requires that `y` columns be a subset of `x`", {
123+
x <- tibble(a = 1, b = 2)
124+
y <- tibble(a = 1, b = 2, c = 3)
125+
126+
expect_snapshot({
127+
(expect_error(rows_append(x, y)))
128+
})
129+
})
130+
131+
test_that("rows_append() doesn't require that `x` columns be a subset of `y`", {
132+
x <- tibble(a = 1, b = 2, c = 3)
133+
y <- tibble(a = 1, b = 2)
134+
135+
out <- rows_append(x, y)
136+
expect_identical(out$c, c(3, NA))
137+
})
138+
92139
# ------------------------------------------------------------------------------
93140

94141
test_that("rows_update() works", {

0 commit comments

Comments
 (0)