Using ddi-l from R¶
R users can call ddi-l directly through
reticulate, the standard R interface
to Python. reticulate runs Python inside your R session and converts values in
both directions, so there is no separate R package to install and every
feature of ddi-l is available.
This page shows the R equivalents of the common tasks. The Python
User guide covers each feature in more depth; the calls are
the same, with $ in place of ..
Setup¶
Install reticulate once, then create a Python environment for ddi-l:
install.packages("reticulate")
library(reticulate)
virtualenv_create("ddi-l")
py_install("ddi-l", envname = "ddi-l") # or "ddi-l[full]" for faster parsing
In each session, select that environment before using Python:
reticulate 1.41 and later
reticulate::py_require("ddi-l") declares the dependency and lets
reticulate provision a suitable Python environment automatically, in place
of the two steps above.
Import the package. The object ddi is the Python module ddi_l:
Create a study¶
doc <- ddi$new_study(title = "Household Survey", agency = "example.org")
q_age <- doc$add_question(text = "How old are you?", label = "Age question")
age <- doc$add_variable(name = "age", question = q_age, label = "Age in years")
age <- age$set_numeric("Integer", low = 0L, high = 120L)
doc$save("household.xml")
print(doc)
# -> Document(title='Household Survey', agency='example.org', questions=1, variables=1)
Model objects stay attached to the document: you can keep editing age after
save(), and the next save includes the change.
Build a study from a data frame¶
Loop over the columns of a data frame and describe each one. The example reads the curriculum's sample file; save it in your working directory first:
survey <- read.csv("survey_sample.csv", stringsAsFactors = FALSE)
doc <- ddi$new_study(title = "Survey sample", agency = "example.org")
for (column in names(survey)) {
values <- survey[[column]]
variable <- doc$add_variable(name = column, label = column)
if (is.numeric(values)) {
type <- if (all(values == round(values))) "Integer" else "Decimal"
variable <- variable$set_numeric(
type,
low = as.character(min(values)),
high = as.character(max(values))
)
} else {
variable <- variable$set_text()
}
}
doc$save("survey.xml")
length(doc$variables)
# -> 5
Read a study into a data frame¶
Python lists arrive in R as lists, so vapply() turns them into columns:
opened <- ddi$open_ddi("survey.xml")
variables <- opened$variables
catalogue <- data.frame(
name = vapply(variables, function(v) v$names[[1]]$text, character(1)),
label = vapply(variables, function(v) v$labels[[1]]$text, character(1)),
identifier = vapply(variables, function(v) v$identifier, character(1))
)
catalogue[, c("name", "label")]
Validate and lint¶
validate() returns schema issues and lint() returns quality findings; both
are empty for a clean document:
issues <- opened$validate()
length(issues)
# -> 0
findings <- opened$lint()
lint_table <- data.frame(
rule = vapply(findings, function(f) f$rule_id, character(1)),
severity = vapply(findings, function(f) f$severity, character(1))
)
nrow(lint_table)
# -> 0
Custom properties and lookups¶
age <- opened$find(catalogue$identifier[2])
age$set_property("myorg:source_system", "CRM-2024")
age$get_property("myorg:source_system")
# -> "CRM-2024"
opened$save("survey.xml")
Handle errors¶
A Python exception becomes an R error, so tryCatch() works as usual.
py_last_error() tells you which ddi-l exception it was:
message <- tryCatch(
ddi$read_ddi("<DDIInstance><Broken></DDIInstance>"),
error = function(e) conditionMessage(e)
)
py_last_error()$type
# -> "DDIParseError"
See Validation for the full list of exception types.
JSON and linked data¶
ddi_l.jsonld renders a study as JSON-LD. The result arrives as a named R
list, ready for jsonlite:
jsonld <- import("ddi_l.jsonld")
graph <- jsonld$to_jsonld(opened)
names(graph)
json_text <- jsonlite::toJSON(graph, auto_unbox = TRUE, pretty = TRUE)
The command line from R¶
The ddi command is the module ddi_l.cli, so it can be run with the same
Python that reticulate uses:
output <- system2(
py_exe(),
c("-m", "ddi_l.cli", "validate", "survey.xml"),
stdout = TRUE
)
output
# -> "Document is valid."
Tips for reticulate¶
- Methods and attributes use
$:doc$add_variable(...),variable$identifier. - Keyword arguments are named R arguments:
label = "Age". - Integers: R numbers are doubles. Write
120Lwhere Python expects anint, or pass a string whereddi-laccepts one (low = "0"). NULLis converted to PythonNone.- Lists returned by
ddi-lare ordinary R lists, indexed from 1:doc$variables[[1]]. - Chained calls such as
set_numeric()return the object; assign the result (age <- age$set_numeric(...)) so R does not print it. - Special names need backticks:
ddi$`__version__`.