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.
Files changed (40) hide show
  1. data_sampler-3.2.1/.gitattributes +2 -0
  2. data_sampler-3.2.1/.github/workflows/release.yml +75 -0
  3. data_sampler-3.2.1/.gitignore +192 -0
  4. data_sampler-3.2.1/CHANGELOG.md +224 -0
  5. data_sampler-3.2.1/LICENSE +21 -0
  6. data_sampler-3.2.1/PKG-INFO +427 -0
  7. data_sampler-3.2.1/README.md +372 -0
  8. data_sampler-3.2.1/RELEASING.md +54 -0
  9. data_sampler-3.2.1/ROADMAP.md +97 -0
  10. data_sampler-3.2.1/TROUBLESHOOTING.md +239 -0
  11. data_sampler-3.2.1/examples/employees.csv +1001 -0
  12. data_sampler-3.2.1/examples/using_data_sampler.ipynb +820 -0
  13. data_sampler-3.2.1/pyproject.toml +59 -0
  14. data_sampler-3.2.1/requirements.txt +7 -0
  15. data_sampler-3.2.1/scripts/run-tui.bat +22 -0
  16. data_sampler-3.2.1/scripts/run-tui.sh +20 -0
  17. data_sampler-3.2.1/src/data_sampler/__init__.py +82 -0
  18. data_sampler-3.2.1/src/data_sampler/__main__.py +7 -0
  19. data_sampler-3.2.1/src/data_sampler/_logging.py +61 -0
  20. data_sampler-3.2.1/src/data_sampler/_names.py +60 -0
  21. data_sampler-3.2.1/src/data_sampler/anonymize.py +463 -0
  22. data_sampler-3.2.1/src/data_sampler/cli.py +363 -0
  23. data_sampler-3.2.1/src/data_sampler/engine.py +628 -0
  24. data_sampler-3.2.1/src/data_sampler/io.py +75 -0
  25. data_sampler-3.2.1/src/data_sampler/report.py +289 -0
  26. data_sampler-3.2.1/src/data_sampler/sampling.py +193 -0
  27. data_sampler-3.2.1/src/data_sampler/stats.py +174 -0
  28. data_sampler-3.2.1/src/data_sampler/tui/__init__.py +16 -0
  29. data_sampler-3.2.1/src/data_sampler/tui/app.py +904 -0
  30. data_sampler-3.2.1/src/data_sampler/workflow.py +285 -0
  31. data_sampler-3.2.1/tests/conftest.py +31 -0
  32. data_sampler-3.2.1/tests/test_anonymize.py +373 -0
  33. data_sampler-3.2.1/tests/test_cli.py +229 -0
  34. data_sampler-3.2.1/tests/test_engine.py +364 -0
  35. data_sampler-3.2.1/tests/test_io.py +74 -0
  36. data_sampler-3.2.1/tests/test_report.py +122 -0
  37. data_sampler-3.2.1/tests/test_sampling.py +103 -0
  38. data_sampler-3.2.1/tests/test_stats.py +98 -0
  39. data_sampler-3.2.1/tests/test_tui.py +223 -0
  40. data_sampler-3.2.1/tests/test_workflow.py +294 -0
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -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.