ultrasav 0.1.0__tar.gz → 0.2.4__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.
- {ultrasav-0.1.0/src/ultrasav.egg-info → ultrasav-0.2.4}/PKG-INFO +598 -565
- {ultrasav-0.1.0 → ultrasav-0.2.4}/README.md +109 -61
- {ultrasav-0.1.0 → ultrasav-0.2.4}/pyproject.toml +5 -16
- {ultrasav-0.1.0 → ultrasav-0.2.4}/src/ultrasav/__init__.py +43 -34
- ultrasav-0.1.0/src/ultrasav/def_add_cases.py → ultrasav-0.2.4/src/ultrasav/_add_cases.py +4 -4
- ultrasav-0.1.0/src/ultrasav/class_data.py → ultrasav-0.2.4/src/ultrasav/_data.py +6 -3
- ultrasav-0.1.0/src/ultrasav/def_merge_data.py → ultrasav-0.2.4/src/ultrasav/_merge_data.py +1 -1
- ultrasav-0.1.0/src/ultrasav/def_merge_meta.py → ultrasav-0.2.4/src/ultrasav/_merge_meta.py +1 -1
- ultrasav-0.2.4/src/ultrasav/_metadata.py +1239 -0
- ultrasav-0.2.4/src/ultrasav/_set_value_labels.py +209 -0
- ultrasav-0.1.0/src/ultrasav/def_write_files.py → ultrasav-0.2.4/src/ultrasav/_write_files.py +12 -13
- {ultrasav-0.1.0 → ultrasav-0.2.4}/src/ultrasav/metaman/__init__.py +18 -12
- ultrasav-0.2.4/src/ultrasav/metaman/_describe.py +421 -0
- ultrasav-0.1.0/src/ultrasav/metaman/def_get_meta.py → ultrasav-0.2.4/src/ultrasav/metaman/_get_meta.py +2 -2
- ultrasav-0.1.0/src/ultrasav/metaman/def_make_datamap.py → ultrasav-0.2.4/src/ultrasav/metaman/_make_datamap.py +1 -1
- ultrasav-0.1.0/src/ultrasav/metaman/def_make_labels.py → ultrasav-0.2.4/src/ultrasav/metaman/_make_labels.py +2 -2
- ultrasav-0.1.0/src/ultrasav/metaman/def_map_engine.py → ultrasav-0.2.4/src/ultrasav/metaman/_map_engine.py +530 -529
- ultrasav-0.1.0/src/ultrasav/metaman/def_map_to_excel.py → ultrasav-0.2.4/src/ultrasav/metaman/_map_to_excel.py +2 -2
- ultrasav-0.1.0/src/ultrasav/metaman/def_write_excel_engine.py → ultrasav-0.2.4/src/ultrasav/metaman/_write_excel_engine.py +1 -1
- ultrasav-0.1.0/LICENSE +0 -21
- ultrasav-0.1.0/PKG-INFO +0 -565
- ultrasav-0.1.0/setup.cfg +0 -4
- ultrasav-0.1.0/src/ultrasav/class_metadata.py +0 -570
- ultrasav-0.1.0/src/ultrasav.egg-info/SOURCES.txt +0 -26
- ultrasav-0.1.0/src/ultrasav.egg-info/dependency_links.txt +0 -1
- ultrasav-0.1.0/src/ultrasav.egg-info/requires.txt +0 -25
- ultrasav-0.1.0/src/ultrasav.egg-info/top_level.txt +0 -1
- /ultrasav-0.1.0/src/ultrasav/def_make_dummy.py → /ultrasav-0.2.4/src/ultrasav/_make_dummy.py +0 -0
- /ultrasav-0.1.0/src/ultrasav/def_read_files.py → /ultrasav-0.2.4/src/ultrasav/_read_files.py +0 -0
- /ultrasav-0.1.0/src/ultrasav/metaman/pastel_color_schemes.py → /ultrasav-0.2.4/src/ultrasav/metaman/_color_schemes.py +0 -0
- /ultrasav-0.1.0/src/ultrasav/metaman/def_detect_variable_type.py → /ultrasav-0.2.4/src/ultrasav/metaman/_detect_variable_type.py +0 -0
|
@@ -1,565 +1,598 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name: ultrasav
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Python package for working with SPSS/SAV files with two-track architecture separating data and metadata operations
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
License: MIT
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
ul.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
| gender |
|
|
336
|
-
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
#
|
|
398
|
-
data = data.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
meta
|
|
439
|
-
|
|
440
|
-
meta
|
|
441
|
-
|
|
442
|
-
#
|
|
443
|
-
meta = meta.update(
|
|
444
|
-
column_labels
|
|
445
|
-
variable_value_labels
|
|
446
|
-
variable_measure
|
|
447
|
-
variable_format
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
#
|
|
469
|
-
meta.
|
|
470
|
-
|
|
471
|
-
#
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
**
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
```python
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ultrasav
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: A Python package for working with SPSS/SAV files with two-track architecture separating data and metadata operations
|
|
5
|
+
Keywords: spss,spss labels,spss files,sav files,sav,statistics,data-science,data-processing,survey-data,metadata,spss metadata,pyreadstat,dataframe-agnostic,polars,pandas,read spss,read sav,write spss,write sav,merge spss,merge sav,datamap,spss-datamap,validation,data-quality,tidyspss,metaprinter
|
|
6
|
+
Author: Albert Li
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Dist: pandas>=2.2.0
|
|
9
|
+
Requires-Dist: polars>=1.3.0
|
|
10
|
+
Requires-Dist: pyreadstat>=1.3.2
|
|
11
|
+
Requires-Dist: narwhals>=2.11.0
|
|
12
|
+
Requires-Dist: openpyxl>=3.0.0
|
|
13
|
+
Requires-Dist: xlsxwriter>=3.1.0
|
|
14
|
+
Requires-Dist: ultrasav[excel,dev,docs] ; extra == 'all'
|
|
15
|
+
Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-cov>=4.0.0 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: black>=23.0.0 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: mypy>=1.0.0 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: pre-commit>=3.0.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: sphinx>=6.0.0 ; extra == 'docs'
|
|
22
|
+
Requires-Dist: sphinx-rtd-theme>=1.0.0 ; extra == 'docs'
|
|
23
|
+
Requires-Dist: myst-parser>=1.0.0 ; extra == 'docs'
|
|
24
|
+
Requires-Dist: fastexcel>=0.9.0 ; extra == 'excel'
|
|
25
|
+
Maintainer: Albert Li
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Project-URL: Homepage, https://github.com/albertxli/ultrasav
|
|
28
|
+
Project-URL: Documentation, https://ultrasav.readthedocs.io
|
|
29
|
+
Project-URL: Changelog, https://github.com/albertxli/ultrasav/blob/main/CHANGELOG.md
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Provides-Extra: excel
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# ⚡ultrasav
|
|
37
|
+
|
|
38
|
+
An 'Ultra-powerful' Python package for preparing production-ready SPSS/SAV files using a two-track architecture that separates data and metadata operations.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## 💡 Motivation
|
|
43
|
+
|
|
44
|
+
**ultrasav** is built as a thoughtful wrapper around the excellent pyreadstat package. We're not here to reinvent the wheel for reading and writing SAV files - pyreadstat already does that brilliantly!
|
|
45
|
+
|
|
46
|
+
Instead, ultrasav provides additional transformation tools for tasks that are commonly done by folks who work with SAV files regularly:
|
|
47
|
+
- 🏷️ **Rename variables** - Change variable names in batch with clean methodology
|
|
48
|
+
- 🔄 **Recode values** - Transform codes across multiple variables with clean syntax
|
|
49
|
+
- 🏷️ **Update labels** - Batch update variable labels and value labels without losing track
|
|
50
|
+
- 📊 **Reorganize columns** - Move variables to specific positions for standardized layouts
|
|
51
|
+
- 📀 **Merge files intelligently** - Stack survey data while preserving all metadata
|
|
52
|
+
- 🎯 **Handle missing values** - Consistent missing value definitions across datasets
|
|
53
|
+
- 🦸 **Inspect & report metadata** - Generate datamaps and validation reports with metaman
|
|
54
|
+
|
|
55
|
+
## 🎯 Core Philosophy
|
|
56
|
+
|
|
57
|
+
**ultrasav** follows a simple but powerful principle: **Data and Metadata are two independent layers that only come together at read/write time.**
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
┌─────────────┐ ┌─────────────┐
|
|
61
|
+
│ DATA │ │ METADATA │
|
|
62
|
+
│ DataFrame │ │ Labels │
|
|
63
|
+
│ Operations │ │ Formats │
|
|
64
|
+
└─────────────┘ └─────────────┘
|
|
65
|
+
│ │
|
|
66
|
+
└────────┬────────────────┘
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
┌─────────────┐
|
|
70
|
+
│ WRITE SAV │
|
|
71
|
+
└─────────────┘
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### The Common Problems
|
|
75
|
+
|
|
76
|
+
If you work with SPSS files in Python, you've probably asked yourself:
|
|
77
|
+
|
|
78
|
+
- How do I bulk update variable labels and value labels?
|
|
79
|
+
- How do I quickly relocate variables to ideal positions?
|
|
80
|
+
- How do I merge datasets — and more specifically, how are the labels being merged?
|
|
81
|
+
- How can I see a comprehensive datamap of my data?
|
|
82
|
+
- Most importantly: **How do I prepare a tidy SPSS file with clean labels and metadata that is production-ready?**
|
|
83
|
+
|
|
84
|
+
ultrasav answers all of these.
|
|
85
|
+
|
|
86
|
+
### The ultrasav Way
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import ultrasav as ul
|
|
90
|
+
|
|
91
|
+
# Read → splits into two independent tracks
|
|
92
|
+
df, meta = ul.read_sav("survey.sav")
|
|
93
|
+
|
|
94
|
+
# Track 1 - Data: Transform data freely
|
|
95
|
+
data = ul.Data(df) # Wrap df into our Data class
|
|
96
|
+
df = data.move(first=['id']).rename({'Q1': 'satisfaction'}).replace({'satisfaction': {6: 99}}).to_native()
|
|
97
|
+
|
|
98
|
+
# Track 2 - Metadata: Update metadata independently (immutable - returns NEW object)
|
|
99
|
+
meta = ul.Metadata(meta) # Wrap meta into our Metadata class
|
|
100
|
+
meta = meta.update(
|
|
101
|
+
column_labels={'satisfaction': 'Overall satisfaction'},
|
|
102
|
+
variable_value_labels={'recommend': {0: 'No', 1: 'Yes'}}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Convergence: Reunite at write time
|
|
106
|
+
ul.write_sav(df, meta, "clean_survey.sav")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The goal is to provide you with a **clean and easy-to-understand way** to transform your SPSS data that you can use in real production workflows with minimal tweaking.
|
|
110
|
+
|
|
111
|
+
### 🚀 DataFrame-Agnostic Design
|
|
112
|
+
|
|
113
|
+
One of ultrasav's superpowers is being **dataframe-agnostic** — it works seamlessly with both **polars** and **pandas** thanks to [narwhals](https://github.com/MarcoGorelli/narwhals) under the hood:
|
|
114
|
+
|
|
115
|
+
- 🐻❄️ **Polars by default** - Blazing fast performance out of the box
|
|
116
|
+
- 🐼 **Pandas fully supported** - Use `output_format="pandas"` when needed
|
|
117
|
+
- 🔄 **Switch freely** - Convert between pandas and polars anytime
|
|
118
|
+
- 🔧 **Future-proof** - Ready for whatever dataframe library comes next
|
|
119
|
+
|
|
120
|
+
**Default output format: Polars** — All operations return polars DataFrames by default for blazing-fast performance. Pandas is fully supported via the `output_format="pandas"` parameter.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import ultrasav as ul
|
|
124
|
+
|
|
125
|
+
# Polars by default
|
|
126
|
+
df_pl, meta = ul.read_sav("survey.sav", output_format="polars")
|
|
127
|
+
|
|
128
|
+
# Or explicitly request pandas
|
|
129
|
+
df_pd, meta = ul.read_sav("survey.sav", output_format="pandas")
|
|
130
|
+
|
|
131
|
+
# The Data class works with either
|
|
132
|
+
data = ul.Data(df_pl) # Works with both Polars and pandas!
|
|
133
|
+
|
|
134
|
+
# Transform using ultrasav's consistent API
|
|
135
|
+
data = data.rename({"Q1": "satisfaction"}).replace({'satisfaction': {6: 99}})
|
|
136
|
+
df_native = data.to_native() # Get back your polars DataFrame
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Who Is This For?
|
|
140
|
+
|
|
141
|
+
- 📊 **Market Researchers** - Merge waves, standardize labels, prepare deliverables
|
|
142
|
+
- 🔬 **Data Scientists** - Clean survey data, prepare features, maintain metadata
|
|
143
|
+
- 🏭 **Data Engineers** - Build robust pipelines that preserve SPSS metadata
|
|
144
|
+
- 🎓 **Academic Researchers** - Manage longitudinal studies, harmonize datasets
|
|
145
|
+
- 📈 **Anyone working with SPSS** - If you use SAV files regularly, this is for you!
|
|
146
|
+
|
|
147
|
+
## 🚀 Installation
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Using uv
|
|
151
|
+
uv add ultrasav
|
|
152
|
+
|
|
153
|
+
# Or using pip
|
|
154
|
+
pip install ultrasav
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 📚 Quick Start
|
|
158
|
+
|
|
159
|
+
### Basic Usage
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import ultrasav as ul
|
|
163
|
+
|
|
164
|
+
# Read SPSS file - automatically splits into data and metadata
|
|
165
|
+
df, meta = ul.read_sav("survey.sav")
|
|
166
|
+
# Note: You can also use pyreadstat directly - our classes work with pyreadstat meta objects too
|
|
167
|
+
|
|
168
|
+
# Track 1: Process data independently
|
|
169
|
+
data = ul.Data(df) # Wrap in Data class for transformations
|
|
170
|
+
data = data.move(first=["ID", "Date"]) # Reorder columns
|
|
171
|
+
data = data.rename({"Q1": "Satisfaction"}) # Rename columns
|
|
172
|
+
data = data.replace({"Satisfaction": {99: None}}) # Replace values
|
|
173
|
+
df = data.to_native() # Back to native DataFrame
|
|
174
|
+
|
|
175
|
+
# Track 2: Process metadata independently (immutable updates)
|
|
176
|
+
meta = ul.Metadata(meta)
|
|
177
|
+
meta = meta.update(
|
|
178
|
+
column_labels={"Satisfaction": "Customer Satisfaction Score"},
|
|
179
|
+
variable_value_labels={
|
|
180
|
+
"Satisfaction": {1: "Very Dissatisfied", 5: "Very Satisfied"}
|
|
181
|
+
},
|
|
182
|
+
variable_measure={
|
|
183
|
+
'Satisfaction': 'ordinal',
|
|
184
|
+
'Gender': 'nominal',
|
|
185
|
+
'Age': 'scale',
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Convergence: Write both tracks to SPSS
|
|
190
|
+
ul.write_sav(df, meta, "cleaned_survey.sav")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Merging Files
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
import ultrasav as ul
|
|
197
|
+
|
|
198
|
+
# Merge multiple files vertically with automatic metadata handling
|
|
199
|
+
df, meta = ul.add_cases([
|
|
200
|
+
"wave1.sav",
|
|
201
|
+
"wave2.sav",
|
|
202
|
+
"wave3.sav"
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
# Metadata is automatically preserved from top to bottom.
|
|
206
|
+
# A source-tracking column is automatically added to show each row's origin.
|
|
207
|
+
# Example: mrgsrc: ["wave1.sav", "wave2.sav", "wave3.sav"]
|
|
208
|
+
|
|
209
|
+
ul.write_sav(df, meta, "merged_output.sav")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Advanced Merging
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
import ultrasav as ul
|
|
216
|
+
|
|
217
|
+
# Use specific metadata template for all files
|
|
218
|
+
standard_meta = ul.Metadata() # Create an empty meta object
|
|
219
|
+
standard_meta = standard_meta.update(
|
|
220
|
+
column_labels={"Q1": "Satisfaction", "Q2": "Loyalty"},
|
|
221
|
+
variable_value_labels={
|
|
222
|
+
"Satisfaction": {1: "Very Dissatisfied", 5: "Very Satisfied"}
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
data, meta = ul.add_cases(
|
|
227
|
+
inputs=["file1.sav", "file2.sav", "file3.csv"],
|
|
228
|
+
meta=[standard_meta], # Apply this metadata to merged data
|
|
229
|
+
source_col="mrgsrc", # Auto append column 'mrgsrc' to track source files
|
|
230
|
+
output_format="polars" # Explicit format (polars is default)
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Writing Back
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Read SPSS file
|
|
238
|
+
df, meta = ul.read_sav("huge_survey.sav")
|
|
239
|
+
|
|
240
|
+
# All ultrasav operations work the same
|
|
241
|
+
df = ul.Data(df).rename({"Q1": "satisfaction"}).drop(["unused_var"]).to_native()
|
|
242
|
+
|
|
243
|
+
# Efficient write-back
|
|
244
|
+
# Simply provide the 'meta' object; labels and formats are applied automatically.
|
|
245
|
+
# Compatible with both ultrasav and pyreadstat meta objects.
|
|
246
|
+
ul.write_sav(df, meta, "processed_data.sav")
|
|
247
|
+
|
|
248
|
+
# For compressed output, use .zsav extension with compress=True
|
|
249
|
+
meta = ul.Metadata(meta).update(compress=True)
|
|
250
|
+
ul.write_sav(df, meta, "compressed_data.zsav")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## 🦸 Metaman: The Metadata Submodule
|
|
254
|
+
|
|
255
|
+
ultrasav includes **metaman**, a powerful submodule for metadata inspection, extraction, and reporting. All metaman functions are accessible directly from the top-level `ul` namespace.
|
|
256
|
+
|
|
257
|
+
### Generate Validation Datamaps
|
|
258
|
+
|
|
259
|
+
Create comprehensive datamaps showing variable types, value distributions, and data quality metrics:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
import ultrasav as ul
|
|
263
|
+
|
|
264
|
+
df, meta = ul.read_sav("survey.sav")
|
|
265
|
+
|
|
266
|
+
# Create a validation datamap
|
|
267
|
+
datamap = ul.make_datamap(df, meta)
|
|
268
|
+
|
|
269
|
+
# Export to beautifully formatted Excel
|
|
270
|
+
# This function supports polars only at the moment
|
|
271
|
+
ul.map_to_excel(datamap, "validation_report.xlsx")
|
|
272
|
+
|
|
273
|
+
# Use custom color schemes
|
|
274
|
+
ul.map_to_excel(
|
|
275
|
+
datamap,
|
|
276
|
+
"validation_report.xlsx",
|
|
277
|
+
alternating_group_formats=ul.get_color_scheme("pastel_blue")
|
|
278
|
+
)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The datamap includes:
|
|
282
|
+
- Variable names and labels
|
|
283
|
+
- Variable types (single-select, multi-select, numeric, text, date)
|
|
284
|
+
- Value codes and labels
|
|
285
|
+
- Value counts and percentages
|
|
286
|
+
- Missing data flags
|
|
287
|
+
- Missing value label detection
|
|
288
|
+
|
|
289
|
+
**Note: Variable types are inferred from both SPSS data and metadata on a best-effort basis and may not always perfectly reflect the true underlying types.**
|
|
290
|
+
|
|
291
|
+
### Extract Metadata to Python Files
|
|
292
|
+
|
|
293
|
+
Save existing metadata (if any) from a sav file as importable Python dictionaries for reuse across projects:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
import ultrasav as ul
|
|
297
|
+
|
|
298
|
+
df, meta = ul.read_sav("survey.sav")
|
|
299
|
+
|
|
300
|
+
# Extract metadata (labels) to in-memory python object
|
|
301
|
+
meta_dict = ul.get_meta(meta)
|
|
302
|
+
|
|
303
|
+
# Extract and save ALL metadata to a Python file
|
|
304
|
+
meta_dict = ul.get_meta(meta, include_all=True, output_path="survey_labels.py")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Create Labels from Excel Templates
|
|
308
|
+
|
|
309
|
+
Build label dictionaries from scratch using Excel templates - perfect for translating surveys or standardizing labels:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import ultrasav as ul
|
|
313
|
+
|
|
314
|
+
# Excel file with 'col_label' and 'value_label' sheets
|
|
315
|
+
col_labels, val_labels = ul.make_labels(
|
|
316
|
+
input_path="label_template.xlsx",
|
|
317
|
+
output_path="translated_labels.py" # optional
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Excel Structure:**
|
|
322
|
+
|
|
323
|
+
Your Excel file should have two sheets:
|
|
324
|
+
|
|
325
|
+
1. **Column Labels Sheet** (default sheet name: "col_label"):
|
|
326
|
+
| variable | label |
|
|
327
|
+
|----------|-------|
|
|
328
|
+
| age | Age of respondent |
|
|
329
|
+
| gender | Gender |
|
|
330
|
+
| income | Annual household income |
|
|
331
|
+
|
|
332
|
+
2. **Value Labels Sheet** (default sheet name: "value_label"):
|
|
333
|
+
| variable | value | label |
|
|
334
|
+
|----------|-------|-------|
|
|
335
|
+
| gender | 1 | Male |
|
|
336
|
+
| gender | 2 | Female |
|
|
337
|
+
| income | 1 | Under $25k |
|
|
338
|
+
| income | 2 | $25k-50k |
|
|
339
|
+
|
|
340
|
+
## 📖 API Reference
|
|
341
|
+
|
|
342
|
+
### Core Functions
|
|
343
|
+
|
|
344
|
+
#### `read_sav(filepath, output_format="polars")`
|
|
345
|
+
Read SPSS file and return separated data and metadata.
|
|
346
|
+
This is a wrapper around pyreadstat.read_sav with some additional encoding handling
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
df, meta = ul.read_sav("survey.sav")
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### `write_sav(data, meta, filepath, **overrides)`
|
|
353
|
+
Write data and metadata to SPSS file.
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
ul.write_sav(df, meta, "processed_data.sav")
|
|
357
|
+
|
|
358
|
+
# With compression (must use .zsav extension)
|
|
359
|
+
meta_compressed = ul.Metadata(meta).update(compress=True)
|
|
360
|
+
ul.write_sav(df, meta_compressed, "compressed_data.zsav")
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Compression Validation:** When `compress=True` in metadata, the destination file must have a `.zsav` extension. A `ValueError` is raised if you attempt to write a compressed file with a `.sav` extension.
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
# This will raise ValueError
|
|
367
|
+
meta = ul.Metadata().update(compress=True)
|
|
368
|
+
ul.write_sav(df, meta, "output.sav") # ❌ Wrong extension!
|
|
369
|
+
# ValueError: Metadata has compress=True but destination file 'output.sav'
|
|
370
|
+
# has extension '.sav'. Compressed SPSS files must use the '.zsav' extension.
|
|
371
|
+
|
|
372
|
+
# Correct usage
|
|
373
|
+
ul.write_sav(df, meta, "output.zsav") # ✅ Correct
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### `add_cases(inputs, meta=None, source_col="mrgsrc")`
|
|
377
|
+
Merge multiple files/dataframes vertically with metadata handling, return merged data and metadata.
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
df_merged, meta_merged = ul.add_cases(["wave1.sav","wave2.sav", "wave3.sav"])
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Classes
|
|
384
|
+
|
|
385
|
+
#### `Data`
|
|
386
|
+
Handles all dataframe operations while maintaining compatibility with both Polars and pandas.
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
import ultrasav as ul
|
|
390
|
+
|
|
391
|
+
df, meta = ul.read_sav("survey.sav") # Returns a Polars DataFrame and meta object
|
|
392
|
+
|
|
393
|
+
# Convert polars or pandas df into our ul.Data() class
|
|
394
|
+
data = ul.Data(df)
|
|
395
|
+
|
|
396
|
+
# Data Class Methods
|
|
397
|
+
# move - to relocate columns
|
|
398
|
+
data = data.move(
|
|
399
|
+
first=['respondent_id'],
|
|
400
|
+
last=['timestamp'],
|
|
401
|
+
before={'age': 'gender'}, # place 'age' column before 'gender'
|
|
402
|
+
after={'wave': ['age', 'gender', 'income']} # place demographic columns after 'wave'
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# rename - to rename columns
|
|
406
|
+
data = data.rename({"old": "new"})
|
|
407
|
+
|
|
408
|
+
# replace - to replace/recode values
|
|
409
|
+
data = data.replace({"col": {1: 100}})
|
|
410
|
+
|
|
411
|
+
# select - to select columns
|
|
412
|
+
data = data.select(['age', 'gender'])
|
|
413
|
+
|
|
414
|
+
# drop - to drop columns
|
|
415
|
+
data = data.drop(['id', 'language'])
|
|
416
|
+
|
|
417
|
+
# to_native - to return ul.Data(df) back to its native dataframe
|
|
418
|
+
df = data.to_native() # Get back Polars/pandas DataFrame
|
|
419
|
+
|
|
420
|
+
# Optionally, use chaining for cleaner code
|
|
421
|
+
df = (
|
|
422
|
+
ul.Data(df)
|
|
423
|
+
.move(first=['respondent_id'])
|
|
424
|
+
.rename({"old": "new"})
|
|
425
|
+
.replace({"col": {1: 100}})
|
|
426
|
+
.select(['age', 'gender'])
|
|
427
|
+
.drop(['id', 'language'])
|
|
428
|
+
.to_native()
|
|
429
|
+
)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### `Metadata`
|
|
433
|
+
Manages all SPSS metadata independently from data. Uses **immutable updates** - all update operations return NEW Metadata objects, nothing is modified in place.
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
import ultrasav as ul
|
|
437
|
+
|
|
438
|
+
df, meta = ul.read_sav("survey.sav")
|
|
439
|
+
|
|
440
|
+
meta = ul.Metadata(meta)
|
|
441
|
+
|
|
442
|
+
# Use .update() to update metadata (returns NEW object)
|
|
443
|
+
meta = meta.update(
|
|
444
|
+
column_labels={"Q1": "Question 1"},
|
|
445
|
+
variable_value_labels={"Q1": {1: "Yes", 0: "No"}},
|
|
446
|
+
variable_measure={"age": "scale"},
|
|
447
|
+
variable_format={"age": "F3.0", "city_name": "A50"},
|
|
448
|
+
variable_display_width={"city_name": 50},
|
|
449
|
+
missing_ranges={"Q1": [99], "Q2": [{"lo": 998, "hi": 999}]},
|
|
450
|
+
note="Created on 2025-02-15",
|
|
451
|
+
file_label="My Survey 2025",
|
|
452
|
+
compress=False, # Set to True for .zsav output
|
|
453
|
+
row_compress=False
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Or use convenience with_*() methods for single updates
|
|
457
|
+
meta = meta.with_column_labels({"Q2": "Question 2"})
|
|
458
|
+
meta = meta.with_file_label("Updated Survey 2025")
|
|
459
|
+
meta = meta.with_compress(True) # For .zsav output
|
|
460
|
+
|
|
461
|
+
# Chain multiple updates
|
|
462
|
+
meta = (meta
|
|
463
|
+
.with_column_labels({"Q1": "Question 1"})
|
|
464
|
+
.with_variable_measure({"Q1": "nominal"})
|
|
465
|
+
.with_file_label("My Survey 2025")
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Access metadata properties (read-only)
|
|
469
|
+
print(meta.column_labels) # {'Q1': 'Question 1', ...}
|
|
470
|
+
print(meta.variable_value_labels) # {'Q1': {1: 'Yes', 0: 'No'}, ...}
|
|
471
|
+
print(meta.compress) # True/False
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Immutable Design:**
|
|
475
|
+
- Original metadata is preserved and never destroyed
|
|
476
|
+
- All `update()` and `with_*()` methods return NEW Metadata objects
|
|
477
|
+
- The original object remains unchanged
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
meta1 = ul.Metadata(meta)
|
|
481
|
+
meta2 = meta1.update(column_labels={"Q1": "New Label"})
|
|
482
|
+
# meta1 is UNCHANGED, meta2 has the update
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Metadata Updating Logic:**
|
|
486
|
+
- User updates overlay on top of originals
|
|
487
|
+
- When you update `column_labels={"Q1": "New Label"}`:
|
|
488
|
+
- This updates Q1's column label if there is an existing column label
|
|
489
|
+
- If Q1 is not in the original metadata, Q1's new label will be appended
|
|
490
|
+
- All other column labels remain unchanged
|
|
491
|
+
|
|
492
|
+
**Note on `variable_value_labels` Update Behavior:**
|
|
493
|
+
|
|
494
|
+
When updating `variable_value_labels`, the entire value-label dictionary for a variable is **replaced**, not merged.
|
|
495
|
+
|
|
496
|
+
```python
|
|
497
|
+
# Original metadata
|
|
498
|
+
meta = ul.Metadata({"variable_value_labels": {"Q1": {1: "Yes", 2: "No", 99: "Unsure"}}})
|
|
499
|
+
|
|
500
|
+
# User update
|
|
501
|
+
meta = meta.update(variable_value_labels={"Q1": {1: "Yes", 0: "No"}})
|
|
502
|
+
|
|
503
|
+
# Result for Q1 becomes:
|
|
504
|
+
{"Q1": {1: "Yes", 0: "No"}} # Previous values 2 and 99 are NOT preserved
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
This means:
|
|
508
|
+
- Only the value-label pairs explicitly provided in the update are kept
|
|
509
|
+
- The entire dictionary for that variable is replaced at once
|
|
510
|
+
- Variable-level entries are preserved (e.g., "Q1" still exists), but value-level merging does not occur
|
|
511
|
+
|
|
512
|
+
This follows ultrasav's design principle: metadata updates overlay at the variable level — never partially merged — ensuring clean and intentional metadata after each update.
|
|
513
|
+
|
|
514
|
+
**Critical Design Choice:**
|
|
515
|
+
- When you rename an existing column "Q1" to "Q1a" in data, the associated metadata does not automatically carry over
|
|
516
|
+
- You must explicitly provide new metadata for the newly renamed column "Q1a"
|
|
517
|
+
- No automatic tracking or mapping between old and new names
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
### 🦸 Metaman Functions
|
|
521
|
+
|
|
522
|
+
#### `make_datamap(df, meta, output_format=None)`
|
|
523
|
+
Create a validation datamap from data and metadata.
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
datamap = ul.make_datamap(df, meta)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
#### `map_to_excel(df, file_path, **kwargs)`
|
|
530
|
+
Export datamap to formatted Excel with merged cells and alternating colors.
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
ul.map_to_excel(datamap, "report.xlsx") # Saves datamap to Excel
|
|
534
|
+
ul.map_to_excel(datamap, "report.xlsx", alternating_group_formats=ul.get_color_scheme("pastel_blue"))
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
#### `get_meta(meta, output_path=None, include_all=False)`
|
|
538
|
+
Extract metadata to a Python file or dictionary.
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
meta_dict = ul.get_meta(meta) # Returns meta_dict in memory
|
|
542
|
+
ul.get_meta(meta, output_path="labels.py") # Saves to file
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### `make_labels(input_path, output_path=None)`
|
|
546
|
+
Create label dictionaries from an Excel template.
|
|
547
|
+
|
|
548
|
+
```python
|
|
549
|
+
col_labels, val_labels = ul.make_labels("template.xlsx") # Returns label dicts in memory
|
|
550
|
+
col_labels, val_labels = ul.make_labels("template.xlsx", "labels.py") # Saves to file
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
#### `detect_variable_type(df, meta, column)`
|
|
554
|
+
Detect variable type (single-select, multi-select, numeric, text, date).
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
var_type = ul.detect_variable_type(df, meta, "Q1")
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### `get_color_scheme(name)`
|
|
561
|
+
Get a color scheme for Excel formatting.
|
|
562
|
+
|
|
563
|
+
```python
|
|
564
|
+
scheme = ul.get_color_scheme("pastel_blue")
|
|
565
|
+
# Options: "classic_grey", "pastel_green", "pastel_blue", "pastel_purple", "pastel_indigo"
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### `describe(df, meta, columns)`
|
|
569
|
+
|
|
570
|
+
Quickly variable summary including variable metadata and value distributions:
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
# Single variable
|
|
574
|
+
ul.describe(df, meta, "Q1")
|
|
575
|
+
|
|
576
|
+
# Multiple variables
|
|
577
|
+
ul.describe(df, meta, ["Q1", "Q2", "Q3"])
|
|
578
|
+
|
|
579
|
+
# Get summary dict without printing
|
|
580
|
+
summary = ul.describe(df, meta, "Q1", print_output=False)
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## ⚡ Why "ultrasav"?
|
|
584
|
+
|
|
585
|
+
The name combines "Ultra" (super-powered) with "SAV" (SPSS file format), representing the ultra-powerful transformation capabilities of this package. Just like Ultraman's Specium Ray, ultrasav splits and recombines data with precision and power!
|
|
586
|
+
|
|
587
|
+
And **metaman**? He's the metadata superhero who swoops in to inspect, validate, and report on your SPSS data! 🦸
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
## 📄 License
|
|
591
|
+
|
|
592
|
+
MIT License - see LICENSE file for details.
|
|
593
|
+
|
|
594
|
+
## 🙏 Acknowledgments
|
|
595
|
+
|
|
596
|
+
- Built on top of [pyreadstat](https://github.com/Roche/pyreadstat) for SPSS file handling
|
|
597
|
+
- Uses [narwhals](https://github.com/MarcoGorelli/narwhals) for dataframe compatibility
|
|
598
|
+
- Excel export powered by [xlsxwriter](https://github.com/jmcnamara/XlsxWriter)
|