Codelists and controlled terminology¶
A codelist may come from published controlled terminology or from a sponsor's
own definitions. Both are written the same way: the lists of one source go in
a file that declares type: codelists, the project file lists those files,
and a column binds a list by its id. A specification uses the lists only
when it is checked or run through its project.
Select the sources and versions¶
The programming lead selects the terminology release when preparing the
specification. Review the published SDTM terminology
and ADaM terminology,
choose the lists the study needs, and record each selected release in its
file's standard.version. The publishing sets can use different releases,
but a Define-XML document cites one release of each: give every file of one
publishing set the same standard.version. Keep the sponsor's own lists in a
file without standard.
| Source | How to declare it |
|---|---|
| Published terminology | A codelists file whose standard gives name, publishing_set and version once for all its lists. |
| Sponsor-defined lists | A codelists file without standard, listing each list and its items. |
| Sponsor addition to a published list | An item marked extended: true, with no alias, in a published list marked extensible: true. |
The terminology release is separate from the language version in the
yamaa: "1.0" header and from the implementation guides a project section
lists in standards. yamaa reads the declarations the author supplies; it
does not download the publisher's catalogue or verify that the declared items
match that release. Review the selected values, concept aliases and
extensibility against the chosen release before using them in a study.
Write one file per source¶
A codelists file holds one source: an optional standard naming its
published terminology, and its codelists. The published examples below show
selected values from the illustrative release 2023-12-15; they are not
complete copies of either publishing set. Save the SDTM terminology as
codelists/sdtm-ct.yaml:
yamaa: "1.0"
type: codelists
standard:
name: CDISC/NCI
publishing_set: SDTM
version: "2023-12-15"
codelists:
- id: SEX
name: Sex
alias: C66731
format_name: $SEX
items:
- {value: F, decode: Female, alias: C16576}
- {value: M, decode: Male, alias: C20197}
- {value: U, decode: Unknown, alias: C17998}
- id: EPOCH
name: Epoch
extensible: true
alias: C99079
items:
- {value: SCREENING, alias: C48262}
- {value: TREATMENT, alias: C101526}
- {value: EXTENSION, extended: true}
the ADaM terminology as codelists/adam-ct.yaml:
yamaa: "1.0"
type: codelists
standard:
name: CDISC/NCI
publishing_set: ADaM
version: "2023-12-15"
codelists:
- id: DTYPE
name: Derivation Type
extensible: true
alias: C81224
items:
- {value: AVERAGE, alias: C81209}
and the sponsor's own list, whose age groups are invented, as
codelists/sponsor.yaml:
yamaa: "1.0"
type: codelists
codelists:
- id: AGEGR1
name: Sponsor Age Group
items:
- {value: "<65"}
- {value: ">=65"}
A list's id is what a column binds, and its name is the human-readable
description. An id is declared once in a file, and a later file may
replace a list by its id (below); a name is
unique among the lists the project keeps. A repeated id in one file, or a
repeated name, fails validation with duplicate_define_identifier.
alias records the published concept identifier of a list or of one item,
independent of the binding id. A Define-XML document under
context: Submission requires an alias on every CDISC/NCI list that
declares items and on each of its items except a sponsor addition, as the
Define-XML rule describes.
Each list declares either items or external, never both and never
neither. An external list defers to a dictionary and names the version
used, such as external: {dictionary: MedDRA, version: "26.1"}, with an
optional href. Coded values are unique within a list and of its
data_type: text, the default, integer or float. decode and rank
are declared for every item of a list or for none, and items keep their
declared order in a generated document.
List the files in the project¶
Save the project file as project.yaml. It lists the three codelists files,
and its adam section lists the specification of the next step, so the
project can open it by its domain name:
yamaa: "1.0"
type: project
codelists:
- codelists/sdtm-ct.yaml
- codelists/adam-ct.yaml
- codelists/sponsor.yaml
adam:
specs: [spec.yaml]
standards:
- {name: ADaMIG, version: "1.3", status: Final}
Relative paths resolve from the project file. A source may also be written
inline, as an entry of codelists that holds standard and codelists
itself in place of a path; a file lets several studies share one terminology
release. A project may declare lists that no specification binds: a
Define-XML document carries only the lists its datasets bind, so one set of
files serves the sdtm and adam sections alike.
Replace a shared list¶
The submission packages of one study can share the study's codelists files
and declare only the lists a package changes. The package's project lists the
shared files first, then its own file, and a list in a later file replaces
every earlier list of the same id:
codelists:
- ../study/codelists/sponsor.yaml
- codelists/package.yaml # declares only DCSREAS
The replacing list is the later declaration alone, its name, items and
decodes included; nothing of the list it replaces is kept. It belongs to its
own file, so it is drawn from that file's standard, and a Define-XML
document lists it where that file lists it. Every other list of the shared
files is kept as declared. A name stays unique among the lists the project
keeps: a later list that reuses a kept list's name under another id, such
as a replacement written under a misspelled id, fails with
duplicate_define_identifier. The
terminology rule states the
composition.
A package whose project file names the study's as its parent inherits the
study's codelists and needs none of its own. To replace a list, it writes
codelists itself, and since a list it writes replaces the inherited one
whole, it lists the study's files as well as its own, each path written from
the package's project file
(project layering).
Terminology standards and implementation guides¶
A codelists file's standard and a section's standards name different
things. The section's standards lists the implementation guides its
datasets follow, here ADaMIG 1.3: one or more guides of the section's model,
each named once. Each codelists file's standard names the terminology
release its lists are drawn from; terminology is never listed in a section's
standards.
standard.name takes the closed list a guide's name takes, the eleven
standard names Define-XML 2.1 publishes, in their exact case: ADaMIG,
ADaMIG-MD, BIMO, CDISC/NCI, SDTMIG, SDTMIG-AP, SDTMIG-MD,
SENDIG, SENDIG-AR, SENDIG-DART and SENDIG-GENETOX. CDISC Controlled
Terminology is CDISC/NCI. Any other name, such as CDISC CT, fails with
value_not_permitted at the source's standard.name wherever the project's
codelists are read: before any data, whether or not a document is generated.
publishing_set is one of ADaM, CDASH, DEFINE-XML, SDTM and SEND,
and version names the release, such as 2023-12-15.
A Define-XML document lists both kinds of standard: every guide of the
section's standards in declaration order, whether or not a dataset binds
it, then the terminology standard of each source whose lists its datasets
bind. Each dataset is bound to the guide its submission.standard names, or
to the first when it names none; a name the section does not list fails with
unknown_standard. Each list of a published source names its terminology
standard, and a sponsor list with items is marked def:IsNonStandard="Yes".
Sources that declare the same name, publishing_set and version share
one standard, and a document cites one release per publishing set: when two
sources whose lists one document binds declare one publishing_set with a
different name or version, the later source's publishing_set fails with
duplicate_define_identifier.
Bind and check a column¶
Save this minimal synthetic specification as spec.yaml beside
project.yaml, and the invented input below beside it as input.csv. The
example only demonstrates terminology bindings; it is not a complete ADaM
submission specification.
yamaa: "1.0"
type: spec
model: ADaM
domain: EXAMPLE
keys: [ID]
input:
RAW: input.csv
output:
path: example.csv
columns: [ID, SEX, EPOCH, DTYPE, AGEGR1]
columns:
- name: ID
type: str
label: Synthetic Identifier
derivation: RAW.ID
- name: SEX
type: str
label: Sex
derivation: RAW.SEX
submission:
codelist: SEX
origins:
- type: Assigned
- name: EPOCH
type: str
label: Epoch
derivation: RAW.EPOCH
submission:
codelist: EPOCH
origins:
- type: Assigned
- name: DTYPE
type: str
label: Derivation Type
derivation: RAW.DTYPE
submission:
codelist: DTYPE
origins:
- type: Assigned
- name: AGEGR1
type: str
label: Pooled Age Group 1
derivation: RAW.AGEGR1
submission:
codelist: AGEGR1
origins:
- type: Assigned
ID,SEX,EPOCH,DTYPE,AGEGR1
SYNTHETIC-001,F,TREATMENT,AVERAGE,<65
SYNTHETIC-002,M,EXTENSION,AVERAGE,>=65
library(yamaa)
spec <- yamaa_project("project.yaml")$spec("EXAMPLE")
spec$check()$issues # Checks the bindings; reads no study data.
run <- spec$run()
run$issues # Also checks the completed values.
run$output
import yamaa
spec = yamaa.project("project.yaml").spec("EXAMPLE")
print(spec.check().issues) # Checks the bindings; reads no study data.
run = spec.run()
print(run.issues) # Also checks the completed values.
print(run.output)
yamaa check project.yaml --spec EXAMPLE
yamaa run project.yaml --spec EXAMPLE # Also saves example.csv.
submission.codelist carries the list's id, not its values, and every
column naming that id shares the one declaration. A codelist binding checks
a completed value; it does not translate an input code. Use the mapping
derivation when source values need translation before the check.
| Bound list | What happens to completed column values |
|---|---|
List with items, extensible or not |
Every non-missing value must equal a declared item; missing values pass. Declare mandatory: true in the column's submission metadata if absence must fail. |
external dictionary |
The binding imposes no value check; yamaa does not read the dictionary. |
In this example, changing SEX to INVALID, DTYPE to OTHER or AGEGR1
to OTHER fails the run's value check with allowed_values_failed, which
names the column, the list, the row's key and the value. A text list binds a
str column, an integer list an int column and a float list a float
column; any other pairing fails validation with codelist_type_mismatch.
A list with items already states its column's value set, so an
allowed_values verification on the same column is rejected as a
redundant_declaration, even when the two lists agree. Beside an external
dictionary, or on a column with no binding, allowed_values stays an
ordinary check. A value-level entry may bind its own list for the rows its
where selects, and those rows are checked against that list instead.
Extend a published list¶
The published epoch list is extensible, so the study adds the period it
lacks, its open-label EXTENSION, as an item marked extended: true:
extended: trueis admitted only in a list markedextensible: truewhose file declaresstandard. Anywhere else it fails validation withcodelist_extension_not_admitted.- An added value has no published concept identifier, so it declares no
alias, and one that does fails withcodelist_extended_alias. A Define-XML document requires no alias of it and marks itdef:ExtendedValue="Yes". - A published term the study uses is an ordinary item with its
alias, even in an extensible list.
Both declaration checks run wherever the project's codelists are read, in
every context and before any data. An extensible list's declared items, the
additions included, are the whole value set the study admits: the published
list's openness is what lets the study declare an addition, and a value the
study never declared fails like any other. Changing an EPOCH in the input
to RUN-IN fails the run's value check although the list is extensible.
Format names¶
format_name is optional and names the SAS format a reader applies to a
list's coded values; a Define-XML document writes it as SASFormatName. A
text list's format is a character format and begins with $, as $SEX
does above. An integer or float list's is a numeric format and does not,
such as AGEGRN for numbered age groups. A text list whose format_name
lacks the $, or a numeric list's that begins with one, fails validation with
value_not_permitted at that format_name. The check runs wherever the
project's codelists are read, before any data, so a run refuses it although
only a Define-XML document carries the name.
Decode a coded value¶
A codelist's items pair each coded value with its decode, so a derivation
reads a parameter or visit name from the list instead of restating it in a
mapping. decode returns the decode of the item whose value equals its
source, as SAS put(PARAMCD, $PARAM.) does:
- name: PARAM
type: str
derivation:
decode:
source: PARAMCD
codelist: PARAMCD
It reads the list by its id, never by its format_name, and takes
mapping's answers: a missing source gives missing, or missing when none
is declared, and a value no item lists gives unmapped, and fails as
unmapped_value without it. Before any data is read, the source's type must
be the one the list's data_type is held in, str for a text list, and a
list the project does not declare fails with unknown_codelist, one without
decodes, or an external one, with codelist_without_decodes
(EXPR-15). The
parameter names example
decodes a text and an integer list.
A specification checked without its project¶
A specification that binds a codelist names something only its project supplies. Checked or run on its own, it fails validation at each binding, before any input is read:
yamaa_spec("spec.yaml")$check()$issues
print(yamaa.spec("spec.yaml").check().issues)
yamaa check spec.yaml
Each of the four bound columns gives one project_binding_missing issue
(CT-13) at its binding, such as
columns.SEX.submission.codelist, with the message
the codelist at columns.SEX.submission.codelist needs a project file, and none is given.
Its context names the binding, here codelist, the column and the
codelist. run() reports the same issues and builds nothing. A column that
declares inventory_vocabulary: true and a call to a project function fail
the same way, since only a project file supplies them. Through its project,
as above, each binding is checked in full, and one naming a list the project
does not declare fails with unknown_codelist.
yamaa_schema("codelists"), yamaa.schema("codelists") or
yamaa schema codelists gives the codelists file's JSON Schema, which an
editor can use to check a file as it is typed. See the
terminology schema for the fields a
codelists file or an inline source accepts and the
controlled terminology rule for
binding, extension and verification requirements. The
DM metadata example
shows versioned terminology sources in a complete Define-XML project, and
the sponsor epoch example
an extended item in its document.