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:

  1. Get all values that represent missing from the parts sheet

    1. Filter the parts to only include those whose partType column is missingness

    2. The partID column has the missing value

  2. Get the table names from the dictionary

  3. Get all mandatory columns for each table

  4. 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:

  1. A forbidden rule which includes all the missingness values from the dictionary

  2. A custom rule called emptyTrimmed for empty values which also handles trimming. Although the cerberus library has an empty 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 and partType column

  • and 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