datasets-lint 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datasets_lint-0.1.0/.github/workflows/ci.yml +68 -0
- datasets_lint-0.1.0/.github/workflows/publish.yml +32 -0
- datasets_lint-0.1.0/.gitignore +14 -0
- datasets_lint-0.1.0/.pre-commit-hooks.yaml +7 -0
- datasets_lint-0.1.0/CONTRIBUTING.md +48 -0
- datasets_lint-0.1.0/LICENSE +21 -0
- datasets_lint-0.1.0/PKG-INFO +159 -0
- datasets_lint-0.1.0/README.md +123 -0
- datasets_lint-0.1.0/ROADMAP.md +173 -0
- datasets_lint-0.1.0/action.yml +60 -0
- datasets_lint-0.1.0/datasets-lint.toml.example +24 -0
- datasets_lint-0.1.0/docs/report.png +0 -0
- datasets_lint-0.1.0/pyproject.toml +59 -0
- datasets_lint-0.1.0/scripts/make_bench.py +58 -0
- datasets_lint-0.1.0/src/datasets_lint/__init__.py +21 -0
- datasets_lint-0.1.0/src/datasets_lint/baseline.py +71 -0
- datasets_lint-0.1.0/src/datasets_lint/checks/__init__.py +18 -0
- datasets_lint-0.1.0/src/datasets_lint/checks/base.py +38 -0
- datasets_lint-0.1.0/src/datasets_lint/checks/dup.py +187 -0
- datasets_lint-0.1.0/src/datasets_lint/checks/pii.py +134 -0
- datasets_lint-0.1.0/src/datasets_lint/checks/stats.py +96 -0
- datasets_lint-0.1.0/src/datasets_lint/cli.py +168 -0
- datasets_lint-0.1.0/src/datasets_lint/config.py +64 -0
- datasets_lint-0.1.0/src/datasets_lint/dataset.py +207 -0
- datasets_lint-0.1.0/src/datasets_lint/engine.py +55 -0
- datasets_lint-0.1.0/src/datasets_lint/report/__init__.py +8 -0
- datasets_lint-0.1.0/src/datasets_lint/report/html_out.py +106 -0
- datasets_lint-0.1.0/src/datasets_lint/report/json_out.py +17 -0
- datasets_lint-0.1.0/src/datasets_lint/report/templates/report.html.j2 +124 -0
- datasets_lint-0.1.0/src/datasets_lint/result.py +110 -0
- datasets_lint-0.1.0/tests/fixtures/clean.jsonl +10 -0
- datasets_lint-0.1.0/tests/fixtures/dirty.jsonl +12 -0
- datasets_lint-0.1.0/tests/test_checks.py +92 -0
- datasets_lint-0.1.0/tests/test_cli.py +89 -0
- datasets_lint-0.1.0/tests/test_dataset.py +57 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
24
|
+
enable-cache: true
|
|
25
|
+
cache-dependency-glob: "pyproject.toml"
|
|
26
|
+
- name: Install
|
|
27
|
+
run: uv pip install -e ".[dev]"
|
|
28
|
+
- name: Lint
|
|
29
|
+
run: uv run ruff check .
|
|
30
|
+
- name: Format check
|
|
31
|
+
run: uv run ruff format --check .
|
|
32
|
+
- name: Tests
|
|
33
|
+
run: uv run pytest --cov=datasets_lint --cov-report=term-missing
|
|
34
|
+
|
|
35
|
+
self-lint:
|
|
36
|
+
# dogfood: the linter must run cleanly on its own fixtures
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: astral-sh/setup-uv@v5
|
|
41
|
+
with:
|
|
42
|
+
enable-cache: true
|
|
43
|
+
cache-dependency-glob: "pyproject.toml"
|
|
44
|
+
- name: Audit fixtures
|
|
45
|
+
run: |
|
|
46
|
+
uvx --from . datasets-lint run tests/fixtures/clean.jsonl --report clean.html
|
|
47
|
+
uvx --from . datasets-lint run tests/fixtures/dirty.jsonl --fail-on none --report dirty.html
|
|
48
|
+
- uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: self-lint-reports
|
|
51
|
+
path: "*.html"
|
|
52
|
+
|
|
53
|
+
build:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: astral-sh/setup-uv@v5
|
|
58
|
+
- run: uv build
|
|
59
|
+
- name: Smoke-test the wheel in a clean venv
|
|
60
|
+
run: |
|
|
61
|
+
python -m venv /tmp/venv
|
|
62
|
+
/tmp/venv/bin/pip install dist/*.whl
|
|
63
|
+
/tmp/venv/bin/datasets-lint --version
|
|
64
|
+
/tmp/venv/bin/datasets-lint run tests/fixtures/dirty.jsonl --fail-on none
|
|
65
|
+
- uses: actions/upload-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: dist
|
|
68
|
+
path: dist/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write # trusted publishing to PyPI (no API token needed)
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv build
|
|
18
|
+
- uses: actions/upload-artifact@v4
|
|
19
|
+
with:
|
|
20
|
+
name: dist
|
|
21
|
+
path: dist/
|
|
22
|
+
|
|
23
|
+
publish:
|
|
24
|
+
needs: build
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/download-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping! This project is built to be contributed to: every check is a
|
|
4
|
+
single small file, and the plugin API is intentionally tiny.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```console
|
|
9
|
+
git clone https://github.com/horizonbymuneeb/datasets-lint
|
|
10
|
+
cd datasets-lint
|
|
11
|
+
uv venv && uv pip install -e ".[dev]"
|
|
12
|
+
pytest
|
|
13
|
+
ruff check . && ruff format .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
No GPU, no API keys, no network needed for tests.
|
|
17
|
+
|
|
18
|
+
## Add a check (the main way to contribute)
|
|
19
|
+
|
|
20
|
+
1. Copy an existing check, e.g. `src/datasets_lint/checks/stats.py`.
|
|
21
|
+
2. Implement `run(self, ds: Dataset, ctx: Context) -> CheckResult`.
|
|
22
|
+
- read data via `ds.texts()` or `ds.rows` (already sampled)
|
|
23
|
+
- read thresholds via `ctx.threshold(self.id, "name", default)`
|
|
24
|
+
- return `severity`, a one-line `summary`, numeric `metrics`, and up to ~50 `Finding`s
|
|
25
|
+
- **never** put raw PII in `message` — use `datasets_lint.checks.pii.redact`
|
|
26
|
+
3. Register it in `src/datasets_lint/checks/__init__.py`.
|
|
27
|
+
4. Add a tiny fixture under `tests/fixtures/` and a test in `tests/test_checks.py`.
|
|
28
|
+
5. Document the id, what it finds and its default threshold in `README.md`.
|
|
29
|
+
|
|
30
|
+
That's it — a reviewable PR of ~100 lines.
|
|
31
|
+
|
|
32
|
+
## Check design rules
|
|
33
|
+
|
|
34
|
+
- **Deterministic**: seed everything; the same input must give the same report.
|
|
35
|
+
- **Bounded**: cap findings and any quadratic work; sample if needed.
|
|
36
|
+
- **Fast**: a check should stay under ~10 s for 100k rows on 4 vCPUs.
|
|
37
|
+
- **Honest**: if a metric is an estimate, say so in the `summary` or `metrics` key name.
|
|
38
|
+
- **Actionable**: the finding message should tell the user what to do, not just what is wrong.
|
|
39
|
+
|
|
40
|
+
## Ideas that are not built yet
|
|
41
|
+
|
|
42
|
+
`contamination`, `leakage`, `langid`, `schema`, `toxicity`, `dedup-apply`,
|
|
43
|
+
SARIF output, HuggingFace Hub input (`hf:org/name`), WASM playground.
|
|
44
|
+
Open an issue before starting a big one so we don't duplicate work.
|
|
45
|
+
|
|
46
|
+
## Commit style
|
|
47
|
+
|
|
48
|
+
`check(dup): report cross-split clusters` — conventional-ish, no ceremony.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 datasets-lint contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: datasets-lint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ruff for training data: audit datasets for duplicates, PII, leakage and contamination
|
|
5
|
+
Project-URL: Homepage, https://github.com/horizonbymuneeb/datasets-lint
|
|
6
|
+
Project-URL: Issues, https://github.com/horizonbymuneeb/datasets-lint/issues
|
|
7
|
+
Author: datasets-lint contributors
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: data-quality,datasets,deduplication,linter,llm,machine-learning,pii
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: datasketch>=1.6
|
|
24
|
+
Requires-Dist: jinja2>=3.1
|
|
25
|
+
Requires-Dist: polars>=1.0
|
|
26
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
31
|
+
Provides-Extra: hf
|
|
32
|
+
Requires-Dist: datasets>=2.18; extra == 'hf'
|
|
33
|
+
Provides-Extra: ner
|
|
34
|
+
Requires-Dist: spacy>=3.7; extra == 'ner'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# datasets-lint
|
|
38
|
+
|
|
39
|
+
**ruff for training data.** Audit a dataset for duplicates, PII, distribution problems and
|
|
40
|
+
eval-set contamination — offline, on CPU, in one command, with a CI-friendly exit code.
|
|
41
|
+
|
|
42
|
+
```console
|
|
43
|
+
$ datasets-lint run train.jsonl --report out.html
|
|
44
|
+
datasets-lint 0.1.0 · train.jsonl
|
|
45
|
+
120,000 rows audited (of 120,000, seed 0) · text=text label=label
|
|
46
|
+
|
|
47
|
+
✓ stats 0.42s 120,000 rows · 0.00% empty · worst column 0.0% null
|
|
48
|
+
✗ dup 12.10s 18.20% near-duplicate (4,211 clusters), 12.00% exact
|
|
49
|
+
✗ pii 0.91s 0.41% of rows contain PII (email 487, phone 12, api_key 1)
|
|
50
|
+
|
|
51
|
+
verdict: error — 2 errors, 0 warnings, 1 ok · 13.43s
|
|
52
|
+
report → out.html
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Why
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
Every team that fine-tunes or publishes a dataset re-writes the same checks in a
|
|
60
|
+
throwaway notebook: duplicates, PII, contamination. It is slow, inconsistent, and never
|
|
61
|
+
runs in CI. `datasets-lint` packages it once:
|
|
62
|
+
|
|
63
|
+
- **fast** — samples 100k rows by default and finishes in seconds, not hours
|
|
64
|
+
- **offline** — no API keys, no cloud, no data leaves the machine
|
|
65
|
+
- **CI-native** — `exit 1` on threshold breach, JSON output, baseline diffing
|
|
66
|
+
- **readable** — one self-contained HTML report you can attach to a dataset card or a PR
|
|
67
|
+
- **extensible** — one check = one file; the plugin API is ~20 lines
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```console
|
|
72
|
+
uvx datasets-lint --help # run without installing
|
|
73
|
+
# or
|
|
74
|
+
pip install datasets-lint
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```console
|
|
80
|
+
datasets-lint run data.jsonl # text summary
|
|
81
|
+
datasets-lint run data.parquet --report out.html # single-file HTML report
|
|
82
|
+
datasets-lint run train.jsonl test.jsonl --checks dup # cross-split duplicate check
|
|
83
|
+
datasets-lint run data.csv --json-out report.json --fail-on warn
|
|
84
|
+
datasets-lint run data.jsonl --baseline prev.json # did this PR make the data worse?
|
|
85
|
+
datasets-lint checks # list checks
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Supported inputs: `.jsonl`, `.ndjson`, `.json`, `.csv`, `.parquet`.
|
|
89
|
+
|
|
90
|
+
Exit codes: `0` ok · `1` threshold breached · `2` usage/input error.
|
|
91
|
+
|
|
92
|
+
## Checks
|
|
93
|
+
|
|
94
|
+
| id | what it finds | default severity |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `stats` | empty rows, nulls, length percentiles, label balance | `error` if >1% empty |
|
|
97
|
+
| `dup` | exact + near duplicates (MinHash/LSH), cross-file overlap | `error` if >2% near-dup |
|
|
98
|
+
| `pii` | emails, phones, IPs, IBANs, SSNs, API keys (redacted) | `error` on secrets |
|
|
99
|
+
| `contamination` | overlap with eval sets (GSM8K, MMLU, HumanEval) | _planned for v0.2_ |
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
`datasets-lint.toml` in the working directory (or `--config path`):
|
|
104
|
+
|
|
105
|
+
```toml
|
|
106
|
+
[defaults]
|
|
107
|
+
sample = 100000
|
|
108
|
+
seed = 0
|
|
109
|
+
fail_on = "error" # none | warn | error
|
|
110
|
+
|
|
111
|
+
[checks.dup]
|
|
112
|
+
threshold = 0.9 # Jaccard threshold for near-duplicates
|
|
113
|
+
num_perm = 64
|
|
114
|
+
shingle_size = 5
|
|
115
|
+
max_near_dup_pct = 2.0
|
|
116
|
+
|
|
117
|
+
[checks.pii]
|
|
118
|
+
max_error_findings_pct = 0.1
|
|
119
|
+
max_warn_findings_pct = 5.0
|
|
120
|
+
|
|
121
|
+
[checks.stats]
|
|
122
|
+
max_empty_pct = 1.0
|
|
123
|
+
max_null_pct = 50.0
|
|
124
|
+
min_label_pct = 1.0
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Writing a check
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# src/datasets_lint/checks/short.py
|
|
131
|
+
from datasets_lint.checks.base import Check, Context
|
|
132
|
+
from datasets_lint.dataset import Dataset
|
|
133
|
+
from datasets_lint.result import CheckResult, Severity
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ShortRowsCheck(Check):
|
|
137
|
+
id = "short"
|
|
138
|
+
title = "Short rows"
|
|
139
|
+
description = "Flags texts under N characters."
|
|
140
|
+
|
|
141
|
+
def run(self, ds: Dataset, ctx: Context) -> CheckResult:
|
|
142
|
+
limit = ctx.threshold(self.id, "min_chars", 20)
|
|
143
|
+
bad = [i for i, t in ds.texts() if 0 < len(t) < limit]
|
|
144
|
+
pct = round(100 * len(bad) / max(ds.audited_rows, 1), 2)
|
|
145
|
+
return CheckResult(
|
|
146
|
+
check_id=self.id,
|
|
147
|
+
title=self.title,
|
|
148
|
+
severity=Severity.WARN if bad else Severity.OK,
|
|
149
|
+
summary=f"{pct}% of rows are shorter than {limit} chars",
|
|
150
|
+
metrics={"short_pct": pct},
|
|
151
|
+
findings=[], # add Finding(...) entries for samples
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Register it in `src/datasets_lint/checks/__init__.py`. Add a fixture and a test — that's it.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# datasets-lint
|
|
2
|
+
|
|
3
|
+
**ruff for training data.** Audit a dataset for duplicates, PII, distribution problems and
|
|
4
|
+
eval-set contamination — offline, on CPU, in one command, with a CI-friendly exit code.
|
|
5
|
+
|
|
6
|
+
```console
|
|
7
|
+
$ datasets-lint run train.jsonl --report out.html
|
|
8
|
+
datasets-lint 0.1.0 · train.jsonl
|
|
9
|
+
120,000 rows audited (of 120,000, seed 0) · text=text label=label
|
|
10
|
+
|
|
11
|
+
✓ stats 0.42s 120,000 rows · 0.00% empty · worst column 0.0% null
|
|
12
|
+
✗ dup 12.10s 18.20% near-duplicate (4,211 clusters), 12.00% exact
|
|
13
|
+
✗ pii 0.91s 0.41% of rows contain PII (email 487, phone 12, api_key 1)
|
|
14
|
+
|
|
15
|
+
verdict: error — 2 errors, 0 warnings, 1 ok · 13.43s
|
|
16
|
+
report → out.html
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
Every team that fine-tunes or publishes a dataset re-writes the same checks in a
|
|
24
|
+
throwaway notebook: duplicates, PII, contamination. It is slow, inconsistent, and never
|
|
25
|
+
runs in CI. `datasets-lint` packages it once:
|
|
26
|
+
|
|
27
|
+
- **fast** — samples 100k rows by default and finishes in seconds, not hours
|
|
28
|
+
- **offline** — no API keys, no cloud, no data leaves the machine
|
|
29
|
+
- **CI-native** — `exit 1` on threshold breach, JSON output, baseline diffing
|
|
30
|
+
- **readable** — one self-contained HTML report you can attach to a dataset card or a PR
|
|
31
|
+
- **extensible** — one check = one file; the plugin API is ~20 lines
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
uvx datasets-lint --help # run without installing
|
|
37
|
+
# or
|
|
38
|
+
pip install datasets-lint
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
datasets-lint run data.jsonl # text summary
|
|
45
|
+
datasets-lint run data.parquet --report out.html # single-file HTML report
|
|
46
|
+
datasets-lint run train.jsonl test.jsonl --checks dup # cross-split duplicate check
|
|
47
|
+
datasets-lint run data.csv --json-out report.json --fail-on warn
|
|
48
|
+
datasets-lint run data.jsonl --baseline prev.json # did this PR make the data worse?
|
|
49
|
+
datasets-lint checks # list checks
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Supported inputs: `.jsonl`, `.ndjson`, `.json`, `.csv`, `.parquet`.
|
|
53
|
+
|
|
54
|
+
Exit codes: `0` ok · `1` threshold breached · `2` usage/input error.
|
|
55
|
+
|
|
56
|
+
## Checks
|
|
57
|
+
|
|
58
|
+
| id | what it finds | default severity |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `stats` | empty rows, nulls, length percentiles, label balance | `error` if >1% empty |
|
|
61
|
+
| `dup` | exact + near duplicates (MinHash/LSH), cross-file overlap | `error` if >2% near-dup |
|
|
62
|
+
| `pii` | emails, phones, IPs, IBANs, SSNs, API keys (redacted) | `error` on secrets |
|
|
63
|
+
| `contamination` | overlap with eval sets (GSM8K, MMLU, HumanEval) | _planned for v0.2_ |
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
`datasets-lint.toml` in the working directory (or `--config path`):
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
[defaults]
|
|
71
|
+
sample = 100000
|
|
72
|
+
seed = 0
|
|
73
|
+
fail_on = "error" # none | warn | error
|
|
74
|
+
|
|
75
|
+
[checks.dup]
|
|
76
|
+
threshold = 0.9 # Jaccard threshold for near-duplicates
|
|
77
|
+
num_perm = 64
|
|
78
|
+
shingle_size = 5
|
|
79
|
+
max_near_dup_pct = 2.0
|
|
80
|
+
|
|
81
|
+
[checks.pii]
|
|
82
|
+
max_error_findings_pct = 0.1
|
|
83
|
+
max_warn_findings_pct = 5.0
|
|
84
|
+
|
|
85
|
+
[checks.stats]
|
|
86
|
+
max_empty_pct = 1.0
|
|
87
|
+
max_null_pct = 50.0
|
|
88
|
+
min_label_pct = 1.0
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Writing a check
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# src/datasets_lint/checks/short.py
|
|
95
|
+
from datasets_lint.checks.base import Check, Context
|
|
96
|
+
from datasets_lint.dataset import Dataset
|
|
97
|
+
from datasets_lint.result import CheckResult, Severity
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ShortRowsCheck(Check):
|
|
101
|
+
id = "short"
|
|
102
|
+
title = "Short rows"
|
|
103
|
+
description = "Flags texts under N characters."
|
|
104
|
+
|
|
105
|
+
def run(self, ds: Dataset, ctx: Context) -> CheckResult:
|
|
106
|
+
limit = ctx.threshold(self.id, "min_chars", 20)
|
|
107
|
+
bad = [i for i, t in ds.texts() if 0 < len(t) < limit]
|
|
108
|
+
pct = round(100 * len(bad) / max(ds.audited_rows, 1), 2)
|
|
109
|
+
return CheckResult(
|
|
110
|
+
check_id=self.id,
|
|
111
|
+
title=self.title,
|
|
112
|
+
severity=Severity.WARN if bad else Severity.OK,
|
|
113
|
+
summary=f"{pct}% of rows are shorter than {limit} chars",
|
|
114
|
+
metrics={"short_pct": pct},
|
|
115
|
+
findings=[], # add Finding(...) entries for samples
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Register it in `src/datasets_lint/checks/__init__.py`. Add a fixture and a test — that's it.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# datasets-lint — Roadmap & Build Plan
|
|
2
|
+
|
|
3
|
+
> `ruff` for training data. A fast, CI-friendly linter that audits a dataset for
|
|
4
|
+
> duplicates, PII, label leakage, contamination and distribution problems, then
|
|
5
|
+
> emits a JSON + HTML report and a non-zero exit code when thresholds are breached.
|
|
6
|
+
|
|
7
|
+
**Status:** v0.1 core built — contamination + launch pending · **License:** MIT · **Stack:** Python 3.10+ (uv, polars, datasketch, jinja2)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Problem
|
|
12
|
+
|
|
13
|
+
Before fine-tuning or publishing a dataset, people hand-roll the same checks in
|
|
14
|
+
throwaway notebooks:
|
|
15
|
+
|
|
16
|
+
- exact/near-duplicate rows (inflate loss, leak eval answers)
|
|
17
|
+
- PII (emails, phones, keys) that must not ship
|
|
18
|
+
- train/test contamination against MMLU / GSM8K / HumanEval
|
|
19
|
+
- label leakage (target encoded in a feature)
|
|
20
|
+
- class imbalance, length outliers, empty/near-empty rows
|
|
21
|
+
|
|
22
|
+
Nothing packaged, nothing CI-able, nothing that produces a report a reviewer can read.
|
|
23
|
+
`datasets-lint` is that tool.
|
|
24
|
+
|
|
25
|
+
## 2. Non-goals (v0.1)
|
|
26
|
+
|
|
27
|
+
- No model training, no GPU, no LLM judges (must run offline on a laptop)
|
|
28
|
+
- No dataset hosting/versioning (we read local files + HF `datasets`)
|
|
29
|
+
- No automatic "fixing" of data — we report, the user decides
|
|
30
|
+
- No web UI/server in v1 (static HTML report only)
|
|
31
|
+
|
|
32
|
+
## 3. Success criteria
|
|
33
|
+
|
|
34
|
+
| Metric | Target |
|
|
35
|
+
|---|---|
|
|
36
|
+
| 100k-row JSONL audit (4 vCPU) | < 60 s, < 2 GB RSS |
|
|
37
|
+
| Checks shipped in v0.1 | 4 (`stats`, `dup`, `pii`, `contamination`) |
|
|
38
|
+
| Test coverage of core | ≥ 85% |
|
|
39
|
+
| Contributor on-ramp | one new check = one file + one fixture |
|
|
40
|
+
| Install | `uvx datasets-lint` works with zero config |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 4. Phases
|
|
45
|
+
|
|
46
|
+
### Phase 0 — Skeleton (½ day)
|
|
47
|
+
- [x] Repo init, MIT license, `.gitignore`, README with the one-command demo
|
|
48
|
+
- [x] `pyproject.toml` (hatchling, src-layout, `console_scripts: datasets-lint`)
|
|
49
|
+
- [x] `uv sync`, ruff + pytest config
|
|
50
|
+
- **Exit:** `uv run datasets-lint --help` prints help.
|
|
51
|
+
|
|
52
|
+
### Phase 1 — Core engine (1 day)
|
|
53
|
+
- [x] `Dataset` loader: `.jsonl`, `.csv`, `.parquet` (HF `datasets` input is backlog)
|
|
54
|
+
- [x] Plugin API:
|
|
55
|
+
`class Check(Protocol): id, name, run(ds, ctx) -> CheckResult`
|
|
56
|
+
- [x] `CheckResult` = severity, summary, metrics dict, findings (capped sample)
|
|
57
|
+
- [x] Registry + config file `datasets-lint.toml` (thresholds, ignore rules)
|
|
58
|
+
- [x] CLI: `datasets-lint run PATH [--checks ...] [--sample N] [--fail-on error]`
|
|
59
|
+
- **Exit:** two dummy checks run end-to-end and print a text summary.
|
|
60
|
+
|
|
61
|
+
### Phase 2 — First real checks (2–3 days)
|
|
62
|
+
- [x] `stats`: row count, nulls, token-length histogram, class balance, empty rows
|
|
63
|
+
- [x] `dup`: exact hash + MinHash/LSH near-dup, cluster report, % duplicated
|
|
64
|
+
- [x] `pii`: regex pack (email, phone, IBAN, IP, API-key shapes) + optional
|
|
65
|
+
small NER via `[ner]` extra; findings redacted in report
|
|
66
|
+
- **Exit:** on a 50k sample of a public dataset, `dup` and `pii` produce
|
|
67
|
+
plausible numbers in < 30 s.
|
|
68
|
+
|
|
69
|
+
### Phase 3 — Differentiator: contamination (2 days)
|
|
70
|
+
- [ ] Bundled mini eval-set fingerprints (GSM8K, MMLU sample, HumanEval) as n-gram
|
|
71
|
+
sketches, downloaded on demand and cached
|
|
72
|
+
- [ ] `contamination`: n-gram overlap + MinHash match, per-eval-set hit rate
|
|
73
|
+
- [ ] Clear verdict: "3.1% of rows overlap GSM8K (threshold 1%)"
|
|
74
|
+
- **Exit:** a known-contaminated public dataset is correctly flagged; a clean one is not.
|
|
75
|
+
|
|
76
|
+
### Phase 4 — Reports (1–2 days)
|
|
77
|
+
- [x] JSON schema (stable, versioned) for machine consumption
|
|
78
|
+
- [x] HTML report via jinja2: summary cards, per-check sections, sample findings,
|
|
79
|
+
histogram charts (inline SVG, no JS deps)
|
|
80
|
+
- [x] `--report out.html`, `--format json|text|html`
|
|
81
|
+
- [x] `--baseline prev.json` diff mode (did this PR make the data worse?)
|
|
82
|
+
- **Exit:** report opens standalone, looks good, includes exit-code rationale.
|
|
83
|
+
|
|
84
|
+
### Phase 5 — Packaging & distribution (1 day)
|
|
85
|
+
- [x] `uv build` → sdist + wheel; install into a clean venv and run the CLI
|
|
86
|
+
- [x] Extras: `[ner]`, `[hf]`, `[dev]`
|
|
87
|
+
- [ ] TestPyPI dry run → PyPI publish (Trusted Publishing via GitHub OIDC on tag)
|
|
88
|
+
- [ ] GitHub Action (`action.yml`): runs the linter, uploads report as artifact,
|
|
89
|
+
posts a PR comment
|
|
90
|
+
- [ ] pre-commit hook definition
|
|
91
|
+
- **Exit:** `uvx datasets-lint run x.jsonl` works from a clean machine.
|
|
92
|
+
|
|
93
|
+
### Phase 6 — Launch (1 day)
|
|
94
|
+
- [ ] README: problem → 30-second demo → GIF of the HTML report → install → checks table
|
|
95
|
+
- [ ] `CONTRIBUTING.md` + `docs/writing-a-check.md` (10-line check example)
|
|
96
|
+
- [ ] 5–8 `good first issue` entries (one per missing check)
|
|
97
|
+
- [ ] CI matrix: Python 3.10–3.13, ruff, pytest, self-lint a fixture dataset
|
|
98
|
+
- [ ] Show HN / r/MachineLearning / HF forum post draft with real numbers
|
|
99
|
+
- **Exit:** a stranger can add a check in < 30 minutes using only the docs.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 5. Repo layout
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
datasets-lint/
|
|
107
|
+
├── pyproject.toml
|
|
108
|
+
├── README.md ROADMAP.md CONTRIBUTING.md LICENSE
|
|
109
|
+
├── action.yml # GitHub Action
|
|
110
|
+
├── .pre-commit-hooks.yaml
|
|
111
|
+
├── src/datasets_lint/
|
|
112
|
+
│ ├── cli.py
|
|
113
|
+
│ ├── dataset.py # loaders + sampling
|
|
114
|
+
│ ├── config.py # datasets-lint.toml
|
|
115
|
+
│ ├── registry.py # plugin discovery
|
|
116
|
+
│ ├── result.py # CheckResult, Severity, Report
|
|
117
|
+
│ ├── checks/{stats,dup,pii,contamination}.py
|
|
118
|
+
│ └── report/{json_out.py,html_out.py,templates/report.html.j2}
|
|
119
|
+
├── tests/{test_cli.py,test_checks/,fixtures/*.jsonl}
|
|
120
|
+
└── .github/workflows/{ci.yml,publish.yml}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 6. Key design decisions
|
|
124
|
+
|
|
125
|
+
| Decision | Choice | Why |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| Data engine | `polars` (+ `pyarrow`) | fast on CPU, low RAM, lazy scans |
|
|
128
|
+
| Near-dup | `datasketch` MinHashLSH | battle-tested, tunable threshold |
|
|
129
|
+
| Sampling | default cap 100k rows, seeded | keeps CI fast and deterministic |
|
|
130
|
+
| Plugin model | plain Python protocol + registry | no entrypoint magic needed for v1 |
|
|
131
|
+
| Reports | jinja2 + inline SVG | single-file, no CDN, offline |
|
|
132
|
+
| Findings cap | 50 samples per check in report | avoid 500 MB HTML files |
|
|
133
|
+
| Exit codes | 0 ok, 1 threshold breach, 2 usage error | CI-friendly |
|
|
134
|
+
|
|
135
|
+
## 7. Risks & mitigations
|
|
136
|
+
|
|
137
|
+
| Risk | Mitigation |
|
|
138
|
+
|---|---|
|
|
139
|
+
| Near-dup too slow on big files | sampling + LSH band tuning; `--sample` flag; benchmark in CI |
|
|
140
|
+
| Eval-set fingerprints are large | store 5-gram sketches, not text; lazy download + cache |
|
|
141
|
+
| PII regex false positives | severity `warn` by default, `error` only for high-confidence patterns |
|
|
142
|
+
| Scope creep (10 checks) | v0.1 ships 4; everything else is a `good first issue` |
|
|
143
|
+
| Name collision later | `datasets-lint` verified free on PyPI (2026-10-31) |
|
|
144
|
+
|
|
145
|
+
## 8. Day-by-day (compressed)
|
|
146
|
+
|
|
147
|
+
| Day | Deliverable |
|
|
148
|
+
|---|---|
|
|
149
|
+
| 1 | Phase 0 + Phase 1: skeleton, loader, plugin API, CLI |
|
|
150
|
+
| 2 | `stats` + `dup` working, tests |
|
|
151
|
+
| 3 | `pii`, config file, sampling, benchmarks |
|
|
152
|
+
| 4–5 | `contamination` + eval-set fingerprints |
|
|
153
|
+
| 6 | HTML/JSON reports + baseline diff |
|
|
154
|
+
| 7 | Packaging, TestPyPI, GitHub Action, pre-commit |
|
|
155
|
+
| 8 | Docs, launch README, good-first-issues, publish v0.1.0 |
|
|
156
|
+
|
|
157
|
+
## 9. Backlog after v0.1
|
|
158
|
+
|
|
159
|
+
`leakage` (target in features) · `dedup-apply` (emit cleaned file) · `langid` mix check ·
|
|
160
|
+
`toxicity` (small model) · `schema` check · SARIF output for GitHub code scanning ·
|
|
161
|
+
HF Hub integration (`datasets-lint run hf:org/name`) · Rust/`polars` fast path ·
|
|
162
|
+
web playground (WASM) · leaderboard of "lint scores" for popular public datasets.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 10. Definition of done for v0.1.0
|
|
167
|
+
|
|
168
|
+
1. `uvx datasets-lint run data.jsonl --report out.html` works on a clean machine
|
|
169
|
+
2. 4 checks, documented, each with tests and a fixture
|
|
170
|
+
3. HTML + JSON report, exit codes correct
|
|
171
|
+
4. Published on PyPI, tag `v0.1.0`, GitHub Action usable
|
|
172
|
+
5. README has real benchmark numbers from this VM (4 vCPU / 8 GB)
|
|
173
|
+
6. ≥ 5 `good first issue`s open
|