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
partTypecolumn ismissingnessThe
partIDcolumn 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
missingnessvalues from the dictionaryA custom rule called
emptyTrimmedfor empty values which also handles trimming. Although the cerberus library has anemptyrule, 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
partIDandpartTypecolumnand the row from the parts sheet for the mandatory column. The entry should include the
partIDand the<table_name>Requiredfields.
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,
version1Locationversion1Tableversion1Variable