Great Expectations lets you write tests for data the same way you write unit tests for code. This page explains the core objects, walks through a runnable GX Core 1.x example, and compares GX with dbt tests, Soda, Deequ and Pandera.
Great Expectations (GX) is an open-source Python framework for testing data the way unit tests check code. You declare assertions about a dataset — "order_id is never null", "amount lies between 0 and 10,000", "the table has between 1,000 and 1,000,000 rows" — and GX evaluates them against real batches of data, returning a structured pass/fail result and human-readable documentation. Each assertion is called an expectation.
GX works against pandas DataFrames, Spark DataFrames and SQL databases (through SQLAlchemy), so the same rules can follow data from a CSV on a laptop to a Snowflake or Postgres table in production. The open-source library is now called GX Core; version 1.0 (2024) reorganised the API, and the examples on this page use the 1.x style.
Pipelines fail silently. A source system renames a column, a vendor starts sending amounts in cents instead of dollars, a join fans out and doubles revenue. None of these raise an exception; the job finishes "green" and wrong numbers reach dashboards and models. Data tests turn these silent failures into loud ones at the point where they enter the system, when they are cheap to fix.
GX also produces Data Docs: static HTML pages listing every expectation and its latest result. These become a contract that data producers and consumers can read without opening code, which is why GX often appears in data governance programmes alongside catalogues and lineage tools.
For machine-learning teams the stakes are higher still: a model trained on a silently corrupted feature does not crash, it simply gets worse, and the drop may only show up weeks later in business metrics. Validating training data and inference inputs with the same suite is one of the cheapest defences against that kind of slow failure, and it gives you a written record of what "good data" meant when the model was built.
The diagram shows the objects you create in GX 1.x. They live in a Data Context, the project configuration (in memory, or on disk under gx/).
Every expectation returns a result object containing success, the observed values and a sample of unexpected rows. The mostly parameter turns a hard rule into a tolerance: mostly=0.99 passes if at least 99% of non-null values satisfy the condition. Expectations are also declarative: GX translates them into pandas operations, Spark jobs or SQL queries, so on a warehouse the checking happens inside the database rather than by pulling data out.
Common expectation families: schema (ExpectColumnToExist, ExpectTableColumnsToMatchOrderedList), completeness (ExpectColumnValuesToNotBeNull), validity (ExpectColumnValuesToBeInSet, ExpectColumnValuesToMatchRegex), uniqueness (ExpectColumnValuesToBeUnique), volume (ExpectTableRowCountToBeBetween) and distribution (ExpectColumnMeanToBeBetween).
The script below validates an orders DataFrame with GX Core 1.x (pip install great_expectations). The bad rows are deliberate so you can see failures.
import pandas as pd
import great_expectations as gx
df = pd.DataFrame({
"order_id": [1, 2, 3, 4, None],
"status": ["paid", "paid", "refunded", "lost", "paid"],
"amount": [12.5, 40.0, -3.0, 18.0, 22.0],
})
context = gx.get_context() # ephemeral, in-memory context
source = context.data_sources.add_pandas("pandas")
asset = source.add_dataframe_asset(name="orders")
batch_def = asset.add_batch_definition_whole_dataframe("all_rows")
suite = context.suites.add(gx.ExpectationSuite(name="orders_suite"))
suite.add_expectation(gx.expectations.ExpectColumnValuesToNotBeNull(column="order_id"))
suite.add_expectation(gx.expectations.ExpectColumnValuesToBeInSet(
column="status", value_set=["paid", "refunded", "pending"]))
suite.add_expectation(gx.expectations.ExpectColumnValuesToBeBetween(
column="amount", min_value=0, max_value=10_000))
suite.add_expectation(gx.expectations.ExpectTableRowCountToBeBetween(
min_value=1, max_value=1_000_000))
vd = context.validation_definitions.add(
gx.ValidationDefinition(name="orders_vd", data=batch_def, suite=suite))
checkpoint = context.checkpoints.add(gx.Checkpoint(
name="orders_checkpoint",
validation_definitions=[vd],
actions=[gx.checkpoint.UpdateDataDocsAction(name="update_docs")],
))
result = checkpoint.run(batch_parameters={"dataframe": df})
print("overall success:", result.success)
for vr in result.run_results.values():
for r in vr.results:
print(r.expectation_config.type, r.success)
Three expectations fail (one null id, the unknown status lost, the negative amount) and the row-count check passes, so result.success is False. In an orchestrator you would raise on that flag so the downstream task never runs. Call context.open_data_docs() with a file-backed context (gx.get_context(mode="file")) to browse the HTML report.
A typical production layout uses two gates: a cheap structural gate right after landing (schema, nulls, row counts) and a semantic gate after transformation (business rules, reconciliations against source totals). Failing batches are moved to a quarantine location and the owning team is alerted, rather than the pipeline publishing partial data.
| Tool | Style | Strengths | Trade-offs |
|---|---|---|---|
| Great Expectations | Python objects / JSON suites | Large expectation library, Data Docs, pandas + Spark + SQL | More concepts to learn; API changed significantly at 1.0 |
| dbt tests | SQL / YAML next to models | Zero extra infrastructure if you already use dbt | Warehouse only; simpler assertions unless you add packages such as dbt-expectations |
| Soda Core | YAML (SodaCL) checks | Concise check language, easy to read | Smaller community; some features in the paid cloud product |
| Deequ / PyDeequ | Scala/Python on Spark | Built for very large Spark datasets, metric repository, constraint suggestion | Spark only |
| Pandera | Schema classes for DataFrames | Lightweight, type-hint style, good in Python code and tests | DataFrame-focused; no docs site or checkpoint system |
| Collibra / Monte Carlo style platforms | Managed, often ML-inferred rules | Anomaly detection without writing rules, lineage, UI | Commercial; less explicit control |
A common pattern is dbt tests for warehouse models plus GX (or Soda) for raw files and non-SQL sources.
mostly, and revisit them with real history.get_validator, add_or_update_expectation_suite and YAML checkpoints. Pin your version and use docs that match it.