data-sampler 3.2.1__py3-none-any.whl
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/__init__.py +82 -0
- data_sampler/__main__.py +7 -0
- data_sampler/_logging.py +61 -0
- data_sampler/_names.py +60 -0
- data_sampler/anonymize.py +463 -0
- data_sampler/cli.py +363 -0
- data_sampler/engine.py +628 -0
- data_sampler/io.py +75 -0
- data_sampler/report.py +289 -0
- data_sampler/sampling.py +193 -0
- data_sampler/stats.py +174 -0
- data_sampler/tui/__init__.py +16 -0
- data_sampler/tui/app.py +904 -0
- data_sampler/workflow.py +285 -0
- data_sampler-3.2.1.dist-info/METADATA +427 -0
- data_sampler-3.2.1.dist-info/RECORD +19 -0
- data_sampler-3.2.1.dist-info/WHEEL +4 -0
- data_sampler-3.2.1.dist-info/entry_points.txt +3 -0
- data_sampler-3.2.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-sampler
|
|
3
|
+
Version: 3.2.1
|
|
4
|
+
Summary: Representative (stratified) sampling and column anonymization for tabular data files, with a terminal UI.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aaronified/data-sampler
|
|
6
|
+
Project-URL: Changelog, https://github.com/aaronified/data-sampler/blob/main/CHANGELOG.md
|
|
7
|
+
Author: aaronified
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 programanalytics-isb
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: anonymization,data,pandas,sampling,stratified,tui
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Environment :: Console
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Requires-Dist: numpy>=1.23
|
|
43
|
+
Requires-Dist: openpyxl>=3.0
|
|
44
|
+
Requires-Dist: pandas>=1.5
|
|
45
|
+
Requires-Dist: pyarrow>=10
|
|
46
|
+
Requires-Dist: textual>=0.86
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: build; extra == 'dev'
|
|
49
|
+
Requires-Dist: duckdb>=1.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
51
|
+
Requires-Dist: twine; extra == 'dev'
|
|
52
|
+
Provides-Extra: large
|
|
53
|
+
Requires-Dist: duckdb>=1.0; extra == 'large'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# data-sampler
|
|
57
|
+
|
|
58
|
+
Creates representative samples from data files, using stratified sampling to
|
|
59
|
+
preserve the statistical variety of your data — with optional per-column
|
|
60
|
+
anonymization and a colorful terminal UI.
|
|
61
|
+
|
|
62
|
+
Everything ships as a single Python package: launch the TUI with one
|
|
63
|
+
function call or command, or use the sampling/anonymization functions
|
|
64
|
+
directly from Python.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pip install data-sampler # once released on PyPI
|
|
70
|
+
# from a clone, today:
|
|
71
|
+
pip install -e ".[dev]"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Requires Python 3.10+.
|
|
75
|
+
|
|
76
|
+
## Terminal UI
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
data-sampler # no arguments → opens the TUI
|
|
80
|
+
data-sampler-tui # explicit TUI entry point
|
|
81
|
+
python -m data_sampler # same as data-sampler
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or from Python:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import data_sampler
|
|
88
|
+
|
|
89
|
+
data_sampler.run_tui() # file picker first
|
|
90
|
+
data_sampler.run_tui("data.csv") # pre-load a file
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The TUI is a panel-based dashboard (think btop / lazydocker):
|
|
94
|
+
|
|
95
|
+
1. **File screen** — type a path or pick a file from the directory browser;
|
|
96
|
+
Excel files take an optional sheet name.
|
|
97
|
+
2. **Columns screen** — every column with its type, missing %, unique count,
|
|
98
|
+
distribution sparkline, and summary (modelled after the Data Wrangler
|
|
99
|
+
VS Code extension). Select a column to see full stats and distribution
|
|
100
|
+
bars, choose an anonymizer for it, and toggle whether it should be
|
|
101
|
+
skipped when preserving statistical variety (stratification). Set the
|
|
102
|
+
sample size, output folder, optional seed, and run.
|
|
103
|
+
3. **Report screen** — the stratification comparison and anonymization summary
|
|
104
|
+
on the left, and a **column histograms** panel on the right showing every
|
|
105
|
+
column's source-vs-sample distribution (numeric columns share bin edges;
|
|
106
|
+
others use the source's top categories) so you can see at a glance how well
|
|
107
|
+
the sample preserved each column. The output path is shown too.
|
|
108
|
+
|
|
109
|
+
Key bindings: `ctrl+r` run sample, `a` auto-suggest anonymizer types,
|
|
110
|
+
`s` toggle stratification skip,
|
|
111
|
+
`escape` back, `ctrl+q` quit.
|
|
112
|
+
|
|
113
|
+
## CLI (headless)
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
data-sampler <source> <count> [options]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Option | Description |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `--sheet NAME` | Sheet name for Excel files (default: first sheet) |
|
|
122
|
+
| `--outdir DIR` | Output folder (default: same folder as source file) |
|
|
123
|
+
| `--random` | Pure random sampling instead of stratified |
|
|
124
|
+
| `--seed N` | Seed for reproducible sampling and anonymization |
|
|
125
|
+
| `--skip COL[,COL]` | Exclude column(s) from stratification (repeatable) |
|
|
126
|
+
| `--anon COL=KIND[:k=v,...]` | Anonymize a column (repeatable) |
|
|
127
|
+
| `-i`, `--interactive` | Guided workflow: choose an anonymizer type per column from a menu |
|
|
128
|
+
| `--suggest` | Auto-assign a suggested anonymizer type to each column from its stats |
|
|
129
|
+
| `--engine {auto,pandas,duckdb}` | Sampling engine (default `auto`: DuckDB for Parquet/large inputs, pandas otherwise) |
|
|
130
|
+
| `--threads N` | DuckDB engine: number of threads (default: all cores) |
|
|
131
|
+
| `--memory-limit SIZE` | DuckDB engine: memory limit before spilling to disk (e.g. `8GB`) |
|
|
132
|
+
| `--tui` | Open the TUI (optionally preloading `source`) |
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
data-sampler data.csv 500
|
|
138
|
+
data-sampler report.xlsx 200 --sheet "Sheet2" --outdir C:\samples
|
|
139
|
+
data-sampler data.csv 100 --skip region,notes --seed 7 \
|
|
140
|
+
--anon "name=names" \
|
|
141
|
+
--anon "cust_id=sequential_id:start=1000,interval=7" \
|
|
142
|
+
--anon "salary=numeric_jitter:pct=0.1" \
|
|
143
|
+
--anon "email=hex:length=12"
|
|
144
|
+
|
|
145
|
+
# large / out-of-core: sample a Parquet file in parallel with DuckDB
|
|
146
|
+
data-sampler huge.parquet 10000 --engine duckdb --threads 8 --memory-limit 8GB --suggest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Python API
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import data_sampler as ds
|
|
153
|
+
|
|
154
|
+
df = ds.load_file("data.xlsx", sheet="Sheet2")
|
|
155
|
+
|
|
156
|
+
# Data Wrangler-style column stats
|
|
157
|
+
for s in ds.compute_stats(df):
|
|
158
|
+
print(s.name, s.kind, s.unique, s.summary())
|
|
159
|
+
|
|
160
|
+
# representative sample; 'notes' never used for stratification
|
|
161
|
+
result = ds.sample(df, 500, exclude_columns=["notes"], random_state=7)
|
|
162
|
+
print(ds.format_stratification_report(df, result))
|
|
163
|
+
|
|
164
|
+
# per-column source-vs-sample histograms (or ds.column_histogram_data for the raw numbers)
|
|
165
|
+
print(ds.format_column_histograms(df, result.data))
|
|
166
|
+
|
|
167
|
+
# anonymize chosen columns of the sample (consistent mapping, NaN preserved)
|
|
168
|
+
anon = ds.anonymize(
|
|
169
|
+
result.data,
|
|
170
|
+
{
|
|
171
|
+
"name": "names",
|
|
172
|
+
"cust_id": ("sequential_id", {"start": 1000, "interval": 7}),
|
|
173
|
+
"salary": ("numeric_jitter", {"pct": 0.1}),
|
|
174
|
+
"email": {"kind": "hex", "length": 12},
|
|
175
|
+
},
|
|
176
|
+
seed=7,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
ds.save_output(anon, "data.xlsx", tag="sample_500_anon")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Try it: bundled example
|
|
183
|
+
|
|
184
|
+
The repo ships a 1,000-row dummy dataset, [examples/employees.csv](examples/employees.csv),
|
|
185
|
+
built to be stratifiable: `department`, `region`, and `employment_type` have
|
|
186
|
+
skewed categorical distributions, `performance_rating` is low-cardinality
|
|
187
|
+
numeric, and `employee_id`/`full_name`/`email`/`salary` are there to
|
|
188
|
+
anonymize.
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
employee_id,full_name,email,department,region,employment_type,performance_rating,salary
|
|
192
|
+
E1001,Emily Lee,emily.lee001@example.com,Sales,North,Full-time,4,62000
|
|
193
|
+
E1002,Joshua Clark,joshua.clark002@example.com,Finance,South,Full-time,4,50000
|
|
194
|
+
E1003,Donald Martin,donald.martin003@example.com,Operations,East,Contract,3,68500
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### In the TUI
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
data-sampler examples/employees.csv --tui
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The columns screen opens with the stats table. Try: press **`a`** to
|
|
204
|
+
auto-suggest an anonymizer type for every column, then adjust — select
|
|
205
|
+
`full_name` and set its anonymizer to **names**; select `employee_id` and
|
|
206
|
+
choose **sequential id** (start 1000); select `salary` and choose **numeric
|
|
207
|
+
jitter**; select `performance_rating` and flip **skip when stratifying** to
|
|
208
|
+
keep it out of the variety-preservation logic. Set rows to `100`, seed to
|
|
209
|
+
`42`, and press `ctrl+r` — the report screen shows how closely the sample
|
|
210
|
+
tracks the original distributions.
|
|
211
|
+
|
|
212
|
+
### With the Python functions
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
import data_sampler as ds
|
|
216
|
+
|
|
217
|
+
df = ds.load_file("examples/employees.csv")
|
|
218
|
+
|
|
219
|
+
result = ds.sample(df, 100, random_state=42) # stratifies automatically
|
|
220
|
+
print(ds.format_stratification_report(df, result))
|
|
221
|
+
|
|
222
|
+
anon = ds.anonymize(
|
|
223
|
+
result.data,
|
|
224
|
+
{
|
|
225
|
+
"full_name": "names",
|
|
226
|
+
"employee_id": ("sequential_id", {"start": 1000}),
|
|
227
|
+
"salary": "numeric_jitter",
|
|
228
|
+
"email": {"kind": "hex", "length": 10},
|
|
229
|
+
},
|
|
230
|
+
seed=42,
|
|
231
|
+
)
|
|
232
|
+
ds.save_output(anon, "examples/employees.csv", tag="sample_100_anon")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### From the CLI
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
data-sampler examples/employees.csv 100 --seed 42 \
|
|
239
|
+
--anon "full_name=names" \
|
|
240
|
+
--anon "employee_id=sequential_id:start=1000" \
|
|
241
|
+
--anon "salary=numeric_jitter" \
|
|
242
|
+
--anon "email=hex:length=10"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The run stratifies on `employment_type`, `region`, and `department`, and the
|
|
246
|
+
report shows original vs. sample side by side (excerpt):
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
Column: 'employment_type' (3 categories)
|
|
250
|
+
Value Original Sample
|
|
251
|
+
─────────────────────────────────────────────────────────────────────
|
|
252
|
+
Contract ██░░░░░░░░░░░░░ 10.1% █░░░░░░░░░░░░░░ 9.0%
|
|
253
|
+
Full-time ███████████████ 68.9% ███████████████ 68.0%
|
|
254
|
+
Part-time ████░░░░░░░░░░░ 21.0% █████░░░░░░░░░░ 23.0%
|
|
255
|
+
─────────────────────────────────────────────────────────────────────
|
|
256
|
+
Totals 1000 100
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The anonymized sample keeps the structure but none of the identities —
|
|
260
|
+
repeated values still repeat, salaries stay within ±20 % of the originals:
|
|
261
|
+
|
|
262
|
+
```text
|
|
263
|
+
employee_id,full_name,email,department,region,employment_type,performance_rating,salary
|
|
264
|
+
1000,Ravi Andersen,6a78c49ea2,Engineering,South,Full-time,1,62264
|
|
265
|
+
1001,Thomas Gomez,0e32684b27,Engineering,North,Part-time,3,102743
|
|
266
|
+
1002,Fatima Singh,b95e909348,Operations,North,Full-time,3,46793
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Notebook and launcher scripts
|
|
270
|
+
|
|
271
|
+
- [examples/using_data_sampler.ipynb](examples/using_data_sampler.ipynb) —
|
|
272
|
+
the full package walkthrough as an executed Jupyter notebook (load →
|
|
273
|
+
stats → sample → anonymize → save, with outputs included).
|
|
274
|
+
- [scripts/run-tui.sh](scripts/run-tui.sh) — opens the TUI on Linux (any
|
|
275
|
+
distro) and macOS; falls back from the `data-sampler` command to
|
|
276
|
+
`python3 -m data_sampler` and prints install instructions if neither is
|
|
277
|
+
available.
|
|
278
|
+
- [scripts/run-tui.bat](scripts/run-tui.bat) — the same for Windows
|
|
279
|
+
(double-clickable).
|
|
280
|
+
|
|
281
|
+
Both scripts pass arguments through, e.g. `./scripts/run-tui.sh data.csv`.
|
|
282
|
+
|
|
283
|
+
## Anonymizers
|
|
284
|
+
|
|
285
|
+
Every anonymizer maps each unique original value to exactly one replacement,
|
|
286
|
+
so repeated values stay repeated and the column's distribution — the
|
|
287
|
+
statistical variety this tool exists to preserve — survives anonymization.
|
|
288
|
+
Missing values are left as missing. All anonymizers accept a seed (via
|
|
289
|
+
`anonymize(..., seed=N)` or `--seed`) for reproducible output.
|
|
290
|
+
|
|
291
|
+
| Kind | Replaces values with | Options (defaults) |
|
|
292
|
+
| --- | --- | --- |
|
|
293
|
+
| `names` | Realistic names from a bundled library of first, middle, and last names | `style`: `first_last`, `first_middle_last`, `last_first`, `first`, `last` |
|
|
294
|
+
| `sequential_id` | `start`, `start+interval`, ... in order of first appearance | `start` (1), `interval` (1), `prefix` (`""`), `width` (0, zero-pads) |
|
|
295
|
+
| `numeric_jitter` | A random number within ±`pct` of the original | `pct` (0.2 = ±20 %), `round_to` (decimal places) |
|
|
296
|
+
| `datetime_jitter` | A date/time shifted by a random offset within ±`max_delta` | `max_delta` (`"7D"`; any `pandas.Timedelta` string), `unit` (`"s"`; jitter resolution) |
|
|
297
|
+
| `random_string` | Random character sequences, unique per value | `length` (8), `charset` (`alphanumeric`, `letters`, `digits`, `hex`), `prefix` (`""`) |
|
|
298
|
+
| `hex` | Shorthand for `random_string` with `charset="hex"` | `length` (8) |
|
|
299
|
+
|
|
300
|
+
## Anonymization workflow
|
|
301
|
+
|
|
302
|
+
Rather than spell out every column by hand, you can drive a guided workflow —
|
|
303
|
+
give it your columns and pick a *type* for each. The three ways to do it share
|
|
304
|
+
one engine (`AnonymizationPlan`) and the same auto-suggestion (`suggest_type`),
|
|
305
|
+
which infers a type from each column's stats (datetime → datetime jitter,
|
|
306
|
+
name/email columns → names/hex, id-ish high-uniqueness columns → sequential id,
|
|
307
|
+
numbers → numeric jitter, free text → random string; categorical/boolean columns
|
|
308
|
+
are left alone so the categories you stratify on survive).
|
|
309
|
+
|
|
310
|
+
- **Choose from options (interactive):** `data-sampler data.csv 100 --interactive`
|
|
311
|
+
walks each column and offers a numbered menu, defaulting to the suggested
|
|
312
|
+
type — press Enter to accept or type a number to override.
|
|
313
|
+
- **Pre-specify through a function (Python):**
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
import data_sampler as ds
|
|
317
|
+
|
|
318
|
+
df = ds.load_file("data.csv")
|
|
319
|
+
plan = ds.AnonymizationPlan.suggest(df) # auto-infer every column…
|
|
320
|
+
plan.assign("salary", "numeric_jitter", pct=0.1) # …then override as needed
|
|
321
|
+
plan.clear("region")
|
|
322
|
+
anon = plan.apply(df, seed=7) # runs ds.anonymize under the hood
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
- **Click in the TUI:** open the columns screen, select a column, and pick its
|
|
326
|
+
anonymizer — or press **`a`** to auto-suggest a type for every column at once,
|
|
327
|
+
then tweak. The `anonymizer` column shows each choice at a glance.
|
|
328
|
+
|
|
329
|
+
`--suggest` applies the suggestions non-interactively (columns you also set with
|
|
330
|
+
`--anon` keep your explicit choice).
|
|
331
|
+
|
|
332
|
+
## How sampling works
|
|
333
|
+
|
|
334
|
+
**Stratified (default):** columns suitable for stratification are detected
|
|
335
|
+
automatically — categorical or low-cardinality columns with 2–100 unique
|
|
336
|
+
values; long text and ID-like numeric columns are avoided, as are any
|
|
337
|
+
columns you mark as skipped. Rows are grouped by the joint combination of
|
|
338
|
+
all selected columns and sampled proportionally per group, so the sample
|
|
339
|
+
mirrors the original joint distribution. Missing values count as their own
|
|
340
|
+
category. A side-by-side distribution report is produced for every run.
|
|
341
|
+
|
|
342
|
+
**Pure random (`--random`):** rows are drawn uniformly at random.
|
|
343
|
+
|
|
344
|
+
If no suitable stratification columns exist, the tool falls back to pure
|
|
345
|
+
random sampling automatically.
|
|
346
|
+
|
|
347
|
+
## Large data: the out-of-core DuckDB engine
|
|
348
|
+
|
|
349
|
+
The default pandas path loads the whole file into memory. For inputs that are
|
|
350
|
+
too big for that (toward billions of rows, especially Parquet), install the
|
|
351
|
+
optional engine and let **DuckDB** do the work — multi-threaded, and able to
|
|
352
|
+
spill to disk, so only the resulting sample is ever materialized:
|
|
353
|
+
|
|
354
|
+
```sh
|
|
355
|
+
pip install "data-sampler[large]"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
from data_sampler.engine import DuckDBEngine, should_use_engine
|
|
360
|
+
|
|
361
|
+
# reads Parquet/CSV natively; only the sample (count rows) comes back as a DataFrame
|
|
362
|
+
with DuckDBEngine(threads=8, memory_limit="8GB") as engine:
|
|
363
|
+
result = engine.sample("huge.parquet", 10_000, seed=42) # stratifies automatically
|
|
364
|
+
result.data.to_parquet("sample.parquet", index=False)
|
|
365
|
+
|
|
366
|
+
should_use_engine("huge.parquet") # True — Parquet always benefits from pushdown
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
- **Parallel + out-of-core:** all cores by default; a `memory_limit` makes it
|
|
370
|
+
spill instead of running out of memory.
|
|
371
|
+
- **Native readers:** Parquet is read with projection pushdown (only the scanned
|
|
372
|
+
columns); CSV/TSV/JSON and pandas DataFrames work too. Excel still goes through
|
|
373
|
+
the pandas path.
|
|
374
|
+
- **Streaming sampling:** reservoir sampling for the random case (exact count,
|
|
375
|
+
single pass) and two-pass proportional sampling for the stratified case.
|
|
376
|
+
- **Reproducible:** pass `seed=` (seeded stratified runs go single-threaded so
|
|
377
|
+
the result is deterministic; the distribution is preserved either way).
|
|
378
|
+
|
|
379
|
+
`large_materialization_warning(n_rows, n_cols)` returns a heads-up when a dataset
|
|
380
|
+
is big enough that loading it fully into pandas may exhaust memory — Parquet in
|
|
381
|
+
particular expands well beyond its compressed on-disk size.
|
|
382
|
+
|
|
383
|
+
Measured on a 20M-row Parquet file (5 columns, 12-core machine), sampling
|
|
384
|
+
10,000 rows:
|
|
385
|
+
|
|
386
|
+
| threads | stratified sample | reservoir sample | `stats()` |
|
|
387
|
+
| ---: | ---: | ---: | ---: |
|
|
388
|
+
| 1 | 14.5 s | 0.39 s | 9.5 s |
|
|
389
|
+
| 4 | 5.1 s | 0.16 s | 2.8 s |
|
|
390
|
+
| 8 | 3.9 s | 0.13 s | 2.1 s |
|
|
391
|
+
| 12 | 4.0 s | 0.10 s | 1.7 s |
|
|
392
|
+
|
|
393
|
+
The pandas path on the same file: 5.6 s total while materializing a ~0.9 GB
|
|
394
|
+
frame in RAM — the engine's reservoir sampling is ~50× faster and never
|
|
395
|
+
materializes the source at all.
|
|
396
|
+
|
|
397
|
+
## Supported formats
|
|
398
|
+
|
|
399
|
+
| Format | Extensions |
|
|
400
|
+
| --- | --- |
|
|
401
|
+
| CSV | `.csv` |
|
|
402
|
+
| TSV | `.tsv` |
|
|
403
|
+
| JSON | `.json` |
|
|
404
|
+
| Excel | `.xlsx`, `.xls` |
|
|
405
|
+
| Parquet | `.parquet` |
|
|
406
|
+
|
|
407
|
+
Output keeps the source format and is named
|
|
408
|
+
`{stem}_sample_{count}{ext}` — with an `_anon` suffix when anonymization ran
|
|
409
|
+
(e.g. `data_sample_500_anon.csv`).
|
|
410
|
+
|
|
411
|
+
## Development
|
|
412
|
+
|
|
413
|
+
```sh
|
|
414
|
+
pip install -e ".[dev]"
|
|
415
|
+
pytest # full suite, incl. headless TUI tests
|
|
416
|
+
python -m build # build the wheel + sdist into dist/
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Logging is controlled by `DATA_SAMPLER_LOG` (`quiet`/`info`/`verbose`) and
|
|
420
|
+
`DATA_SAMPLER_LOG_FILE`. See `ROADMAP.md` for planned work and
|
|
421
|
+
`TROUBLESHOOTING.md` for known failure modes.
|
|
422
|
+
|
|
423
|
+
PyPI releases are manual and happen only after extensive testing.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
Built with the assistance of [Claude Code](https://claude.ai/code) (Anthropic).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
data_sampler/__init__.py,sha256=XHkEEHybLbHtTd33mgT_0ZJMSSuG8xfYvtT2_qpWBM0,2445
|
|
2
|
+
data_sampler/__main__.py,sha256=ajDpX5YMj1AQQxkJrosiHaGyVaYXPef2J55ogOA7xJg,103
|
|
3
|
+
data_sampler/_logging.py,sha256=aOdWjKE48RYQRZK7fahSYPdSZS1VxOz2OVQQYwgqroc,1838
|
|
4
|
+
data_sampler/_names.py,sha256=mc8sEQHRfkpBdh7NtyfiHRguox_qi7CEPzcoq-zRRDg,4913
|
|
5
|
+
data_sampler/anonymize.py,sha256=ueaHZaEolNFqd3ORhDVT53xkOkmNAY5DkqCGT7X2om0,18964
|
|
6
|
+
data_sampler/cli.py,sha256=0RO_e1FYQsPNglTX85ufVsGMD7CPoVUF5E7PyoLCYBc,13672
|
|
7
|
+
data_sampler/engine.py,sha256=P04za_5QJz3CmpqDw8ZK84ARHdltxcAw8uYBz-jcSDw,26962
|
|
8
|
+
data_sampler/io.py,sha256=dfTZhEUgXC2PBVXTndT28Qm2Nd0z33mlxb3C05DVlWg,2477
|
|
9
|
+
data_sampler/report.py,sha256=6uZYyD05nXd1-UYWqBMY92EzvVXh7Ei6DuEzxQwz-QU,12857
|
|
10
|
+
data_sampler/sampling.py,sha256=UoR630lkRVVJ_3-1R97yW8sH1cwcx77yj-9xkHpxfpQ,5985
|
|
11
|
+
data_sampler/stats.py,sha256=SJKx3E41ylJnS4iP7gB1qZuf5K3HFsttkUJwXgkUCkk,6186
|
|
12
|
+
data_sampler/workflow.py,sha256=OMyDlbBIiZBoEhuBRpdKiaATBEhOfHJJ1dyTAVGtHbE,12778
|
|
13
|
+
data_sampler/tui/__init__.py,sha256=L4SgMXL1CvJUGR9YA5FElb-_hzHXE3dSGeZE5tXbWno,391
|
|
14
|
+
data_sampler/tui/app.py,sha256=UpfkQ2CbcY7u9Xc1e_AwR97bep5lQCgiB2wt7Xm0jOA,37060
|
|
15
|
+
data_sampler-3.2.1.dist-info/METADATA,sha256=Bgs0bQPxo1CqBoewk0qG9pKSSvMns0_Arn3XMJBdvDk,17971
|
|
16
|
+
data_sampler-3.2.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
17
|
+
data_sampler-3.2.1.dist-info/entry_points.txt,sha256=PD6Tr46XjB-Dd35Xc-lYdRdaa2nRJOTQ5G4lU9P3hF0,99
|
|
18
|
+
data_sampler-3.2.1.dist-info/licenses/LICENSE,sha256=u-mGJadAxmib3xMszc-6e3GO2Hma0HxWegIj-O5TJ8o,1077
|
|
19
|
+
data_sampler-3.2.1.dist-info/RECORD,,
|
|
@@ -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.
|