Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend xportr_write to accept metadata and deprecate label #185

Merged
merged 13 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: xportr
Title: Utilities to Output CDISC SDTM/ADaM XPT Files
Version: 0.3.1
Version: 0.3.1.9001
Authors@R:
c(
person(given = "Eli",
Expand Down
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# xportr 0.3.1.9001

## New Features and Bug Fixes
* `xportr_write()` now accepts `metadata` argument which can be used to set the dataset label to stay consistent with the other `xportr_*` functions. It is noteworthy that the dataset label set using the `xportr_df_label()` function will be retained during the `xportr_write()`.

## Deprecation and Breaking Changes
* The `label` argument from the `xportr_write()` function is deprecated in favor of the `metadata` argument.

# xportr 0.3.0

## New Features and Bug Fixes
Expand Down
4 changes: 4 additions & 0 deletions R/df_label.R
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ xportr_df_label <- function(.df,
abort("Length of dataset label must be 40 characters or less.")
}

if (stringr::str_detect(label, "[^[:ascii:]]")) {
abort("`label` cannot contain any non-ASCII, symbol or special characters.")
}

attr(.df, "label") <- label

.df
Expand Down
46 changes: 31 additions & 15 deletions R/write.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#' @param .df A data frame to write.
#' @param path Path where transport file will be written. File name sans will be
#' used as `xpt` name.
#' @param label Dataset label. It must be <=40 characters.
#' @param label `r lifecycle::badge("deprecated")` Previously used to to set the Dataset label.
#' Use the `metadata` argument to set the dataset label.
#' @param strict_checks If TRUE, xpt validation will report errors and not write
#' out the dataset. If FALSE, xpt validation will report warnings and continue
#' with writing out the dataset. Defaults to FALSE
#' @inheritParams xportr_length
#'
#' @details
#' * Variable and dataset labels are stored in the "label" attribute.
Expand All @@ -32,17 +34,43 @@
#' Param = c("param1", "param2", "param3")
#' )
#'
#' var_spec <- data.frame(dataset = "adsl", label = "Subject-Level Analysis Dataset")
#' xportr_write(adsl,
#' path = paste0(tempdir(), "/adsl.xpt"),
#' label = "Subject-Level Analysis",
#' metadata = var_spec,
#' strict_checks = FALSE
#' )
#'
xportr_write <- function(.df, path, label = NULL, strict_checks = FALSE) {
xportr_write <- function(.df,
path,
metadata = NULL,
domain = NULL,
strict_checks = FALSE,
label = deprecated()) {
path <- normalizePath(path, mustWork = FALSE)

name <- tools::file_path_sans_ext(basename(path))

## Common section to detect domain from argument or pipes

df_arg <- tryCatch(as_name(enexpr(.df)), error = function(err) NULL)
domain <- get_domain(.df, df_arg, domain)
if (!is.null(domain)) attr(.df, "_xportr.df_arg_") <- domain

## End of common section

if (!missing(label)) {
lifecycle::deprecate_warn(
when = "0.3.2",
what = "xportr_write(label = )",
with = "xportr_write(metadata = )"
)
metadata <- data.frame(dataset = domain, label = label)
}
if (!is.null(metadata)) {
.df <- xportr_df_label(.df, metadata = metadata, domain = domain)
}

if (nchar(name) > 8) {
abort("`.df` file name must be 8 characters or less.")
}
Expand All @@ -51,18 +79,6 @@ xportr_write <- function(.df, path, label = NULL, strict_checks = FALSE) {
abort("`.df` cannot contain any non-ASCII, symbol or underscore characters.")
}

if (!is.null(label)) {
if (nchar(label) > 40) {
abort("`label` must be 40 characters or less.")
}

if (stringr::str_detect(label, "[^[:ascii:]]")) {
abort("`label` cannot contain any non-ASCII, symbol or special characters.")
}

attr(.df, "label") <- label
}

checks <- xpt_validate(.df)

if (length(checks) > 0) {
Expand Down
4 changes: 2 additions & 2 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ adsl %>%
xportr_label(var_spec, "ADSL", verbose = "warn") %>%
xportr_order(var_spec, "ADSL", verbose = "warn") %>%
xportr_format(var_spec, "ADSL") %>%
xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
xportr_write("adsl.xpt")
vedhav marked this conversation as resolved.
Show resolved Hide resolved
```

The `xportr_metadata()` function can reduce duplication by setting the variable specification and domain explicitly at the top of a pipeline. If you would like to use the `verbose` argument, you will need to set in each function call.
Expand All @@ -145,7 +145,7 @@ adsl %>%
xportr_label() %>%
xportr_order() %>%
xportr_format() %>%
xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
xportr_write("adsl.xpt")
```

That's it! We now have a xpt file created in R with all appropriate types, lengths, labels, ordering and formats. Please check out the [Get Started](https://atorus-research.github.io/xportr/articles/xportr.html) for more information and detailed walk through of each `xportr_` function.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ adsl %>%
xportr_label(var_spec, "ADSL", verbose = "warn") %>%
xportr_order(var_spec, "ADSL", verbose = "warn") %>%
xportr_format(var_spec, "ADSL") %>%
xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
xportr_write("adsl.xpt")
```

The `xportr_metadata()` function can reduce duplication by setting the
Expand All @@ -156,7 +156,7 @@ adsl %>%
xportr_label() %>%
xportr_order() %>%
xportr_format() %>%
xportr_write("adsl.xpt", label = "Subject-Level Analysis Dataset")
xportr_write("adsl.xpt")
```

That’s it! We now have a xpt file created in R with all appropriate
Expand Down
22 changes: 19 additions & 3 deletions man/xportr_write.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

125 changes: 106 additions & 19 deletions tests/testthat/test-write.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,48 @@ test_that("xportr_write: exported data can be saved to a file", {
expect_equal(read_xpt(tmp), data_to_save)
})

test_that("xportr_write: exported data can be saved to a file with a label", {
test_that("xportr_write: exported data can still be saved to a file with a label", {
tmpdir <- tempdir()
tmp <- file.path(tmpdir, "xyz.xpt")

on.exit(unlink(tmpdir))

xportr_write(data_to_save, path = tmp, label = "Lorem ipsum dolor sit amet")
suppressWarnings(xportr_write(data_to_save, path = tmp, label = "Lorem ipsum dolor sit amet"))
expect_output(str(read_xpt(tmp)), "Lorem ipsum dolor sit amet")
})

test_that("xportr_write: exported data can be saved to a file with a metadata", {
tmpdir <- tempdir()
tmp <- file.path(tmpdir, "xyz.xpt")

on.exit(unlink(tmpdir))

xportr_write(
data_to_save,
path = tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "Lorem ipsum dolor sit amet"
)
)
expect_output(str(read_xpt(tmp)), "Lorem ipsum dolor sit amet")
})

test_that("xportr_write: exported data can be saved to a file with a existing metadata", {
tmpdir <- tempdir()
tmp <- file.path(tmpdir, "xyz.xpt")

on.exit(unlink(tmpdir))

df <- xportr_df_label(
data_to_save,
data.frame(
dataset = "data_to_save",
label = "Lorem ipsum dolor sit amet"
)
)

xportr_write(df, path = tmp)
expect_output(str(read_xpt(tmp)), "Lorem ipsum dolor sit amet")
})

Expand All @@ -26,7 +61,16 @@ test_that("xportr_write: expect error when invalid multibyte string is passed in

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = "Lorizzle ipsizzle dolizzl\xe7 pizzle"))
expect_error(
xportr_write(
data_to_save,
tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "Lorizzle ipsizzle dolizzl\xe7 pizzle"
vedhav marked this conversation as resolved.
Show resolved Hide resolved
)
)
)
})

test_that("xportr_write: expect error when file name is over 8 characters long", {
Expand All @@ -35,7 +79,7 @@ test_that("xportr_write: expect error when file name is over 8 characters long",

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = "asdf"))
expect_error(xportr_write(data_to_save, tmp))
})

test_that("xportr_write: expect error when file name contains non-ASCII symbols or special characters", {
Expand All @@ -44,7 +88,7 @@ test_that("xportr_write: expect error when file name contains non-ASCII symbols

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = "asdf"))
expect_error(xportr_write(data_to_save, tmp))
})

test_that("xportr_write: expect error when label contains non-ASCII symbols or special characters", {
Expand All @@ -53,7 +97,22 @@ test_that("xportr_write: expect error when label contains non-ASCII symbols or s

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = "çtestç"))
expect_error(
xportr_write(
data_to_save,
tmp,
expect_error(
xportr_write(
data_to_save,
tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "çtestç"
)
)
)
)
)
})

test_that("xportr_write: expect error when label is over 40 characters", {
Expand All @@ -62,7 +121,16 @@ test_that("xportr_write: expect error when label is over 40 characters", {

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = paste(rep("a", 41), collapse = "")))
expect_error(
xportr_write(
data_to_save,
tmp,
metadata = data.frame(
dataset = "data_to_save",
label = paste(rep("a", 41), collapse = "")
)
)
)
})

test_that("xportr_write: expect error when an xpt validation fails with strict_checks set to TRUE", {
Expand All @@ -72,7 +140,16 @@ test_that("xportr_write: expect error when an xpt validation fails with strict_c

on.exit(unlink(tmpdir))

expect_error(xportr_write(data_to_save, tmp, label = "label", strict_checks = TRUE))
expect_error(
xportr_write(
data_to_save, tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "label"
),
strict_checks = TRUE
)
)
})

test_that("xportr_write: expect warning when an xpt validation fails with strict_checks set to FALSE", {
Expand All @@ -82,18 +159,18 @@ test_that("xportr_write: expect warning when an xpt validation fails with strict

on.exit(unlink(tmpdir))

expect_warning(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE))
expect_warning(
xportr_write(
data_to_save, tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "label"
),
strict_checks = FALSE
)
)
})

test_that("xportr_write: expect warning when an xpt validation fails with strict_checks set to FALSE", {
tmpdir <- tempdir()
tmp <- file.path(tmpdir, "xyz.xpt")
attr(data_to_save$X, "format.sas") <- "foo"

on.exit(unlink(tmpdir))

expect_warning(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE))
})

test_that("xportr_write: Capture errors by haven and report them as such", {
tmpdir <- tempdir()
Expand All @@ -102,8 +179,18 @@ test_that("xportr_write: Capture errors by haven and report them as such", {

on.exit(unlink(tmpdir))


expect_error(
suppressWarnings(xportr_write(data_to_save, tmp, label = "label", strict_checks = FALSE)),
suppressWarnings(
xportr_write(
data_to_save, tmp,
metadata = data.frame(
dataset = "data_to_save",
label = "label"
),
strict_checks = FALSE
)
),
"Error reported by haven"
)
})
Loading
Loading