1. Introduction
Data quality validation is an essential step in building reliable data pipelines. Instead of relying on manual inspections, organizations increasingly use automated validation frameworks to detect data issues early and ensure consistent data quality over time.
This notebook demonstrates how to implement an automated data quality validation workflow using Great Expectations. The project covers the complete validation process, from defining expectations based on business rules to executing validations and generating documentation.
The examples use the OurAirports Argentina dataset to illustrate how data quality checks can be automated in a practical, reproducible, and maintainable way.
import great_expectations as gx
import pandas as pd
2. Dataset
Load the OurAirports dataset, select the columns required for validation, and filter the records to include airports located in Argentina.
# Load the worldwide airports dataset and select the relevant columns
airports_df = pd.read_csv("airports.csv")
airports_df = airports_df[['id', 'ident', 'type', 'name', 'latitude_deg', 'longitude_deg',
'elevation_ft', 'iso_country', 'gps_code']]
# Filter airports located in Argentina
argentina_df = airports_df[airports_df['iso_country'] == 'AR'].reset_index(drop=True)
# Display the first five rows
argentina_df.head()
| id | ident | type | name | latitude_deg | longitude_deg | elevation_ft | iso_country | gps_code |
|---|---|---|---|---|---|---|---|---|
| 35333 | AR-0001 | small_airport | Cullen Airport | -52.885740 | -68.414956 | 132.0 | AR | NaN |
| 35334 | AR-0002 | small_airport | Estancia Los Cerros Airport | -54.343000 | -67.837532 | 1914.0 | AR | NaN |
| 35335 | AR-0003 | small_airport | Rio Bellavista Airport | -53.982700 | -68.523598 | 201.0 | AR | NaN |
| 35398 | AR-0004 | small_airport | Merlo Airport | -32.358200 | -65.017403 | 796.0 | AR | NaN |
| 35399 | AR-0005 | small_airport | Bragado Airport | -35.145811 | -60.480294 | 196.0 | AR | SA2X |
3. Environment Setup
This project uses a persistent Great Expectations context rather than an in-memory configuration. Persisting the project structure allows expectations, validation results, and documentation to be managed consistently and prepares the project for future integration into automated data pipelines.
# Initialize the Great Expectations context
context = gx.get_context(project_root_dir="gx")
4. Creating the Validation Batch
Configure the data source, create a data asset, define a batch, and load the dataset into Great Expectations for validation.
# Create a Pandas data source
data_source = context.data_sources.add_pandas(
name="local_pandas"
)
# Create a data asset
data_asset = data_source.add_dataframe_asset(
name="airports"
)
# Create a batch definition
batch_definition = data_asset.add_batach_definition_whole_dataframe(
name="whole_dataframe"
)
# Create a batch from the DataFrame
batch = batch_definition.get_batch(
batch_parameters={
"dataframe": argentina_df
}
)
# Display the batch
batch.head()
id ident type name latitude_deg \
0 35333 AR-0001 small_airport Cullen Airport -52.885740
1 35334 AR-0002 small_airport Estancia Los Cerros Airport -54.343000
2 35335 AR-0003 small_airport Rio Bellavista Airport -53.982700
3 35398 AR-0004 small_airport Merlo Airport -32.358200
4 35399 AR-0005 small_airport Bragado Airport -35.145811
longitude_deg elevation_ft iso_country gps_code
0 -68.414956 132.0 AR NaN
1 -67.837532 1914.0 AR NaN
2 -68.523598 201.0 AR NaN
3 -65.017403 796.0 AR NaN
4 -60.480294 196.0 AR SA2X
5. Defining Expectations
The expectations implemented in this notebook are derived from business rules designed to validate the quality of airport data during the ingestion process.
The focus is on data quality rules that can be evaluated automatically against a single dataset, making them suitable for recurring validation workflows. These rules cover intrinsic data quality dimensions such as completeness, validity, uniqueness, and consistency.
Basic geographic validation is also included by verifying that airport coordinates fall within the approximate geographic boundaries of Argentina. This validation provides a simple sanity check to identify records with clearly invalid coordinates. More advanced geospatial validation rules, such as detecting airports located over water bodies, have been intentionally excluded because they require external spatial reference datasets and specialized geographic analysis beyond the scope of this notebook.
In production environments, it is also considered a good practice to implement reasonability checks. These rules compare consecutive data deliveries to detect unexpected changes, such as sudden increases in the number of records, unusual variations in data distributions, or spikes in missing values. However, since this notebook validates the dataset for the first time, no previous AIRAC cycle is available as a baseline for comparison. For this reason, reasonability rules are not included in the current implementation.
Different compliance thresholds have been defined according to the criticality of each business rule. The completeness of ident, latitude_deg, and longitude_deg is expected to reach 100%, as these attributes are essential for uniquely identifying and locating each airport. Likewise, the uniqueness of ident and the format of gps_code, when present, must also achieve 100% compliance.
For the remaining rules, a compliance threshold of 98% is applied. This tolerance reflects a realistic production scenario where a limited number of exceptions may be acceptable without significantly affecting the overall quality of the dataset.
| Rule ID | Business Rule | Quality Dimension | Threshold |
|---|---|---|---|
| DQ-001 | ident must not be null |
Completeness | 100% |
| DQ-002 | Latitude and longitude must not be null | Completeness | 100% |
| DQ-003 | Elevation must not be null | Completeness | 98% |
| DQ-004 | ident must be unique |
Uniqueness | 100% |
| DQ-005 | Airport coordinates must fall within the approximate geographic boundaries of Argentina | Validity | 98% |
| DQ-006 | Elevation must fall within an acceptable range | Validity | 98% |
| DQ-007 | If gps_code is available, it must be a valid four-character alphanumeric location identifier |
Consistency | 100% |
# Create Expectation Suite
suite = gx.ExpectationSuite(
name="airport_data_quality_suite"
)
suite = context.suites.add(suite)
# Add Expectations
# DQ-001: ident must not be null (Threshold: 100%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToNotBeNull(
column="ident"
)
)
# DQ-002: Latitude and longitude must not be null (Threshold: 100%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToNotBeNull(
column="latitude_deg"
)
)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToNotBeNull(
column="longitude_deg"
)
)
# DQ-003: Elevation must not be null (Threshold: 98%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToNotBeNull(
column="elevation_ft",
mostly=0.98
)
)
# DQ-004: ident must be unique (Threshold: 100%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToBeUnique(
column="ident"
)
)
# DQ-005: Airport coordinates must fall within the approximate
# geographic boundaries of Argentina (Threshold: 98%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToBeBetween(
column="latitude_deg",
min_value=-56,
max_value=-21,
mostly=0.98
)
)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToBeBetween(
column="longitude_deg",
min_value=-74,
max_value=-53,
mostly=0.98
)
)
# DQ-006: Elevation must fall within an acceptable range (Threshold: 98%)
suite.add_expectation(
gx.expectationas.ExpectColumnValuesToBeBetween(
column="elevation_ft",
min_value=-100,
max_value=14000,
mostly=0.98
)
)
# DQ-007: If gps_code is available, it must be a valid
# ICAO location indicator (Threshold: 100%)
suite.add_expectation(
gx.expectations.ExpectColumnValuesToMatchRegex(
column="gps_code",
regex=r"^[A-Z0-9]{4}$",
row_condition="gps_code.notnull()",
condition_parser="pandas"
)
)
6. Running Validation
Before executing the validation, a Validation Definition is created. This object links together the data batch and the Expectation Suite, defining what data will be validated and which business rules will be applied.
Once configured, the validation can be executed repeatedly against new data batches without redefining the expectations, making the process suitable for automated data quality workflows.
# Create Validation Definition
validation_definition = gx.ValidationDefinition(
name="airport_validation",
data=batch_definition,
suite=suite,
)
validation_definition = context.validation_definitions.add(
validation_definition
)
7. Validation Results
The Expectation Suite is executed against the configured batch, and Great Expectations evaluates each business rule defined for the dataset.
To facilitate interpretation, the validation results are summarized in a business-oriented scorecard. For each rule, the report displays the quality dimension, the affected column, the expected compliance threshold, the observed compliance, and the final validation status.
This summary provides a concise overview of the dataset’s quality and highlights any business rules that require further investigation before the data is consumed downstream.
results = validation_definition.run(
batch_parameters={
"dataframe": argentina_df
}
)
# Map each Great Expectations expectation to its corresponding
# business rule, quality dimension, and required compliance threshold
business_rules = {
("expect_column_values_to_not_be_null", "ident"): (
"DQ-001",
"`ident` must not be null",
"Completeness",
"100%"
),
("expect_column_values_to_not_be_null", "latitude_deg"): (
"DQ-002",
"Latitude and longitude must not be null",
"Completeness",
"100%"
),
("expect_column_values_to_not_be_null", "longitude_deg"): (
"DQ-002",
"Latitude and longitude must not be null",
"Completeness",
"100%"
),
("expect_column_values_to_not_be_null", "elevation_ft"): (
"DQ-003",
"Elevation must not be null",
"Completeness",
"98%"
),
("expect_column_values_to_be_unique", "ident"): (
"DQ-004",
"`ident` must be unique",
"Uniqueness",
"100%"
),
("expect_column_values_to_be_between", "latitude_deg"): (
"DQ-005",
"Airport coordinates must fall within the approximate geographic boundaries of Argentina",
"Validity",
"98%"
),
("expect_column_values_to_be_between", "longitude_deg"): (
"DQ-005",
"Airport coordinates must fall within the approximate geographic boundaries of Argentina",
"Validity",
"98%"
),
("expect_column_values_to_be_between", "elevation_ft"): (
"DQ-006",
"Elevation must fall within an acceptable range",
"Validity",
"98%"
),
("expect_column_values_to_match_regex", "gps_code"): (
"DQ-007",
"If `gps_code` is available, it must be a valid four-character alphanumeric location identifier",
"Consistency",
"100%"
),
}
# Initialize an empty list to store the scorecard rows
rows = []
# Iterate through each validation result
for result in results.results:
# Retrieve the expectation type and the validated column
expectation = result.expectation_config.type
column = result.expectation_config.kwargs["column"]
# Look up the corresponding business rule metadata
rule_id, business_rule, dimension, threshold = business_rules[
(expectation, column)
]
# Retrieve validation statistics
evaluated = result.result.get("element_count", 0)
unexpected = result.result.get("unexpected_count", 0)
# Calculate the compliance percentage
if evaluated > 0:
compliance = round((evaluated - unexpected) / evaluated * 100, 2)
else:
compliance = None
# Append the validation results as a new scorecard row
rows.append({
"Rule ID": rule_id,
"Business Rule": business_rule,
"Quality Dimension": dimension,
"Required Compliance": threshold,
"Evaluated Rows": evaluated,
"Compliance (%)": compliance,
"Unexpected Records": unexpected,
"Status": "✅ Pass" if result.success else "❌ Fail"
})
# Convert the results into a DataFrame and sort by Rule ID
scorecard = (
pd.DataFrame(rows)
.sort_values("Rule ID")
.reset_index(drop=True)
)
# Group multiple expectations that belong to the same business rule
# into a single scorecard entry
scorecard = (
pd.DataFrame(rows)
.groupby(
[
"Rule ID",
"Business Rule",
"Quality Dimension",
"Required Compliance"
],
as_index=False
)
.agg({
# Keep the number of evaluated rows
"Evaluated Rows": "first",
# Use the lowest compliance value among the expectations
# associated with the same business rule
"Compliance (%)": "min",
# Sum the number of unexpected records
"Unexpected Records": "sum"
})
)
# Determine whether each business rule passes its required threshold
scorecard["Status"] = scorecard.apply(
lambda row:
"✅ Pass"
if row["Compliance (%)"] >= float(row["Required Compliance"].strip("%"))
else "❌ Fail",
axis=1
)
# Display the final data quality scorecard
scorecard
| Rule ID | Business Rule | Quality Dimension | Required Compliance | Evaluated Rows | Compliance (%) | Unexpected Records | Status |
|---|---|---|---|---|---|---|---|
| DQ-001 | ident must not be null |
Completeness | 100% | 942 | 100.00 | 0 | ✅ Pass |
| DQ-002 | Latitude and longitude must not be null | Completeness | 100% | 942 | 100.00 | 0 | ✅ Pass |
| DQ-003 | Elevation must not be null | Completeness | 98% | 942 | 96.39 | 34 | ❌ Fail |
| DQ-004 | ident must be unique |
Uniqueness | 100% | 942 | 100.00 | 0 | ✅ Pass |
| DQ-005 | Airport coordinates must fall within the approximate geographic boundaries of Argentina | Validity | 98% | 942 | 100.00 | 0 | ✅ Pass |
| DQ-006 | Elevation must fall within an acceptable range | Validity | 98% | 942 | 100.00 | 0 | ✅ Pass |
| DQ-007 | If gps_code is available, it must be a valid four-character alphanumeric location identifier |
Consistency | 100% | 225 | 99.56 | 1 | ❌ Fail |
8. Building Data Docs
Great Expectations can automatically generate Data Docs, an interactive HTML report that documents the execution of each expectation.
The report provides detailed information about the validation process, including expectation results, execution statistics, and failed records. These reports facilitate collaboration between data engineers, data stewards, and other stakeholders by providing a centralized view of data quality assessments.
# Build Data Docs
context.build_data_docs()
# Open Data Docs
context.open_data_docs()

9. Automating the Workflow
One of the main advantages of Great Expectations is that the validation logic is reusable. Once the Expectation Suite has been defined, the same business rules can be applied to every new AIRAC data release without modifying the validation code.
In a production environment, this workflow can be integrated into an ETL/ELT pipeline, allowing data quality checks to be executed automatically whenever new data is ingested. This reduces manual effort, ensures consistent validation, and enables early detection of data quality issues.
New AIRAC Dataset
│
▼
Load Dataset
│
▼
Run Great Expectations
│
▼
Validate Business Rules
│
▼
Generate Data Docs
│
▼
Publish Validation Report
10. Conclusion
This project demonstrated how Great Expectations can be used to automate business rule validation for airport data.
By implementing reusable expectations and applying them to a real-world dataset, the validation process becomes consistent, repeatable, and suitable for integration into automated data pipelines. The generated validation reports also provide a clear and accessible overview of data quality, supporting governance activities and improving confidence in downstream data consumption.
As a next step, this project will be extended by incorporating reasonability checks based on consecutive AIRAC cycles. Comparing historical data deliveries will make it possible to detect unexpected changes, such as unusual variations in record counts, missing values, or data distributions, complementing the intrinsic quality rules implemented in this notebook.
Finally, the validation workflow will be integrated into a dbt pipeline, demonstrating how Great Expectations can be incorporated into modern data transformation workflows to automate data quality validation as part of a production-ready data platform.