dasket 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dasket-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,772 @@
1
+ Metadata-Version: 2.4
2
+ Name: dasket
3
+ Version: 0.1.0
4
+ Summary: A composable Python library for preparing and assessing datasets for AI workflows.
5
+ Author: amar
6
+ Author-email: amar <amargurung1013@gmail.com>
7
+ License-Expression: MIT
8
+ Requires-Dist: polars>=2.1.0
9
+ Requires-Dist: pyarrow>=26.0.1
10
+ Requires-Dist: pydantic>=2.14.2
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ <div align="center">
15
+
16
+ <img src="docs/assets/dasket.png" alt="Dasket logo" width="520">
17
+
18
+ ### Prepare and assess datasets for AI workflows.
19
+
20
+ A composable Python library for dataset ingestion, validation, transformation, profiling, and preparation.
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## Overview
27
+
28
+ **Dasket** is a Python library designed to simplify the process of preparing and assessing datasets before they enter AI and machine learning workflows.
29
+
30
+ Working with raw data often involves repetitive operations: inspecting schemas, handling missing values, normalizing text, detecting duplicates, validating records, and preparing reproducible dataset partitions.
31
+
32
+ Dasket provides a consistent interface for these operations while leveraging established data-processing libraries rather than reinventing their underlying functionality.
33
+
34
+ Built on **Polars**, **Pydantic**, and **PyArrow**, Dasket focuses on composability, predictable behavior, and explicit data transformations.
35
+
36
+ ### Design principles
37
+
38
+ - **Composability:** Dataset operations return Dataset instances, allowing transformations to be chained or combined.
39
+ - **Explicit validation:** Invalid inputs should produce meaningful errors rather than unexpected behavior.
40
+ - **Reproducibility:** Randomized dataset splitting supports deterministic seeds.
41
+ - **Separation of concerns:** Dasket provides a higher-level dataset interface while delegating dataframe operations to Polars.
42
+ - **Minimal abstraction:** Avoid unnecessary frameworks and hide as little of the underlying data-processing behavior as possible.
43
+
44
+ ---
45
+
46
+ ## Features
47
+
48
+ | Category | Capabilities |
49
+ |---|---|
50
+ | Data ingestion | CSV, JSON, JSONL, Parquet |
51
+ | Data export | CSV, JSON, JSONL, Parquet |
52
+ | Schema validation | Pydantic model-based validation |
53
+ | Text processing | Whitespace normalization, lowercase conversion |
54
+ | Duplicate detection | Duplicate identification by selected columns |
55
+ | Missing values | Null-value analysis and row removal |
56
+ | Dataset profiling | Dimensions, column types, null counts, duplicate counts |
57
+ | Column operations | Selection, removal, renaming |
58
+ | Filtering | Polars expression-based filtering |
59
+ | Dataset splitting | Random train/test splitting |
60
+ | Stratification | Class-preserving train/test splitting |
61
+
62
+ ---
63
+
64
+ ## Architecture
65
+
66
+ Dasket uses a small wrapper abstraction around Polars DataFrames.
67
+
68
+ ```text
69
+ Application
70
+ |
71
+ v
72
+ +---------------+
73
+ | Dasket |
74
+ | |
75
+ | Dataset |
76
+ +---------------+
77
+ |
78
+ +-----------+-----------+
79
+ | | |
80
+ v v v
81
+ Ingestion Transformations Profiling
82
+ | | |
83
+ +-----------+-----------+
84
+ |
85
+ v
86
+ Polars DataFrame
87
+ |
88
+ +----------+----------+
89
+ | |
90
+ v v
91
+ Validation Storage
92
+ | |
93
+ v v
94
+ Pydantic PyArrow
95
+ ```
96
+
97
+ ### Technology stack
98
+
99
+ | Technology | Responsibility |
100
+ |---|---|
101
+ | Python 3.12+ | Core implementation |
102
+ | Polars | Dataframe operations and transformations |
103
+ | Pydantic | Schema validation |
104
+ | PyArrow | Columnar data interoperability |
105
+ | pytest | Automated testing |
106
+ | Ruff | Formatting and linting |
107
+ | uv | Dependency and environment management |
108
+
109
+ Dasket deliberately delegates dataframe execution and storage-format handling to established libraries.
110
+
111
+ ---
112
+
113
+ ## Installation
114
+
115
+ ### Requirements
116
+
117
+ - Python >= 3.12
118
+ - uv (recommended)
119
+
120
+ ### Development installation
121
+
122
+ Clone the repository:
123
+
124
+ ```bash
125
+ git clone <repository-url>
126
+ cd dasket
127
+ ```
128
+
129
+ Install dependencies:
130
+
131
+ ```bash
132
+ uv sync
133
+ ```
134
+
135
+ Activate the virtual environment:
136
+
137
+ ```bash
138
+ source .venv/bin/activate
139
+ ```
140
+
141
+ Verify the installation:
142
+
143
+ ```bash
144
+ uv run python -c "from dasket import Dataset; print(Dataset)"
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Quick Start
150
+
151
+ ```python
152
+ import polars as pl
153
+
154
+ from dasket import Dataset
155
+
156
+ # Create a dataset
157
+ df = pl.DataFrame(
158
+ {
159
+ "name": [" Amar ", "SAM", "Alex", "Amar"],
160
+ "age": [21, 24, None, 21],
161
+ "language": ["Python", "Go", "Rust", "Python"],
162
+ }
163
+ )
164
+
165
+ dataset = Dataset.from_dataframe(df)
166
+
167
+ # Inspect dataset dimensions
168
+ print(dataset.shape)
169
+ print(dataset.columns)
170
+ print(dataset.row_count)
171
+
172
+ # Normalize text
173
+ normalized = dataset.normalize(
174
+ columns=["name", "language"],
175
+ lowercase=["name", "language"],
176
+ )
177
+
178
+ # Inspect missing values
179
+ print(normalized.missing_values())
180
+
181
+ # Remove rows containing null values
182
+ cleaned = normalized.drop_missing()
183
+
184
+ # Generate a dataset profile
185
+ profile = cleaned.profile()
186
+ print(profile)
187
+
188
+ # Filter records
189
+ filtered = cleaned.filter(pl.col("age") > 20)
190
+
191
+ # Split into training and testing datasets
192
+ train, test = filtered.split(
193
+ train_ratio=0.8,
194
+ seed=42,
195
+ )
196
+ ```
197
+
198
+ Every transformation returns a new `Dataset` instance. The original dataset remains unchanged.
199
+
200
+ ---
201
+
202
+ ## API Reference
203
+
204
+ ### 1. Dataset Construction
205
+
206
+ Dasket supports construction from an existing Polars DataFrame or a supported file format.
207
+
208
+ #### `Dataset.from_dataframe()`
209
+
210
+ ```python
211
+ import polars as pl
212
+
213
+ from dasket import Dataset
214
+
215
+ df = pl.DataFrame(
216
+ {
217
+ "id": [1, 2, 3],
218
+ "name": ["Amar", "Sam", "Alex"],
219
+ }
220
+ )
221
+
222
+ dataset = Dataset.from_dataframe(df)
223
+ ```
224
+
225
+ #### `Dataset.from_file()`
226
+
227
+ ```python
228
+ dataset = Dataset.from_file("data.csv")
229
+ ```
230
+
231
+ Supported extensions:
232
+
233
+ | Extension | Polars reader |
234
+ |---|---|
235
+ | `.csv` | `pl.read_csv` |
236
+ | `.json` | `pl.read_json` |
237
+ | `.jsonl` | `pl.read_ndjson` |
238
+ | `.parquet` | `pl.read_parquet` |
239
+
240
+ File extensions are matched case-insensitively.
241
+
242
+ Unsupported formats raise `ValueError`. Missing files and non-file paths raise `FileNotFoundError`.
243
+
244
+ #### Dataset properties
245
+
246
+ ```python
247
+ dataset.data
248
+ dataset.shape
249
+ dataset.columns
250
+ dataset.row_count
251
+ ```
252
+
253
+ | Property | Description |
254
+ |---|---|
255
+ | `data` | Underlying Polars DataFrame |
256
+ | `shape` | Tuple containing rows and columns |
257
+ | `columns` | List of column names |
258
+ | `row_count` | Number of rows |
259
+
260
+ ---
261
+
262
+ ### 2. Schema Validation
263
+
264
+ Dasket integrates with Pydantic to validate dataset records against Python models.
265
+
266
+ ```python
267
+ from pydantic import BaseModel
268
+
269
+ from dasket import Dataset
270
+
271
+
272
+ class User(BaseModel):
273
+ name: str
274
+ age: int
275
+ email: str
276
+
277
+
278
+ dataset = Dataset.from_file("users.json")
279
+
280
+ report = dataset.validate(User)
281
+
282
+ print(report)
283
+ ```
284
+
285
+ Validation is delegated to Dasket's validation module, which uses the supplied Pydantic model as the record schema.
286
+
287
+ This allows application developers to define their own validation requirements rather than relying on a fixed collection of predefined schemas.
288
+
289
+ ---
290
+
291
+ ### 3. Text Normalization
292
+
293
+ #### `normalize(columns, lowercase=None)`
294
+
295
+ Normalizes selected string columns by stripping surrounding whitespace and optionally converting values to lowercase.
296
+
297
+ ```python
298
+ normalized = dataset.normalize(
299
+ columns=["name", "email"],
300
+ lowercase=["email"],
301
+ )
302
+ ```
303
+
304
+ Behavior:
305
+
306
+ - Removes surrounding whitespace.
307
+ - Supports optional lowercase conversion.
308
+ - Preserves null values.
309
+ - Rejects missing columns.
310
+ - Rejects non-string columns.
311
+ - Requires lowercase columns to be included in the selected columns.
312
+ - Does not modify the original dataset.
313
+
314
+ Example:
315
+
316
+ ```text
317
+ Before:
318
+
319
+ " AMAR "
320
+ " Sam "
321
+ " ALEX "
322
+
323
+ After:
324
+
325
+ "amar"
326
+ "Sam"
327
+ "ALEX"
328
+ ```
329
+
330
+ ---
331
+
332
+ ### 4. Duplicate Detection
333
+
334
+ #### `find_duplicates(columns=None)`
335
+
336
+ Identifies duplicate records based on selected columns.
337
+
338
+ ```python
339
+ duplicates = dataset.find_duplicates()
340
+ ```
341
+
342
+ Use specific columns:
343
+
344
+ ```python
345
+ duplicates = dataset.find_duplicates(columns=["email"])
346
+ ```
347
+
348
+ If no columns are specified, all columns are used.
349
+
350
+ **Return semantics:** The method returns one representative row for each duplicate group, retaining the first occurrence and preserving group order.
351
+
352
+ Example:
353
+
354
+ ```text
355
+ Original:
356
+
357
+ id name
358
+ 1 Amar
359
+ 1 Amar
360
+ 2 Sam
361
+ 2 Sam
362
+ 3 Alex
363
+
364
+ Result:
365
+
366
+ id name
367
+ 1 Amar
368
+ 2 Sam
369
+ ```
370
+
371
+ This method does not return every occurrence belonging to a duplicate group.
372
+
373
+ ---
374
+
375
+ ### 5. Missing-Value Analysis
376
+
377
+ #### `missing_values()`
378
+
379
+ Returns the number of null values in each column.
380
+
381
+ ```python
382
+ missing = dataset.missing_values()
383
+
384
+ print(missing)
385
+ ```
386
+
387
+ Example:
388
+
389
+ ```python
390
+ {
391
+ "name": 0,
392
+ "age": 3,
393
+ "email": 2,
394
+ }
395
+ ```
396
+
397
+ The operation counts actual null values, not empty strings or whitespace-only strings.
398
+
399
+ #### `drop_missing(columns=None)`
400
+
401
+ Removes rows containing null values.
402
+
403
+ Remove rows with nulls in any column:
404
+
405
+ ```python
406
+ cleaned = dataset.drop_missing()
407
+ ```
408
+
409
+ Consider only selected columns:
410
+
411
+ ```python
412
+ cleaned = dataset.drop_missing(columns=["email"])
413
+ ```
414
+
415
+ An empty column selection is treated as a no-op.
416
+
417
+ ---
418
+
419
+ ### 6. Dataset Profiling
420
+
421
+ #### `profile()`
422
+
423
+ Generates a structural summary of the dataset.
424
+
425
+ ```python
426
+ report = dataset.profile()
427
+
428
+ print(report)
429
+ ```
430
+
431
+ Example structure:
432
+
433
+ ```python
434
+ {
435
+ "row_count": 1000,
436
+ "column_count": 3,
437
+ "columns": {
438
+ "name": {
439
+ "dtype": "String",
440
+ "null_count": 5,
441
+ },
442
+ "age": {
443
+ "dtype": "Int64",
444
+ "null_count": 12,
445
+ },
446
+ "email": {
447
+ "dtype": "String",
448
+ "null_count": 3,
449
+ },
450
+ },
451
+ "duplicate_count": 8,
452
+ }
453
+ ```
454
+
455
+ The profile includes:
456
+
457
+ - Total row count.
458
+ - Total column count.
459
+ - Column data types.
460
+ - Null count per column.
461
+ - Duplicate row count.
462
+
463
+ **Important:** `duplicate_count` counts all rows belonging to duplicate groups, not the number of duplicate groups or only the additional occurrences.
464
+
465
+ ---
466
+
467
+ ### 7. Column Operations
468
+
469
+ #### `select_columns(columns)`
470
+
471
+ Selects specific columns.
472
+
473
+ ```python
474
+ subset = dataset.select_columns(["name", "email"])
475
+ ```
476
+
477
+ Column order follows the supplied list.
478
+
479
+ At least one column must be selected.
480
+
481
+ #### `drop_columns(columns)`
482
+
483
+ Removes selected columns.
484
+
485
+ ```python
486
+ reduced = dataset.drop_columns(["temporary_field", "internal_id"])
487
+ ```
488
+
489
+ Dasket rejects attempts to drop all columns.
490
+
491
+ #### `rename_columns(mapping)`
492
+
493
+ Renames columns using a mapping.
494
+
495
+ ```python
496
+ renamed = dataset.rename_columns(
497
+ {
498
+ "user_name": "name",
499
+ "user_email": "email",
500
+ }
501
+ )
502
+ ```
503
+
504
+ The operation validates that:
505
+
506
+ - Original column names exist.
507
+ - New names are non-empty strings.
508
+ - Renaming does not create duplicate column names.
509
+
510
+ ---
511
+
512
+ ### 8. Expression-Based Filtering
513
+
514
+ #### `filter(expression)`
515
+
516
+ Dasket exposes Polars expression-based filtering through the Dataset abstraction.
517
+
518
+ ```python
519
+ import polars as pl
520
+
521
+ adults = dataset.filter(pl.col("age") >= 18)
522
+ ```
523
+
524
+ Multiple conditions:
525
+
526
+ ```python
527
+ filtered = dataset.filter((pl.col("age") >= 18) & (pl.col("country") == "India"))
528
+ ```
529
+
530
+ String filtering:
531
+
532
+ ```python
533
+ filtered = dataset.filter(pl.col("email").str.ends_with("@gmail.com"))
534
+ ```
535
+
536
+ Dasket accepts Polars expressions rather than implementing a separate filtering language.
537
+
538
+ Invalid expression types raise `TypeError`. Column resolution and expression execution are handled by Polars.
539
+
540
+ ---
541
+
542
+ ### 9. Dataset Splitting
543
+
544
+ #### `split(train_ratio=0.8, seed=None)`
545
+
546
+ Splits a dataset into training and testing partitions.
547
+
548
+ ```python
549
+ train, test = dataset.split(
550
+ train_ratio=0.8,
551
+ seed=42,
552
+ )
553
+ ```
554
+
555
+ The split:
556
+
557
+ - Randomly shuffles row indices.
558
+ - Uses the supplied ratio to determine the training partition size.
559
+ - Preserves all rows across both partitions.
560
+ - Supports reproducibility through a random seed.
561
+ - Does not modify the original dataset.
562
+
563
+ For datasets containing at least two rows, both partitions contain at least one row.
564
+
565
+ An empty dataset produces two empty partitions. A one-row dataset raises `ValueError`.
566
+
567
+ ---
568
+
569
+ ### 10. Stratified Dataset Splitting
570
+
571
+ #### `stratified_split(column, train_ratio=0.8, seed=None)`
572
+
573
+ Performs a train/test split while preserving class representation across partitions.
574
+
575
+ ```python
576
+ train, test = dataset.stratified_split(
577
+ column="label",
578
+ train_ratio=0.8,
579
+ seed=42,
580
+ )
581
+ ```
582
+
583
+ For example:
584
+
585
+ ```text
586
+ Original distribution:
587
+
588
+ Class A: 100
589
+ Class B: 100
590
+ Class C: 100
591
+
592
+ Training distribution:
593
+
594
+ Class A: 80
595
+ Class B: 80
596
+ Class C: 80
597
+
598
+ Testing distribution:
599
+
600
+ Class A: 20
601
+ Class B: 20
602
+ Class C: 20
603
+ ```
604
+
605
+ The operation:
606
+
607
+ 1. Groups row indices by class label.
608
+ 2. Shuffles indices within each class.
609
+ 3. Calculates the training partition size independently for each class.
610
+ 4. Ensures each class appears in both partitions.
611
+ 5. Shuffles the resulting training and testing indices.
612
+
613
+ Constraints:
614
+
615
+ - The specified column must exist.
616
+ - Null class labels are rejected.
617
+ - NaN class labels are rejected.
618
+ - Class labels must be hashable.
619
+ - Every class must contain at least two samples.
620
+ - The training ratio must be between zero and one.
621
+
622
+ The random seed makes the operation reproducible.
623
+
624
+ ---
625
+
626
+ ### 11. Dataset Export
627
+
628
+ #### `to_file(path)`
629
+
630
+ Exports the underlying DataFrame to a supported file format.
631
+
632
+ ```python
633
+ dataset.to_file("output.csv")
634
+ ```
635
+
636
+ Supported formats:
637
+
638
+ ```python
639
+ dataset.to_file("output.csv")
640
+ dataset.to_file("output.json")
641
+ dataset.to_file("output.jsonl")
642
+ dataset.to_file("output.parquet")
643
+ ```
644
+
645
+ The destination directory is created automatically when necessary.
646
+
647
+ Unsupported extensions raise `ValueError`.
648
+
649
+ ---
650
+
651
+ ## Testing
652
+
653
+ Dasket uses pytest for automated testing and Ruff for formatting and linting.
654
+
655
+ Run the complete test suite:
656
+
657
+ ```bash
658
+ uv run pytest -v
659
+ ```
660
+
661
+ Run linting:
662
+
663
+ ```bash
664
+ uv run ruff check .
665
+ ```
666
+
667
+ Format the codebase:
668
+
669
+ ```bash
670
+ uv run ruff format .
671
+ ```
672
+
673
+ Run the complete project checks:
674
+
675
+ ```bash
676
+ make check
677
+ ```
678
+
679
+ The `make check` target executes formatting, linting, and automated tests.
680
+
681
+ ---
682
+
683
+ ## Project Structure
684
+
685
+ ```text
686
+ dasket/
687
+ │
688
+ ├── src/
689
+ │ └── dasket/
690
+ │ ├── __init__.py
691
+ │ ├── dataset.py
692
+ │ ├── reports.py
693
+ │ └── validation.py
694
+ │
695
+ ├── tests/
696
+ │ ├── test_dataset.py
697
+ │ ├── test_validation.py
698
+ │ ├── test_normalization.py
699
+ │ ├── test_duplicates.py
700
+ │ ├── test_missing_values.py
701
+ │ ├── test_drop_missing.py
702
+ │ ├── test_columns.py
703
+ │ ├── test_filter.py
704
+ │ ├── test_split.py
705
+ │ └── test_stratified_split.py
706
+ │
707
+ ├── docs/
708
+ │ └── assets/
709
+ │ └── dasket.png
710
+ │
711
+ ├── Makefile
712
+ ├── pyproject.toml
713
+ ├── uv.lock
714
+ └── README.md
715
+ ```
716
+
717
+ ---
718
+
719
+ ## Design Scope
720
+
721
+ Dasket is intended to provide practical dataset preparation and assessment primitives.
722
+
723
+ It is not intended to replace:
724
+
725
+ - Polars or Pandas as dataframe engines.
726
+ - Pydantic as a validation framework.
727
+ - PyArrow as a columnar data interchange layer.
728
+ - Dedicated machine learning frameworks.
729
+ - Distributed data-processing engines.
730
+
731
+ Instead, Dasket provides a consistent interface that combines common dataset preparation operations into one composable API.
732
+
733
+ ---
734
+
735
+ ## Roadmap
736
+
737
+ Potential future directions:
738
+
739
+ - [ ] Richer dataset profiling and statistical summaries.
740
+ - [ ] Configurable missing-value strategies.
741
+ - [ ] Dataset comparison and drift analysis.
742
+ - [ ] Schema inference.
743
+ - [ ] More advanced dataset sampling strategies.
744
+ - [ ] Improved validation diagnostics.
745
+ - [ ] Dataset lineage and transformation tracking.
746
+
747
+ These are potential directions, not currently implemented features.
748
+
749
+ ---
750
+
751
+ ## Contributing
752
+
753
+ Contributions, bug reports, and suggestions are welcome.
754
+
755
+ For development:
756
+
757
+ ```bash
758
+ git clone <repository-url>
759
+ cd dasket
760
+
761
+ uv sync
762
+
763
+ make check
764
+ ```
765
+
766
+ Please ensure that formatting, linting, and tests pass before submitting changes.
767
+
768
+ ---
769
+
770
+ ## License
771
+
772
+ See the repository's license file for licensing information.