microdf-python 0.4.4__py3-none-any.whl → 0.4.6__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/generic.py CHANGED
@@ -1,15 +1,19 @@
1
- from typing import Callable, Union
2
- from functools import wraps
3
- import warnings
4
1
  import copy
2
+ import logging
3
+ import warnings
4
+ from functools import wraps
5
+ from typing import Callable, List, Optional, Union
6
+
5
7
  import numpy as np
6
8
  import pandas as pd
7
9
 
10
+ logger = logging.getLogger(__name__)
11
+
8
12
 
9
13
  class MicroSeries(pd.Series):
10
14
  def __init__(self, *args, weights: np.array = None, **kwargs):
11
- """A Series-inheriting class for weighted microdata.
12
- Weights can be provided at initialisation, or using set_weights.
15
+ """A Series-inheriting class for weighted microdata. Weights can be
16
+ provided at initialisation, or using set_weights.
13
17
 
14
18
  :param weights: Array of weights.
15
19
  :type weights: np.array
@@ -76,7 +80,7 @@ class MicroSeries(pd.Series):
76
80
 
77
81
  @scalar_function
78
82
  def mean(self) -> float:
79
- """Calculates the weighted mean of the MicroSeries
83
+ """Calculates the weighted mean of the MicroSeries.
80
84
 
81
85
  :returns: The weighted mean.
82
86
  :rtype: float
@@ -121,7 +125,7 @@ class MicroSeries(pd.Series):
121
125
  return self.quantile(0.5)
122
126
 
123
127
  @scalar_function
124
- def gini(self, negatives: str = None) -> float:
128
+ def gini(self, negatives: Optional[str] = None) -> float:
125
129
  """Calculates Gini index.
126
130
 
127
131
  :param negatives: An optional string indicating how to treat negative
@@ -172,7 +176,7 @@ class MicroSeries(pd.Series):
172
176
  return top_x_pct_sum / total_sum
173
177
 
174
178
  @scalar_function
175
- def bottom_x_pct_share(self, bottom_x_pct) -> float:
179
+ def bottom_x_pct_share(self, bottom_x_pct: float) -> float:
176
180
  """Calculates bottom x% share.
177
181
 
178
182
  :param bottom_x_pct: Decimal between 0 and 1 of the top %, e.g. 0.1,
@@ -232,9 +236,8 @@ class MicroSeries(pd.Series):
232
236
  def t10_b50(self) -> float:
233
237
  """Calculates ratio between the top 10% and bottom 50% shares.
234
238
 
235
- :returns: The weighted share held by the top 10% divided by
236
- the weighted share held by the bottom 50%.
237
-
239
+ :returns: The weighted share held by the top 10% divided by the
240
+ weighted share held by the bottom 50%.
238
241
  """
239
242
  t10 = self.top_10_pct_share()
240
243
  b50 = self.bottom_50_pct_share()
@@ -242,181 +245,274 @@ class MicroSeries(pd.Series):
242
245
 
243
246
  @vector_function
244
247
  def cumsum(self) -> pd.Series:
248
+ logger.warning(
249
+ "cumsum() returns cumulative sums of weighted values as a regular "
250
+ "pandas Series. The original weights have already been applied "
251
+ "and cannot be reused with the cumulative results."
252
+ )
245
253
  return pd.Series(self * self.weights).cumsum()
246
254
 
247
255
  @vector_function
248
- def rank(self, pct=False) -> pd.Series:
256
+ def rank(self, pct: Optional[bool] = False) -> pd.Series:
257
+ weights_sum = self.weights.values.sum()
258
+ if weights_sum == 0:
259
+ raise ZeroDivisionError(
260
+ "Cannot calculate rank with zero total weight. "
261
+ "All weights in the MicroSeries are zero, which would result "
262
+ "in division by zero."
263
+ )
264
+
249
265
  order = np.argsort(self.values)
250
266
  inverse_order = np.argsort(order)
251
267
  ranks = np.array(self.weights.values)[order].cumsum()[inverse_order]
252
268
  if pct:
253
- ranks /= self.weights.values.sum()
254
- np.where(ranks > 1.0, 1.0, ranks)
255
- return pd.Series(ranks, index=self.index)
269
+ ranks /= weights_sum
270
+ ranks = np.where(ranks > 1.0, 1.0, ranks)
271
+ return MicroSeries(ranks, index=self.index, weights=self.weights)
256
272
 
257
273
  @vector_function
258
- def decile_rank(self):
259
- return MicroSeries(np.minimum(np.ceil(self.rank(pct=True) * 10), 10))
274
+ def decile_rank(self, negatives_in_zero: Optional[bool] = False):
275
+ """Calculate decile ranks (1-10) with optional zero decile for
276
+ negatives.
277
+
278
+ :param negatives_in_zero: If True, negative values are assigned to
279
+ decile 0. If False (default), all values are ranked 1-10.
280
+ :type negatives_in_zero: bool
281
+ :returns: MicroSeries with decile ranks
282
+ :rtype: MicroSeries
283
+ """
284
+ if negatives_in_zero:
285
+ negative_mask = self < 0
286
+ if negative_mask.any():
287
+ non_negative_values = self[~negative_mask]
288
+ if len(non_negative_values) > 0:
289
+ non_neg_ranks = non_negative_values.rank(pct=True)
290
+ deciles = np.minimum(np.ceil(non_neg_ranks * 10), 10)
291
+ else:
292
+ deciles = np.array([])
293
+
294
+ result = np.zeros(len(self))
295
+ result[negative_mask] = 0
296
+ if len(deciles) > 0:
297
+ result[~negative_mask] = deciles
298
+
299
+ return MicroSeries(result, weights=self.weights)
300
+
301
+ # Default behavior: rank all values 1-10
302
+ return MicroSeries(
303
+ np.minimum(np.ceil(self.rank(pct=True) * 10), 10),
304
+ weights=self.weights,
305
+ )
260
306
 
261
307
  @vector_function
262
- def quintile_rank(self):
263
- return MicroSeries(np.minimum(np.ceil(self.rank(pct=True) * 5), 5))
308
+ def quintile_rank(self) -> "MicroSeries":
309
+ return MicroSeries(
310
+ np.minimum(np.ceil(self.rank(pct=True) * 5), 5),
311
+ weights=self.weights,
312
+ )
264
313
 
265
314
  @vector_function
266
- def quartile_rank(self):
267
- return MicroSeries(np.minimum(np.ceil(self.rank(pct=True) * 4), 4))
315
+ def quartile_rank(self) -> "MicroSeries":
316
+ return MicroSeries(
317
+ np.minimum(np.ceil(self.rank(pct=True) * 4), 4),
318
+ weights=self.weights,
319
+ )
268
320
 
269
321
  @vector_function
270
- def percentile_rank(self):
271
- return MicroSeries(np.minimum(np.ceil(self.rank(pct=True) * 100), 100))
322
+ def percentile_rank(self) -> "MicroSeries":
323
+ return MicroSeries(
324
+ np.minimum(np.ceil(self.rank(pct=True) * 100), 100),
325
+ weights=self.weights,
326
+ )
272
327
 
273
- def groupby(self, *args, **kwargs):
328
+ def groupby(self, *args, **kwargs) -> "MicroSeriesGroupBy":
274
329
  gb = super().groupby(*args, **kwargs)
275
330
  gb.__class__ = MicroSeriesGroupBy
276
331
  gb._init()
277
332
  gb.weights = pd.Series(self.weights).groupby(*args, **kwargs)
278
333
  return gb
279
334
 
280
- def copy(self, deep=True):
335
+ def copy(self, deep: Optional[bool] = True):
281
336
  res = super().copy(deep)
282
337
  res = MicroSeries(res, weights=self.weights.copy(deep))
283
338
  return res
284
339
 
285
- def equals(self, other) -> bool:
340
+ def clip(
341
+ self,
342
+ lower: Optional[float] = None,
343
+ upper: Optional[float] = None,
344
+ axis: Optional[int] = None,
345
+ inplace: Optional[bool] = False,
346
+ *args,
347
+ **kwargs,
348
+ ) -> "MicroSeries":
349
+ res = super().clip(
350
+ lower=lower,
351
+ upper=upper,
352
+ axis=axis,
353
+ inplace=inplace,
354
+ *args,
355
+ **kwargs,
356
+ )
357
+ if not inplace:
358
+ return MicroSeries(res, weights=self.weights)
359
+ return self
360
+
361
+ def round(
362
+ self, decimals: Optional[int] = 0, *args, **kwargs
363
+ ) -> "MicroSeries":
364
+ res = super().round(decimals=decimals, *args, **kwargs)
365
+ return MicroSeries(res, weights=self.weights)
366
+
367
+ def equals(self, other: "MicroSeries") -> bool:
286
368
  equal_values = super().equals(other)
287
369
  equal_weights = self.weights.equals(other.weights)
288
370
  return equal_values and equal_weights
289
371
 
290
- def __getitem__(self, key):
372
+ def __getitem__(
373
+ self, key: Union[str, int, slice, List, np.ndarray]
374
+ ) -> Union["MicroSeries", pd.Series]:
291
375
  result = super().__getitem__(key)
292
376
  if isinstance(result, pd.Series):
293
377
  weights = self.weights.__getitem__(key)
294
378
  return MicroSeries(result, weights=weights)
295
379
  return result
296
380
 
297
- def __getattr__(self, name):
381
+ def __getattr__(self, name: str) -> "MicroSeries":
298
382
  return MicroSeries(super().__getattr__(name), weights=self.weights)
299
383
 
300
384
  # operators
301
385
 
302
- def __add__(self, other):
386
+ def __add__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
303
387
  return MicroSeries(super().__add__(other), weights=self.weights)
304
388
 
305
- def __sub__(self, other):
389
+ def __sub__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
306
390
  return MicroSeries(super().__sub__(other), weights=self.weights)
307
391
 
308
- def __mul__(self, other):
392
+ def __mul__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
309
393
  return MicroSeries(super().__mul__(other), weights=self.weights)
310
394
 
311
- def __floordiv__(self, other):
395
+ def __floordiv__(
396
+ self, other: Union[int, float, pd.Series]
397
+ ) -> "MicroSeries":
312
398
  return MicroSeries(super().__floordiv__(other), weights=self.weights)
313
399
 
314
- def __truediv__(self, other):
400
+ def __truediv__(
401
+ self, other: Union[int, float, pd.Series]
402
+ ) -> "MicroSeries":
315
403
  return MicroSeries(super().__truediv__(other), weights=self.weights)
316
404
 
317
- def __mod__(self, other):
405
+ def __mod__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
318
406
  return MicroSeries(super().__mod__(other), weights=self.weights)
319
407
 
320
- def __pow__(self, other):
408
+ def __pow__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
321
409
  return MicroSeries(super().__pow__(other), weights=self.weights)
322
410
 
323
- def __xor__(self, other):
411
+ def __xor__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
324
412
  return MicroSeries(super().__xor__(other), weights=self.weights)
325
413
 
326
- def __and__(self, other):
414
+ def __and__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
327
415
  return MicroSeries(super().__and__(other), weights=self.weights)
328
416
 
329
- def __or__(self, other):
417
+ def __or__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
330
418
  return MicroSeries(super().__or__(other), weights=self.weights)
331
419
 
332
- def __invert__(self):
420
+ def __invert__(self) -> "MicroSeries":
333
421
  return MicroSeries(super().__invert__(), weights=self.weights)
334
422
 
335
- def __radd__(self, other):
423
+ def __radd__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
336
424
  return MicroSeries(super().__radd__(other), weights=self.weights)
337
425
 
338
- def __rsub__(self, other):
426
+ def __rsub__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
339
427
  return MicroSeries(super().__rsub__(other), weights=self.weights)
340
428
 
341
- def __rmul__(self, other):
429
+ def __rmul__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
342
430
  return MicroSeries(super().__rmul__(other), weights=self.weights)
343
431
 
344
- def __rfloordiv__(self, other):
432
+ def __rfloordiv__(
433
+ self, other: Union[int, float, pd.Series]
434
+ ) -> "MicroSeries":
345
435
  return MicroSeries(super().__rfloordiv__(other), weights=self.weights)
346
436
 
347
- def __rtruediv__(self, other):
437
+ def __rtruediv__(
438
+ self, other: Union[int, float, pd.Series]
439
+ ) -> "MicroSeries":
348
440
  return MicroSeries(super().__rtruediv__(other), weights=self.weights)
349
441
 
350
- def __rmod__(self, other):
442
+ def __rmod__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
351
443
  return MicroSeries(super().__rmod__(other), weights=self.weights)
352
444
 
353
- def __rpow__(self, other):
445
+ def __rpow__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
354
446
  return MicroSeries(super().__rpow__(other), weights=self.weights)
355
447
 
356
- def __rand__(self, other):
448
+ def __rand__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
357
449
  return MicroSeries(super().__rand__(other), weights=self.weights)
358
450
 
359
- def __ror__(self, other):
451
+ def __ror__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
360
452
  return MicroSeries(super().__ror__(other), weights=self.weights)
361
453
 
362
- def __rxor__(self, other):
454
+ def __rxor__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
363
455
  return MicroSeries(super().__rxor__(other), weights=self.weights)
364
456
 
365
457
  # comparators
366
458
 
367
- def __lt__(self, other):
459
+ def __lt__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
368
460
  return MicroSeries(super().__lt__(other), weights=self.weights)
369
461
 
370
- def __le__(self, other):
462
+ def __le__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
371
463
  return MicroSeries(super().__le__(other), weights=self.weights)
372
464
 
373
- def __eq__(self, other):
465
+ def __eq__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
374
466
  return MicroSeries(super().__eq__(other), weights=self.weights)
375
467
 
376
- def __ne__(self, other):
468
+ def __ne__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
377
469
  return MicroSeries(super().__ne__(other), weights=self.weights)
378
470
 
379
- def __ge__(self, other):
471
+ def __ge__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
380
472
  return MicroSeries(super().__ge__(other), weights=self.weights)
381
473
 
382
- def __gt__(self, other):
474
+ def __gt__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
383
475
  return MicroSeries(super().__gt__(other), weights=self.weights)
384
476
 
385
477
  # assignment operators
386
478
 
387
- def __iadd__(self, other):
479
+ def __iadd__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
388
480
  return MicroSeries(super().__iadd__(other), weights=self.weights)
389
481
 
390
- def __isub__(self, other):
482
+ def __isub__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
391
483
  return MicroSeries(super().__isub__(other), weights=self.weights)
392
484
 
393
- def __imul__(self, other):
485
+ def __imul__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
394
486
  return MicroSeries(super().__imul__(other), weights=self.weights)
395
487
 
396
- def __ifloordiv__(self, other):
488
+ def __ifloordiv__(
489
+ self, other: Union[int, float, pd.Series]
490
+ ) -> "MicroSeries":
397
491
  return MicroSeries(super().__ifloordiv__(other), weights=self.weights)
398
492
 
399
- def __idiv__(self, other):
493
+ def __idiv__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
400
494
  return MicroSeries(super().__idiv__(other), weights=self.weights)
401
495
 
402
- def __itruediv__(self, other):
496
+ def __itruediv__(
497
+ self, other: Union[int, float, pd.Series]
498
+ ) -> "MicroSeries":
403
499
  return MicroSeries(super().__itruediv__(other), weights=self.weights)
404
500
 
405
- def __imod__(self, other):
501
+ def __imod__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
406
502
  return MicroSeries(super().__imod__(other), weights=self.weights)
407
503
 
408
- def __ipow__(self, other):
504
+ def __ipow__(self, other: Union[int, float, pd.Series]) -> "MicroSeries":
409
505
  return MicroSeries(super().__ipow__(other), weights=self.weights)
410
506
 
411
507
  # other
412
508
 
413
- def __neg__(self, other):
414
- return MicroSeries(super().__neg__(other), weights=self.weights)
509
+ def __neg__(self) -> "MicroSeries":
510
+ return MicroSeries(super().__neg__(), weights=self.weights)
415
511
 
416
- def __pos__(self, other):
417
- return MicroSeries(super().__pos__(other), weights=self.weights)
512
+ def __pos__(self) -> "MicroSeries":
513
+ return MicroSeries(super().__pos__(), weights=self.weights)
418
514
 
419
- def __repr__(self):
515
+ def __repr__(self) -> str:
420
516
  return pd.DataFrame(
421
517
  dict(value=self.values, weight=self.weights.values)
422
518
  ).__repr__()
@@ -456,7 +552,9 @@ class MicroSeriesGroupBy(pd.core.groupby.generic.SeriesGroupBy):
456
552
  fn = getattr(MicroSeries, name)
457
553
 
458
554
  @wraps(fn)
459
- def _weighted_agg_fn(*args, **kwargs):
555
+ def _weighted_agg_fn(
556
+ *args, **kwargs
557
+ ) -> Union[pd.Series, pd.DataFrame]:
460
558
  arrays = self.apply(np.array)
461
559
  weights = self.weights.apply(np.array)
462
560
  df = pd.DataFrame(dict(a=arrays, w=weights))
@@ -489,7 +587,7 @@ class MicroSeriesGroupBy(pd.core.groupby.generic.SeriesGroupBy):
489
587
 
490
588
 
491
589
  class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
492
- def _init(self, by: Union[str, list]):
590
+ def _init(self, by: Union[str, List]):
493
591
  self.columns = list(self.obj.columns)
494
592
  if isinstance(by, list):
495
593
  for column in by:
@@ -515,8 +613,8 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
515
613
  setattr(self, fn_name, get_fn(fn_name))
516
614
  for fn_name in MicroSeries.VECTOR_FUNCTIONS:
517
615
 
518
- def get_fn(name):
519
- def fn(*args, **kwargs):
616
+ def get_fn(name) -> Callable:
617
+ def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
520
618
  return MicroDataFrame(
521
619
  {
522
620
  col: getattr(getattr(self, col), name)(
@@ -533,9 +631,8 @@ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
533
631
 
534
632
  class MicroDataFrame(pd.DataFrame):
535
633
  def __init__(self, *args, weights=None, **kwargs):
536
- """A DataFrame-inheriting class for weighted microdata.
537
- Weights can be provided at initialisation, or using set_weights or
538
- set_weight_col.
634
+ """A DataFrame-inheriting class for weighted microdata. Weights can be
635
+ provided at initialisation, or using set_weights or set_weight_col.
539
636
 
540
637
  :param weights: Array of weights.
541
638
  :type weights: np.array
@@ -546,11 +643,11 @@ class MicroDataFrame(pd.DataFrame):
546
643
  self._link_all_weights()
547
644
  self.override_df_functions()
548
645
 
549
- def override_df_functions(self):
646
+ def override_df_functions(self) -> None:
550
647
  for name in MicroSeries.FUNCTIONS:
551
648
 
552
- def get_fn(name):
553
- def fn(*args, **kwargs):
649
+ def get_fn(name) -> Callable:
650
+ def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
554
651
  is_array = len(args) > 0 and hasattr(args[0], "__len__")
555
652
  if (
556
653
  name in MicroSeries.SCALAR_FUNCTIONS
@@ -584,17 +681,19 @@ class MicroDataFrame(pd.DataFrame):
584
681
  setattr(self, name, get_fn(name))
585
682
 
586
683
  def get_args_as_micro_series(*kwarg_names: tuple) -> Callable:
587
- """Decorator for auto-parsing column names into MicroSeries objects.
588
- If given, kwarg_names limits arguments checked to keyword arguments
684
+ """Decorator for auto-parsing column names into MicroSeries objects. If
685
+ given, kwarg_names limits arguments checked to keyword arguments
589
686
  specified.
590
687
 
591
688
  :param arg_names: argument names to restrict to.
592
689
  :type arg_names: str
593
690
  """
594
691
 
595
- def arg_series_decorator(fn):
692
+ def arg_series_decorator(fn) -> Callable:
596
693
  @wraps(fn)
597
- def series_function(self, *args, **kwargs):
694
+ def series_function(
695
+ self, *args, **kwargs
696
+ ) -> Union[pd.Series, pd.DataFrame]:
598
697
  new_args = []
599
698
  new_kwargs = {}
600
699
  if len(kwarg_names) == 0:
@@ -620,26 +719,26 @@ class MicroDataFrame(pd.DataFrame):
620
719
 
621
720
  return arg_series_decorator
622
721
 
623
- def __setitem__(self, *args, **kwargs):
722
+ def __setitem__(self, *args, **kwargs) -> None:
624
723
  super().__setitem__(*args, **kwargs)
625
724
  self._link_all_weights()
626
725
 
627
- def _link_weights(self, column):
726
+ def _link_weights(self, column) -> None:
628
727
  # self[column] = ... triggers __setitem__, which forces pd.Series
629
728
  # this workaround avoids that
630
729
  self[column].__class__ = MicroSeries
631
730
  self[column].set_weights(self.weights)
632
731
 
633
- def _link_all_weights(self):
732
+ def _link_all_weights(self) -> None:
634
733
  if self.weights is None:
635
734
  self.set_weights(np.ones((len(self))))
636
735
  for column in self.columns:
637
736
  if column != self.weights_col:
638
737
  self._link_weights(column)
639
738
 
640
- def set_weights(self, weights) -> None:
641
- """Sets the weights for the MicroDataFrame. If a string is received,
642
- it will be assumed to be the column name of the weight column.
739
+ def set_weights(self, weights: np.ndarray) -> None:
740
+ """Sets the weights for the MicroDataFrame. If a string is received, it
741
+ will be assumed to be the column name of the weight column.
643
742
 
644
743
  :param weights: Array of weights.
645
744
  :type weights: np.array
@@ -665,7 +764,9 @@ class MicroDataFrame(pd.DataFrame):
665
764
  self.weight_col = column
666
765
  self._link_all_weights()
667
766
 
668
- def __getitem__(self, key):
767
+ def __getitem__(
768
+ self, key: Union[str, List]
769
+ ) -> Union[pd.Series, pd.DataFrame]:
669
770
  result = super().__getitem__(key)
670
771
  if isinstance(result, pd.DataFrame):
671
772
  try:
@@ -675,21 +776,21 @@ class MicroDataFrame(pd.DataFrame):
675
776
  return MicroDataFrame(result, weights=weights)
676
777
  return result
677
778
 
678
- def catch_series_relapse(self):
779
+ def catch_series_relapse(self) -> None:
679
780
  for col in self.columns:
680
781
  if self[col].__class__ == pd.Series:
681
782
  self._link_weights(col)
682
783
 
683
- def __setattr__(self, key, value):
784
+ def __setattr__(self, key, value) -> None:
684
785
  super().__setattr__(key, value)
685
786
  self.catch_series_relapse()
686
787
 
687
- def reset_index(self):
788
+ def reset_index(self) -> "MicroDataFrame":
688
789
  res = super().reset_index()
689
790
  res = MicroDataFrame(res, weights=self.weights)
690
791
  return res
691
792
 
692
- def copy(self, deep=True):
793
+ def copy(self, deep: Optional[bool] = True) -> "MicroDataFrame":
693
794
  res = super().copy(deep)
694
795
  # This changes the original columns to Series. Undo it:
695
796
  for col in self.columns:
@@ -697,19 +798,20 @@ class MicroDataFrame(pd.DataFrame):
697
798
  res = MicroDataFrame(res, weights=self.weights.copy(deep))
698
799
  return res
699
800
 
700
- def equals(self, other) -> bool:
801
+ def equals(self, other: "MicroDataFrame") -> bool:
701
802
  equal_values = super().equals(other)
702
803
  equal_weights = self.weights.equals(other.weights)
703
804
  return equal_values and equal_weights
704
805
 
705
806
  @get_args_as_micro_series()
706
- def groupby(self, by: Union[str, list], *args, **kwargs):
707
- """
708
- Returns a GroupBy object with MicroSeriesGroupBy objects for
709
- each column
807
+ def groupby(
808
+ self, by: Union[str, List], *args, **kwargs
809
+ ) -> "MicroDataFrameGroupBy":
810
+ """Returns a GroupBy object with MicroSeriesGroupBy objects for each
811
+ column.
710
812
 
711
813
  :param by: column to group by
712
- :type by: Union[str, list]
814
+ :type by: Union[str, List]
713
815
 
714
816
  return: DataFrameGroupBy object with columns using weights
715
817
  rtype: DataFrameGroupBy
@@ -729,8 +831,8 @@ class MicroDataFrame(pd.DataFrame):
729
831
 
730
832
  @get_args_as_micro_series()
731
833
  def poverty_rate(self, income: str, threshold: str) -> float:
732
- """Calculate poverty rate, i.e., the population share with income
733
- below their poverty threshold.
834
+ """Calculate poverty rate, i.e., the population share with income below
835
+ their poverty threshold.
734
836
 
735
837
  :param income: Column indicating income.
736
838
  :type income: str
@@ -791,8 +893,8 @@ class MicroDataFrame(pd.DataFrame):
791
893
  @get_args_as_micro_series()
792
894
  def squared_poverty_gap(self, income: str, threshold: str) -> float:
793
895
  """Calculate squared poverty gap, i.e., the total squared gap between
794
- income and poverty thresholds for all people in poverty.
795
- Also known as the poverty severity index.
896
+ income and poverty thresholds for all people in poverty. Also known as
897
+ the poverty severity index.
796
898
 
797
899
  :param income: Column indicating income.
798
900
  :type income: str
@@ -802,7 +904,7 @@ class MicroDataFrame(pd.DataFrame):
802
904
  :rtype: float
803
905
  """
804
906
  gaps = (threshold - income)[threshold > income]
805
- squared_gaps = gaps ** 2
907
+ squared_gaps = gaps**2
806
908
  return squared_gaps.sum()
807
909
 
808
910
  @get_args_as_micro_series()
@@ -811,8 +913,7 @@ class MicroDataFrame(pd.DataFrame):
811
913
  income: Union[MicroSeries, str],
812
914
  threshold: Union[MicroSeries, str],
813
915
  ) -> int:
814
- """
815
- Calculates the number of entities with income below a poverty
916
+ """Calculates the number of entities with income below a poverty
816
917
  threshold.
817
918
 
818
919
  :param income: income array or column name
@@ -827,7 +928,7 @@ class MicroDataFrame(pd.DataFrame):
827
928
  in_poverty = income < threshold
828
929
  return in_poverty.sum()
829
930
 
830
- def __repr__(self):
931
+ def __repr__(self) -> str:
831
932
  df = pd.DataFrame(self)
832
933
  df["weight"] = self.weights
833
934
  return df[[df.columns[-1]] + list(df.columns[:-1])].__repr__()
@@ -1,3 +1,5 @@
1
+ import pandas as pd
2
+
1
3
  import microdf as mdf
2
4
 
3
5
  # See
@@ -5,7 +7,7 @@ import microdf as mdf
5
7
  # for a comparison of income measures used here.
6
8
 
7
9
 
8
- def cash_income(df):
10
+ def cash_income(df: pd.DataFrame) -> pd.Series:
9
11
  """Calculates income after taxes and cash transfers.
10
12
 
11
13
  Defined as aftertax_income minus non-cash benefits.
@@ -22,7 +24,6 @@ def cash_income(df):
22
24
  * vet_ben
23
25
  * wic_ben
24
26
  :returns: A pandas Series with the cash income for each row in df.
25
-
26
27
  """
27
28
  return (
28
29
  df.aftertax_income
@@ -38,27 +39,25 @@ def cash_income(df):
38
39
  )
39
40
 
40
41
 
41
- def tpc_eci(df):
42
+ def tpc_eci(df: pd.DataFrame) -> pd.Series:
42
43
  """Approximates Tax Policy Center's Expanded Cash Income measure.
43
44
 
44
45
  Subtracts WIC, housing assistance, veteran's benefits, Medicare, and
45
- Medicaid from expanded_income. ECI adds income measures not modeled in
46
- Tax-Calculator, so these are ignored and will create a discrepancy
47
- compared to TPC's ECI.
46
+ Medicaid from expanded_income. ECI adds income measures not modeled in Tax-
47
+ Calculator, so these are ignored and will create a discrepancy compared to
48
+ TPC's ECI.
48
49
 
49
50
  :param df: DataFrame with columns from Tax-Calculator.
50
51
  :returns: pandas Series with TPC's ECI.
51
-
52
52
  """
53
53
  return df.expanded_income - df[mdf.ECI_REMOVE_COLS].sum(axis=1)
54
54
 
55
55
 
56
- def market_income(df):
57
- """Approximates CBO's market income concept, which is income
58
- before social insurance, means-tested transfers, and taxes.
56
+ def market_income(df: pd.DataFrame) -> pd.Series:
57
+ """Approximates CBO's market income concept, which is income before social
58
+ insurance, means-tested transfers, and taxes.
59
59
 
60
60
  :param df: DataFrame with expanded_income and benefits.
61
61
  :returns: pandas Series of the same length as df.
62
-
63
62
  """
64
63
  return df.expanded_income - df[mdf.BENS].sum(axis=1)