microdf-python 1.0.1__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 CHANGED
@@ -44,14 +44,17 @@ class MicroDataFrame(pd.DataFrame):
44
44
  """
45
45
 
46
46
  def fn(*args, **kwargs) -> pd.Series:
47
- results = pd.Series(
48
- [
49
- getattr(self[col], name)(*args, **kwargs)
50
- for col in self.columns
51
- ]
52
- )
53
- results.index = self.columns
54
- return results
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 = pd.DataFrame(
67
- [
68
- getattr(self[col], name)(*args, **kwargs)
69
- for col in self.columns
70
- ]
71
- )
72
- results.index = self.columns
73
- return results
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 = pd.DataFrame(
92
- [
93
- getattr(self[col], name)(*args, **kwargs)
94
- for col in self.columns
95
- ]
96
- )
97
- results.index = self.columns
98
- return results
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 = pd.Series(
102
- [
103
- getattr(self[col], name)(*args, **kwargs)
104
- for col in self.columns
105
- ]
106
- )
107
- results.index = self.columns
108
- return results
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
- :param weights: Array of weights.
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 weights: np.array
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,6 +255,15 @@ 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]:
@@ -303,6 +352,138 @@ class MicroDataFrame(pd.DataFrame):
303
352
  res = MicroDataFrame(res, weights=self.weights.copy(deep))
304
353
  return res
305
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
+
306
487
  def equals(self, other: "MicroDataFrame") -> bool:
307
488
  equal_values = super().equals(other)
308
489
  equal_weights = self.weights.equals(other.weights)
@@ -469,17 +650,29 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
469
650
  elif isinstance(by, str):
470
651
  self.columns.remove(by)
471
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
+ ]
472
659
  for fn_name in MicroSeries.SCALAR_FUNCTIONS:
473
660
 
474
661
  def get_fn(name):
475
662
  def fn(*args, **kwargs):
476
- return MicroDataFrame(
477
- {
478
- col: getattr(getattr(self, col), name)(
663
+ results = {}
664
+ for col in self.numeric_columns:
665
+ try:
666
+ results[col] = getattr(getattr(self, col), name)(
479
667
  *args, **kwargs
480
668
  )
481
- for col in self.columns
482
- }
669
+ except Exception:
670
+ # Skip columns that can't be aggregated
671
+ pass
672
+ return (
673
+ MicroDataFrame(results)
674
+ if results
675
+ else MicroDataFrame()
483
676
  )
484
677
 
485
678
  return fn
@@ -489,13 +682,19 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
489
682
 
490
683
  def get_fn(name) -> Callable:
491
684
  def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
492
- return MicroDataFrame(
493
- {
494
- col: getattr(getattr(self, col), name)(
685
+ results = {}
686
+ for col in self.numeric_columns:
687
+ try:
688
+ results[col] = getattr(getattr(self, col), name)(
495
689
  *args, **kwargs
496
690
  )
497
- for col in self.columns
498
- }
691
+ except Exception:
692
+ # Skip columns that can't be aggregated
693
+ pass
694
+ return (
695
+ MicroDataFrame(results)
696
+ if results
697
+ else MicroDataFrame()
499
698
  )
500
699
 
501
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,14 @@ 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
+
26
34
 
27
35
  def test_handles_empty_index() -> None:
28
36
  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.1
3
+ Version: 1.0.2
4
4
  Summary: Weighted pandas DataFrames and Series for survey microdata
5
- Author-email: Max Ghenis <max@ubicenter.org>
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=vM-vddwp50JQv7OoyoCmC9AbkTUT0dvEnCokSzvpe4s,18329
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=UwcaSCo-1wVjp9TtUgG632ziQ8yOJvr6T7PQipgUhAs,9102
6
- microdf_python-1.0.1.dist-info/licenses/LICENSE,sha256=uPs-ASYnzlldpf2z8jeRgQFeEH3FLhSuX0rw0OKWoDU,1067
7
- microdf_python-1.0.1.dist-info/METADATA,sha256=so2yCume7-DDPKDnx25w-q0TWfQjGRdB47ydO-J5-Qs,2483
8
- microdf_python-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- microdf_python-1.0.1.dist-info/top_level.txt,sha256=T2WFPTygQQMdS3GF8YpZ12DKfMGrspbZ3r7z-e3KfiM,8
10
- microdf_python-1.0.1.dist-info/RECORD,,