microdf-python 1.0.0__py3-none-any.whl → 1.0.2__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.
- microdf/microdataframe.py +250 -49
- microdf/microseries.py +8 -0
- microdf/tests/test_microseries_dataframe.py +17 -0
- {microdf_python-1.0.0.dist-info → microdf_python-1.0.2.dist-info}/METADATA +2 -2
- microdf_python-1.0.2.dist-info/RECORD +10 -0
- microdf_python-1.0.0.dist-info/RECORD +0 -10
- {microdf_python-1.0.0.dist-info → microdf_python-1.0.2.dist-info}/WHEEL +0 -0
- {microdf_python-1.0.0.dist-info → microdf_python-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {microdf_python-1.0.0.dist-info → microdf_python-1.0.2.dist-info}/top_level.txt +0 -0
microdf/microdataframe.py
CHANGED
|
@@ -44,14 +44,17 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
46
|
def fn(*args, **kwargs) -> pd.Series:
|
|
47
|
-
results =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
results = {}
|
|
48
|
+
for col in self.columns:
|
|
49
|
+
if pd.api.types.is_numeric_dtype(self[col]):
|
|
50
|
+
try:
|
|
51
|
+
results[col] = getattr(self[col], name)(
|
|
52
|
+
*args, **kwargs
|
|
53
|
+
)
|
|
54
|
+
except Exception:
|
|
55
|
+
# Skip columns that can't be aggregated
|
|
56
|
+
pass
|
|
57
|
+
return pd.Series(results)
|
|
55
58
|
|
|
56
59
|
return fn
|
|
57
60
|
|
|
@@ -63,14 +66,24 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
63
66
|
"""
|
|
64
67
|
|
|
65
68
|
def fn(*args, **kwargs) -> pd.DataFrame:
|
|
66
|
-
results =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
results = []
|
|
70
|
+
columns = []
|
|
71
|
+
for col in self.columns:
|
|
72
|
+
if pd.api.types.is_numeric_dtype(self[col]):
|
|
73
|
+
try:
|
|
74
|
+
result = getattr(self[col], name)(*args, **kwargs)
|
|
75
|
+
results.append(result)
|
|
76
|
+
columns.append(col)
|
|
77
|
+
except Exception:
|
|
78
|
+
# Skip columns that can't be aggregated
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
if results:
|
|
82
|
+
df = pd.DataFrame(results)
|
|
83
|
+
df.index = columns
|
|
84
|
+
return df
|
|
85
|
+
else:
|
|
86
|
+
return pd.DataFrame()
|
|
74
87
|
|
|
75
88
|
return fn
|
|
76
89
|
|
|
@@ -88,24 +101,37 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
88
101
|
|
|
89
102
|
if is_array:
|
|
90
103
|
# Use vector function behavior
|
|
91
|
-
results =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
results = []
|
|
105
|
+
columns = []
|
|
106
|
+
for col in self.columns:
|
|
107
|
+
if pd.api.types.is_numeric_dtype(self[col]):
|
|
108
|
+
try:
|
|
109
|
+
result = getattr(self[col], name)(*args, **kwargs)
|
|
110
|
+
results.append(result)
|
|
111
|
+
columns.append(col)
|
|
112
|
+
except Exception:
|
|
113
|
+
# Skip columns that can't be aggregated
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
if results:
|
|
117
|
+
df = pd.DataFrame(results)
|
|
118
|
+
df.index = columns
|
|
119
|
+
return df
|
|
120
|
+
else:
|
|
121
|
+
return pd.DataFrame()
|
|
99
122
|
else:
|
|
100
123
|
# Use scalar function behavior
|
|
101
|
-
results =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
results = {}
|
|
125
|
+
for col in self.columns:
|
|
126
|
+
if pd.api.types.is_numeric_dtype(self[col]):
|
|
127
|
+
try:
|
|
128
|
+
results[col] = getattr(self[col], name)(
|
|
129
|
+
*args, **kwargs
|
|
130
|
+
)
|
|
131
|
+
except Exception:
|
|
132
|
+
# Skip columns that can't be aggregated
|
|
133
|
+
pass
|
|
134
|
+
return pd.Series(results)
|
|
109
135
|
|
|
110
136
|
return fn
|
|
111
137
|
|
|
@@ -185,6 +211,7 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
185
211
|
if isinstance(weights, str):
|
|
186
212
|
self.weights_col = weights
|
|
187
213
|
self.weights = pd.Series(self[weights], dtype=float)
|
|
214
|
+
self._link_all_weights()
|
|
188
215
|
elif weights is not None:
|
|
189
216
|
if len(weights) != len(self):
|
|
190
217
|
raise ValueError(
|
|
@@ -203,11 +230,24 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
203
230
|
"""Sets the weights for the MicroDataFrame by specifying the name of
|
|
204
231
|
the weight column.
|
|
205
232
|
|
|
206
|
-
|
|
233
|
+
.. deprecated:: 1.0.2
|
|
234
|
+
Use :meth:`set_weights` with a string argument instead.
|
|
235
|
+
This method will be removed in a future version.
|
|
236
|
+
|
|
237
|
+
:param column: Name of the column to use as weights.
|
|
207
238
|
:param preserve_old: If True, keeps the old weights as a column when
|
|
208
239
|
new weights are provided.
|
|
209
|
-
:type
|
|
240
|
+
:type column: str
|
|
210
241
|
"""
|
|
242
|
+
import warnings
|
|
243
|
+
|
|
244
|
+
warnings.warn(
|
|
245
|
+
"set_weight_col is deprecated and will be removed in a "
|
|
246
|
+
"future version. Use set_weights(column_name) instead.",
|
|
247
|
+
DeprecationWarning,
|
|
248
|
+
stacklevel=2,
|
|
249
|
+
)
|
|
250
|
+
|
|
211
251
|
if preserve_old and self.weights_col is not None:
|
|
212
252
|
self["old_" + self.weights_col] = self.weights
|
|
213
253
|
|
|
@@ -215,16 +255,27 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
215
255
|
self.weights_col = column
|
|
216
256
|
self._link_all_weights()
|
|
217
257
|
|
|
258
|
+
def nullify_weights(self) -> None:
|
|
259
|
+
"""Set all weights to 1, effectively making the DataFrame unweighted.
|
|
260
|
+
|
|
261
|
+
This is useful for comparing weighted and unweighted statistics or when
|
|
262
|
+
you want to temporarily ignore weights.
|
|
263
|
+
"""
|
|
264
|
+
self.weights = np.ones(len(self))
|
|
265
|
+
self._link_all_weights()
|
|
266
|
+
|
|
218
267
|
def __getitem__(
|
|
219
268
|
self, key: Union[str, List]
|
|
220
269
|
) -> Union[pd.Series, pd.DataFrame]:
|
|
270
|
+
# Let pandas handle the initial slicing
|
|
221
271
|
result = super().__getitem__(key)
|
|
272
|
+
|
|
273
|
+
# If the result is a DataFrame, re-synchronize the weights
|
|
222
274
|
if isinstance(result, pd.DataFrame):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return MicroDataFrame(result, weights=weights)
|
|
275
|
+
new_weights = self.weights.reindex(result.index)
|
|
276
|
+
return MicroDataFrame(result, weights=new_weights)
|
|
277
|
+
|
|
278
|
+
# Otherwise, the result is a Series or a scalar, so just return it
|
|
228
279
|
return result
|
|
229
280
|
|
|
230
281
|
def catch_series_relapse(self) -> None:
|
|
@@ -301,6 +352,138 @@ class MicroDataFrame(pd.DataFrame):
|
|
|
301
352
|
res = MicroDataFrame(res, weights=self.weights.copy(deep))
|
|
302
353
|
return res
|
|
303
354
|
|
|
355
|
+
def drop(
|
|
356
|
+
self,
|
|
357
|
+
labels=None,
|
|
358
|
+
axis=0,
|
|
359
|
+
index=None,
|
|
360
|
+
columns=None,
|
|
361
|
+
level=None,
|
|
362
|
+
inplace=False,
|
|
363
|
+
errors="raise",
|
|
364
|
+
):
|
|
365
|
+
"""Drop specified labels from rows or columns.
|
|
366
|
+
|
|
367
|
+
This method supports all parameters of pandas DataFrame.drop(),
|
|
368
|
+
including the 'inplace' parameter.
|
|
369
|
+
|
|
370
|
+
:param labels: Index or column labels to drop.
|
|
371
|
+
:param axis: Whether to drop labels from the index (0 or 'index') or
|
|
372
|
+
columns (1 or 'columns').
|
|
373
|
+
:param index: Alternative to specifying axis (labels, axis=0 is
|
|
374
|
+
equivalent to index=labels).
|
|
375
|
+
:param columns: Alternative to specifying axis (labels, axis=1 is
|
|
376
|
+
equivalent to columns=labels).
|
|
377
|
+
:param level: For MultiIndex, level from which the labels will be
|
|
378
|
+
removed.
|
|
379
|
+
:param inplace: If False, return a copy. Otherwise, do operation
|
|
380
|
+
inplace and return None.
|
|
381
|
+
:param errors: If 'ignore', suppress error and only existing labels are
|
|
382
|
+
dropped.
|
|
383
|
+
:return: MicroDataFrame or None if inplace=True.
|
|
384
|
+
"""
|
|
385
|
+
if inplace:
|
|
386
|
+
weights_backup = self.weights.copy()
|
|
387
|
+
# Perform in-place drop on the parent DataFrame
|
|
388
|
+
super().drop(
|
|
389
|
+
labels=labels,
|
|
390
|
+
axis=axis,
|
|
391
|
+
index=index,
|
|
392
|
+
columns=columns,
|
|
393
|
+
level=level,
|
|
394
|
+
inplace=True,
|
|
395
|
+
errors=errors,
|
|
396
|
+
)
|
|
397
|
+
self.weights = weights_backup
|
|
398
|
+
self._link_all_weights()
|
|
399
|
+
return None
|
|
400
|
+
else:
|
|
401
|
+
res = super().drop(
|
|
402
|
+
labels=labels,
|
|
403
|
+
axis=axis,
|
|
404
|
+
index=index,
|
|
405
|
+
columns=columns,
|
|
406
|
+
level=level,
|
|
407
|
+
inplace=False,
|
|
408
|
+
errors=errors,
|
|
409
|
+
)
|
|
410
|
+
return MicroDataFrame(res, weights=self.weights)
|
|
411
|
+
|
|
412
|
+
def merge(
|
|
413
|
+
self,
|
|
414
|
+
right,
|
|
415
|
+
how="inner",
|
|
416
|
+
on=None,
|
|
417
|
+
left_on=None,
|
|
418
|
+
right_on=None,
|
|
419
|
+
left_index=False,
|
|
420
|
+
right_index=False,
|
|
421
|
+
sort=False,
|
|
422
|
+
suffixes=("_x", "_y"),
|
|
423
|
+
copy=True,
|
|
424
|
+
indicator=False,
|
|
425
|
+
validate=None,
|
|
426
|
+
):
|
|
427
|
+
"""Merge DataFrame or named Series objects with a database-style join.
|
|
428
|
+
|
|
429
|
+
This method overrides pandas DataFrame.merge() to return a
|
|
430
|
+
MicroDataFrame.
|
|
431
|
+
|
|
432
|
+
:param right: Object to merge with.
|
|
433
|
+
:param how: Type of merge to be performed.
|
|
434
|
+
:param on: Column or index level names to join on.
|
|
435
|
+
:param left_on: Column or index level names to join on in the left
|
|
436
|
+
DataFrame.
|
|
437
|
+
:param right_on: Column or index level names to join on in the right
|
|
438
|
+
DataFrame.
|
|
439
|
+
:param left_index: Use the index from the left DataFrame as the join
|
|
440
|
+
key(s).
|
|
441
|
+
:param right_index: Use the index from the right DataFrame as the join
|
|
442
|
+
key(s).
|
|
443
|
+
:param sort: Sort the join keys lexicographically in the result
|
|
444
|
+
DataFrame.
|
|
445
|
+
:param suffixes: A length-2 sequence where each element is optionally a
|
|
446
|
+
string indicating the suffix to add to overlapping column names.
|
|
447
|
+
:param copy: If False, avoid copy if possible.
|
|
448
|
+
:param indicator: If True, adds a column to output DataFrame called
|
|
449
|
+
"_merge".
|
|
450
|
+
:param validate: If specified, checks if merge is of specified type.
|
|
451
|
+
:return: MicroDataFrame with merged data.
|
|
452
|
+
"""
|
|
453
|
+
res = super().merge(
|
|
454
|
+
right,
|
|
455
|
+
how=how,
|
|
456
|
+
on=on,
|
|
457
|
+
left_on=left_on,
|
|
458
|
+
right_on=right_on,
|
|
459
|
+
left_index=left_index,
|
|
460
|
+
right_index=right_index,
|
|
461
|
+
sort=sort,
|
|
462
|
+
suffixes=suffixes,
|
|
463
|
+
copy=copy,
|
|
464
|
+
indicator=indicator,
|
|
465
|
+
validate=validate,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# For inner join, both dataframes must have the same weights on
|
|
469
|
+
# matching rows. For now, we'll use the left dataframe's weights.
|
|
470
|
+
# This is a simplification and may need more sophisticated handling
|
|
471
|
+
return MicroDataFrame(res, weights=self.weights)
|
|
472
|
+
|
|
473
|
+
def __getattr__(self, name):
|
|
474
|
+
"""Allow accessing columns as attributes (e.g., df.column_name).
|
|
475
|
+
|
|
476
|
+
This enables more intuitive column access while preserving MicroSeries
|
|
477
|
+
functionality when accessing columns.
|
|
478
|
+
|
|
479
|
+
:param name: Attribute name to access
|
|
480
|
+
:return: MicroSeries if the attribute is a column, otherwise delegates
|
|
481
|
+
to parent
|
|
482
|
+
"""
|
|
483
|
+
if name in self.columns:
|
|
484
|
+
return self[name]
|
|
485
|
+
return super().__getattr__(name)
|
|
486
|
+
|
|
304
487
|
def equals(self, other: "MicroDataFrame") -> bool:
|
|
305
488
|
equal_values = super().equals(other)
|
|
306
489
|
equal_weights = self.weights.equals(other.weights)
|
|
@@ -467,17 +650,29 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
|
|
|
467
650
|
elif isinstance(by, str):
|
|
468
651
|
self.columns.remove(by)
|
|
469
652
|
self.columns.remove("__tmp_weights")
|
|
653
|
+
# Filter to only numeric columns
|
|
654
|
+
self.numeric_columns = [
|
|
655
|
+
col
|
|
656
|
+
for col in self.columns
|
|
657
|
+
if pd.api.types.is_numeric_dtype(self.obj[col])
|
|
658
|
+
]
|
|
470
659
|
for fn_name in MicroSeries.SCALAR_FUNCTIONS:
|
|
471
660
|
|
|
472
661
|
def get_fn(name):
|
|
473
662
|
def fn(*args, **kwargs):
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
663
|
+
results = {}
|
|
664
|
+
for col in self.numeric_columns:
|
|
665
|
+
try:
|
|
666
|
+
results[col] = getattr(getattr(self, col), name)(
|
|
477
667
|
*args, **kwargs
|
|
478
668
|
)
|
|
479
|
-
|
|
480
|
-
|
|
669
|
+
except Exception:
|
|
670
|
+
# Skip columns that can't be aggregated
|
|
671
|
+
pass
|
|
672
|
+
return (
|
|
673
|
+
MicroDataFrame(results)
|
|
674
|
+
if results
|
|
675
|
+
else MicroDataFrame()
|
|
481
676
|
)
|
|
482
677
|
|
|
483
678
|
return fn
|
|
@@ -487,13 +682,19 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
|
|
|
487
682
|
|
|
488
683
|
def get_fn(name) -> Callable:
|
|
489
684
|
def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
685
|
+
results = {}
|
|
686
|
+
for col in self.numeric_columns:
|
|
687
|
+
try:
|
|
688
|
+
results[col] = getattr(getattr(self, col), name)(
|
|
493
689
|
*args, **kwargs
|
|
494
690
|
)
|
|
495
|
-
|
|
496
|
-
|
|
691
|
+
except Exception:
|
|
692
|
+
# Skip columns that can't be aggregated
|
|
693
|
+
pass
|
|
694
|
+
return (
|
|
695
|
+
MicroDataFrame(results)
|
|
696
|
+
if results
|
|
697
|
+
else MicroDataFrame()
|
|
497
698
|
)
|
|
498
699
|
|
|
499
700
|
return fn
|
microdf/microseries.py
CHANGED
|
@@ -66,6 +66,14 @@ class MicroSeries(pd.Series):
|
|
|
66
66
|
|
|
67
67
|
self.weights = pd.Series(weights, dtype=float)
|
|
68
68
|
|
|
69
|
+
def nullify_weights(self) -> None:
|
|
70
|
+
"""Set all weights to 1, effectively making the Series unweighted.
|
|
71
|
+
|
|
72
|
+
This is useful for comparing weighted and unweighted statistics or when
|
|
73
|
+
you want to temporarily ignore weights.
|
|
74
|
+
"""
|
|
75
|
+
self.weights = pd.Series(np.ones(len(self)), dtype=float)
|
|
76
|
+
|
|
69
77
|
@vector_function
|
|
70
78
|
def weight(self) -> pd.Series:
|
|
71
79
|
"""Calculates the weighted value of the MicroSeries.
|
|
@@ -23,6 +23,23 @@ def test_df_init() -> None:
|
|
|
23
23
|
df.set_weight_col("w")
|
|
24
24
|
assert df.a.mean() == np.average(arr, weights=w)
|
|
25
25
|
|
|
26
|
+
# Test set_weights with string (column name)
|
|
27
|
+
df2 = mdf.MicroDataFrame()
|
|
28
|
+
df2["a"] = arr
|
|
29
|
+
df2["w"] = w
|
|
30
|
+
df2.set_weights("w") # Using string column name instead of set_weight_col
|
|
31
|
+
assert df2.a.mean() == np.average(arr, weights=w)
|
|
32
|
+
assert np.array_equal(df2.weights.values, w)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_handles_empty_index() -> None:
|
|
36
|
+
arr = np.array([0, 1, 1])
|
|
37
|
+
w = np.array([3, 0, 9])
|
|
38
|
+
df = mdf.MicroDataFrame({"a": arr}, weights=w)
|
|
39
|
+
|
|
40
|
+
empty_index = pd.Index([])
|
|
41
|
+
df[empty_index] # Implicit assert; checking for ValueError
|
|
42
|
+
|
|
26
43
|
|
|
27
44
|
def test_series_getitem() -> None:
|
|
28
45
|
arr = np.array([0, 1, 1])
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microdf-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Weighted pandas DataFrames and Series for survey microdata
|
|
5
|
-
Author-email: Max Ghenis <max@
|
|
5
|
+
Author-email: Max Ghenis <max@policyengine.org>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
microdf/__init__.py,sha256=ldRvhE8t4cD7qlxQ5yLndMnWEUYpDw4m-jCDV_psXkY,319
|
|
2
|
+
microdf/microdataframe.py,sha256=TU4MPHlGSICibPgGkATJYDpVBQ2NNXnyBAqw3kjteeI,25771
|
|
3
|
+
microdf/microseries.py,sha256=MFBStp1IaNVABaf3_Ap_VwXSC3bA8V7rXD4TEG_091g,22452
|
|
4
|
+
microdf/tests/conftest.py,sha256=u-EMyX1-u_nM-YO0RJYCzYHQDXxUI2WQE6GkyJlErqg,150
|
|
5
|
+
microdf/tests/test_microseries_dataframe.py,sha256=2pm_fLZPbv-ccDKnEyqIiw-_pTZwt9J2lnM5YsZHzx4,9401
|
|
6
|
+
microdf_python-1.0.2.dist-info/licenses/LICENSE,sha256=uPs-ASYnzlldpf2z8jeRgQFeEH3FLhSuX0rw0OKWoDU,1067
|
|
7
|
+
microdf_python-1.0.2.dist-info/METADATA,sha256=sOLX9w2xCHwyNeLcW7BpXu6iiTTeaTmxNdMsdJpmD7s,2486
|
|
8
|
+
microdf_python-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
microdf_python-1.0.2.dist-info/top_level.txt,sha256=T2WFPTygQQMdS3GF8YpZ12DKfMGrspbZ3r7z-e3KfiM,8
|
|
10
|
+
microdf_python-1.0.2.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
microdf/__init__.py,sha256=ldRvhE8t4cD7qlxQ5yLndMnWEUYpDw4m-jCDV_psXkY,319
|
|
2
|
-
microdf/microdataframe.py,sha256=5jYQ76PYjndACJO6hsJCaCL0g0clAsQbWltVcPtJ7yU,18202
|
|
3
|
-
microdf/microseries.py,sha256=URFT9bqry5Q_qezl3ge9lW8FSbQ6qg0XRaFQwN0IkwQ,22130
|
|
4
|
-
microdf/tests/conftest.py,sha256=u-EMyX1-u_nM-YO0RJYCzYHQDXxUI2WQE6GkyJlErqg,150
|
|
5
|
-
microdf/tests/test_microseries_dataframe.py,sha256=QSxdJ0ZfXO1XZgER9LRqDt5SGpVGyME03gefixH6spk,8855
|
|
6
|
-
microdf_python-1.0.0.dist-info/licenses/LICENSE,sha256=uPs-ASYnzlldpf2z8jeRgQFeEH3FLhSuX0rw0OKWoDU,1067
|
|
7
|
-
microdf_python-1.0.0.dist-info/METADATA,sha256=mC0MB2zQwVaqrPXfmh9mIc1KX5lmSO1zS9ilCvZQQg0,2483
|
|
8
|
-
microdf_python-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
microdf_python-1.0.0.dist-info/top_level.txt,sha256=T2WFPTygQQMdS3GF8YpZ12DKfMGrspbZ3r7z-e3KfiM,8
|
|
10
|
-
microdf_python-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|