{"id":51443377,"url":"https://github.com/imann128/tsauditor","last_synced_at":"2026-07-05T14:00:36.865Z","repository":{"id":365490336,"uuid":"1246760670","full_name":"imann128/tsauditor","owner":"imann128","description":"A data quality auditing library for time-series tabular data in financial and sensor domains.","archived":false,"fork":false,"pushed_at":"2026-06-30T09:01:28.000Z","size":897,"stargazers_count":8,"open_issues_count":9,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-30T11:04:49.170Z","etag":null,"topics":["anomaly-detection","chronological-data","eda","financial-analysis","leakage-detection","sensor-analysis","sensor-data","time-series","time-series-analysis"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imann128.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-22T14:23:08.000Z","updated_at":"2026-06-30T09:01:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/imann128/tsauditor","commit_stats":null,"previous_names":["imann128/tsauditor"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/imann128/tsauditor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imann128%2Ftsauditor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imann128%2Ftsauditor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imann128%2Ftsauditor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imann128%2Ftsauditor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imann128","download_url":"https://codeload.github.com/imann128/tsauditor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imann128%2Ftsauditor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35156494,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anomaly-detection","chronological-data","eda","financial-analysis","leakage-detection","sensor-analysis","sensor-data","time-series","time-series-analysis"],"created_at":"2026-07-05T14:00:26.528Z","updated_at":"2026-07-05T14:00:36.837Z","avatar_url":"https://github.com/imann128.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tsauditor\n[![CI](https://github.com/imann128/tsauditor/actions/workflows/ci.yml/badge.svg)](https://github.com/imann128/tsauditor/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/github/imann128/tsauditor/graph/badge.svg)](https://codecov.io/github/imann128/tsauditor)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n![Version](https://img.shields.io/badge/version-0.2.0-blue.svg)\n\nA data-quality auditing library for **time-series tabular data**, with a focus on\nfinancial and sensor domains. `tsauditor` scans a `DataFrame` and returns a\nstructured report of structural problems, anomalies, and — its core contribution —\n**data-leakage** between features and the prediction target. It can also *repair*\nthe flagged issues on a copy, score data health, export a formal report, and hand\na clean array straight to a forecasting model.\n\nThe project grew out of a real bug in a Pakistani equity (OGDC) direction-prediction\nmodel: a same-day percentage-change feature (`ChangeP`) was mathematically near-identical\nto the target it was meant to predict. With `ChangeP` included, a Random Forest\nclassifier reached 99.68% accuracy (AUC 0.9987); a Gradient Boosting classifier reached\nthe same 99.68% accuracy (AUC 0.9967). Removing it — along with same-day `Open`, `High`,\nand `Low`, which are equally unavailable at prediction time — dropped accuracy to 69.81%\n(RF, AUC 0.7795) and 73.70% (GBM, AUC 0.8072) on a held-out test period\n(2025-01-09 to 2026-04-03). Both models still beat a 50% baseline, but the headline\naccuracy had been almost entirely an artifact of the leak. `tsauditor` exists to catch\nthis class of mistake automatically before it reaches a model.\nSee [`examples/ogdc_leakage_case`](examples/ogdc_leakage_case) for the full experiment,\nscript, and measured results.\n\n## Not just price and direction\n\n`tsauditor` is **column-agnostic** — it never hard-codes `price`, `Direction`, or any\nother column. `price`/`Direction` are simply the columns in the OGDC example above. The\nstructural (PRF), anomaly (ANO), and target-relative leakage (LEK001–003) checks apply to\n*any* numeric time-series column. Version 0.2.0 adds two **declarative** mechanisms —\n`available_at=` (point-in-time release correctness) and `constraints=` (domain validity) —\nso you can also audit macro, sentiment, order-book, volatility, and other alternative-data\ncolumns correctly. tsauditor never *computes* these features; you point it at your columns\nand, where relevant, declare their semantics.\n\n| Column type | What can go wrong | Check |\n|-------------|-------------------|-------|\n| Macro indicators (CPI, rates, unemployment) | Published weeks after their reference date → used early | LEK004 as-of (`available_at=`) |\n| Sentiment scores (news / social) | Publish lag; also must sit in a bounded range | LEK004 + VAL001 bounds |\n| Order book (bid, ask) | Crossed book (`ask \u003c bid`) | VAL002 relation |\n| Bid-ask spread | Zero or negative spread | VAL001 strict-positive bound |\n| Volume | Negative volume (feed glitch) | VAL001 non-negative bound |\n| Bounded indicators (RSI 0–100, probabilities, ratios) | Out-of-range values | VAL001 bounds |\n| Realized volatility / drawdown | Impossible negatives | VAL001 non-negative bound |\n| OHLC bars | `Low \u003e High`, `Open`/`Close` outside `[Low, High]` | VAL002 relations |\n| Earnings / fundamentals | Point-in-time restatement / release lag | LEK004 as-of |\n| Any numeric series | Gaps, stuck runs, outliers, non-stationarity, target leakage | PRF / ANO / LEK001–003 |\n\nSee [`examples/beyond_price_direction`](examples/beyond_price_direction) (validity on real\nvolume/RSI/OHLC columns) and\n[`examples/new_features_walkthrough.ipynb`](examples/new_features_walkthrough.ipynb)\n(as-of leakage, sentiment bounds).\n\n## Installation\n\n```bash\npip install tsauditor\n```\n\nRequires Python ≥ 3.9. Core dependencies: `pandas`, `numpy`, `scipy`, `statsmodels`, `rich`.\n\nOptional extras (install only what you need):\n\n```bash\npip install 'tsauditor[pdf]'      # PDF report export (matplotlib)\npip install 'tsauditor[polars]'   # polars DataFrame input\npip install 'tsauditor[dev]'      # test + lint toolchain (contributors)\n```\n\n### Development setup\n\n```bash\ngit clone https://github.com/imann128/tsauditor.git\ncd tsauditor\npip install -e \".[dev]\"\n```\n\n## **Note:** Set domain=\"None\" for domain agnostic usage. Similarly, it works well without defining a domain at all.\n\n**For usage snippets, scroll down in the readme or check out the [examples](./examples) directory for sample scripts and notebooks.**\n\n## Quickstart\n\n```python\nimport tsauditor as tsa\n\nreport = tsa.scan(df, target=\"Direction\", domain=\"finance\")\n\nreport.summary()                 # rich-formatted CLI table\nreport.critical                  # list[Issue] that block modeling\nreport.filter(module=\"leakage\")  # programmatic filtering\nreport.leaky_columns()           # the shortlist of features to review/remove\nreport.to_json(\"report.json\")    # structured export\n\n# Repair on a copy and keep the audit trail (original is never modified):\nclean, report = tsa.fix(df, target=\"Direction\", domain=\"finance\")\nprint(report.last_fixes)         # exactly what changed\n```\n\n`scan()` returns a `GuardReport` holding `Issue` dataclasses bucketed by severity\n(`critical`, `warnings`, `info`) plus dataset metadata.\n\n\n\n### Example report\n\n![tsauditor financial report](https://raw.githubusercontent.com/imann128/tsauditor/main/images/financial_report.png)\n\n## Sensor:\n\n###  Real-World Sensor Validation Example\n\nBelow is an example using real weather station telemetry data. To showcase how `tsauditor` behaves during typical field failures, we manually inject three classic hardware faults: a frozen sensor reading, a complete network dropout gap, and a high-voltage electrical spike.\n\n```python\nimport pandas as pd\nimport tsauditor as tsa\n\nprint(\" Fetching real-world weather station sensor dataset...\")\nurl = \"[https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv](https://raw.githubusercontent.com/jbrownlee/Datasets/master/daily-min-temperatures.csv)\"\n\ntry:\n    df = pd.read_csv(url, parse_dates=[\"Date\"], index_col=\"Date\")\n    df.columns = [\"air_temperature\"]\n    print(\" Dataset successfully into memory\")\nexcept Exception as e:\n    print(f\" Error loading dataset: {e}\")\n\nprint(\" Injecting typical hardware field failures for evaluation...\")\n# 1. Stuck sensor condition: flatlined at 12.2°C for 15 days straight\ndf.iloc[100:115] = 12.2\n\n# 2. Transmission blackout: 10 days of completely missing telemetry\ndf.iloc[300:310] = None\n\n# 3. Electrical surge: an impossible 75°C transient spike\ndf.iloc[500] = 75.0\n\nprint(\"\\n Running `tsauditor` validation sweep\")\n\n# Execute the audit using the optimized sensor preset\nreport = tsa.scan(df, domain=\"sensor\")\nreport.summary()\n```\n\n### Example output\n\n![tsauditor sensor report](https://raw.githubusercontent.com/imann128/tsauditor/main/images/sensor_report.png)\n\n## What it checks\n\n| Module | Code | Severity | Detects |\n|--------|------|----------|---------|\n| profiler | PRF001 | warning | Irregular timestamp frequency |\n| profiler | PRF002 | warning | Clustered missing values |\n| profiler | PRF003 | info | Non-stationarity (Augmented Dickey-Fuller) |\n| profiler | PRF004 | critical | Duplicate timestamps |\n| profiler | PRF005 | warning | Clustered gaps |\n| profiler | PRF006 | warning | High overall missing rate |\n| anomaly | ANO001 | warning | Stuck / repeated constant values |\n| anomaly | ANO002 | warning | Point outliers (z-score + IQR) |\n| anomaly | ANO003 | warning | Contextual spikes (local rolling z-score) |\n| leakage | LEK001 | critical | Target equivalence (feature reproduces the target) |\n| leakage | LEK002 | warning | Positive-lag cross-correlation peak (future info) |\n| leakage | LEK003 | warning | Rolling-window lookahead (excess over persistence) |\n| leakage | LEK004 | critical | As-of leakage (value used before its release time) |\n| validity | VAL001 | warning | Out-of-range value (declared per-column bounds) |\n| validity | VAL002 | critical | Ordering violation (e.g. crossed book, `bid \u003e ask`) |\n\nCodes marked **critical** block modeling; **warning** and **info** are advisory.\n\n### Leakage detection (the research core)\n\nLeakage checks are **rank-based**, chosen by target type:\n\n- **LEK001 — equivalence.** Continuous targets use `|Spearman ρ|`; binary targets use\n  **AUC separation** (`max(AUC, 1−AUC)`). This is deliberate: Pearson against a binary\n  0/1 target is point-biserial correlation, which is capped near `√(2/π) ≈ 0.798`, so a\n  feature whose sign *defines* the target scores only ~0.80 and slips under a naive\n  threshold. AUC scores it 1.0.\n- **LEK002 — cross-correlation.** Flags features whose peak association with the target\n  falls at a *positive* lag (the feature aligns with the target's future).\n- **LEK003 — temporal lookahead.** Flags features that correlate with the future target\n  *beyond* what the target's own autocorrelation can explain — the signature of a\n  forward-looking or centered window. The persistence baseline is what keeps a\n  legitimate trailing feature from being false-flagged.\n- **LEK004 — as-of / point-in-time.** Flags a feature whose value sits at a timestamp\n  *earlier* than when it was actually published — the classic macro/sentiment trap. See\n  [As-of leakage](#as-of-leakage-point-in-time) below.\n\nLEK002/LEK003 are WARNING-level *suspicions*: in pure cross-correlation a genuine strong\npredictor and a leak are distinguishable only by magnitude. LEK001 and LEK004 are CRITICAL\nbecause equivalence and confirmed availability violations are near-deterministic.\n\n### As-of leakage (point-in-time)\n\nMacro releases (CPI, rates, unemployment), earnings, and news/social sentiment describe a\n*reference period* but are only published later. A value aligned to its reference date and\nused on that date leaks the future. This cannot be inferred from values alone, so LEK004 is\n**opt-in**: you declare when each value became available.\n\n```python\nimport pandas as pd\n\n# CPI for a reference month is released ~30 days later:\nreport = tsa.scan(df, available_at={\"cpi\": pd.Timedelta(days=30)})\n\n# Or, for a ragged real release calendar, pass per-row publish timestamps:\nreport = tsa.scan(df, available_at={\"cpi\": publish_times})   # a pd.Series on df.index\n```\n\nThe fix LEK004 suggests is not to drop the column but to **shift it to its release\nschedule** so each value is only used on or after publication.\n\n### Validity checks (domain constraints)\n\nSome values are not merely surprising, they are *impossible*: a non-positive bid-ask\nspread, a sentiment score outside `[-1, 1]`, a crossed order book. tsauditor can't guess\nthese rules, so you declare them via `constraints`:\n\n```python\nreport = tsa.scan(\n    df,\n    constraints={\n        \"bounds\": {\n            \"spread\":    {\"min\": 0, \"min_exclusive\": True},  # strictly positive\n            \"sentiment\": {\"min\": -1, \"max\": 1},\n        },\n        \"relations\": [(\"bid\", \"ask\")],   # bid \u003c= ask must hold every row\n    },\n)\n```\n\n`bounds` violations raise **VAL001** (WARNING); a broken `relations` ordering (a crossed\nbook) raises **VAL002** (CRITICAL). Validity issues are data errors, so they are *not*\ncounted as leakage in `leaky_columns()`.\n\n## Repair \u0026 remediation\n\ntsauditor is advisory by default — it reports and suggests, but only edits your data when\nyou ask. Every repair happens on a **copy**; your original frame is your backup.\n\n```python\n# Advisory only:\nreport.suggestions()             # per-issue suggested action, ordered by severity\n\n# One-shot scan + repair, returning both the clean copy and the report:\nclean, report = tsa.fix(df, target=\"Direction\", domain=\"finance\")\n\n# Or repair from an existing report with fine-grained control:\nclean = report.apply_fixes(\n    df,\n    missing=\"interpolate\",   # impute clustered-missing + anything NaN-ed below\n    outliers=\"clip\",         # winsorize ANO002 points / ANO003 spikes (\"nan\" to drop-to-NaN)\n    stuck=\"nan\",             # replace stuck runs with NaN, then impute\n    leakage=None,            # \"drop\" to remove leaky columns (off by default)\n)\nprint(report.last_fixes)     # structured change log: column, action, cells changed\n```\n\nRepairs are **report-driven** (only flagged columns are touched), **time-series safe**\n(an outlier is set to NaN and imputed, never deleted — deleting rows would break the\nindex), and the **target label is never repaired** (interpolating a 0/1 label into\nfractions is always wrong).\n\n### Data Health Score\n\n```python\nreport.health_score(df)   # % of numeric cells NOT implicated by a quality issue\n```\n\n`100 × (1 − affected_cells / total_cells)`, leakage excluded (a leaky column is a modeling\nrisk, not corrupt data). It re-scans the frame you pass, so calling it on a `fix()` output\ngives a true \"after\" score.\n\n## Export (JSON + PDF)\n\n```python\nreport.to_json(\"report.json\", df=df, fixed_df=clean)   # includes health + before/after\nreport.to_pdf(\"report.pdf\", df=df, fixed_df=clean)     # needs 'tsauditor[pdf]'\n```\n\n`to_pdf` produces a formal, vector, text-selectable report (Times New Roman, black text,\nheadings and tables — no charts, no colour coding): a Data Health Scorecard, dataset\noverview, before/after comparison, target-leakage callout, executive summary, and a\npaginated issues table.\n\n## Feeding a forecasting model (TimesFM adapter)\n\nZero-shot forecasters such as Google TimesFM tokenize a clean, contiguous, finite context\nwindow; a raw series with gaps fails tokenization. The adapter audits, repairs, and returns\na plain `float32` array — and **verifies it is finite** before returning, so a NaN never\nreaches the model. It adds no `timesfm` dependency.\n\n```python\narray = tsa.adapters.to_timesfm(df, target_col=\"close_price\", domain=\"finance\")\n# array is now safe to pass to model.forecast(inputs=[array], ...)\n\n# keep the audit trail too:\narray, report = tsa.adapters.to_timesfm(df, target_col=\"close_price\", return_report=True)\n```\n\n`context_len` / `min_context` are your knobs, not TimesFM constants — TimesFM 2.5 accepts a\nwide range of context lengths (up to 16k). See\n[`examples/timesfm_adapter`](examples/timesfm_adapter) for a full walkthrough — the\nfiniteness guard, context truncation, and the model call.\n\n## Architecture\n\n```\ntsauditor/\n├── scanner.py            # scan() — orchestrates all modules into a GuardReport\n├── profiler/             # structural checks: frequency, missing, stationarity\n├── anomaly/              # point.py, contextual.py\n├── leakage/              # equivalence.py, correlation.py, temporal.py, asof.py\n├── validity.py           # domain-constraint checks (bounds + relations)\n├── remediate.py          # apply_fixes / fix engine, health score (repair on a copy)\n├── adapters/             # boundary adapters (e.g. timesfm.py)\n├── report/\n│   ├── summary.py        # GuardReport + Issue dataclasses, rich/JSON output\n│   ├── remediation.py    # code -\u003e suggested-action advisory lookup\n│   └── pdf.py            # to_pdf export\n└── utils/validation.py   # input validation \u0026 DataFrame normalization\n```\n\n## Scaling\n\n**polars input.** A polars `DataFrame` works anywhere a pandas one does — just name\nthe datetime column via `time_col` (polars has no index):\n\n```python\nreport = tsa.scan(pl_df, target=\"Direction\", time_col=\"Date\", domain=\"finance\")\n```\n\nInstall with `pip install 'tsauditor[polars]'`. tsauditor converts to pandas at the\nboundary; the audit logic is identical. (See issue #28.)\n\n**Audit a whole universe in parallel.** `scan()` is a pure function and `GuardReport`\nis a plain, picklable dataclass, so it parallelises cleanly with `joblib` — ideal for\nsweeping every symbol for leakage before you train:\n\n```python\nfrom joblib import Parallel, delayed\nimport tsauditor as tsa\n\ndef audit(symbol, df):\n    return symbol, tsa.scan(df, target=\"Direction\", domain=\"finance\")\n\nreports = dict(Parallel(n_jobs=-1)(\n    delayed(audit)(sym, frames[sym]) for sym in frames\n))\n\n# every symbol whose feature set leaks into the target\nleaky = {s: r.leaky_columns() for s, r in reports.items() if r.leaky_columns()}\n```\n\n**Skip the expensive check.** The ADF stationarity test (PRF003) dominates runtime;\n`scan(run_stationarity=False)` skips it for a much faster structural/anomaly/leakage sweep.\n\n## Examples\n\nRun `pip install -e \".[dev,examples]\"` for running all example notebooks easily.\n\nSee [`examples/`](examples) (indexed in [`examples/README.md`](examples/README.md)):\n\n- `ogdc_leakage_case/` — the flagship LEK001 case on real OGDC data (script + notebook).\n- `beyond_price_direction/` — validity (VAL001/VAL002) on real volume, RSI, and OHLC\n  columns: concrete proof tsauditor audits more than price and direction.\n- `timesfm_adapter/` — the messy-data -\u003e finite float32 array bridge for Google TimesFM\n  (finiteness guard, context truncation).\n- `sensor-example/` — structural/anomaly checks on a sensor stream, plus a PDF-report demo.\n- `new_features_walkthrough.ipynb` — LEK004, validity checks, `tsa.fix`, and the TimesFM\n  adapter, end-to-end.\n- `validation_comparison/` — time-series validation vs general profiling.\n\n## Testing\n\n```bash\npytest -q\n```\n\n## Contributing\n\nContributions are welcome. Check [open issues](https://github.com/imann128/tsauditor/issues)\nfor ideas, or look for the `good first issue` label. Run `pytest -q` before opening a PR —\nthe full suite (164 tests) must pass, and CI will verify this across\nPython 3.9–3.14 on Linux, Windows, and macOS. See [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Featured On:\nFeatured #7 on [Data Science Weekly Issue - 657](https://datascienceweekly.substack.com/p/data-science-weekly-issue-657)\n\n[Article about tsauditor on LineUp Digest](https://lineupdigest.com/en/article/poka-vse-molcat-tsauditor-meniaet-podxod-k-upravleniiu-time-series-dannymi)\n\n## Status\n\nBeta (`0.2.0`). Profiler, anomaly, leakage, validity, remediation, and export modules are\nimplemented and tested (164 tests passing; CI across Python 3.9–3.14 on\nLinux, Windows, macOS).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimann128%2Ftsauditor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimann128%2Ftsauditor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimann128%2Ftsauditor/lists"}