studyframe keeps observations together with the information needed to interpret them: observational units, measurements, sampling, weights, assignment, and derivations. An analysis selects the relevant declarations and checks its requirements before calling an established statistical package.
studyframe is an experimental R package with a bounded schema and tested analysis adapters. It is not yet on CRAN. A well-formed declaration does not establish that its scientific claims or identification assumptions are true.
From a local checkout:
R CMD INSTALL .library(studyframe)The core package declares and validates data and context. Install survey for
survey analysis, estimatr for experimental analysis, and arrow plus zip for
portable bundle I/O:
install.packages(c("survey", "estimatr", "arrow", "zip"))The collection-to-inference vignette
executes a complete simulated handoff. Schema 0.3 and .studyframe bundles are
experimental and may change before a stable release.
library(studyframe)
observations <- data.frame(
id = 1:8,
school = rep(1:4, each = 2),
district = rep(1:2, each = 4),
weight = rep(2, 8),
income = c(12, 15, 18, 11, 20, 24, 16, 22)
)
study <- studyframe(
observations,
keys = "id",
unit = "pupil",
dataset_id = "pupil_survey",
variables = list(variable_spec("income", units = "USD/month")),
sampling = list(sampling_spec(
"schools",
population = "Pupils in the study districts",
stages = list(sampling_stage(
"school", method = "srs", strata = "district", replacement = "with"
)),
source = "Study sampling protocol"
)),
weights = list(weight_spec(
"main", "weight", sampling = "schools", source = "Released design weights"
))
)
result <- estimate_mean(study, outcome = "income", weight = "main")
result$fit
result$analysisWeights are selected explicitly. Measurement waves and row applicability are checked. Sampling and assignment are separate declarations. Missing information stays unknown; it does not silently become simple random sampling.
district_one <- study[study$district == 1, ]
estimate_mean(district_one, "income", "main", domain = TRUE)
renamed <- rename_studyframe(study, c(income = "monthly_income"))
estimate_mean(renamed, "income", "main")Stable variable IDs survive renaming. The survey design is constructed on the
full applicable roster before the domain is selected. domain = TRUE is an
analyst declaration, not a claim that any arbitrary restriction is ignorable.
A filtered view still contains and exports the full collection roster. It is not a redacted release. To distribute fewer original records, explicitly construct a new dataset and reconsider its design declarations.
derive_studyframe() adds a defined measurement without overwriting original facts.
recode_studyframe() records an explicit value mapping into a new measurement and
requires a policy for unmapped values.
join_studyframe() requires keys and a unique right-hand match. rbind() accepts
disjoint views of the same collection; it rejects incompatible collections.
Aggregation requires an explicit new observational unit and dataset. Direct
replacement operations are refused, and adapter-time validation detects mutations
that bypass the supported methods.
Schema 0.3 separates received responses from invited, sampled, and randomized rosters. Ordinary dataframe imports start with unknown completeness. Experimental inference requires an asserted-complete randomized roster scoped to its assignment, with source evidence. Evidence records carry source locations, optional hashes, capture times, producer identities, and versions. Instrument references connect measurements to question and subquestion IDs. Unknown facts remain unknown; planned sampling or assignment declarations cannot stand in for realized designs.
write_studyframe(study, "study.studyframe")
received <- read_studyframe("study.studyframe")
write_codebook(received, "codebook.md")One ZIP bundle contains data.parquet, roster.parquet, context.json, and an
integrity manifest. It contains no executable R expressions. Writers refuse to
overwrite an existing destination. RDS also preserves the R object, but the bundle
is the cross-language format. Bare data extraction is explicit:
as.data.frame(study) removes context.
This repository ships the R package. The bundle format and JSON schema remain language-independent. The producer example writes bundles with PyArrow and Python's standard library, without importing studyframe. R tests read small checked-in bundles from that producer and verify both accepted and rejected cases. Running the R package's tests does not require Python.
The earlier Python reader is preserved on
archive/python-reader.
It is deferred work, outside this package's build and release process.
request <- analysis_request("mean", "income", weight = "main")
requirements <- check_analysis(study, request)
requirements$issues
if (requirements$ok) result <- run_analysis(study, request)Checking prepares the design without fitting an outcome estimator. Issues include stable codes, categories, affected declarations, evidence references, and remedies. Estimation uses the same requirements and returns a native statistical fit together with the request, input identity, selected declarations, assumptions, omissions, and interval convention. A passing check supports execution of the declared analysis; it does not verify the study's scientific assertions.
run_analysis() raises studyframe_analysis_error with an issues field when
requirements fail. Catch that class to inspect the same issue records returned by
check_analysis(); underlying errors are retained in parent for debugging.
Issue records are plain data and can be serialized without the R condition or
its backtrace. Import errors similarly provide issues under
studyframe_import_error; both inherit from studyframe_error.
Request scalar fields must be ordinary strings or logical values. Classed scalars and one-cell matrices are rejected because they can change shape during JSON serialization. Missing-data, preview, and recode policy names require exact matches rather than abbreviations.
For an observational linear model, use regression_request() with numeric
predictor identifiers, an explicit target, and the assumptions supporting the
analysis. The adapter uses estimatr::lm_robust() with HC2 uncertainty for
unclustered observations or CR2/stata uncertainty for declared clusters. It
records the selected observations, joint missingness exclusions, clustering, and
model assumptions. Its coefficients describe associations; this route does not
certify causal identification or population representativeness, and it does not
apply sampling weights.
responses <- read_qualtrics("responses.csv", "instrument.qsf")
write_codebook(responses, "codebook.md")
context(responses)$diagnosticsThe importer maps supported questions through stable import IDs, preserves explicit recodes and source hashes, and can join responses to an authoritative external roster. Response-only imports are also supported when the QSF is unavailable; instrument identity and coding remain unresolved. Preview records require an explicit policy. An exported response table does not establish the original sampling or randomized roster.
See the collection workflow for supported question
types, a complete executable example, and composition with qualtRics downloads.
| Component | Implemented |
|---|---|
| Sampling | Explicit SRS stages; one-stage with replacement; multistage without replacement with FPC at every stage |
| Replicate weights | BRR, Fay, jackknife, bootstrap, and explicitly scaled other specifications through R survey |
| Survey analysis | Numeric means; explicit weight, wave, domain, and missing-data policies |
| Assignment | Two-condition complete randomization, including blocks and clusters, with constant declared assignment probability |
| Experimental analysis | estimatr::difference_in_means() on a complete roster and complete numeric outcomes |
| Regression | Unweighted numeric linear models with an intercept; HC2 or clustered CR2/stata uncertainty, explicit target and assumptions |
| Measurement | Definitions, units, category labels, named missingness codes, measurement occasion |
| Portability | Primitive integer, double, string, and boolean columns; one current table and its attached roster |
PPS, unknown collection mechanisms, and Bernoulli assignment can be recorded but are not translated by the current adapters. Taylor analysis of explicitly adjusted weights is refused because calibration and adjustment variance structures are not yet represented. Replicate specifications can carry adjustment effects when the producer supplies the appropriate replicates. Multiple imputation, variable eligibility logic, longitudinal estimators, arbitrary joint inclusion probabilities, and general multi-table studies are future work. Collection import currently covers a bounded set of Qualtrics CSV/QSF representations.
Factors, dates, nested columns, infinity, and NaN require explicit conversion.
Numeric missing codes remain in storage and are converted only for analysis.
missing = "omit" records an omission; it does not justify a nonresponse assumption.
Unsupported dataframe libraries may drop the class or alter its contents; only
documented studyframe operations provide the preservation contract.
See the schema and requirements, four reproducible workflows, and API documentation.
Rscript -e 'pak::local_install_dev_deps()'
make cimake ci runs R linting, statistical and bundle-conformance tests, R CMD check,
and an R source-package build. make document regenerates the R reference
documentation. The README is the package website's main page. make format uses
Air for R.
The four deterministic workflows cover stratified cluster sampling and domains, replicate-weight inference, blocked cluster randomization, and wave-specific weights. Tests compare estimates, standard errors, and degrees of freedom against direct backend calls and deliberately break alignment and applicability.
For a stochastic collection-to-inference demonstration, run:
source(system.file("examples", "simulated_study.R", package = "studyframe"))
run_simulated_study("simulation", repetitions = 500L)This creates a finite population, samples schools, randomizes treatment, collects observed outcomes, joins shuffled records, and writes and reloads a portable bundle before analysis. Repeated runs report bias, estimated versus empirical uncertainty, and interval coverage against known truth. See the simulation design for the two inferential targets and limitations.
MIT. See LICENSE.