context("data.table and tbl_df inheritance from data.frame") test_that("basic coercion", { skip_on_cran() ### create empty frames empty_df <- data.frame() if (requireNamespace("data.table", quietly = TRUE)) { empty_dt <- data.table::as.data.table(empty_df) expect_equal(dim(empty_dt), c(0, 0)) } if (requireNamespace("tibble", quietly = TRUE)) { empty_tb <- tibble::as_tibble(empty_df) expect_equal(dim(empty_tb), c(0, 0)) } ### .as.data.frame.aqp wrapper on data.frames expect_equal(.as.data.frame.aqp(empty_df, "data.frame"), empty_df) ## data frame with 0 columns and 0 rows expect_equal(.as.data.frame.aqp(empty_df, "data.table"), empty_dt) ## Null data.table (0 rows and 0 cols) expect_equal(.as.data.frame.aqp(empty_df, "tbl_df"), empty_tb) ## A tibble: 0 x 0 # as.data.frame wrapper on tibbles expect_equal(.as.data.frame.aqp(empty_tb, "data.table"), empty_dt) # Null data.table (0 rows and 0 cols) # expect_equal(.as.data.frame.aqp(empty_tb, "data.frame"), empty_df) # data frame with 0 columns and 0 rows # expect_equal(.as.data.frame.aqp(empty_tb, "tbl_df"), empty_tb) # A tibble: 0 x 0 # as.data.frame wrapper on data.table expect_equal(.as.data.frame.aqp(empty_dt, "data.table"), empty_dt) # Null data.table (0 rows and 0 cols) expect_equal(.as.data.frame.aqp(empty_dt, "data.frame"), empty_df) # data frame with 0 columns and 0 rows expect_equivalent(.as.data.frame.aqp(empty_dt, "tbl_df"), empty_tb) # note: that because of slight internal differences (leftover from data.table?) # cannot use expect_equal -- though they are clearly equivalent # # > str(.as.data.frame.aqp(empty_dt, "tbl_df")) # tibble [0 × 0] (S3: tbl_df/tbl/data.frame) # Named list() # - attr(*, ".internal.selfref")= # # > str(empty_tb) # tibble [0 × 0] (S3: tbl_df/tbl/data.frame) # Named list() }) dfclasses <- list("data.frame", "data.table", "tbl_df") res <- lapply(dfclasses, function(use_class) { ## ## ## debug note -- this is vectorized over the data.frame classes ## ## # debug # use_class <- "data.frame" test_that(sprintf("basic functionality of %s", use_class), { # class to use (data.frame, data.table or tbl_df) cc <- function(l) { do.call('c', as.list(l)) } # basic function for converting inputs btween tibble and data.frame test_object <- function(object, use_class) { .as.data.frame.aqp(object, as.class = use_class) } # make some fake data df <- data.frame( id = cc(lapply(1:4, function(i) rep(i, 10))), top = cc(rep(0:9, 4)), bottom = cc(rep(1:10, 4)), siteprop = 8, prop = 18 ) # construct a test object of type use_class test <- test_object(object = df, use_class = use_class) test2 <- test # promote to SPC -- convert numeric ids to character expect_message(depths(test) <- id ~ top + bottom, c("converting profile IDs from integer to character")) # add fake coordinates crds <- data.frame(id = profile_id(test), y = rnorm(length(test)), x = rnorm(length(test))) site(test) <- crds # promote to spatial initSpatial(test, crs = "+proj=longlat +datum=WGS84") <- ~ x + y # show method should be produce output without error expect_output(show(test)) expect_output(show(test[0,])) # fill in diagnostics and restrictions with fake data diagnostic_hz(test) <- data.frame(id = profile_id(test), featkind = "foo", featdept = 0, featdepb = 10) restrictions(test) <- data.frame(id = profile_id(test), restrkind = "bar", restrdept = 0, restrdepb = 10) # try the character vector interface too expect_message(depths(test2) <- c("id", "top", "bottom"), c("converting profile IDs from integer to character")) # test rebuild expect_message(rebuildSPC(test2), "using `hzID` as a unique horizon ID") # test enforce_df_class expect_silent(.enforce_df_class(test2, use_class)) # "normalize" (horizon -> site) a site-level attribute site(test) <- ~ siteprop # new random XY data test$y <- rnorm(length(test)) test$x <- rnorm(length(test)) test$newx <- denormalize(test, "x") test$newy <- denormalize(test, "y") # promote to spatial works from horizons # formula and character are allowed inputs on RHS # LHS specifies optional CRS initSpatial(test, crs = "OGC:CRS84") <- ~ newx + newy # siteprop removed from horizons if (use_class == "tbl_df") { expect_warning(expect_null(horizons(test)$siteprop)) } else { expect_null(horizons(test)$siteprop) } # newx is still in horizon data (converted to site level only on coercion) expect_false(is.null(horizons(test)$newx)) # check that ids are in order expect_equal(profile_id(test), site(test)$id) # check that siteprop is in site expect_equal(site(test)$siteprop, rep(8, 4)) # check that siteprop is not in horizons (anymore) switch( use_class, # data.frame and data.table expect_equal(horizons(test)$siteprop, NULL), # note: this is only to cover a warning and to document differences # tbl_df will not return NULL -- warning and length(0) "tbl_df" = expect_warning( expect_equal(length(horizons(test)$siteprop), 0), "Unknown or uninitialised column: `siteprop`." ) ) # "denormalize" into a different-named horizon attribute horizons(test)$siteprop_hz <- denormalize(test, "siteprop") # check that siteprop_hz is in horizons # print(str(names(horizons(test)$siteprop_hz))) if(use_class == "tbl_df") { expect_equivalent(horizons(test)$siteprop_hz, rep(8, 40)) # note: tbl_df will have names attribute attached to result # which precludes expect_equal } else { expect_equal(horizons(test)$siteprop_hz, rep(8, 40)) } # add some site data, for only two sites value <- test_object(data.frame(id = as.character(2:3), siteclass = state.abb[1:2]), use_class) # left join site(test) <- value # inspect site site(test) # inspect site table with new variable (first/last NA), sorted correct expect_equal(which(is.na(site(test)$siteclass)), c(1, 4)) # inspect horizon table horizons(test) # test $ [[ setter/getter test$foo <- rep(100, nrow(test)) expect_true(all(test[['foo']] == horizons(test)$foo)) test[["foo"]] <- rep(200, nrow(test)) expect_equal(horizons(test)$foo, rep(200, nrow(test))) expect_equal(horizons(test)[[idname(test)]], as.character(cc(lapply(1:4, rep, 10)))) expect_equal(horizons(test)[[horizonDepths(test)[2]]], cc(rep(1:10, 4))) # creating and destroying site and horizon column vectors site(test)$a <- "foo" site(test)[["b"]] <- "bar" horizons(test)$c <- "baz" horizons(test)[["d"]] <- "qux" expect_equal(test$a, rep("foo", length(test))) expect_equal(test$b, rep("bar", length(test))) expect_equal(test$c, rep("baz", nrow(test))) expect_equal(test$d, rep("qux", nrow(test))) site(test)$a <- NULL site(test)[["b"]] <- NULL test$c <- NULL horizons(test)$d <- NULL expect_null(test$a) expect_null(test$b) expect_null(test$c) expect_null(test$d) # add some horizon data value <- test_object(data.frame( id = as.character(2:3), hzID = as.character(20:21), hzclass = letters[1:2] ), use_class) switch( use_class, # data.frame and tbl_df expect_silent({ horizons(test) <- value }), # note: data.table sorts correctly without invoking re-order "data.table" = { expect_silent( { horizons(test) <- value } ) } ) expect_equal(horizons(test)[[idname(test)]], as.character(cc(lapply(1:4, rep, 10)))) expect_equal(horizons(test)[[horizonDepths(test)[2]]], cc(rep(1:10, 4))) # deliberately mess up order without adding anything expect_silent(horizons(test) <- horizons(test)[c(1:10,20:30,11:19,31:40),]) # horizons<- fixes the order expect_true(spc_in_sync(test)$valid) # if there is no new data and IDs don't match, a message is issued and object is unchanged hzbad <- horizons(test) hzbad[[idname(test)]] <- paste0("h",1:nrow(hzbad)) expect_message(horizons(test) <- hzbad , "Some profile IDs in input data are not present in object and no new columns to merge. Doing nothing.") ## make sample data using current class type data(sp1, package = 'aqp') sp1df <- .as.data.frame.aqp(sp1, use_class, stripFactors = TRUE) depths(sp1df) <- id ~ top + bottom site(sp1df) <- ~ group # add real coordinates sp1df$x <- seq(-119, -120, length.out = length(sp1df)) sp1df$y <- seq(38, 39, length.out = length(sp1df)) ## tests test_that(sprintf("SPC construction from a %s", use_class), { # did it work? expect_true(inherits(sp1df, 'SoilProfileCollection')) # ID correctly initialized? expect_equal(idname(sp1df), 'id') expect_true(length(profile_id(sp1df)) == length(sp1df)) # ID in the correct order? expect_identical(profile_id(sp1df), site(sp1df)[[idname(sp1df)]]) # depth names? expect_equal(horizonDepths(sp1df), c('top', 'bottom')) # site-level attributes correctly initialized? expect_true(length(sp1df$group) == length(sp1df)) # correct number of profiles and horizons? expect_equal(length(sp1df), 9) expect_equal(nrow(sp1df), 60) # this should work foo <- explainPlotSPC(sp1df) expect_true(is.list(foo)) }) test_that(sprintf("SPC diagnostics and restrictions (%s)", use_class), { # diagnostic & restriction slot should be initialized as an empty data.frame sp1.dh <- diagnostic_hz(sp1df) expect_true(inherits(sp1.dh, 'data.frame')) expect_equal(nrow(sp1.dh), 0) sp1.rh <- restrictions(sp1df) expect_true(inherits(sp1.rh, 'data.frame')) expect_equal(nrow(sp1.rh), 0) }) test_that(sprintf("SPC data.frame interface (%s)", use_class), { # init site-level data sp1df$x <- seq(-119, -120, length.out = length(sp1df)) sp1df$y <- seq(38, 39, length.out = length(sp1df)) # init hz-level data sp1df$z <- runif(n = nrow(sp1df)) expect_equal(length(sp1df$x), length(sp1df)) expect_equal(length(sp1df$z), nrow(sp1df)) }) test_that(sprintf("SPC deconstruction into a data.frame (%s)", use_class), { # do it here h <- horizons(sp1df) s <- site(sp1df) d <- .as.data.frame.aqp(as(sp1df, 'data.frame'), use_class) expect_true(inherits(h, use_class)) expect_true(inherits(s, use_class)) expect_true(inherits(d, use_class)) }) test_that(sprintf("SPC deconstruction into a list (%s)", use_class), { # do it here l <- as(sp1df, 'list') # result should be a list expect_true(inherits(l, 'list')) # there should be no NULL data, e.g. missing slots res <- sapply(l, is.null) expect_false(any(res)) # check internals expect_equivalent(l$idcol, idname(sp1df)) expect_equivalent(l$hzidcol, hzidname(sp1df)) expect_equivalent(l$depthcols, horizonDepths(sp1df)) expect_equivalent(l$metadata, metadata(sp1df)) expect_equivalent(l$horizons, horizons(sp1df)) expect_equivalent(l$site, site(sp1df)) expect_equivalent(l$sp, sp1df@sp) expect_equivalent(l$diagnostic, diagnostic_hz(sp1df)) expect_equivalent(l$restrictions, restrictions(sp1df)) # check internals after [-subsetting sp1.sub <- sp1df[2:3,] # none of these slots should change, the others will be subset # verifying these are transferred ensures key info slots are handled # by the SPC subset method expect_equivalent(l$idcol, idname(sp1.sub)) expect_equivalent(l$hzidcol, hzidname(sp1.sub)) expect_equivalent(l$depthcols, horizonDepths(sp1.sub)) # TODO: better checking of metadata consistency # offload more important things to slots # use units package for depth units, and offer conversion? # inches to centimeters could be usefl expect_equivalent(names(l$metadata), names(metadata(sp1.sub))) }) test_that(sprintf("SPC subsetting (%s)", use_class), { # profile subsets expect_true(inherits(sp1df[1, ], 'SoilProfileCollection')) expect_true(inherits(sp1df[1:5, ], 'SoilProfileCollection')) # profile and horizon subsets expect_true(inherits(sp1df[1, 1], 'SoilProfileCollection')) # horizon subsets expect_true(inherits(sp1df[, 2], 'SoilProfileCollection')) # there should only be 1 profile and 1 horizon expect_equal(length(sp1df[1, 1]), 1) expect_equal(nrow(sp1df[1, 1]), 1) # there should be 5 profiles and 1 horizon / profile expect_equal(length(sp1df[1:5, 1]), 5) expect_equal(nrow(sp1df[1:5, 1]), 5) }) test_that(sprintf("SPC subsetting with tidy verbs (%s)", use_class), { # filter works as expected expect_equal(length(subset(sp1df, structure_type == "PL")), 1) # ensure multiple expressions yields same result as single expression l1 <- subset(sp1df, !is.na(texture), prop > mean(prop, na.rm = TRUE)) l2 <- subset(sp1df, !is.na(texture) & prop > mean(prop, na.rm = TRUE)) expect_equivalent(length(l1), length(l2)) # mixing of site and horizon level expressions is the intersection l1 <- subset(sp1df, group == 2, prop > mean(prop, na.rm = TRUE)) expect_equivalent(length(l1), 4) # grepSPC works as expected expect_equal(length(grepSPC(sp1df, texture, "SCL")), 1) # subApply works as expected expect_equal(length(subApply(sp1df, function(p) TRUE)), length(sp1df)) }) if (use_class == "data.table") { test_that("data.table specific", { expect_equal(min(sp1df), 59) expect_equal(max(sp1df), 240) }) } }) })