% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/schema.R
\name{sch_schema}
\alias{sch_schema}
\alias{sch_others}
\alias{sch_any}
\alias{sch_multiple}
\alias{sch_nest}
\alias{sch_numeric}
\alias{sch_integer}
\alias{sch_logical}
\alias{sch_character}
\alias{sch_factor}
\alias{sch_date}
\alias{sch_datetime}
\alias{sch_inherits}
\alias{sch_list_of}
\alias{sch_custom}
\title{Define a structured data type}
\usage{
sch_schema(..., .desc = NULL, .relationships = NULL)

sch_others()

sch_any(desc = NULL, missing = TRUE, required = TRUE, distinct = FALSE)

sch_multiple(
  name,
  type,
  desc = NULL,
  required = TRUE,
  check = NULL,
  msg = NULL,
  coerce = NULL
)

sch_nest(..., .desc = NULL)

sch_numeric(
  desc = NULL,
  bounds = c(-Inf, Inf),
  closed = c(TRUE, TRUE),
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_integer(
  desc = NULL,
  bounds = c(-Inf, Inf),
  closed = c(TRUE, TRUE),
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_logical(desc = NULL, missing = TRUE, required = TRUE, distinct = FALSE)

sch_character(desc = NULL, missing = TRUE, required = TRUE, distinct = FALSE)

sch_factor(
  desc = NULL,
  levels = NULL,
  strict = TRUE,
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_date(
  desc = NULL,
  bounds = c(as.Date(-Inf), as.Date(Inf)),
  closed = c(FALSE, FALSE),
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_datetime(
  desc = NULL,
  bounds = c(as.POSIXct(-Inf), as.POSIXct(Inf)),
  closed = c(FALSE, FALSE),
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_inherits(
  desc = NULL,
  class,
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_list_of(
  desc = NULL,
  class,
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)

sch_custom(
  name,
  desc = NULL,
  check,
  msg,
  coerce,
  ...,
  missing = TRUE,
  required = TRUE,
  distinct = FALSE
)
}
\arguments{
\item{...}{Column specifications, in the form of `col_name = col_type` pairs,
  where `col_type` is a call to a column type constructor listed here, such
  as `sch_numeric()`. Every type must be a kind of vector, i.e.,
  [vctrs::obj_is_vector()] must return `TRUE`.

  All columns must be named, except for `sch_others()`, as described below,
  and `sch_multiple()`, which describes a group of columns sharing the same
  type. A named `sch_nest()` describes columns stored as a nested data frame.

  The special function `sch_others()` indicates the preferred location of
  other columns not explicitly mentioned in the schema. If no `sch_others()`
  appears, then other columns are not allowed.
  Trailing commas are permitted.}

\item{.relationships}{An optional one-sided formula describing the structural
relationships between values in different columns. Formulas can only involve
named arguments to `...`. Use `*` to signify crossed levels, which will
verify all combinations exist, `/` to signify nested levels, and `+` to create
compound keys (bundling columns into a single identifier).
See the examples below.}

\item{desc, .desc}{A description of the column for consumers of the schema.
The type contraints will be described separately and do not need to be
included in the description.  For example for "age", the description might
be "Age of the patient in years", not "Non-negative integer representing
the age of the patient in years". For the overall `sch_schema`, the `desc`
will be printed as part of the header for data frames implementing the
schema, by default.}

\item{missing}{If `TRUE`, the column may be contain missing values. Otherwise,
any missing values result in an error.}

\item{required}{If `TRUE` (default), the group entry in `sch_groups` must
contain at least one column name. If `FALSE`, an empty character vector
for that entry is also accepted.}

\item{distinct}{If `TRUE`, the column must contain no duplicate values (after
accounting for nesting structure).}

\item{name}{A name for the custom type.}

\item{type}{A column type constructor (e.g. [sch_numeric()]) specifying the
expected type of every column in the group.}

\item{check}{A two-argument function that checks whether an object satisfies
the type. The first argument is the object to check, and the second is the
full type specification.}

\item{msg}{A one-argument function that generates a descriptive message
about the type when passed the type object itself. Should not end with a period.}

\item{coerce}{A two-argument function that attempts to coerce an object to the
type. The first argument is the object to coerce, and the second is the full
type specification.}

\item{bounds}{Length-two vector `c(min, max)` specifying the allowed range of values.}

\item{closed}{Length-two logical vector specifying whether the bounds are
closed (inclusive) or open (exclusive).}

\item{levels}{A character vector of factor levels, or NULL not enforce specific levels.}

\item{strict}{If `TRUE`, only factors with the specified levels are accepted.
If `FALSE`, character vectors with the specified levels are also accepted.}

\item{class}{A character vector of class names.}
}
\value{
An object of class `sch_schema`,
}
\description{
Defines the structure of a single 'observation' for a structured data frame.
Each column has type restrictions and may be required or optional.
Schemas support nesting relationships.
}
\section{Functions}{
\itemize{
\item \code{sch_others()}: A placeholder for other non-required columns in a schema.

\item \code{sch_any()}: A column of any type. No type checking is performed.

\item \code{sch_multiple()}: A group of multiple columns sharing the same type. The
  group is identified by `name`, which must appear as an entry in the
  `sch_groups` attribute of the data frame being validated. That entry is a
  character vector of column names that belong to this group.

  Optionally accepts cross-column `check`, `msg`, and `coerce` functions that
  are applied to the entire group after per-column type checks pass. These
  must all be provided together or not at all.

  `sch_multiple()` must be unnamed in an `sch_schema()` call. Per-column
  constraints such as `missing` and `distinct` are set on the inner `type`
  argument.

\item \code{sch_nest()}: A set of columns stored as a nested list-column of
data frames. Must be given a name in the outer `sch_schema()`.

\item \code{sch_numeric()}: A numeric vector that is optionally constrained to be
within a certain range.

\item \code{sch_integer()}: An integer vector that is optionally constrained to be
within a certain range.

\item \code{sch_logical()}: A logical vector.

\item \code{sch_character()}: A character vector.

\item \code{sch_factor()}: A factor with specified levels.

\item \code{sch_date()}: A Date vector that is optionally constrained to be
within a certain range.

\item \code{sch_datetime()}: A POSIXct vector that is optionally constrained to be
within a certain range.

\item \code{sch_inherits()}: A list-column whose elements satisfy `inherits(_, class)`.

\item \code{sch_list_of()}: A vector satisfying `inherits(_, class)`.

\item \code{sch_custom()}: A custom type defined by user-provided check, type message,
and coercion functions. Additional named values to be stored along with the
type specification may be passed via `...` and will be available to the
check, message, and coercion function as elements of the `type` argument.

}}
\section{Worked examples}{

Three larger end-to-end example scripts are bundled with the package and
show how to build a schema, attach group metadata, validate a compliant
data frame, and exercise a range of corruption cases. List them with:

```r
list.files(system.file("examples", package = "schmear"), full.names = TRUE)
```

The included scripts are `mcmc_draws.R` (fully-crossed MCMC posterior
draws), `ei_spec.R` (an ecological-inference specification using
[sch_multiple()] groups with a row-sum cross-column check), and
`election_data.R` (tidy election returns with a compound primary key and
nested/crossed relationship structure).
}

\examples{
sch_schema(
    .desc = "MCMC draws",
    .relationships = ~ chain * draw * parameter,
    chain = sch_integer("Chain number"),
    draw = sch_integer("Draw number", bounds = c(1, Inf), closed = c(TRUE, FALSE)),
    parameter = sch_factor("Parameter name", levels = c("mu", "sigma", "log_lik")),
    value = sch_numeric("Parameter value")
)

sch_schema(
    .desc = "Student data",
    .relationships = ~ (grade + teacher) / table_group,
    birthday = sch_date("Date of birth", required = FALSE),
    height = sch_numeric(
        "Height in inches",
        bounds = c(0, 108),
        closed = c(FALSE, TRUE)
    ),
    grade = sch_factor(strict = FALSE, levels = c("Kindergarten", "1st", "2nd")),
    teacher = sch_nest(
        first = sch_character("First name"),
        last = sch_character("Last name")
    ),
    table_group = sch_integer(bounds=c(1, 6)),
    enrolled = sch_logical(missing = FALSE),
    sch_others()
)

sch_schema(
    .desc = "Causal inference data",
    treatment = sch_factor(levels = c("control", "treatment"), missing = FALSE),
    outcome = sch_numeric(missing = FALSE),
    sch_multiple("covariates", type = sch_any(missing = FALSE), required = FALSE),
    sch_others()
)

sch_custom(
   name = "even",
   check = function(x, type) is.integer(x) && all(x \%\% 2 == 0),
   msg = function(type) "vector of even integers",
   coerce = function(x, type) (as.integer(x) \%/\% 2) * 2
)
}
