data-sampler 3.4.0__tar.gz → 3.5.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.
- {data_sampler-3.4.0 → data_sampler-3.5.0}/CHANGELOG.md +66 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/PKG-INFO +61 -16
- {data_sampler-3.4.0 → data_sampler-3.5.0}/README.md +60 -15
- data_sampler-3.5.0/docs/img/tui-columns.png +0 -0
- data_sampler-3.5.0/docs/img/tui-file.png +0 -0
- data_sampler-3.5.0/docs/img/tui-report.png +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/pyproject.toml +1 -1
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/__init__.py +26 -2
- data_sampler-3.5.0/src/data_sampler/_names.py +685 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/anonymize.py +246 -34
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/engine.py +50 -5
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/io.py +26 -6
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/stats.py +7 -0
- data_sampler-3.5.0/src/data_sampler/tui/app.py +1812 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_anonymize.py +6 -5
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_engine.py +17 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_io.py +36 -1
- data_sampler-3.5.0/tests/test_names.py +194 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_stats.py +19 -0
- data_sampler-3.5.0/tests/test_tui.py +740 -0
- data_sampler-3.4.0/src/data_sampler/_names.py +0 -60
- data_sampler-3.4.0/src/data_sampler/tui/app.py +0 -985
- data_sampler-3.4.0/tests/test_tui.py +0 -321
- {data_sampler-3.4.0 → data_sampler-3.5.0}/.gitattributes +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/.github/workflows/release.yml +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/.gitignore +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/LICENSE +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/ROADMAP.md +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0/docs}/RELEASING.md +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0/docs}/TROUBLESHOOTING.md +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/examples/employees.csv +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/examples/using_data_sampler.ipynb +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/requirements.txt +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/scripts/run-tui.bat +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/scripts/run-tui.sh +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/__main__.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/_logging.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/cli.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/reduce.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/report.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/sampling.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/tui/__init__.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/src/data_sampler/workflow.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/conftest.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_cli.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_reduce.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_report.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_sampling.py +0 -0
- {data_sampler-3.4.0 → data_sampler-3.5.0}/tests/test_workflow.py +0 -0
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.5.0 — 2026-07-24
|
|
4
|
+
|
|
5
|
+
- **Names anonymizer is now gender- and ethnicity-aware.** The bundled library
|
|
6
|
+
(`_names.py`) is regrouped into real, hand-checked names keyed by
|
|
7
|
+
`<ethnicity>_<gender>` across **33 ethnic groups** (multiple Indian
|
|
8
|
+
regions/cultures, Chinese, Japanese, Korean, Vietnamese, Anglo, Italian,
|
|
9
|
+
French, German, Russian, Polish, Scandinavian, Greek, Turkish, Persian,
|
|
10
|
+
Arab-Levantine, Sudanese, Algerian, Moroccan, Ethiopian, Rwandan, Yoruba,
|
|
11
|
+
Igbo, Ghanaian-Akan, Hispanic, Brazilian, Filipino, Indonesian). Surnames are
|
|
12
|
+
grouped too — gendered where real (Russian Ivanov/Ivanova, Polish -ski/-ska)
|
|
13
|
+
and with distinctly-female additions (North-Indian Devi/Kumari) over unisex
|
|
14
|
+
bases (Kumar/Das). `NameAnonymizer(gender=…, ethnicity=…)` fixes a gender
|
|
15
|
+
(`male`/`female`/`third`/`undisclosed`) and/or an ethnic group; **third
|
|
16
|
+
gender** mixes given names within an ethnicity, **undisclosed** draws from any
|
|
17
|
+
ethnicity.
|
|
18
|
+
- **Gender/ethnicity can be read from another column** and mapped:
|
|
19
|
+
`NameAnonymizer(gender_column=…, ethnicity_column=…)` with per-value
|
|
20
|
+
`gender_map` / `ethnicity_map` overrides. Values in any encoding (M/F, 0/1 per
|
|
21
|
+
ISO-5218, `male`/`female`, other languages) are auto-detected
|
|
22
|
+
(`suggest_gender_mapping` / `suggest_ethnicity_mapping`) with a manual
|
|
23
|
+
fallback. `randomize_gender=True` reassigns genders and rewrites the gender
|
|
24
|
+
column so both fields are anonymized together.
|
|
25
|
+
- **Custom name libraries.** `export_names_library()` writes the current library
|
|
26
|
+
as an editable module; `load_names_library(path=…)` (or the
|
|
27
|
+
`DATA_SAMPLER_NAMES` env var) activates one for the session; and
|
|
28
|
+
`install_names_library(path=…)` installs one permanently into the package.
|
|
29
|
+
- **Report screen shows a "reproduce this in Python" snippet** — the exact
|
|
30
|
+
`ds.load_file / sample / anonymize / reduce_columns / save_output` calls that
|
|
31
|
+
reproduce the run — and it's included in the saved report `.txt`. Every TUI
|
|
32
|
+
capability now maps to the public API.
|
|
33
|
+
- **Remote inputs.** `load_file` (and the TUI file screen) accept `http(s)` URLs
|
|
34
|
+
(e.g. GitHub raw links). The DuckDB engine loads `httpfs` on demand so it can
|
|
35
|
+
sample remote `s3://` / `https://` **Parquet with HTTP range requests** — a
|
|
36
|
+
multi-GB remote file is sampled out-of-core without downloading it whole.
|
|
37
|
+
- **TUI restyle** — btop-inspired rounded, quiet-until-focused inputs / selects
|
|
38
|
+
/ switches and outlined (non-solid) buttons; the columns table's per-stat
|
|
39
|
+
`mean`/`median`/`mode`/`sd` columns; the report save clarifies it includes the
|
|
40
|
+
histograms.
|
|
41
|
+
|
|
42
|
+
## v3.4.1 — 2026-07-24
|
|
43
|
+
|
|
44
|
+
- **TUI columns table now breaks the single summary cell into separate
|
|
45
|
+
`mean` / `median` / `mode` / `sd` columns.** Numeric columns fill all four;
|
|
46
|
+
non-numeric columns show `mode` (their most frequent value) with dashes for
|
|
47
|
+
the numeric-only stats. `ColumnStats` gained a `mode` field, populated for
|
|
48
|
+
every column kind by `compute_column_stats`.
|
|
49
|
+
- **Bulk configuration via multi-select on the columns screen.** Ctrl-click
|
|
50
|
+
toggles individual rows, shift-click selects a contiguous range (keyboard:
|
|
51
|
+
`space` toggles the cursor row, `x` clears the selection). Any anonymizer
|
|
52
|
+
choice, stratification-skip, or reduction-skip then applies to every
|
|
53
|
+
selected column at once; the panel title shows the selection count.
|
|
54
|
+
- **Columns can now be excluded from the PCA reduction from the TUI.** A new
|
|
55
|
+
“skip from reduction (PCA)” switch (and the `d` key) sets a per-column
|
|
56
|
+
`skip_reduce` flag that is passed to `reduce_columns(exclude=…)`, so chosen
|
|
57
|
+
numeric columns pass through the reduction unchanged. The columns table
|
|
58
|
+
gained a `reduce` column showing each column's candidacy / skip state, and
|
|
59
|
+
excluded columns are listed in the run report.
|
|
60
|
+
- **Undo / redo for column configuration** (`ctrl+z` / `ctrl+y`), keeping well
|
|
61
|
+
over the last ten steps. Bursts of edits to one option field coalesce into a
|
|
62
|
+
single undo step.
|
|
63
|
+
- **File screen: refresh the directory browser** with `ctrl+r` or the new
|
|
64
|
+
⟳ refresh button, so files created after launch appear without restarting.
|
|
65
|
+
- **Report screen: save the report + column histograms to a text file** with
|
|
66
|
+
`ctrl+s` or the 💾 save .txt button; it writes `<sample-name>_report.txt`
|
|
67
|
+
next to the sample output.
|
|
68
|
+
|
|
3
69
|
## v3.4.0 — 2026-07-24
|
|
4
70
|
|
|
5
71
|
- **Added optional PCA column reduction** (`data_sampler.reduce`): after
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-sampler
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: Hand someone data that looks and behaves like your production data, isn't your production data, and provably kept its statistical variety — stratified sampling + consistent anonymization, with a terminal UI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/aaronified/data-sampler
|
|
6
6
|
Project-URL: Changelog, https://github.com/aaronified/data-sampler/blob/main/CHANGELOG.md
|
|
@@ -75,6 +75,10 @@ Use it to:
|
|
|
75
75
|
trigger it.
|
|
76
76
|
- **Cut a 2 GB export down to 500 rows** for a demo or prototype that still
|
|
77
77
|
behaves like the real thing.
|
|
78
|
+
- **Narrow a wide table, not just shorten it** — optionally collapse the
|
|
79
|
+
numeric columns into a handful of principal components (PCA) on the way out,
|
|
80
|
+
so the shared sample is short *and* narrow, with a report of how much
|
|
81
|
+
variance each component keeps and which columns moved together.
|
|
78
82
|
- **Build test fixtures that mirror production skew** instead of uniform toy
|
|
79
83
|
data.
|
|
80
84
|
- **Give a class or workshop realistic data** without a data-sharing
|
|
@@ -103,6 +107,20 @@ Requires Python 3.10+. For development, clone the repo and
|
|
|
103
107
|
|
|
104
108
|
## Terminal UI
|
|
105
109
|
|
|
110
|
+
*Columns screen — per-column stats (mean / median / mode / sd), anonymizer,
|
|
111
|
+
stratify and reduce config, with multi-select for bulk edits:*
|
|
112
|
+
|
|
113
|
+

|
|
114
|
+
|
|
115
|
+
*File screen — type a path or pick one from the directory browser:*
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+
*Report screen — stratification report on the left, source-vs-sample column
|
|
120
|
+
histograms on the right:*
|
|
121
|
+
|
|
122
|
+

|
|
123
|
+
|
|
106
124
|
```sh
|
|
107
125
|
data-sampler # no arguments → opens the TUI
|
|
108
126
|
data-sampler-tui # explicit TUI entry point
|
|
@@ -120,24 +138,35 @@ data_sampler.run_tui("data.csv") # pre-load a file
|
|
|
120
138
|
|
|
121
139
|
The TUI is a panel-based dashboard (think btop / lazydocker):
|
|
122
140
|
|
|
123
|
-
1. **File screen** — type a path or pick a file from the directory browser
|
|
124
|
-
|
|
141
|
+
1. **File screen** — type a path or pick a file from the directory browser
|
|
142
|
+
(**`ctrl+r`** or the **⟳ refresh** button re-scans the folder so files
|
|
143
|
+
created since launch show up); Excel files take an optional sheet name.
|
|
125
144
|
2. **Columns screen** — every column with its type, missing %, unique count,
|
|
126
|
-
distribution sparkline, and
|
|
127
|
-
VS Code extension). Select a column to
|
|
128
|
-
bars, choose an anonymizer for it, and
|
|
129
|
-
skipped when preserving statistical variety
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
distribution sparkline, and per-stat **mean / median / mode / sd** columns
|
|
146
|
+
(modelled after the Data Wrangler VS Code extension). Select a column to
|
|
147
|
+
see full stats and distribution bars, choose an anonymizer for it, and
|
|
148
|
+
toggle whether it should be skipped when preserving statistical variety
|
|
149
|
+
(stratification) or excluded from the PCA reduction. **Multi-select** rows
|
|
150
|
+
to configure them in bulk: **ctrl-click** toggles individual rows,
|
|
151
|
+
**shift-click** selects a range (**space** toggles the cursor row from the
|
|
152
|
+
keyboard, **`x`** clears) — then any anonymizer / skip / reduce choice
|
|
153
|
+
applies to every selected column at once. **`ctrl+z` / `ctrl+y`** undo and
|
|
154
|
+
redo column-config changes (at least the last ten steps). Set the sample
|
|
155
|
+
size, output folder, optional seed, an optional PCA column reduction
|
|
156
|
+
(`reduce`: N components or a variance target), and run.
|
|
132
157
|
3. **Report screen** — the stratification comparison and anonymization summary
|
|
133
158
|
on the left, and a **column histograms** panel on the right showing every
|
|
134
159
|
column's source-vs-sample distribution (numeric columns share bin edges;
|
|
135
160
|
others use the source's top categories) so you can see at a glance how well
|
|
136
|
-
the sample preserved each column. The output path is shown too
|
|
161
|
+
the sample preserved each column. The output path is shown too, and
|
|
162
|
+
**`ctrl+s`** (or **💾 save .txt**) writes the report and histograms to a
|
|
163
|
+
`*_report.txt` beside the sample.
|
|
137
164
|
|
|
138
|
-
Key bindings: `ctrl+r` run sample, `a` auto-suggest
|
|
139
|
-
`s` toggle stratification skip,
|
|
140
|
-
`
|
|
165
|
+
Key bindings — columns screen: `ctrl+r` run sample, `a` auto-suggest
|
|
166
|
+
anonymizer types, `s` toggle stratification skip, `d` toggle reduction skip,
|
|
167
|
+
`space` select row, `x` clear selection, `ctrl+z`/`ctrl+y` undo/redo,
|
|
168
|
+
`escape` back. Report screen: `ctrl+s` save report to text, `n` new file.
|
|
169
|
+
Everywhere: `ctrl+q` quit.
|
|
141
170
|
|
|
142
171
|
## CLI (headless)
|
|
143
172
|
|
|
@@ -191,6 +220,9 @@ data-sampler wide.csv 500 --reduce-variance 0.9
|
|
|
191
220
|
import data_sampler as ds
|
|
192
221
|
|
|
193
222
|
df = ds.load_file("data.xlsx", sheet="Sheet2")
|
|
223
|
+
# …or straight from a URL (GitHub raw, S3, etc.); large remote Parquet can be
|
|
224
|
+
# sampled out-of-core with the DuckDB engine, without downloading it whole:
|
|
225
|
+
# df = ds.load_file("https://raw.githubusercontent.com/owner/repo/main/data.csv")
|
|
194
226
|
|
|
195
227
|
# Data Wrangler-style column stats
|
|
196
228
|
for s in ds.compute_stats(df):
|
|
@@ -215,6 +247,18 @@ anon = ds.anonymize(
|
|
|
215
247
|
seed=7,
|
|
216
248
|
)
|
|
217
249
|
|
|
250
|
+
# gender- and ethnicity-aware names: fix them, or read them from other columns
|
|
251
|
+
gendered = ds.anonymize(result.data, {
|
|
252
|
+
"name": ds.NameAnonymizer(gender="female", ethnicity="chinese"),
|
|
253
|
+
# or map per row from existing columns (values auto-detected + overridable):
|
|
254
|
+
# "name": ds.NameAnonymizer(gender_column="sex", ethnicity_column="origin"),
|
|
255
|
+
}, seed=7)
|
|
256
|
+
|
|
257
|
+
# bring your own names: export the library, edit it, load it back
|
|
258
|
+
ds.export_names_library("my_names.py") # editable copy of the current library
|
|
259
|
+
ds.load_names_library(path="my_names.py") # activate it for this session
|
|
260
|
+
# ds.install_names_library("my_names.py") # …or install permanently
|
|
261
|
+
|
|
218
262
|
# optionally collapse the numeric columns into principal components
|
|
219
263
|
red = ds.reduce_columns(anon, variance_ratio=0.9, exclude=["cust_id"])
|
|
220
264
|
print(ds.format_reduction_report(red)) # variance kept + correlated groups
|
|
@@ -333,7 +377,7 @@ Missing values are left as missing. All anonymizers accept a seed (via
|
|
|
333
377
|
|
|
334
378
|
| Kind | Replaces values with | Options (defaults) |
|
|
335
379
|
| --- | --- | --- |
|
|
336
|
-
| `names` | Realistic names from a bundled library
|
|
380
|
+
| `names` | Realistic names from a bundled library grouped by ethnicity + gender (33 groups) | `style`: `first_last`, `first_middle_last`, `last_first`, `first`, `last`; `gender`: `male`/`female`/`third`/`undisclosed`; `ethnicity`; or `gender_column`/`ethnicity_column` (+ `gender_map`/`ethnicity_map`, `randomize_gender`) |
|
|
337
381
|
| `sequential_id` | `start`, `start+interval`, ... in order of first appearance | `start` (1), `interval` (1), `prefix` (`""`), `width` (0, zero-pads) |
|
|
338
382
|
| `numeric_jitter` | A random number within ±`pct` of the original | `pct` (0.2 = ±20 %), `round_to` (decimal places) |
|
|
339
383
|
| `datetime_jitter` | A date/time shifted by a random offset within ±`max_delta` | `max_delta` (`"7D"`; any `pandas.Timedelta` string), `unit` (`"s"`; jitter resolution) |
|
|
@@ -581,10 +625,11 @@ python -m build # build the wheel + sdist into dist/
|
|
|
581
625
|
|
|
582
626
|
Logging is controlled by `DATA_SAMPLER_LOG` (`quiet`/`info`/`verbose`) and
|
|
583
627
|
`DATA_SAMPLER_LOG_FILE`. See `ROADMAP.md` for planned work and
|
|
584
|
-
`TROUBLESHOOTING.md` for known failure modes.
|
|
628
|
+
[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) for known failure modes.
|
|
585
629
|
|
|
586
630
|
Releases go to [PyPI](https://pypi.org/project/data-sampler/) via a
|
|
587
|
-
human-triggered, test-gated GitHub Actions workflow — see
|
|
631
|
+
human-triggered, test-gated GitHub Actions workflow — see
|
|
632
|
+
[`docs/RELEASING.md`](docs/RELEASING.md).
|
|
588
633
|
|
|
589
634
|
---
|
|
590
635
|
|
|
@@ -20,6 +20,10 @@ Use it to:
|
|
|
20
20
|
trigger it.
|
|
21
21
|
- **Cut a 2 GB export down to 500 rows** for a demo or prototype that still
|
|
22
22
|
behaves like the real thing.
|
|
23
|
+
- **Narrow a wide table, not just shorten it** — optionally collapse the
|
|
24
|
+
numeric columns into a handful of principal components (PCA) on the way out,
|
|
25
|
+
so the shared sample is short *and* narrow, with a report of how much
|
|
26
|
+
variance each component keeps and which columns moved together.
|
|
23
27
|
- **Build test fixtures that mirror production skew** instead of uniform toy
|
|
24
28
|
data.
|
|
25
29
|
- **Give a class or workshop realistic data** without a data-sharing
|
|
@@ -48,6 +52,20 @@ Requires Python 3.10+. For development, clone the repo and
|
|
|
48
52
|
|
|
49
53
|
## Terminal UI
|
|
50
54
|
|
|
55
|
+
*Columns screen — per-column stats (mean / median / mode / sd), anonymizer,
|
|
56
|
+
stratify and reduce config, with multi-select for bulk edits:*
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
*File screen — type a path or pick one from the directory browser:*
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
*Report screen — stratification report on the left, source-vs-sample column
|
|
65
|
+
histograms on the right:*
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
51
69
|
```sh
|
|
52
70
|
data-sampler # no arguments → opens the TUI
|
|
53
71
|
data-sampler-tui # explicit TUI entry point
|
|
@@ -65,24 +83,35 @@ data_sampler.run_tui("data.csv") # pre-load a file
|
|
|
65
83
|
|
|
66
84
|
The TUI is a panel-based dashboard (think btop / lazydocker):
|
|
67
85
|
|
|
68
|
-
1. **File screen** — type a path or pick a file from the directory browser
|
|
69
|
-
|
|
86
|
+
1. **File screen** — type a path or pick a file from the directory browser
|
|
87
|
+
(**`ctrl+r`** or the **⟳ refresh** button re-scans the folder so files
|
|
88
|
+
created since launch show up); Excel files take an optional sheet name.
|
|
70
89
|
2. **Columns screen** — every column with its type, missing %, unique count,
|
|
71
|
-
distribution sparkline, and
|
|
72
|
-
VS Code extension). Select a column to
|
|
73
|
-
bars, choose an anonymizer for it, and
|
|
74
|
-
skipped when preserving statistical variety
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
distribution sparkline, and per-stat **mean / median / mode / sd** columns
|
|
91
|
+
(modelled after the Data Wrangler VS Code extension). Select a column to
|
|
92
|
+
see full stats and distribution bars, choose an anonymizer for it, and
|
|
93
|
+
toggle whether it should be skipped when preserving statistical variety
|
|
94
|
+
(stratification) or excluded from the PCA reduction. **Multi-select** rows
|
|
95
|
+
to configure them in bulk: **ctrl-click** toggles individual rows,
|
|
96
|
+
**shift-click** selects a range (**space** toggles the cursor row from the
|
|
97
|
+
keyboard, **`x`** clears) — then any anonymizer / skip / reduce choice
|
|
98
|
+
applies to every selected column at once. **`ctrl+z` / `ctrl+y`** undo and
|
|
99
|
+
redo column-config changes (at least the last ten steps). Set the sample
|
|
100
|
+
size, output folder, optional seed, an optional PCA column reduction
|
|
101
|
+
(`reduce`: N components or a variance target), and run.
|
|
77
102
|
3. **Report screen** — the stratification comparison and anonymization summary
|
|
78
103
|
on the left, and a **column histograms** panel on the right showing every
|
|
79
104
|
column's source-vs-sample distribution (numeric columns share bin edges;
|
|
80
105
|
others use the source's top categories) so you can see at a glance how well
|
|
81
|
-
the sample preserved each column. The output path is shown too
|
|
106
|
+
the sample preserved each column. The output path is shown too, and
|
|
107
|
+
**`ctrl+s`** (or **💾 save .txt**) writes the report and histograms to a
|
|
108
|
+
`*_report.txt` beside the sample.
|
|
82
109
|
|
|
83
|
-
Key bindings: `ctrl+r` run sample, `a` auto-suggest
|
|
84
|
-
`s` toggle stratification skip,
|
|
85
|
-
`
|
|
110
|
+
Key bindings — columns screen: `ctrl+r` run sample, `a` auto-suggest
|
|
111
|
+
anonymizer types, `s` toggle stratification skip, `d` toggle reduction skip,
|
|
112
|
+
`space` select row, `x` clear selection, `ctrl+z`/`ctrl+y` undo/redo,
|
|
113
|
+
`escape` back. Report screen: `ctrl+s` save report to text, `n` new file.
|
|
114
|
+
Everywhere: `ctrl+q` quit.
|
|
86
115
|
|
|
87
116
|
## CLI (headless)
|
|
88
117
|
|
|
@@ -136,6 +165,9 @@ data-sampler wide.csv 500 --reduce-variance 0.9
|
|
|
136
165
|
import data_sampler as ds
|
|
137
166
|
|
|
138
167
|
df = ds.load_file("data.xlsx", sheet="Sheet2")
|
|
168
|
+
# …or straight from a URL (GitHub raw, S3, etc.); large remote Parquet can be
|
|
169
|
+
# sampled out-of-core with the DuckDB engine, without downloading it whole:
|
|
170
|
+
# df = ds.load_file("https://raw.githubusercontent.com/owner/repo/main/data.csv")
|
|
139
171
|
|
|
140
172
|
# Data Wrangler-style column stats
|
|
141
173
|
for s in ds.compute_stats(df):
|
|
@@ -160,6 +192,18 @@ anon = ds.anonymize(
|
|
|
160
192
|
seed=7,
|
|
161
193
|
)
|
|
162
194
|
|
|
195
|
+
# gender- and ethnicity-aware names: fix them, or read them from other columns
|
|
196
|
+
gendered = ds.anonymize(result.data, {
|
|
197
|
+
"name": ds.NameAnonymizer(gender="female", ethnicity="chinese"),
|
|
198
|
+
# or map per row from existing columns (values auto-detected + overridable):
|
|
199
|
+
# "name": ds.NameAnonymizer(gender_column="sex", ethnicity_column="origin"),
|
|
200
|
+
}, seed=7)
|
|
201
|
+
|
|
202
|
+
# bring your own names: export the library, edit it, load it back
|
|
203
|
+
ds.export_names_library("my_names.py") # editable copy of the current library
|
|
204
|
+
ds.load_names_library(path="my_names.py") # activate it for this session
|
|
205
|
+
# ds.install_names_library("my_names.py") # …or install permanently
|
|
206
|
+
|
|
163
207
|
# optionally collapse the numeric columns into principal components
|
|
164
208
|
red = ds.reduce_columns(anon, variance_ratio=0.9, exclude=["cust_id"])
|
|
165
209
|
print(ds.format_reduction_report(red)) # variance kept + correlated groups
|
|
@@ -278,7 +322,7 @@ Missing values are left as missing. All anonymizers accept a seed (via
|
|
|
278
322
|
|
|
279
323
|
| Kind | Replaces values with | Options (defaults) |
|
|
280
324
|
| --- | --- | --- |
|
|
281
|
-
| `names` | Realistic names from a bundled library
|
|
325
|
+
| `names` | Realistic names from a bundled library grouped by ethnicity + gender (33 groups) | `style`: `first_last`, `first_middle_last`, `last_first`, `first`, `last`; `gender`: `male`/`female`/`third`/`undisclosed`; `ethnicity`; or `gender_column`/`ethnicity_column` (+ `gender_map`/`ethnicity_map`, `randomize_gender`) |
|
|
282
326
|
| `sequential_id` | `start`, `start+interval`, ... in order of first appearance | `start` (1), `interval` (1), `prefix` (`""`), `width` (0, zero-pads) |
|
|
283
327
|
| `numeric_jitter` | A random number within ±`pct` of the original | `pct` (0.2 = ±20 %), `round_to` (decimal places) |
|
|
284
328
|
| `datetime_jitter` | A date/time shifted by a random offset within ±`max_delta` | `max_delta` (`"7D"`; any `pandas.Timedelta` string), `unit` (`"s"`; jitter resolution) |
|
|
@@ -526,10 +570,11 @@ python -m build # build the wheel + sdist into dist/
|
|
|
526
570
|
|
|
527
571
|
Logging is controlled by `DATA_SAMPLER_LOG` (`quiet`/`info`/`verbose`) and
|
|
528
572
|
`DATA_SAMPLER_LOG_FILE`. See `ROADMAP.md` for planned work and
|
|
529
|
-
`TROUBLESHOOTING.md` for known failure modes.
|
|
573
|
+
[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) for known failure modes.
|
|
530
574
|
|
|
531
575
|
Releases go to [PyPI](https://pypi.org/project/data-sampler/) via a
|
|
532
|
-
human-triggered, test-gated GitHub Actions workflow — see
|
|
576
|
+
human-triggered, test-gated GitHub Actions workflow — see
|
|
577
|
+
[`docs/RELEASING.md`](docs/RELEASING.md).
|
|
533
578
|
|
|
534
579
|
---
|
|
535
580
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "data-sampler"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.5.0"
|
|
8
8
|
description = "Hand someone data that looks and behaves like your production data, isn't your production data, and provably kept its statistical variety — stratified sampling + consistent anonymization, with a terminal UI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -18,7 +18,7 @@ Public API::
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
__version__ = "3.
|
|
21
|
+
__version__ = "3.5.0"
|
|
22
22
|
|
|
23
23
|
from .io import SUPPORTED_EXTENSIONS, list_sheets, load_file, save_output
|
|
24
24
|
from .reduce import ReductionResult, reduce_columns
|
|
@@ -60,9 +60,16 @@ __all__ = [
|
|
|
60
60
|
"format_column_histograms",
|
|
61
61
|
"anonymize",
|
|
62
62
|
"make_anonymizer",
|
|
63
|
+
"NameAnonymizer",
|
|
64
|
+
"suggest_gender_mapping",
|
|
65
|
+
"suggest_ethnicity_mapping",
|
|
63
66
|
"AnonymizationPlan",
|
|
64
67
|
"suggest_type",
|
|
65
68
|
"TYPE_OPTIONS",
|
|
69
|
+
"export_names_library",
|
|
70
|
+
"load_names_library",
|
|
71
|
+
"install_names_library",
|
|
72
|
+
"name_ethnicities",
|
|
66
73
|
"run_tui",
|
|
67
74
|
]
|
|
68
75
|
|
|
@@ -70,13 +77,30 @@ __all__ = [
|
|
|
70
77
|
def __getattr__(name: str):
|
|
71
78
|
# Lazy imports: keep pandas-only workflows fast and let the core API work
|
|
72
79
|
# even while optional pieces (TUI) are not yet importable.
|
|
73
|
-
if name in (
|
|
80
|
+
if name in (
|
|
81
|
+
"anonymize", "make_anonymizer", "NameAnonymizer",
|
|
82
|
+
"suggest_gender_mapping", "suggest_ethnicity_mapping",
|
|
83
|
+
):
|
|
74
84
|
# importlib, not `from . import anonymize`: the fromlist form calls
|
|
75
85
|
# hasattr(package, "anonymize"), which re-enters this __getattr__.
|
|
76
86
|
import importlib
|
|
77
87
|
|
|
78
88
|
_anon = importlib.import_module(".anonymize", __name__)
|
|
79
89
|
return getattr(_anon, name)
|
|
90
|
+
if name in (
|
|
91
|
+
"export_names_library", "load_names_library",
|
|
92
|
+
"install_names_library", "name_ethnicities",
|
|
93
|
+
):
|
|
94
|
+
import importlib
|
|
95
|
+
|
|
96
|
+
_n = importlib.import_module("._names", __name__)
|
|
97
|
+
_alias = {
|
|
98
|
+
"export_names_library": "export_library",
|
|
99
|
+
"load_names_library": "load_library",
|
|
100
|
+
"install_names_library": "install_library",
|
|
101
|
+
"name_ethnicities": "ETHNICITIES",
|
|
102
|
+
}[name]
|
|
103
|
+
return getattr(_n, _alias)
|
|
80
104
|
if name in ("AnonymizationPlan", "suggest_type", "TYPE_OPTIONS"):
|
|
81
105
|
import importlib
|
|
82
106
|
|