-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from R-ArcGIS/rbind
`rbind_results()` helper function
- Loading branch information
Showing
10 changed files
with
198 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#' Combine multiple data.frames | ||
#' | ||
#' A general function that takes a list of `data.frame`s and returns a single | ||
#' and combines them into a single object. It will use the fastest method | ||
#' available. In order this is [`collapse::rowbind()`], [`data.table::rbindlist()`], | ||
#' [`vctrs::list_unchop()`], then `do.call(rbind.data.frame, x)`. | ||
#' | ||
#' If all items in the list are `data.frame`s, then the result will be a `data.frame`. | ||
#' If all elements are an `sf` object, then the result will be an `sf` object. | ||
#' If the items are mixed, the result will be a `data.frame`. | ||
#' | ||
#' If any items are `NULL`, then an attribute `null_elements` will be attached | ||
#' to the result. The attribute is an integer vector of the indices that | ||
#' were `NULL`. | ||
#' | ||
#' @param x a list where each element is a `data.frame` or `NULL`. | ||
#' @param .ptype currently unused. Reserved for a future release. | ||
#' @export | ||
#' @returns see details. | ||
#' @inheritParams parse_esri_json | ||
#' @examples | ||
#' | ||
#' x <- head(iris) | ||
#' res <- rbind_results(list(x, NULL, x)) | ||
#' attr(res, "null_elements") | ||
rbind_results <- function( | ||
x, | ||
call = rlang::current_env(), | ||
.ptype = data.frame() | ||
) { | ||
|
||
# use for loop for side effects | ||
# check that each element is a data.frame | ||
for (item in x) check_data_frame( | ||
item, | ||
allow_null = TRUE, | ||
call = call, | ||
arg = rlang::caller_arg(x) | ||
) | ||
|
||
# check if all results are sf, if so, we must return sf | ||
return_sf <- all(vapply(x, inherits_or_null, logical(1), class = "sf")) | ||
|
||
# identify which results are missing | ||
missing_elements <- vapply(x, is.null, logical(1)) | ||
are_missing <- which(missing_elements) | ||
|
||
if (all(missing_elements)) { | ||
# return empty data.frame() if all missing | ||
# FIXME should we take a ptype here? to | ||
return(data.frame()) | ||
} | ||
|
||
if (rlang::is_installed("collapse", version = "2.0.0")) { | ||
x <- collapse::rowbind(x) | ||
} else if (rlang::is_installed("data.table")) { | ||
x <- data.table::rbindlist(x) | ||
data.table::setDF(x) | ||
} else if (rlang::is_installed("vctrs")) { | ||
# vctrs::vec_rbind() doesn't handle NULL | ||
x <- vctrs::list_unchop(x, error_call = call) | ||
} else { | ||
x <- do.call(rbind.data.frame, x) | ||
} | ||
|
||
# cast to sf if not already (the case with collapse) | ||
if (return_sf && !rlang::inherits_any(x, "sf")) { | ||
x <- sf::st_as_sf(x) | ||
} | ||
|
||
if (length(are_missing) > 0) { | ||
attr(x, "null_elements") <- are_missing | ||
} | ||
|
||
x | ||
} | ||
|
||
#' Check if an object is NULL or inherits a class | ||
#' | ||
#' Uses [`rlang::inherits_any()`] for the class check. | ||
#' @keywords internal | ||
#' @noRd | ||
inherits_or_null <- function(x, class) { | ||
if (is.null(x)) { | ||
return(TRUE) | ||
} else { | ||
rlang::inherits_any(x, class) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
test_that("rbind sf objects", { | ||
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) | ||
x <- list(nc, nc, NULL, nc) | ||
|
||
res <- rbind_results(x) | ||
|
||
# should be an sf object | ||
expect_true(inherits(res, "sf")) | ||
# should have 1 missing element | ||
expect_identical(attr(res, "null_elements"), 3L) | ||
}) | ||
|
||
test_that("rbind mixed results", { | ||
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) | ||
items <- list(as.data.frame(nc), nc) | ||
|
||
res <- rbind_results(items) | ||
expect_true(rlang::inherits_only(res, "data.frame")) | ||
}) | ||
|
||
test_that("rbind data.frames", { | ||
res <- rbind_results(list(iris, NULL, iris)) | ||
expect_true(rlang::inherits_only(res, "data.frame")) | ||
}) | ||
|
||
test_that("rbind NULL & list(NULL)", { | ||
|
||
# should return empty df | ||
res <- rbind_results(NULL) | ||
expect_identical(res, data.frame()) | ||
|
||
# one null | ||
res <- rbind_results(list(NULL)) | ||
expect_identical(res, data.frame()) | ||
|
||
# multiple | ||
res <- rbind_results(list(NULL, NULL)) | ||
expect_identical(res, data.frame()) | ||
}) | ||
|
||
test_that("rbind errors on non-df objects", { | ||
expect_error(rbind_results(list(iris, NULL, "a"))) | ||
}) | ||
|
||
|