--- title: "02 — Metadata and external sources" author: "Pierpaolo Massoli" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{02 — Metadata and external sources} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` > Operational examples assume that the SSPICE project root is the working directory. Operational chunks are not executed when building the vignette because they require the project data. Standalone chunks explicitly marked `eval=TRUE` are exceptions. # Input files | File | Purpose | Preliminary check | |:--|:--|:--| | `routing_file.csv` | Nodes, categories and routing dependencies | Consistent identifiers and no cycles | | `vaux_rules.csv` | Auxiliary-variable definitions | Consistent conditions and assigned categories | | `logical_edit_rules.csv` | Logical constraints between variables | Consistent categories and operators | | `items_mapping.csv` | Correspondence between routing codes and observed codes for each variable | See [Item mapping](item-mapping-02c.html) for the coding contract and an executable example | | `data-raw/input/questionnaire_data.csv` | Responses to be checked | Agreed record key and missing-value codes | | `data-raw/external/*.csv` | Additional information for exogenous auxiliary variables | Linkage key and uniqueness at the required level of observation | The project's routing, auxiliary-variable and logical-edit metadata use `;` as the separator; `items_mapping.csv` uses commas. Configuration specifies these paths, the metadata separator, `missing_codes` and `record_id` (`DB030` in the included example). Do not infer a file's schema from its name: check its columns and key against the actual rules. ```{r metadata-check} source(file.path("scripts", "00_config.R")) stopifnot(all(file.exists(c(config$routing_file, config$vaux_file, config$logical_edit_rules_file, config$items_mapping_file)))) utils::head(utils::read.csv(config$items_mapping_file, check.names = FALSE)) ``` # Exogenous auxiliary variables and the observation unit An auxiliary variable requiring information on individual household members cannot be inferred from a file containing only the household respondent. In this project, `extfile3.csv` contains `DB030` and `EXIST_MINOR`; the latter indicates whether the household contains at least one individual with `PRESEN_B = 1` and `ETAINT < 15`. Auxiliary rules must refer to the correct external information, linked through `DB030`. Active exogenous auxiliary variables retain their source value during correction; if routing makes them inactive, they may be blanked to `NA`. Check missing source values before interpreting an error as an incorrect questionnaire response. # Defining auxiliary-variable categories `vaux_rules.csv` has nine columns without a header, separated by `;`. Each row contributes to a category definition; multiple rows with the same variable name and category form its condition. | Column | Meaning | |:--|:--| | V1 | Auxiliary-variable name | | V2 | Category assigned when the condition is true | | V3 | Variable used in this row | | V4, V5 | Optional external-source name without `.csv`, and linkage key | | V6 | Exact value; `BLANK` or `NA` denotes a missing value | | V7, V8 | Inclusive interval bounds | | V9 | Operator following the row, or comparison terminating a calculation | For logical conditions, the parser automatically opens a parenthesis at the start of each condition and closes it when it encounters `NULL`. | Logical token in V9 | Effect | |:--|:--| | `AND`, `OR` | Connect this row's condition to the next one | | `) AND (`, `) OR (` | Close the current group and open the next one | | `NULL` | Close and evaluate the condition for the row's category | | `IN(...)`, `NOT IN(...)` | Test membership or non-membership; `BLANK` represents a missing value | For example, the purely logical rule `A = 1 AND (D = 1 OR E = 3)` is represented as follows: | V1 | V2 | V3 | V6 | V7 | V8 | V9 | |:--|--:|:--|:--|:--|:--|:--| | VAUX_ESEMPIO | 1 | A | 1 | . | . | `) AND (` | | VAUX_ESEMPIO | 1 | D | 1 | . | . | `OR` | | VAUX_ESEMPIO | 1 | E | 3 | . | . | `NULL` | In the actual file, V4 and V5 are empty for variables already present in the data. If V4 specifies `extfile1`, `extfile2` or `extfile3`, V5 specifies the key (`DB030` in the project example). ## Purely arithmetic rules | Operation | Simple token in V9 | Grouping token in V9 | |:--|:--|:--| | Addition | `+` | `) + (` | | Subtraction | `-` | `) - (` | | Multiplication | `*` | `) * (` | | Division | `/` | `) / (` | The last row compares the result with a numeric constant: `n`, `>=n`, `=n`, `==n` or `!=n`. Positive or negative constants can be used, for example `<-2` or `>=0.5`. The comparison closes the expression and assigns the V2 category to data rows for which it is true. Purely arithmetic example, `(A+B)*(C-D) >= 10`: | V1 | V2 | V3 | V9 | |:--|--:|:--|:--| | VAUX_ARIT | 1 | A | `+` | | VAUX_ARIT | 1 | B | `) * (` | | VAUX_ARIT | 1 | C | `-` | | VAUX_ARIT | 1 | D | `>=10` | V4–V5 and V6–V8 are empty in this example. Simple operators follow R's arithmetic precedence; tokens containing parentheses close one group and open another. ## Mixed rules To continue after a comparison with a logical condition, write the comparison followed by a space and a connector: `<10 AND`, `<10 ) AND (`, `>=5 ) OR (`, or `=100 NULL`. `OR` and `) OR (` are also accepted. Example: `A < 10 AND (D = 1 OR E = 3)`. This is a strict comparison and also works when A contains decimal values. | V1 | V2 | V3 | V6 | V7 | V8 | V9 | |:--|--:|:--|:--|:--|:--|:--| | VAUX_MISTA | 1 | A | . | . | . | `<10 ) AND (` | | VAUX_MISTA | 1 | D | 1 | . | . | `OR` | | VAUX_MISTA | 1 | E | 3 | . | . | `NULL` | Example combining grouped arithmetic and logic: `((A + B) * (C - D) / F >= 5) OR (E = 1 AND A = 2)`. | V1 | V2 | V3 | V6 | V7 | V8 | V9 | |:--|--:|:--|:--|:--|:--|:--| | VAUX_CALCOLO | 1 | A | . | . | . | `+` | | VAUX_CALCOLO | 1 | B | . | . | . | `) * (` | | VAUX_CALCOLO | 1 | C | . | . | . | `-` | | VAUX_CALCOLO | 1 | D | . | . | . | `) / (` | | VAUX_CALCOLO | 1 | F | . | . | . | `>=5 ) OR (` | | VAUX_CALCOLO | 1 | E | 1 | . | . | `AND` | | VAUX_CALCOLO | 1 | A | 2 | . | . | `NULL` | Missing values in calculations produce conditions that are not true and leave the auxiliary variable at `NA` if no category applies. The available grouping tokens close one group and open another; they do not provide arbitrarily nested parentheses. `NOT IN(...)` supports set non-membership; general `NOT` and `XOR` between predicates are not reliably supported. Check overlapping categories and rows for which no condition is true before applying new rules to real data. ## Application example: project auxiliary variables In the project, `VAUX7` is derived from the external file `extfile3.csv`. The `DB030` key links each household to `EXIST_MINOR`, calculated over the whole household. Its two categories are defined by: ```text VAUX7;1;EXIST_MINOR;extfile3;DB030;1;.;.;NULL VAUX7;2;EXIST_MINOR;extfile3;DB030;0;.;.;NULL ``` `EXIST_MINOR` cannot be reconstructed from the questionnaire respondent alone: it comes from the presence of at least one member with `PRESEN_B = 1` and `ETAINT < 15` in the individual-level file used to prepare the source. The auxiliary rule is applied after linkage on `DB030`; when linkage fails, the category remains missing. A second actual example is `VAUX_PROP2`, which classifies the sum `QPROP1 + QPROP2`: ```text VAUX_PROP2;1;QPROP1;;;;;;+ VAUX_PROP2;1;QPROP2;;;;;;<100 VAUX_PROP2;2;QPROP1;;;;;;+ VAUX_PROP2;2;QPROP2;;;;;;=100 ``` These rows use arithmetic only, without logical operators. They can be inspected in the project outputs without modifying the data: ```{r vaux-case-study, eval=FALSE} # Run from the root of your project copy. d <- readRDS("output/prepared-data/prepared_questionnaire_data.rds") table(d$VAUX7, useNA = "ifany") table(d$VAUX_PROP2, useNA = "ifany") ``` # Dedicated routing guide The complete `routing_file.csv` schema, variable types, categories, conditional and unconditional jumps, and OR/XOR groups are described in [Building the routing file](routing-02b.html). Do not infer routing conventions from the auxiliary-variable file: the two formats have different purposes and fields. # Consistency of external sources Before preparation, check key uniqueness, key types, coverage of questionnaire DB030 values and availability of the values required by the rules. A complete questionnaire file combined with reduced external sources can produce many missing categories even when the original responses have not changed. When extracting a sample, subset each source using the same keys and document links that were already absent from the source. After replacing sources, regenerate prepared data, checks and extended data, and verify cache validity before correction. # Defining semantic edits: `logical_edit_rules.csv` This is a headerless nine-column file separated by semicolons. The columns have the same roles V1--V9 as the VAUX table, but the LEDIT evaluator has its own semantics. Each edit must define both accepted categories 1 and 2. Both are valid outcomes: **2 does not mean error**. If no condition assigns an accepted category, the result remains `NA` and the edit is violated. Choose and document what the two categories mean for each edit. For example, require A + B = 100 when amounts are reported, and allow both amounts to be blank when the edit is inapplicable. Define category 2 first for the all-blank case, then category 1 for the total: ```text LEDIT_TOTAL;2;A;;;BLANK;;;AND LEDIT_TOTAL;2;B;;;BLANK;;;NULL LEDIT_TOTAL;1;A;;;;;;+ LEDIT_TOTAL;1;B;;;;;;=100 ``` V4 and V5 are empty because A and B are input columns. V6 specifies an exact value or BLANK; V7 and V8 specify inclusive bounds instead. Arithmetic rows leave V6--V8 empty. V9 joins rows and closes a predicate with NULL or an arithmetic expression with a comparison. Keep each condition contiguous and use the same V1 and V2 throughout that condition. **Missing arithmetic operands are replaced by zero in the LEDIT evaluator.** Consequently A = 100 and B = NA satisfies the total in this example. If both amounts must be observed, define additional presence requirements. This differs from the VAUX arithmetic behavior described above. Logical conditions evaluating to NA do not assign a category. Conditions are processed in file order; the first matching condition wins. Later conditions cannot overwrite an assigned category. Avoid unintended overlaps. ```{r standalone-ledit, eval=TRUE} local({ edits <- read.table(text = paste( "LEDIT_TOTAL;2;A;;;BLANK;;;AND", "LEDIT_TOTAL;2;B;;;BLANK;;;NULL", "LEDIT_TOTAL;1;A;;;;;;+", "LEDIT_TOTAL;1;B;;;;;;=100", sep = "\n"), sep = ";", header = FALSE, colClasses = "character", na.strings = character(0)) x <- data.frame(ID = 1:5, A = c(40, NA, 40, 100, NA), B = c(60, NA, 50, NA, 20)) create_edits <- getFromNamespace("create_logical_edit_variables", "SSPICE") out <- create_edits(x, edits) print(out) stopifnot(identical(out$LEDIT_TOTAL, c(1, 2, NA_real_, 1, NA_real_))) print(attr(out, "logical_edit_definitions")) }) ``` The output categories are 1, 2, NA, 1, NA. The `logical_edit_definitions` attribute records the expressions and assignment counts. The internal evaluator is called explicitly here to illustrate the metadata; the operational entry point is Step 09, which also appends LEDIT nodes with accepted domain {1, 2} to the routing representation. For membership expressions, `IN(A,B)` compares the current variable with other columns in the same record, not with a list of numeric constants. `IN(BLANK)` tests missingness. Logical tokens include AND, OR, XOR and NOT; NOT in a connector position means AND NOT of the following predicate. For mixed arithmetic and logical groups the comparison connector has the form `>=5) AND (`. Do not copy the more permissive VAUX token syntax without checking it against the LEDIT parser. V4 specifies an external source basename and V5 its linkage key. External keys must be unique. Missing links remain missing, subject to the arithmetic zero convention above. If a referenced column already exists in the input, the evaluator retains that column; avoid conflicting source-column names. # Item mapping and semantic predicates [Item mapping](item-mapping-02c.html) explains the correspondence between observed categories and equidistant structural codes. Keep observed codes in the data and in predicates on those variables in VAUX/LEDIT metadata. Routing bounds and jump conditions use structural codes. Mapping does not rewrite semantic statements or change their arithmetic meaning.