missing_values_found¶
This rule checks if a mandatory column contain any missing values. A value is considered missing if it contains an empty string or if it contains one of the values considered as missing by the ODM dictionary.
Consider a mandatory column named geoLat
in a sites
table. The
following ODM dataset snippet would fail validation due to the first row
having an empty string for the geoLat
column value.
Invalid Dataset ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ siteID ┃ geoLat ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ 1 │ │ │ 2 │ 1 │ └────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘
In addition, column values that are an empty string when trimmed should also fail this validation rule. For example, the following ODM data snippet would also fail validation,
Invalid Dataset ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ siteID ┃ geoLat ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ 1 │ │ │ 2 │ 1 │ └────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘
Finally, if the ODM dictionary considers values of NA
to be missing,
the following would fail validation,
Invalid Dataset ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ siteID ┃ geoLat ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ 1 │ NA │ │ 2 │ 1 │ └────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘
The following dataset snippet would pass validation
Valid Dataset ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ siteID ┃ geoLat ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ 1 │ 2 │ │ 2 │ 1 │ └────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘
Warning report¶
A value that is invalid due to this rule should generate a warning and not an error. The warning report should have the following fields
warningType: missing_values_found
tableName: The name of the table containing the missing values
columnName: The name of the mandatory column containing the missing values
invalidValue: The invalid value
rowNumber: The index of the table row with the error
row: The dictionary containing the row
validationRuleFields: The ODM data dictionary rows used to generate this rule
message: Mandatory column <column_name> in table <table_name> has a missing value in row <row_index>
The warning report object for the example ODM datasets in the previous section are shown below.
Example 1 warning report,
{ │ 'warnings': [ │ │ { │ │ │ 'warningType': 'missing_values_found', │ │ │ 'tableName': 'sites', │ │ │ 'columnName': 'geoLat', │ │ │ 'rowNumber': 1, │ │ │ 'row': { │ │ │ │ 'siteID': '1', │ │ │ │ 'geoLat': '' │ │ │ }, │ │ │ 'invalidValue': '', │ │ │ 'validationRuleFields': [ │ │ │ │ { │ │ │ │ │ 'partID': 'geoLat', │ │ │ │ │ 'sites': 'header', │ │ │ │ │ 'sitesRequired': 'mandatory' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA_missing', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'null', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ } │ │ │ ], │ │ │ 'message': 'missing_values_found rule triggered in table sites, column geoLat, row(s) 1: Empty string found' │ │ } │ ], │ 'errors': [] }
Example 2 warning report,
{ │ 'warnings': [ │ │ { │ │ │ 'warningType': 'missing_values_found', │ │ │ 'tableName': 'sites', │ │ │ 'columnName': 'geoLat', │ │ │ 'invalidValue': ' ', │ │ │ 'rowNumber': 1, │ │ │ 'row': { │ │ │ │ 'siteID': '1', │ │ │ │ 'geoLat': ' ' │ │ │ }, │ │ │ 'validationRuleFields': [ │ │ │ │ { │ │ │ │ │ 'partID': 'geoLat', │ │ │ │ │ 'sites': 'header', │ │ │ │ │ 'sitesRequired': 'mandatory' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA_missing', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'null', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ } │ │ │ ], │ │ │ 'message': 'missing_values_found rule triggered in table sites, column geoLat, row(s) 1: Missing value " "' │ │ } │ ], │ 'errors': [] }
Example 3 warning report,
{ │ 'warnings': [ │ │ { │ │ │ 'warningType': 'missing_values_found', │ │ │ 'tableName': 'sites', │ │ │ 'columnName': 'geoLat', │ │ │ 'invalidValue': 'NA', │ │ │ 'rowNumber': 1, │ │ │ 'row': { │ │ │ │ 'siteID': '1', │ │ │ │ 'geoLat': 'NA' │ │ │ }, │ │ │ 'validationRuleFields': [ │ │ │ │ { │ │ │ │ │ 'partID': 'geoLat', │ │ │ │ │ 'sites': 'header', │ │ │ │ │ 'sitesRequired': 'mandatory' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'NA_missing', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ }, │ │ │ │ { │ │ │ │ │ 'partID': 'null', │ │ │ │ │ 'partType': 'missingness' │ │ │ │ } │ │ │ ], │ │ │ 'message': 'missing_values_found rule triggered in table sites, column geoLat, row(s) 1: Missing value "NA"' │ │ } │ ], │ 'errors': [] }
Rule metadata¶
All the metadata for this rule is contained in the parts sheet in the data dictionary. The steps involved are:
Get all values that represent missing from the parts sheet
Filter the parts to only include those whose
partType
column ismissingness
The
partID
column has the missing value
Add this rule for each mandatory column
For example the ODM snippet below will be used to generate the validation schema for the examples above,
Parts v2 ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ partID ┃ partType ┃ status ┃ sites ┃ sitesRequired ┃ firstReleased ┃ lastUpdated ┃ ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ NA │ missingness │ active │ NA │ NA │ 2.0.0 │ 2.0.0 │ │ null │ missingness │ active │ NA │ NA │ 1.0.0 │ 2.0.0 │ │ NA_missing │ missingness │ active │ NA │ NA │ 1.0.0 │ 2.0.0 │ │ sites │ tables │ active │ NA │ NA │ 1.0.0 │ 2.0.0 │ │ geoLat │ attributes │ active │ header │ mandatory │ 1.0.0 │ 2.0.0 │ │ geoLong │ attributes │ active │ header │ optional │ 1.0.0 │ 2.0.0 │ └────────────────┴─────────────────┴───────────┴──────────┴───────────────────┴───────────────────┴───────────────┘
we would add this rule only to the geoLat
column in the sites
table.
This rule would not be added to the geoLong
column in the same
table.
Cerberus Schema¶
We will need to use two rules from cerberus for this validation rule:
A forbidden rule which includes all the
missingness
values from the dictionaryA custom rule called
emptyTrimmed
for empty values which also handles trimming. Although the cerberus library has anempty
rule, the rule allows values that are “empty” once trimmed.
The cerberus schema for the ODM dictionary snippet above is shown below,
{ │ 'schemaVersion': '2.0.0', │ 'schema': { │ │ 'sites': { │ │ │ 'type': 'list', │ │ │ 'schema': { │ │ │ │ 'type': 'dict', │ │ │ │ 'schema': { │ │ │ │ │ 'geoLat': { │ │ │ │ │ │ 'emptyTrimmed': False, │ │ │ │ │ │ 'forbidden': [ │ │ │ │ │ │ │ 'NA', │ │ │ │ │ │ │ 'NA_missing', │ │ │ │ │ │ │ 'null' │ │ │ │ │ │ ], │ │ │ │ │ │ 'meta': [ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ 'ruleID': 'missing_values_found', │ │ │ │ │ │ │ │ 'meta': [ │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'geoLat', │ │ │ │ │ │ │ │ │ │ 'sites': 'header', │ │ │ │ │ │ │ │ │ │ 'sitesRequired': 'mandatory' │ │ │ │ │ │ │ │ │ }, │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'NA', │ │ │ │ │ │ │ │ │ │ 'partType': 'missingness' │ │ │ │ │ │ │ │ │ }, │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'NA_missing', │ │ │ │ │ │ │ │ │ │ 'partType': 'missingness' │ │ │ │ │ │ │ │ │ }, │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'null', │ │ │ │ │ │ │ │ │ │ 'partType': 'missingness' │ │ │ │ │ │ │ │ │ } │ │ │ │ │ │ │ │ ] │ │ │ │ │ │ │ } │ │ │ │ │ │ ] │ │ │ │ │ } │ │ │ │ }, │ │ │ │ 'meta': [ │ │ │ │ │ { │ │ │ │ │ │ 'partID': 'sites', │ │ │ │ │ │ 'partType': 'tables' │ │ │ │ │ } │ │ │ │ ] │ │ │ } │ │ } │ } }
The meta
field for this rule should have multiple entries,
An entry per missing value from the dictionary. This entry should include the
partID
andpartType
columnand the row from the parts sheet for the mandatory column. The entry should include the
partID
and the<table_name>Required
fields.
ODM Version 1¶
For version 1 validation schemas, we add this rule to only those version 2 columns which have a version 1 equivalent part.
For the ODM snippet below,
Parts v1 ┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ ┃ partID ┃ partType ┃ status ┃ sites ┃ sitesReq… ┃ version1… ┃ version1… ┃ version1… ┃ firstRel… ┃ lastUpda… ┃ ┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━┩ │ NA │ missingn… │ active │ NA │ NA │ NA │ NA │ NA │ 2.0.0 │ 2.0.0 │ │ null │ missingn… │ active │ NA │ NA │ NA │ NA │ NA │ 1.0.0 │ 2.0.0 │ │ NA_missi… │ missingn… │ active │ NA │ NA │ NA │ NA │ NA │ 1.0.0 │ 2.0.0 │ │ sites │ tables │ active │ NA │ NA │ tables │ Site │ NA │ 1.0.0 │ 2.0.0 │ │ geoLat │ attribut… │ active │ header │ mandatory │ variables │ Site │ latitude │ 1.0.0 │ 2.0.0 │ │ geoLong │ attribut… │ active │ header │ optional │ variables │ Site │ longitude │ 1.0.0 │ 2.0.0 │ └───────────┴───────────┴────────┴────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┘
The corresponding cerberus schema would be,
{ │ 'schemaVersion': '1.0.0', │ 'schema': { │ │ 'Site': { │ │ │ 'type': 'list', │ │ │ 'schema': { │ │ │ │ 'type': 'dict', │ │ │ │ 'schema': { │ │ │ │ │ 'latitude': { │ │ │ │ │ │ 'emptyTrimmed': False, │ │ │ │ │ │ 'forbidden': [ │ │ │ │ │ │ │ 'NA_missing', │ │ │ │ │ │ │ 'null' │ │ │ │ │ │ ], │ │ │ │ │ │ 'meta': [ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ 'ruleID': 'missing_values_found', │ │ │ │ │ │ │ │ 'meta': [ │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'geoLat', │ │ │ │ │ │ │ │ │ │ 'sites': 'header', │ │ │ │ │ │ │ │ │ │ 'sitesRequired': 'mandatory', │ │ │ │ │ │ │ │ │ │ 'version1Location': 'variables', │ │ │ │ │ │ │ │ │ │ 'version1Table': 'Site', │ │ │ │ │ │ │ │ │ │ 'version1Variable': 'latitude' │ │ │ │ │ │ │ │ │ }, │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'NA_missing', │ │ │ │ │ │ │ │ │ │ 'partType': 'missingness' │ │ │ │ │ │ │ │ │ }, │ │ │ │ │ │ │ │ │ { │ │ │ │ │ │ │ │ │ │ 'partID': 'null', │ │ │ │ │ │ │ │ │ │ 'partType': 'missingness' │ │ │ │ │ │ │ │ │ } │ │ │ │ │ │ │ │ ] │ │ │ │ │ │ │ } │ │ │ │ │ │ ] │ │ │ │ │ } │ │ │ │ }, │ │ │ │ 'meta': [ │ │ │ │ │ { │ │ │ │ │ │ 'partID': 'sites', │ │ │ │ │ │ 'partType': 'tables', │ │ │ │ │ │ 'version1Location': 'tables', │ │ │ │ │ │ 'version1Table': 'Site' │ │ │ │ │ } │ │ │ │ ] │ │ │ } │ │ } │ } }
The meta field for version 1 includes everything in version 2 but should also include the following columns for the entry for the mandatory column,
version1Location
version1Table
version1Variable