data-sampler 3.2.1__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.
- data_sampler-3.2.1/.gitattributes +2 -0
- data_sampler-3.2.1/.github/workflows/release.yml +75 -0
- data_sampler-3.2.1/.gitignore +192 -0
- data_sampler-3.2.1/CHANGELOG.md +224 -0
- data_sampler-3.2.1/LICENSE +21 -0
- data_sampler-3.2.1/PKG-INFO +427 -0
- data_sampler-3.2.1/README.md +372 -0
- data_sampler-3.2.1/RELEASING.md +54 -0
- data_sampler-3.2.1/ROADMAP.md +97 -0
- data_sampler-3.2.1/TROUBLESHOOTING.md +239 -0
- data_sampler-3.2.1/examples/employees.csv +1001 -0
- data_sampler-3.2.1/examples/using_data_sampler.ipynb +820 -0
- data_sampler-3.2.1/pyproject.toml +59 -0
- data_sampler-3.2.1/requirements.txt +7 -0
- data_sampler-3.2.1/scripts/run-tui.bat +22 -0
- data_sampler-3.2.1/scripts/run-tui.sh +20 -0
- data_sampler-3.2.1/src/data_sampler/__init__.py +82 -0
- data_sampler-3.2.1/src/data_sampler/__main__.py +7 -0
- data_sampler-3.2.1/src/data_sampler/_logging.py +61 -0
- data_sampler-3.2.1/src/data_sampler/_names.py +60 -0
- data_sampler-3.2.1/src/data_sampler/anonymize.py +463 -0
- data_sampler-3.2.1/src/data_sampler/cli.py +363 -0
- data_sampler-3.2.1/src/data_sampler/engine.py +628 -0
- data_sampler-3.2.1/src/data_sampler/io.py +75 -0
- data_sampler-3.2.1/src/data_sampler/report.py +289 -0
- data_sampler-3.2.1/src/data_sampler/sampling.py +193 -0
- data_sampler-3.2.1/src/data_sampler/stats.py +174 -0
- data_sampler-3.2.1/src/data_sampler/tui/__init__.py +16 -0
- data_sampler-3.2.1/src/data_sampler/tui/app.py +904 -0
- data_sampler-3.2.1/src/data_sampler/workflow.py +285 -0
- data_sampler-3.2.1/tests/conftest.py +31 -0
- data_sampler-3.2.1/tests/test_anonymize.py +373 -0
- data_sampler-3.2.1/tests/test_cli.py +229 -0
- data_sampler-3.2.1/tests/test_engine.py +364 -0
- data_sampler-3.2.1/tests/test_io.py +74 -0
- data_sampler-3.2.1/tests/test_report.py +122 -0
- data_sampler-3.2.1/tests/test_sampling.py +103 -0
- data_sampler-3.2.1/tests/test_stats.py +98 -0
- data_sampler-3.2.1/tests/test_tui.py +223 -0
- data_sampler-3.2.1/tests/test_workflow.py +294 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC) — no API token stored anywhere.
|
|
4
|
+
# One-time setup on the PyPI side is documented in RELEASING.md.
|
|
5
|
+
#
|
|
6
|
+
# Triggered by publishing a GitHub Release (recommended) or manually from the
|
|
7
|
+
# Actions tab. It will NOT fire on ordinary pushes or tags, so nothing is
|
|
8
|
+
# uploaded by accident.
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
release:
|
|
12
|
+
types: [published]
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: Test suite
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: true
|
|
21
|
+
matrix:
|
|
22
|
+
os: [ubuntu-latest, windows-latest]
|
|
23
|
+
python-version: ["3.10", "3.12"]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python-version }}
|
|
29
|
+
- name: Install package with dev extras
|
|
30
|
+
run: python -m pip install -e ".[dev]"
|
|
31
|
+
- name: Run tests
|
|
32
|
+
run: python -m pytest -q
|
|
33
|
+
|
|
34
|
+
build:
|
|
35
|
+
name: Build distributions
|
|
36
|
+
needs: test
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
- name: Build sdist and wheel
|
|
44
|
+
run: |
|
|
45
|
+
python -m pip install build twine
|
|
46
|
+
python -m build
|
|
47
|
+
python -m twine check dist/*
|
|
48
|
+
- name: Smoke-test the wheel
|
|
49
|
+
run: |
|
|
50
|
+
python -m pip install dist/*.whl
|
|
51
|
+
python -c "import data_sampler; print('wheel ok', data_sampler.__version__)"
|
|
52
|
+
data-sampler --version
|
|
53
|
+
- uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: dist
|
|
56
|
+
path: dist/
|
|
57
|
+
|
|
58
|
+
publish:
|
|
59
|
+
name: Publish to PyPI
|
|
60
|
+
needs: build
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
# The 'pypi' environment lets you require manual approval in repo settings
|
|
63
|
+
# before anything is uploaded (Settings > Environments > pypi).
|
|
64
|
+
environment:
|
|
65
|
+
name: pypi
|
|
66
|
+
url: https://pypi.org/p/data-sampler
|
|
67
|
+
permissions:
|
|
68
|
+
id-token: write # required for Trusted Publishing (OIDC)
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/download-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: dist
|
|
73
|
+
path: dist/
|
|
74
|
+
- name: Publish
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
build/
|
|
35
|
+
dist/
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
#uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
#poetry.lock
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
#pdm.lock
|
|
115
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
116
|
+
# in version control.
|
|
117
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
118
|
+
.pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
123
|
+
__pypackages__/
|
|
124
|
+
|
|
125
|
+
# Celery stuff
|
|
126
|
+
celerybeat-schedule
|
|
127
|
+
celerybeat.pid
|
|
128
|
+
|
|
129
|
+
# SageMath parsed files
|
|
130
|
+
*.sage.py
|
|
131
|
+
|
|
132
|
+
# Environments
|
|
133
|
+
.env
|
|
134
|
+
.venv
|
|
135
|
+
env/
|
|
136
|
+
venv/
|
|
137
|
+
ENV/
|
|
138
|
+
env.bak/
|
|
139
|
+
venv.bak/
|
|
140
|
+
|
|
141
|
+
# Spyder project settings
|
|
142
|
+
.spyderproject
|
|
143
|
+
.spyproject
|
|
144
|
+
|
|
145
|
+
# Rope project settings
|
|
146
|
+
.ropeproject
|
|
147
|
+
|
|
148
|
+
# mkdocs documentation
|
|
149
|
+
/site
|
|
150
|
+
|
|
151
|
+
# mypy
|
|
152
|
+
.mypy_cache/
|
|
153
|
+
.dmypy.json
|
|
154
|
+
dmypy.json
|
|
155
|
+
|
|
156
|
+
# Pyre type checker
|
|
157
|
+
.pyre/
|
|
158
|
+
|
|
159
|
+
# pytype static type analyzer
|
|
160
|
+
.pytype/
|
|
161
|
+
|
|
162
|
+
# Cython debug symbols
|
|
163
|
+
cython_debug/
|
|
164
|
+
|
|
165
|
+
# PyCharm
|
|
166
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
167
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
168
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
169
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
170
|
+
#.idea/
|
|
171
|
+
|
|
172
|
+
# Ruff stuff:
|
|
173
|
+
.ruff_cache/
|
|
174
|
+
|
|
175
|
+
# PyPI configuration file
|
|
176
|
+
.pypirc
|
|
177
|
+
|
|
178
|
+
# Cursor
|
|
179
|
+
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
|
|
180
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
181
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
182
|
+
.cursorignore
|
|
183
|
+
.cursorindexingignore
|
|
184
|
+
|
|
185
|
+
# VS Code
|
|
186
|
+
.vscode/*
|
|
187
|
+
|
|
188
|
+
# Built Visual Studio Code Extensions
|
|
189
|
+
*.vsix
|
|
190
|
+
|
|
191
|
+
# Claude folder
|
|
192
|
+
.claude/
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v3.2.1 — 2026-07-23
|
|
4
|
+
|
|
5
|
+
- **Fixed a TUI race that only surfaced on slow machines** (caught by the
|
|
6
|
+
v3.2.0 release CI on Windows runners; it blocked the PyPI publish): a late
|
|
7
|
+
mount-time row-highlight for the already-selected column could re-sync the
|
|
8
|
+
config panel and queue a stale `Changed` message that reset a just-made
|
|
9
|
+
anonymizer choice back to `none`. Duplicate highlights are now no-ops and
|
|
10
|
+
the panel sync no longer queues gratuitous widget updates. This is the
|
|
11
|
+
first release published to PyPI (v3.2.0's publish run failed on this bug).
|
|
12
|
+
|
|
13
|
+
## v3.2.0 — 2026-07-23
|
|
14
|
+
|
|
15
|
+
- **Measured parallel scaling** (20M-row Parquet, 12-core machine,
|
|
16
|
+
10k-row sample): stratified sampling 14.5 s → 3.9 s from 1 → 8 threads
|
|
17
|
+
(3.7×), `stats()` 9.5 s → 1.7 s (5.6×), reservoir sampling 0.10 s —
|
|
18
|
+
~50× faster than the pandas path on the same file, with no ~0.9 GB
|
|
19
|
+
in-memory materialization.
|
|
20
|
+
|
|
21
|
+
- **Vectorized every anonymizer** (Block P1 of the v3.2 performance & scale
|
|
22
|
+
effort): the transform pipeline now does a single `pd.factorize`
|
|
23
|
+
(dictionary-encode) plus a vectorized gather instead of `pd.unique` + a
|
|
24
|
+
per-unique Python dict + `Series.map` (the in-process equivalent of a native
|
|
25
|
+
join against a mapping table). Sequential IDs use `np.arange`;
|
|
26
|
+
numeric/datetime jitter use vectorized numpy draws. Benchmarks: ~4–6× faster
|
|
27
|
+
on `sequential_id` and up to ~3.3× on `numeric_jitter`, with `sequential_id`
|
|
28
|
+
output verified bit-identical to the old path. The public `build_mapping`
|
|
29
|
+
API, consistent-mapping guarantee, seed reproducibility, and NaN/dtype
|
|
30
|
+
handling are preserved; nullable string dtypes now round-trip. Docstrings
|
|
31
|
+
clarified that relabelling anonymizers (`names`, `sequential_id`,
|
|
32
|
+
`random_string`, `hex`) are bijective while jitter anonymizers
|
|
33
|
+
(`numeric_jitter`, `datetime_jitter`) are bounded noise (distinct nearby
|
|
34
|
+
values may collide).
|
|
35
|
+
- **Added an optional DuckDB out-of-core engine** (`data_sampler.engine`,
|
|
36
|
+
Blocks P2–P4 of the v3.2 performance & scale effort): install
|
|
37
|
+
`pip install "data-sampler[large]"` to push loading, stratification, and
|
|
38
|
+
sampling into DuckDB instead of pandas. Multi-threaded (`PRAGMA threads`)
|
|
39
|
+
and memory-limited with spill-to-disk; reads CSV/TSV/JSON and Parquet
|
|
40
|
+
natively (Parquet with projection pushdown, the biggest I/O win) plus
|
|
41
|
+
pandas DataFrames — only the resulting sample is ever materialized.
|
|
42
|
+
Sampling: reservoir sampling for the random case (exact count, single
|
|
43
|
+
pass, reproducible via `REPEATABLE`) and two-pass proportional stratified
|
|
44
|
+
sampling, with auto-stratification picking low-cardinality columns via
|
|
45
|
+
HyperLogLog (`approx_count_distinct`). Seed-reproducible end to end
|
|
46
|
+
(seeded stratified runs go single-threaded, since DuckDB's `random()`
|
|
47
|
+
ordering is only reproducible that way); NaN strata are joined with
|
|
48
|
+
`IS NOT DISTINCT FROM` so they aren't silently dropped; column identifiers
|
|
49
|
+
are quoted against injection. New `should_use_engine` auto-selects the
|
|
50
|
+
engine for Parquet/large inputs, and `large_materialization_warning` warns
|
|
51
|
+
before loading a large dataset fully into pandas — the pandas path stays
|
|
52
|
+
the default for small/medium data. Adversarially verified across 4 review
|
|
53
|
+
lenses with zero findings; a 2M-row Parquet file sampled to 1000 rows in
|
|
54
|
+
~1s, out-of-core.
|
|
55
|
+
- **Added approximate stats at scale** (`DuckDBEngine.stats()` and
|
|
56
|
+
module-level `engine.stats()`, Block P5 of the v3.2 performance & scale
|
|
57
|
+
effort): per-column `ColumnStats` computed in DuckDB — distinct counts via
|
|
58
|
+
HyperLogLog (`approx_count_distinct`), median via `approx_quantile`, plus
|
|
59
|
+
min/max/mean/std, missing counts, equal-width numeric histograms, and
|
|
60
|
+
categorical/datetime top-values. Scalar aggregates run in one streaming
|
|
61
|
+
pass so stats stay cheap over billions of rows; `approximate=False` gives
|
|
62
|
+
exact counts/quantiles for small inputs, and `distributions=False` skips
|
|
63
|
+
the per-column passes for a single cheap scalar pass across very wide
|
|
64
|
+
inputs. A new `ColumnStats.approximate` flag marks approximate results.
|
|
65
|
+
- **Wired the DuckDB engine into the CLI** (Block P6 of the v3.2 performance
|
|
66
|
+
& scale effort): new `--engine {auto,pandas,duckdb}` flag (default `auto`
|
|
67
|
+
— DuckDB for Parquet/large inputs, pandas otherwise), plus `--threads` and
|
|
68
|
+
`--memory-limit` to tune the engine. The engine path supports
|
|
69
|
+
`--random`/`--skip`/`--seed`/`--anon` and `--suggest` (suggestions computed
|
|
70
|
+
from the engine's approximate stats, without materializing the full
|
|
71
|
+
input); `--interactive` remains pandas-only. When the pandas path is used
|
|
72
|
+
on a large input, a note suggests the engine. Auto-selection falls back to
|
|
73
|
+
pandas if the optional `duckdb` dependency is absent.
|
|
74
|
+
- **Fixed 22 issues found in a pre-release six-lens adversarial audit** (one
|
|
75
|
+
accepted divergence documented in `TROUBLESHOOTING.md`): the DuckDB
|
|
76
|
+
engine's `stats()` no longer crashes on LIST/STRUCT columns or on float
|
|
77
|
+
files containing real NaN values (every NaN-sensitive aggregate is now
|
|
78
|
+
NaN-filtered); `TIME`/`INTERVAL` and nested types (`LIST`, `STRUCT`,
|
|
79
|
+
`MAP`, …) classify as `"other"` and are never treated as numeric or
|
|
80
|
+
datetime; seeded stratified sampling is now deterministic even under
|
|
81
|
+
allocation-remainder ties (stratum order is pinned before allocation);
|
|
82
|
+
columns-oriented JSON (the pandas `to_json()` default) is detected and
|
|
83
|
+
refused with guidance instead of silently sampling a single row; `--skip`
|
|
84
|
+
column names are validated against the source; `--engine auto` now
|
|
85
|
+
genuinely falls back to pandas on a DuckDB read failure instead of
|
|
86
|
+
raising.
|
|
87
|
+
- Alias anonymizer kinds (e.g. `"seq"` for `sequential_id`) now canonicalize
|
|
88
|
+
on `assign`, so the interactive wizard no longer silently resets them to
|
|
89
|
+
`none`; accepting a seeded assignment in the wizard preserves its options
|
|
90
|
+
instead of discarding them. HyperLogLog-based unique counts are clamped to
|
|
91
|
+
the row count so they can no longer push `unique_pct` past 100%.
|
|
92
|
+
- Performance/robustness: row counts are cached per source (3–4 fewer full
|
|
93
|
+
scans per CLI run); `compute_stats` skips the useless top-values pass for
|
|
94
|
+
numeric columns (~1.9× faster); the TUI now computes stats off the event
|
|
95
|
+
loop so loading a large file no longer freezes the UI; report histograms
|
|
96
|
+
exclude ±inf from their percentage denominators and skip near-unique
|
|
97
|
+
columns instead of drawing a meaningless top-8.
|
|
98
|
+
|
|
99
|
+
## v3.1.0 — 2026-07-23 (released with v3.2.0)
|
|
100
|
+
|
|
101
|
+
- **Added a `datetime_jitter` anonymizer** (`DatetimeJitterAnonymizer`):
|
|
102
|
+
shifts each date/time by a random offset within a ±window (±7 days by
|
|
103
|
+
default), with consistent value mapping preserved so the column's
|
|
104
|
+
distribution shape survives. `NaT` is left untouched, string-date columns
|
|
105
|
+
are coerced via `pandas.to_datetime`, and timezone-aware inputs keep their
|
|
106
|
+
zone. Raises `ValueError` if the window is finer than its `unit`. Reachable
|
|
107
|
+
as kinds `datetime_jitter` / `datetime` / `dates`, and wired into the CLI
|
|
108
|
+
`--anon` option and the TUI anonymizer config panel.
|
|
109
|
+
- **Added a guided anonymization workflow** (`data_sampler.workflow`): an
|
|
110
|
+
`AnonymizationPlan` maps each column to a "type" (anonymizer), buildable
|
|
111
|
+
three ways — programmatically (`assign`/`suggest`), interactively via a
|
|
112
|
+
menu wizard (`choose_interactively`, CLI `-i`/`--interactive`), or by
|
|
113
|
+
clicking in the TUI (new `a` auto-suggest action). `suggest_type` infers a
|
|
114
|
+
type per column from its stats, including date-named string columns →
|
|
115
|
+
`datetime_jitter`, using token-based matching so "candidate"/"mandate"
|
|
116
|
+
aren't misread as dates. New public API: `AnonymizationPlan`,
|
|
117
|
+
`suggest_type`, `TYPE_OPTIONS`. CLI gains `--suggest`. The TUI columns
|
|
118
|
+
table was reordered so the anonymizer/strat columns stay visible.
|
|
119
|
+
- **Added column-level histograms** (`column_histogram_data`,
|
|
120
|
+
`format_column_histograms` in `report`): per-column source-vs-sample
|
|
121
|
+
distributions — numeric columns share bin edges, other columns use the
|
|
122
|
+
source's top categories — computed from the pre-anonymization sample so
|
|
123
|
+
they show how faithfully the sample preserved each column. Surfaced as a
|
|
124
|
+
new right-hand "column histograms" panel on the TUI report screen and a
|
|
125
|
+
"COLUMN DISTRIBUTIONS" section in the CLI output. New public API:
|
|
126
|
+
`column_histogram_data`, `format_column_histograms`.
|
|
127
|
+
|
|
128
|
+
## v3.0.1 — 2026-07-19
|
|
129
|
+
|
|
130
|
+
- **Fixed the bundled name library.** The v3.0.0 library contained invented
|
|
131
|
+
non-name strings (e.g. "Argon", "Ardwin"), so the `names` anonymizer could
|
|
132
|
+
produce gibberish. Replaced with a curated list of 200 first, 60 middle,
|
|
133
|
+
and 220 last real names, globally diverse.
|
|
134
|
+
- **Added a bundled example dataset** (`examples/employees.csv`, 1,000 rows,
|
|
135
|
+
stratifiable) and a worked example section in the README covering the
|
|
136
|
+
TUI, the Python API, and the CLI.
|
|
137
|
+
|
|
138
|
+
## v3.0 — 2026-07-19
|
|
139
|
+
|
|
140
|
+
### Package restructure
|
|
141
|
+
|
|
142
|
+
- **Proper Python package.** The flat scripts are gone; everything now lives
|
|
143
|
+
in a src-layout package (`src/data_sampler/`) built with hatchling and
|
|
144
|
+
installable via `pip install -e .` (wheel-ready for a future PyPI
|
|
145
|
+
release — the release itself stays manual, after extensive testing).
|
|
146
|
+
- **Public API.** `load_file`, `list_sheets`, `save_output`,
|
|
147
|
+
`compute_stats`/`ColumnStats`, `sample`/`SampleResult`,
|
|
148
|
+
`stratified_sample`, `find_stratification_columns`,
|
|
149
|
+
`format_stratification_report`, `anonymize`, `make_anonymizer`, and
|
|
150
|
+
`run_tui` are importable from `data_sampler`.
|
|
151
|
+
- **Old Tkinter GUI removed** (`data-sampler-gui.py`), replaced by the TUI.
|
|
152
|
+
- **Central logging** behind `DATA_SAMPLER_LOG` / `DATA_SAMPLER_LOG_FILE`.
|
|
153
|
+
|
|
154
|
+
### Terminal UI (new)
|
|
155
|
+
|
|
156
|
+
- Colorful, panel-based Textual dashboard (btop / lazydocker style):
|
|
157
|
+
file picker with directory browser, Data Wrangler-style column stats
|
|
158
|
+
table (type, missing %, unique count, distribution sparkline, summary),
|
|
159
|
+
per-column detail panel with distribution bars, anonymizer configuration,
|
|
160
|
+
stratification skip toggles, and a post-run report screen.
|
|
161
|
+
- Launch via `data-sampler` (no args), `data-sampler-tui`,
|
|
162
|
+
`python -m data_sampler`, or `data_sampler.run_tui()`.
|
|
163
|
+
|
|
164
|
+
### Anonymizers (new)
|
|
165
|
+
|
|
166
|
+
- Optional per-column anonymization with **consistent value mapping**
|
|
167
|
+
(repeated values stay repeated, so distributions survive) and NaN
|
|
168
|
+
passthrough: `names` (bundled first/middle/last name library, five
|
|
169
|
+
styles), `sequential_id` (start + interval, optional prefix/zero-pad),
|
|
170
|
+
`numeric_jitter` (±20 % by default), `random_string` / `hex`
|
|
171
|
+
(configurable length/charset). Seedable for reproducible runs.
|
|
172
|
+
|
|
173
|
+
### Sampling engine
|
|
174
|
+
|
|
175
|
+
- `sample()` accepts `exclude_columns` (columns the user marks as skipped
|
|
176
|
+
never become stratifiers) and `random_state` for reproducible sampling.
|
|
177
|
+
- Sampling functions no longer print; they return a `SampleResult` that the
|
|
178
|
+
CLI/TUI render via `format_stratification_report`.
|
|
179
|
+
- Fixed string-column classification under pandas 3.0 (new default
|
|
180
|
+
`StringDtype` no longer matched the `object` dtype checks).
|
|
181
|
+
|
|
182
|
+
### CLI
|
|
183
|
+
|
|
184
|
+
- `data-sampler <source> <count>` now supports `--seed`,
|
|
185
|
+
`--skip COL[,COL]`, and repeatable `--anon "COL=KIND[:k=v,...]"`
|
|
186
|
+
anonymization options; with no arguments it opens the TUI.
|
|
187
|
+
|
|
188
|
+
## v2.0 — 2026-04-13
|
|
189
|
+
|
|
190
|
+
### Sampling engine
|
|
191
|
+
|
|
192
|
+
- **Joint intersection stratification.** Stratification now groups rows by the *simultaneous* combination of all selected columns (e.g. `A=x AND B=y AND C=z`), rather than applying stratifiers sequentially. Proportional allocation is computed once per intersection group, ensuring the sample reflects the true joint distribution.
|
|
193
|
+
|
|
194
|
+
- **Missing values included as a category.** Rows with `NaN` in any stratifier column are no longer silently dropped. They are treated as their own `(missing)` category and sampled proportionally alongside non-null values.
|
|
195
|
+
|
|
196
|
+
- **NaN-safe indexing throughout.** All allocation and sampling operations now use positional (integer) indexing to avoid `KeyError` failures when a MultiIndex contains `NaN` keys — a limitation of pandas label-based `.at[]` / `.get_loc()` accessors.
|
|
197
|
+
|
|
198
|
+
### Stratification report
|
|
199
|
+
|
|
200
|
+
- **Method statement.** The report now opens with a plain-language explanation of how groups were formed, so it is clear whether allocation was joint or single-column.
|
|
201
|
+
|
|
202
|
+
- **Fixed 13-character value labels.** All category labels in the report are right-justified to exactly 13 characters. Values longer than 13 characters are truncated to `10 chars + "..."`. Missing values display as `(missing)`.
|
|
203
|
+
|
|
204
|
+
- **Zero-allocation flags.** Any category that received 0 samples is marked inline with `← not represented`.
|
|
205
|
+
|
|
206
|
+
- **Per-category diagnosis.** When categories are unrepresented, a warning block appears showing — for each such value — the number of intersection groups it appeared in, the size of the largest group, and the minimum group size required to earn at least 1 sample. This confirms whether the cause is data sparsity or cross-column splitting.
|
|
207
|
+
|
|
208
|
+
### GUI
|
|
209
|
+
|
|
210
|
+
- **"Open output folder" button.** Opens the output directory in Windows Explorer after a successful run. Disabled until a run completes.
|
|
211
|
+
|
|
212
|
+
- **"Copy log" button.** Copies the full contents of the log panel to the clipboard in one click.
|
|
213
|
+
|
|
214
|
+
- **GUI no longer duplicates sampling logic.** The GUI now loads `data-sampler.py` directly at runtime via `importlib`, so both the CLI and the GUI always execute identical code. This applies to the built EXE as well — `data-sampler.py` is bundled alongside the executable and loaded from there.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## v1.0 — 2024
|
|
219
|
+
|
|
220
|
+
- Initial release with GUI front-end and CLI.
|
|
221
|
+
- Stratified sampling with automatic column selection.
|
|
222
|
+
- Supports CSV, TSV, JSON, Excel, and Parquet.
|
|
223
|
+
- Side-by-side distribution bar charts in the stratification report.
|
|
224
|
+
- Standalone Windows EXE built with PyInstaller.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 programanalytics-isb
|
|
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.
|