Skip to main content

Causal data provenance for AI safety — automatically identify which pipeline step caused your model's bias or fairness failure.

Project description

DataLineageML

Causal data provenance for AI safety. Find out which pipeline step caused your model's bias or safety failure — automatically, verifiably, and without a cloud account.

Python 3.8+ License: MIT PyPI version Tests Zero deps


The problem this solves

Your model is biased. Or it started failing after a data update. Or an audit found a fairness gap. You know what went wrong — but not where in the pipeline it went wrong.

Existing tools answer the wrong question. MLflow, W&B, and LangSmith tell you what happened inside a pipeline — inputs, outputs, latencies. They do not tell you which data transformation caused the outcome you are trying to fix.

DataLineageML is built for that second question.


A real example

A crop yield model in Oyo State, Nigeria was producing a 34% gender bias in agricultural subsidy allocation. Three months of manual audit found nothing. DataLineageML found the cause in one run:

Step: clean_data (df.dropna())

  Input:   female-headed farms = 38.4%  (n = 12,847)
  Output:  female-headed farms = 22.1%  (n =  7,403)

  Jensen-Shannon divergence on gender: 0.079  [HIGH]

  Finding: 'gender' distribution shifted significantly at 'clean_data'.
           'F' proportion dropped from 38.4% to 22.1%
           (5,444 rows removed, 42.4% of dataset).
           This step is a candidate causal source of bias.

Counterfactual replay — replace dropna() with stratified imputation:
  Original bias score:  0.34  →  After fix: 0.09   (−74%)
  Accuracy change:      −0.3%  (negligible)

The fix took one afternoon.


Quick start

pip install datalineageml
import datalineageml as dlm
from datalineageml import track, LineageContext
from datalineageml.analysis import (
    ShiftDetector, CausalAttributor,
    discover_sensitive_cols,
)
from datalineageml.replay import CounterfactualReplayer

# 1. Initialise — one call, no store= needed anywhere else
dlm.init(db_path="pipeline.db")

# 2. Auto-discover which columns to track
sensitive = discover_sensitive_cols(df)
cols      = [c.column for c in sensitive if c.confidence >= 0.7]
# → ["gender", "age_group", "zone"]

# 3. Instrument your pipeline
@track(name="clean_data", snapshot=True, sensitive_cols=cols)
def clean_data(df):
    return df.dropna()

@track(name="engineer_features")
def engineer_features(df):
    df["score_per_ha"] = df["score"] / df["farm_size_ha"]
    return df

# 4. Run it
with LineageContext(name="subsidy_model_v2"):
    d2 = clean_data(raw_df)
    d3 = engineer_features(d2)

# 5. Detect the shift
store    = dlm.get_default_store()
detector = ShiftDetector(store=store)
shifts   = detector.detect()
detector.print_report(shifts)
# → [HIGH] clean_data → gender  JSD=0.079

# 6. Attribute the causal step
attr = CausalAttributor(store=store).attribute(sensitive_col="gender")
# → {"attributed_step": "clean_data", "confidence": 1.0, ...}

# 7. Prove the fix
def impute_data(df):
    for g in df["gender"].unique():
        mask = (df["gender"] == g) & df["land_title"].isna()
        df.loc[mask, "land_title"] = df.loc[df["gender"]==g, "land_title"].mode()[0]
    return df

result = (CounterfactualReplayer(store=store)
    .register_tracked(clean_data)       # reads @track metadata automatically
    .register("engineer_features", engineer_features)
    .replay(raw_df, "clean_data", impute_data, "gender",
            bias_metric_fn=lambda df: abs(
                df[df.gender=="F"]["eligible"].mean() -
                df[df.gender=="M"]["eligible"].mean())))

# → Verdict: STRONG — Bias reduced by 74%

# 8. Export a shareable HTML audit report
from datalineageml import generate_report
generate_report(store=store, output_path="audit.html",
                attribution_result=attr, counterfactual_result=result)

How it differs from MLflow, W&B, and LangSmith

All three do observability — recording what happened. None do causal attribution — identifying which step caused a safety failure.

Capability MLflow W&B Weave LangSmith DataLineageML
Pipeline tracing
Dataset versioning
Sensitive column auto-discovery
Demographic snapshots per step
Distribution shift detection (JSD + KS)
Causal step attribution
Counterfactual pipeline replay
Cross-run drift detection
Fairness metrics (DPG / EO / PP / Regression)
HTML audit report export
Zero-dependency offline core

Full API reference

Tracking

import datalineageml as dlm
from datalineageml import track, LineageContext, LineageStore, LineageGraph

dlm.init(db_path="pipeline.db")        # global store — one call, optional after

@track(
    name="clean_data",                 # step name (defaults to function name)
    tags={"stage": "prep"},            # arbitrary metadata
    snapshot=True,                     # log demographic snapshots before/after
    sensitive_cols=["gender", "zone"], # columns to track distributions for
)
def clean_data(df): return df.dropna()

with LineageContext(name="pipeline_v1"):
    result = clean_data(raw_df)

Analysis

from datalineageml.analysis import (
    DataFrameProfiler,          # compute + print statistical snapshots
    ShiftDetector,              # detect JSD + KS shifts, ranked by severity
    CausalAttributor,           # attribute a bias to the most likely causal step
    CrossRunComparator,         # detect drift across pipeline runs over time
    DemographicParityGap,       # binary/multiclass: selection rate gap (no model)
    EqualizedOdds,              # binary/multiclass: TPR/FPR gap (requires model)
    PredictiveParity,           # binary/multiclass: precision gap (requires model)
    RegressionFairnessAuditor,  # ME gap, MAE gap, calibration, lazy-solution guard
    compute_metric,             # dispatch any metric by name: 'dpg', 'eo', 'pp'
    discover_sensitive_cols,    # auto-discover demographic columns from data
    suggest_sensitive_cols,     # return column names above confidence threshold
    print_snapshot_comparison,  # human-readable before/after distribution view
)

Counterfactual replay

from datalineageml.replay import CounterfactualReplayer

replayer = (CounterfactualReplayer(store=store)
    .register_tracked(clean_data)          # reads @track metadata automatically
    .register("normalize", normalize_fn))  # or register manually

result = replayer.replay(
    raw_data       = df_raw,
    replace_step   = "clean_data",
    replacement_fn = impute_data,
    sensitive_col  = "gender",
    bias_metric_fn = lambda df: ...,       # optional — any callable(df) → float
)
replayer.print_result(result)

Report export

from datalineageml import generate_report

generate_report(
    store                 = store,
    output_path           = "audit.html",
    pipeline_name         = "oyo_subsidy_v1",
    sensitive_col         = "gender",
    attribution_result    = attr,         # optional
    counterfactual_result = cf_result,    # optional
    title                 = "Fairness Audit — Q1 2026",
)

Installation

pip install datalineageml                    # core — zero mandatory dependencies
pip install "datalineageml[viz]"             # + NetworkX + Plotly lineage graph
pip install "datalineageml[pandas]"          # + pandas integration helpers
pip install "datalineageml[all]"             # everything

# Development
git clone https://github.com/adejumobioluwafemi/data-lineage-ml.git
cd data-lineage-ml
pip install -e ".[dev]"
pytest tests/unit/ -v                        # 318 tests
python run_tests.py                          # stdlib runner, no pytest needed

Fairness metrics — which to use

Metric Requires model? Use when
DemographicParityGap No Who gets selected at all (access/allocation). Run before training.
EqualizedOdds Yes False negatives and false positives both carry harm (medical, credit).
PredictiveParity Yes Being predicted positive should mean the same thing for all groups.
RegressionFairnessAuditor Yes Continuous output; detects ME gap, MAE gap, calibration error, lazy solutions.

When base rates differ across groups, Equalized Odds and Predictive Parity cannot both hold simultaneously (Chouldechova 2017). Choosing a metric is a policy decision about which error type is costlier.


Project structure

data-lineage-ml/
├── src/datalineageml/
│   ├── __init__.py              ← public API: track, LineageContext, LineageStore,
│   │                               LineageGraph, CounterfactualReplayer, generate_report
│   ├── trackers/
│   │   ├── decorator.py         ← @track (stores __track_meta__ for register_tracked)
│   │   └── context.py           ← LineageContext
│   ├── storage/
│   │   └── sqlite_store.py      ← SQLite (zero deps): steps, pipelines, snapshots, metrics
│   ├── visualization/
│   │   └── graph.py             ← Plotly + NetworkX DAG
│   ├── integrations/
│   │   └── pandas_integration.py
│   ├── analysis/
│   │   ├── profiler.py          ← DataFrameProfiler
│   │   ├── shift_detector.py    ← ShiftDetector (JSD + KS)
│   │   ├── attributor.py        ← CausalAttributor
│   │   ├── cross_run.py         ← CrossRunComparator
│   │   ├── metrics.py           ← DPG, EO, PP, RegressionFairnessAuditor
│   │   └── sensitive_cols.py    ← discover_sensitive_cols, suggest_sensitive_cols
│   ├── replay/
│   │   └── replayer.py          ← CounterfactualReplayer
│   └── report.py                ← generate_report (HTML export)
├── tests/unit/                  ← 318 tests, all isolated
├── examples/
│   ├── basic_pipeline.py        ← full loop demo (5 mins to run)
│   └── agro.ipynb               ← Oyo State case study (complete v0.2 walkthrough)
├── run_tests.py                 ← stdlib runner (no pytest needed)
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md
└── README.md

Roadmap

v0.2 — released

  • Global dlm.init() default store
  • Demographic snapshots per step (snapshot=True, sensitive_cols=)
  • Sensitive column auto-discovery (discover_sensitive_cols)
  • Distribution shift detection — JSD (categorical) + KS approximation (numeric)
  • Causal step attribution with confidence scoring
  • Fairness metrics — DPG, EO, PP (binary + multiclass), regression audit
  • Counterfactual pipeline replayer with register_tracked()
  • Cross-run drift detection (CrossRunComparator)
  • HTML audit report export (generate_report)
  • 318 unit tests

v0.3 — planned

  • CLI: datalineageml audit pipeline.db --sensitive gender
  • sklearn Pipeline wrapper (auto-track every fit_transform)
  • Cross-agent provenance tracking via chain_id
  • Prompt versioning and lineage for LLM pipelines
  • Wasserstein distance as alternative shift metric
  • Multi-step attribution (currently attributes to a single step)

Research

DataLineageML is part of an active research programme on causal data provenance for AI safety.

Research question: Can automated pipeline-level provenance graphs reliably identify the specific data transformation responsible for a measurable demographic bias — and can counterfactual replay verify the remediation?

The Oyo State case study demonstrates this end-to-end: a dropna() call attributed automatically (JSD = 0.079, confidence 100%), remediated with stratified imputation, bias reduced 74%, verified by counterfactual replay — without training a model.

📄 Read the research framing paper

Collaborations welcome — especially on fairness attribution, causal ML, data governance, and responsible AI in low-resource settings.


License

MIT — see LICENSE.

Author

Oluwafemi Adejumobi — AI/ML Engineer & Researcher, Ibadan, Nigeria GitHub · LinkedIn · HuggingFace


Part of a portfolio of AI safety tools. See also: EquiTrace · PrivacyAudit · AgentTrace

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

datalineageml-0.2.0.tar.gz (63.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

datalineageml-0.2.0-py3-none-any.whl (65.5 kB view details)

Uploaded Python 3

File details

Details for the file datalineageml-0.2.0.tar.gz.

File metadata

  • Download URL: datalineageml-0.2.0.tar.gz
  • Upload date:
  • Size: 63.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.19

File hashes

Hashes for datalineageml-0.2.0.tar.gz
Algorithm Hash digest
SHA256 616b1593da51c042d3b59f51dec0e78aec27605c5df1e6e9ec95899d5c47dea9
MD5 0bb6f15147ea81d713b5409385edfad0
BLAKE2b-256 6a24706ce270ffcacf965de0886df5bb775311c128b8de2f72ce0fd32f55bdcd

See more details on using hashes here.

File details

Details for the file datalineageml-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: datalineageml-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 65.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.19

File hashes

Hashes for datalineageml-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ed6ebb55cce4b9a04a3cb4339ac85cc7ad20088817b8b786899a6dade7267a6
MD5 1e02c85ade656b826733d54ef62146b3
BLAKE2b-256 d2d01707a640a4064614cd27f44cbfea9f04f225511678ee3afee237f3888ceb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page