A MetadataImport is what import_metadata() returns for a file that describes one dataset. It holds the dataset-level information (name, label, study and domain), a VariableMetadata for each variable, the codelists that the variables use, and any declared missing-value codes, all in the same form whatever the format of the file.
The methods turn the metadata into things Pointblank can use: to_validate() builds a validation plan from the declared types and constraints, to_schema() gives a Schema of the column types, and missing_specs() builds MissingSpec objects from the missing-value codes. Variables and codelists can be looked up with get_variable() and get_codelist(), and summary() describes the whole dataset in text.
A MetadataImport can also be created directly, which is a way to write down the rules for a dataset that doesn’t come with a metadata file and still use the methods above. Calling len() on it gives the number of variables.
Parameters
source_format:str
The format the metadata was read from, such as "frictionless", "cdisc_define" or "spss". Any string can be used for metadata created directly.
source_path:str | None=None
The path of the file that was read. The default is None.
source_version:str | None=None
The version of the format or standard, such as "Define-XML 2.0". The default is None.
dataset_name:str | None=None
The name of the dataset, such as "DM". The default is None.
dataset_label:str | None=None
A human-readable label for the dataset, such as "Demographics". The default is None.
dataset_description:str | None=None
A longer description of the dataset. The default is None.
creation_date:str | None=None
When the metadata was created, as given in the file. The default is None.
study_id:str | None=None
The study identifier, for clinical data. The default is None.
domain:str | None=None
The CDISC domain, such as "DM" or "AE". The default is None.
variables:list[VariableMetadata]=list()
The metadata of each variable, as a list of VariableMetadata objects. The default is an empty list.
codelists:dict[str, Codelist]=dict()
The codelists used by the variables, as a dictionary from codelist name to Codelist. The default is an empty dictionary.
The schema describes six variables, and the allowed values of status were read into a codelist named "status_enum". Metadata can also be written down directly, here for a small survey table:
This turns the declared types and constraints into validation steps, so that a table can be checked against its own documentation. The first step is a col_schema_match() with the schema from to_schema(). Then, for each variable in turn:
col_vals_not_in_set() with its missing_values, if it’s a string variable that declares some
The last step checks that the declared missing-value markers don’t appear in the data, as when a file was read without converting markers like "NA" to nulls. The min_length and max_length of a variable aren’t checked. None of the steps pass null values, so a null in an optional variable fails its range, set and pattern checks. To change any of the steps, get the plan as code with to_code() and edit it.
Parameters
data:Any
The table to validate.
**kwargs:Any
Arguments for Validate, such as tbl_name=, thresholds= or actions=. A label= replaces the default label, which is "Validation: <dataset name> (<format>)", or "Validation from <format> metadata" if the metadata has no dataset name.
Returns
Validate
A Validate object with the steps added. It hasn’t been interrogated yet.
Examples
The bundled Frictionless Data Package describes a table of sales transactions. Let’s check a few transactions against it:
The metadata gave 17 steps. The table matches the declared schema, and three of the constraint checks find a failing row: the amount of 0.00 is below the minimum of 0.01, "toys" isn’t an allowed category, and "not-an-email" doesn’t match the pattern for email. To see how the plan was built, or to start from it and change some steps, print it as code:
Each step can be traced back to the metadata. For instance, the rows_distinct() step for transaction_id comes from its unique constraint, and the col_vals_not_in_set() steps check the "NA" and "N/A" markers declared for the string columns.
The schema has a column for each variable, in order, with the variable’s dtype (or "String" if the dtype isn’t known). It can be used with col_schema_match() to check that a table has the expected columns and types. Only the names and types are included: constraints such as ranges and allowed values aren’t part of a schema, and are checked by the steps that to_validate() adds after its schema check.
The schema lists the eight columns of the transactions table with their types. The dtype names are those of Polars, so a schema check against a table from another backend fails on the type names (pandas, for example, has object and int64 where Polars has String and Int64). Convert such a table to Polars before checking it.
Each variable that declares missing values (such as the user-defined missing values of an SPSS variable, or the missing-value markers of a Frictionless schema) gets a spec made by its to_missing_spec() method. The dictionary can be given to missing_vals_tbl() as its missing=, and a single spec from it can be given to the missing= parameter of a validation method such as col_vals_regex().
The specs keep the values as they were declared. Frictionless and CSVW markers are strings (like "NA") and are declared for every field, but in a table read with typed columns they can only appear in the string columns. Keep only the specs of those columns before using them, since a string marker can’t be compared with a numeric or date column.
Returns
dict[str, MissingSpec]
A dictionary from column name to MissingSpec. Variables that don’t declare missing values aren’t included.
Examples
The bundled Frictionless Data Package declares "NA" and "N/A" as missing-value markers, so every one of its variables gets a spec:
import pointblank as pbmeta = pb.import_metadata(pb.load_metadata_example("datapackage.json"))specs = meta.missing_specs()print(list(specs))print(specs["customer_id"].reasons)
The reasons were made from the markers, as no labels were declared for them. Let’s keep the specs of the string columns and use one of them in a validation step:
import polars as plstring_specs = { name: spec for name, spec in specs.items() if meta.get_variable(name).dtype =="String"}tbl = pl.DataFrame({"customer_id": ["CUST-101", "N/A", "CUST-103", "NA"]})( pb.Validate(data=tbl) .col_vals_regex(columns="customer_id", pattern="^CUST-[0-9]+$") .col_vals_regex( columns="customer_id", pattern="^CUST-[0-9]+$", missing=string_specs["customer_id"] ) .interrogate())
Without the spec, the "N/A" and "NA" markers fail the pattern check in step 1. With it, step 2 treats them as missing data instead, so all four rows pass. The string_specs dictionary could be given to missing_vals_tbl() in the same way, to summarize the missing data in each string column of a table.
The summary gives the format, the source file, the dataset name, label and domain (when known), and the numbers of variables and codelists. It then lists each variable with its dtype, its label, and its constraints: required, unique, the range, the number of allowed values, and the codelist. Printing a MetadataImport with print() shows the same summary.
Metadata Import (custom)
Dataset: survey
Label: Household survey
Variables: 3
Codelists: 0
Variables:
respondent_id (String) — Respondent ID [required, unique]
age (Int64) — Age in years [range=[18, 99]]
region (String) [4 allowed values]
The constraints in brackets are among those that to_validate() turns into validation steps, so the summary is a quick way to see what a table will be checked for (a pattern isn’t listed, though it’s checked too). For metadata read from a file, the summary also has a Source: line with the path of the file.