microdf-python 0.4.6__py3-none-any.whl → 1.0.1__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.
Files changed (39) hide show
  1. microdf/__init__.py +6 -162
  2. microdf/microdataframe.py +503 -0
  3. microdf/{generic.py → microseries.py} +43 -352
  4. microdf/tests/{test_generic.py → test_microseries_dataframe.py} +71 -22
  5. microdf_python-1.0.1.dist-info/METADATA +75 -0
  6. microdf_python-1.0.1.dist-info/RECORD +10 -0
  7. microdf/_optional.py +0 -85
  8. microdf/agg.py +0 -90
  9. microdf/chart_utils.py +0 -37
  10. microdf/charts.py +0 -62
  11. microdf/concat.py +0 -29
  12. microdf/constants.py +0 -40
  13. microdf/custom_taxes.py +0 -174
  14. microdf/income_measures.py +0 -63
  15. microdf/inequality.py +0 -219
  16. microdf/io.py +0 -30
  17. microdf/poverty.py +0 -136
  18. microdf/style.py +0 -54
  19. microdf/tax.py +0 -78
  20. microdf/taxcalc.py +0 -161
  21. microdf/tests/test_compare.py +0 -48
  22. microdf/tests/test_decile_rank.py +0 -91
  23. microdf/tests/test_inequality.py +0 -20
  24. microdf/tests/test_io.py +0 -10
  25. microdf/tests/test_optional_dependency.py +0 -54
  26. microdf/tests/test_poverty.py +0 -73
  27. microdf/tests/test_quantile_chg.py +0 -18
  28. microdf/tests/test_tax.py +0 -54
  29. microdf/tests/test_taxcalc.py +0 -25
  30. microdf/tests/test_utils.py +0 -39
  31. microdf/tests/test_weighted.py +0 -74
  32. microdf/ubi.py +0 -46
  33. microdf/utils.py +0 -73
  34. microdf/weighted.py +0 -235
  35. microdf_python-0.4.6.dist-info/METADATA +0 -54
  36. microdf_python-0.4.6.dist-info/RECORD +0 -37
  37. {microdf_python-0.4.6.dist-info → microdf_python-1.0.1.dist-info}/WHEEL +0 -0
  38. {microdf_python-0.4.6.dist-info → microdf_python-1.0.1.dist-info}/licenses/LICENSE +0 -0
  39. {microdf_python-0.4.6.dist-info → microdf_python-1.0.1.dist-info}/top_level.txt +0 -0
microdf/__init__.py CHANGED
@@ -1,170 +1,14 @@
1
- from .agg import agg, combine_base_reform, pctchg_base_reform
2
- from .chart_utils import currency_format, dollar_format
3
- from .charts import quantile_pct_chg_plot
4
- from .concat import concat
5
- from .constants import (
6
- BENS,
7
- ECI_REMOVE_COLS,
8
- HOUSING_CASH_SHARE,
9
- MCAID_CASH_SHARE,
10
- MCARE_CASH_SHARE,
11
- MED_BENS,
12
- OTHER_CASH_SHARE,
13
- SNAP_CASH_SHARE,
14
- SSI_CASH_SHARE,
15
- TANF_CASH_SHARE,
16
- VET_CASH_SHARE,
17
- WIC_CASH_SHARE,
18
- )
19
- from .custom_taxes import (
20
- CARBON_TAX_INCIDENCE,
21
- FTT_INCIDENCE,
22
- VAT_INCIDENCE,
23
- add_carbon_tax,
24
- add_custom_tax,
25
- add_ftt,
26
- add_vat,
27
- )
28
- from .generic import MicroDataFrame, MicroSeries
29
- from .income_measures import cash_income, market_income, tpc_eci
30
- from .inequality import (
31
- bottom_50_pct_share,
32
- bottom_x_pct_share,
33
- gini,
34
- t10_b50,
35
- top_0_1_pct_share,
36
- top_1_pct_share,
37
- top_10_pct_share,
38
- top_50_pct_share,
39
- top_x_pct_share,
40
- )
41
- from .io import read_stata_zip
42
- from .poverty import (
43
- deep_poverty_gap,
44
- deep_poverty_rate,
45
- fpl,
46
- poverty_gap,
47
- poverty_rate,
48
- squared_poverty_gap,
49
- )
50
- from .style import AXIS_COLOR, DPI, GRID_COLOR, TITLE_COLOR, set_plot_style
51
- from .tax import mtr, tax_from_mtrs
52
- from .taxcalc import (
53
- add_weighted_metrics,
54
- calc_df,
55
- n65,
56
- recalculate,
57
- static_baseline_calc,
58
- )
59
- from .ubi import ubi_or_bens
60
- from .utils import (
61
- cartesian_product,
62
- dedup_list,
63
- flatten,
64
- listify,
65
- ordinal_label,
66
- )
67
- from .weighted import (
68
- add_weighted_quantiles,
69
- quantile_chg,
70
- weight,
71
- weighted_mean,
72
- weighted_median,
73
- weighted_quantile,
74
- weighted_sum,
75
- )
1
+ from .microdataframe import MicroDataFrame, MicroDataFrameGroupBy
2
+ from .microseries import MicroSeries, MicroSeriesGroupBy
76
3
 
77
4
  name = "microdf"
78
5
  __version__ = "0.1.0"
79
6
 
80
7
  __all__ = [
81
- # agg.py
82
- "combine_base_reform",
83
- "pctchg_base_reform",
84
- "agg",
85
- # chart_utils.py
86
- "dollar_format",
87
- "currency_format",
88
- # charts.py
89
- "quantile_pct_chg_plot",
90
- # concat.py
91
- "concat",
92
- # constants.py
93
- "BENS",
94
- "ECI_REMOVE_COLS",
95
- "HOUSING_CASH_SHARE",
96
- "MCAID_CASH_SHARE",
97
- "MCARE_CASH_SHARE",
98
- "MED_BENS",
99
- "OTHER_CASH_SHARE",
100
- "SNAP_CASH_SHARE",
101
- "SSI_CASH_SHARE",
102
- "TANF_CASH_SHARE",
103
- "VET_CASH_SHARE",
104
- "WIC_CASH_SHARE",
105
- # custom_taxes.py
106
- "CARBON_TAX_INCIDENCE",
107
- "FTT_INCIDENCE",
108
- "VAT_INCIDENCE",
109
- "add_custom_tax",
110
- "add_vat",
111
- "add_carbon_tax",
112
- "add_ftt",
113
- # income_measures.py
114
- "cash_income",
115
- "tpc_eci",
116
- "market_income",
117
- # inequality.py
118
- "gini",
119
- "top_x_pct_share",
120
- "bottom_x_pct_share",
121
- "bottom_50_pct_share",
122
- "top_10_pct_share",
123
- "top_1_pct_share",
124
- "top_0_1_pct_share",
125
- "top_50_pct_share",
126
- "t10_b50",
127
- # io.py
128
- "read_stata_zip",
129
- # poverty.py
130
- "fpl",
131
- "poverty_rate",
132
- "deep_poverty_rate",
133
- "poverty_gap",
134
- "squared_poverty_gap",
135
- "deep_poverty_gap",
136
- # style.py
137
- "AXIS_COLOR",
138
- "DPI",
139
- "GRID_COLOR",
140
- "TITLE_COLOR",
141
- "set_plot_style",
142
- # tax.py
143
- "mtr",
144
- "tax_from_mtrs",
145
- # taxcalc.py
146
- "static_baseline_calc",
147
- "add_weighted_metrics",
148
- "n65",
149
- "calc_df",
150
- "recalculate",
151
- # ubi.py
152
- "ubi_or_bens",
153
- # utils.py
154
- "ordinal_label",
155
- "dedup_list",
156
- "listify",
157
- "flatten",
158
- "cartesian_product",
159
- # weighted.py
160
- "weight",
161
- "weighted_sum",
162
- "weighted_mean",
163
- "weighted_quantile",
164
- "weighted_median",
165
- "add_weighted_quantiles",
166
- "quantile_chg",
167
- # generic.py
8
+ # microseries.py
168
9
  "MicroSeries",
10
+ "MicroSeriesGroupBy",
11
+ # microdataframe.py
169
12
  "MicroDataFrame",
13
+ "MicroDataFrameGroupBy",
170
14
  ]
@@ -0,0 +1,503 @@
1
+ import copy
2
+ import logging
3
+ import warnings
4
+ from functools import wraps
5
+ from typing import Callable, List, Optional, Union
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from microdf.microseries import MicroSeries, MicroSeriesGroupBy
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class MicroDataFrame(pd.DataFrame):
16
+ def __init__(self, *args, weights=None, **kwargs):
17
+ """A DataFrame-inheriting class for weighted microdata. Weights can be
18
+ provided at initialisation, or using set_weights or set_weight_col.
19
+
20
+ :param weights: Array of weights.
21
+ :type weights: np.array
22
+ """
23
+ super().__init__(*args, **kwargs)
24
+ self.weights = None
25
+ self.set_weights(weights)
26
+ self._link_all_weights()
27
+ self.override_df_functions()
28
+
29
+ def override_df_functions(self) -> None:
30
+ """Override DataFrame functions to work with weighted operations."""
31
+ for name in MicroSeries.FUNCTIONS:
32
+ if name in MicroSeries.SCALAR_FUNCTIONS:
33
+ setattr(self, name, self._create_scalar_function(name))
34
+ elif name in MicroSeries.VECTOR_FUNCTIONS:
35
+ setattr(self, name, self._create_vector_function(name))
36
+ elif name in MicroSeries.AGNOSTIC_FUNCTIONS:
37
+ setattr(self, name, self._create_agnostic_function(name))
38
+
39
+ def _create_scalar_function(self, name: str) -> Callable:
40
+ """Create a scalar function that returns a Series of results.
41
+
42
+ :param name: Name of the function to create
43
+ :return: Function that applies the operation to all columns
44
+ """
45
+
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
55
+
56
+ return fn
57
+
58
+ def _create_vector_function(self, name: str) -> Callable:
59
+ """Create a vector function that returns a DataFrame of results.
60
+
61
+ :param name: Name of the function to create
62
+ :return: Function that applies the operation to all columns
63
+ """
64
+
65
+ 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
74
+
75
+ return fn
76
+
77
+ def _create_agnostic_function(self, name: str) -> Callable:
78
+ """Create a function that can be either scalar or vector based on
79
+ input.
80
+
81
+ :param name: Name of the function to create
82
+ :return: Function that applies the operation to all columns
83
+ """
84
+
85
+ def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
86
+ # Check if first argument is array-like
87
+ is_array = len(args) > 0 and hasattr(args[0], "__len__")
88
+
89
+ if is_array:
90
+ # 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
99
+ else:
100
+ # 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
109
+
110
+ return fn
111
+
112
+ def get_args_as_micro_series(*kwarg_names: tuple) -> Callable:
113
+ """Decorator for auto-parsing column names into MicroSeries objects. If
114
+ given, kwarg_names limits arguments checked to keyword arguments
115
+ specified.
116
+
117
+ :param arg_names: argument names to restrict to.
118
+ :type arg_names: str
119
+ """
120
+
121
+ def arg_series_decorator(fn) -> Callable:
122
+ @wraps(fn)
123
+ def series_function(
124
+ self, *args, **kwargs
125
+ ) -> Union[pd.Series, pd.DataFrame]:
126
+ new_args = []
127
+ new_kwargs = {}
128
+ if len(kwarg_names) == 0:
129
+ for value in args:
130
+ if isinstance(value, str):
131
+ if value not in self.columns:
132
+ raise Exception("Column not found")
133
+ new_args += [self[value]]
134
+ else:
135
+ new_args += [value]
136
+ for name, value in kwargs.items():
137
+ if isinstance(value, str) and (
138
+ len(kwarg_names) == 0 or name in kwarg_names
139
+ ):
140
+ if value not in self.columns:
141
+ raise Exception("Column not found")
142
+ new_kwargs[name] = self[value]
143
+ else:
144
+ new_kwargs[name] = value
145
+ return fn(self, *new_args, **new_kwargs)
146
+
147
+ return series_function
148
+
149
+ return arg_series_decorator
150
+
151
+ def __setitem__(self, *args, **kwargs) -> None:
152
+ super().__setitem__(*args, **kwargs)
153
+ self._link_all_weights()
154
+
155
+ def _link_weights(self, column) -> None:
156
+ # self[column] = ... triggers __setitem__, which forces pd.Series
157
+ # this workaround avoids that
158
+ self[column].__class__ = MicroSeries
159
+ self[column].set_weights(self.weights)
160
+
161
+ def _link_all_weights(self) -> None:
162
+ if self.weights is None:
163
+ if len(self) > 0:
164
+ self.set_weights(np.ones((len(self))))
165
+ for column in self.columns:
166
+ if column != self.weights_col:
167
+ self._link_weights(column)
168
+
169
+ def set_weights(
170
+ self,
171
+ weights: Union[np.ndarray, str],
172
+ preserve_old: Optional[bool] = False,
173
+ ) -> None:
174
+ """Sets the weights for the MicroDataFrame. If a string is received, it
175
+ will be assumed to be the column name of the weight column.
176
+
177
+ :param weights: Array of weights.
178
+ :param preserve_old: If True, keeps the old weights as a column when
179
+ new weights are provided.
180
+ :type weights: np.array
181
+ """
182
+ if preserve_old and self.weights_col is not None:
183
+ self["old_" + self.weights_col] = self.weights
184
+
185
+ if isinstance(weights, str):
186
+ self.weights_col = weights
187
+ self.weights = pd.Series(self[weights], dtype=float)
188
+ elif weights is not None:
189
+ if len(weights) != len(self):
190
+ raise ValueError(
191
+ f"Length of weights ({len(weights)}) does not match "
192
+ f"length of DataFrame ({len(self)})."
193
+ )
194
+ self.weights_col = None
195
+ with warnings.catch_warnings():
196
+ warnings.filterwarnings("ignore", category=UserWarning)
197
+ self.weights = pd.Series(weights, dtype=float)
198
+ self._link_all_weights()
199
+
200
+ def set_weight_col(
201
+ self, column: str, preserve_old: Optional[bool] = False
202
+ ) -> None:
203
+ """Sets the weights for the MicroDataFrame by specifying the name of
204
+ the weight column.
205
+
206
+ :param weights: Array of weights.
207
+ :param preserve_old: If True, keeps the old weights as a column when
208
+ new weights are provided.
209
+ :type weights: np.array
210
+ """
211
+ if preserve_old and self.weights_col is not None:
212
+ self["old_" + self.weights_col] = self.weights
213
+
214
+ self.weights = np.array(self[column])
215
+ self.weights_col = column
216
+ self._link_all_weights()
217
+
218
+ def __getitem__(
219
+ self, key: Union[str, List]
220
+ ) -> Union[pd.Series, pd.DataFrame]:
221
+ # Let pandas handle the initial slicing
222
+ result = super().__getitem__(key)
223
+
224
+ # If the result is a DataFrame, re-synchronize the weights
225
+ if isinstance(result, pd.DataFrame):
226
+ new_weights = self.weights.reindex(result.index)
227
+ return MicroDataFrame(result, weights=new_weights)
228
+
229
+ # Otherwise, the result is a Series or a scalar, so just return it
230
+ return result
231
+
232
+ def catch_series_relapse(self) -> None:
233
+ for col in self.columns:
234
+ if self[col].__class__ == pd.Series:
235
+ self._link_weights(col)
236
+
237
+ def __setattr__(self, key, value) -> None:
238
+ super().__setattr__(key, value)
239
+ self.catch_series_relapse()
240
+
241
+ def reset_index(
242
+ self,
243
+ level: Optional[int] = None,
244
+ drop: Optional[bool] = False,
245
+ inplace: Optional[bool] = False,
246
+ col_level: Optional[int] = 0,
247
+ col_fill: Optional[str] = "",
248
+ allow_duplicates: Optional[bool] = None,
249
+ names: Optional[List[str]] = None,
250
+ ) -> Union["MicroDataFrame", None]:
251
+ """Reset the index of the MicroDataFrame.
252
+
253
+ This method supports all parameters of pandas DataFrame.reset_index(),
254
+ including the 'inplace' parameter.
255
+
256
+ :param level: Only remove the given levels from the index. Removes all
257
+ levels by default.
258
+ :param drop: Do not try to insert index into dataframe columns. This
259
+ resets the index to the default integer index.
260
+ :param inplace: Modify the DataFrame in place (do not create a new
261
+ object).
262
+ :param col_level: If the columns have multiple levels, determines which
263
+ level the labels are inserted into.
264
+ :param col_fill: If the columns have multiple levels, determines how
265
+ the other levels are named.
266
+ :param allow_duplicates: Allow duplicate column labels to be created.
267
+ :param names: Using the given string, rename the DataFrame column which
268
+ contains the index data.
269
+ :return: MicroDataFrame with reset index or None if inplace=True.
270
+ """
271
+ if inplace:
272
+ weights_backup = self.weights.copy()
273
+ # Perform in-place reset on the parent DataFrame
274
+ super().reset_index(
275
+ level=level,
276
+ drop=drop,
277
+ inplace=True,
278
+ col_level=col_level,
279
+ col_fill=col_fill,
280
+ allow_duplicates=allow_duplicates,
281
+ names=names,
282
+ )
283
+ self.weights = weights_backup
284
+ self._link_all_weights()
285
+ return None
286
+ else:
287
+ res = super().reset_index(
288
+ level=level,
289
+ drop=drop,
290
+ inplace=False,
291
+ col_level=col_level,
292
+ col_fill=col_fill,
293
+ allow_duplicates=allow_duplicates,
294
+ names=names,
295
+ )
296
+ return MicroDataFrame(res, weights=self.weights)
297
+
298
+ def copy(self, deep: Optional[bool] = True) -> "MicroDataFrame":
299
+ res = super().copy(deep)
300
+ # This changes the original columns to Series. Undo it:
301
+ for col in self.columns:
302
+ self[col] = MicroSeries(self[col])
303
+ res = MicroDataFrame(res, weights=self.weights.copy(deep))
304
+ return res
305
+
306
+ def equals(self, other: "MicroDataFrame") -> bool:
307
+ equal_values = super().equals(other)
308
+ equal_weights = self.weights.equals(other.weights)
309
+ return equal_values and equal_weights
310
+
311
+ @get_args_as_micro_series()
312
+ def groupby(
313
+ self, by: Union[str, List], *args, **kwargs
314
+ ) -> "MicroDataFrameGroupBy":
315
+ """Returns a GroupBy object with MicroSeriesGroupBy objects for each
316
+ column.
317
+
318
+ :param by: column to group by
319
+ :type by: Union[str, List]
320
+
321
+ return: DataFrameGroupBy object with columns using weights
322
+ rtype: DataFrameGroupBy
323
+ """
324
+ self["__tmp_weights"] = self.weights
325
+ gb = super().groupby(by, *args, **kwargs)
326
+ weights = copy.deepcopy(gb["__tmp_weights"])
327
+ for col in self.columns: # df.groupby(...)[col]s use weights
328
+ res = gb[col]
329
+ res.__class__ = MicroSeriesGroupBy
330
+ res._init()
331
+ res.weights = weights
332
+ setattr(gb, col, res)
333
+ gb.__class__ = MicroDataFrameGroupBy
334
+ gb._init(by)
335
+ return gb
336
+
337
+ @get_args_as_micro_series()
338
+ def poverty_rate(self, income: str, threshold: str) -> float:
339
+ """Calculate poverty rate, i.e., the population share with income below
340
+ their poverty threshold.
341
+
342
+ :param income: Column indicating income.
343
+ :type income: str
344
+ :param threshold: Column indicating threshold.
345
+ :type threshold: str
346
+ :return: Poverty rate between zero and one.
347
+ :rtype: float
348
+ """
349
+ pov = income < threshold
350
+ return pov.sum() / pov.count()
351
+
352
+ @get_args_as_micro_series()
353
+ def deep_poverty_rate(self, income: str, threshold: str) -> float:
354
+ """Calculate deep poverty rate, i.e., the population share with income
355
+ below half their poverty threshold.
356
+
357
+ :param income: Column indicating income.
358
+ :type income: str
359
+ :param threshold: Column indicating threshold.
360
+ :type threshold: str
361
+ :return: Deep poverty rate between zero and one.
362
+ :rtype: float
363
+ """
364
+ pov = income < (threshold / 2)
365
+ return pov.sum() / pov.count()
366
+
367
+ @get_args_as_micro_series()
368
+ def poverty_gap(self, income: str, threshold: str) -> float:
369
+ """Calculate poverty gap, i.e., the total gap between income and
370
+ poverty thresholds for all people in poverty.
371
+
372
+ :param income: Column indicating income.
373
+ :type income: str
374
+ :param threshold: Column indicating threshold.
375
+ :type threshold: str
376
+ :return: Poverty gap.
377
+ :rtype: float
378
+ """
379
+ gaps = (threshold - income)[threshold > income]
380
+ return gaps.sum()
381
+
382
+ @get_args_as_micro_series()
383
+ def deep_poverty_gap(self, income: str, threshold: str) -> float:
384
+ """Calculate deep poverty gap, i.e., the total gap between income and
385
+ half of poverty thresholds for all people in deep poverty.
386
+
387
+ :param income: Column indicating income.
388
+ :type income: str
389
+ :param threshold: Column indicating threshold.
390
+ :type threshold: str
391
+ :return: Deep poverty gap.
392
+ :rtype: float
393
+ """
394
+ deep_threshold = threshold / 2
395
+ gaps = (deep_threshold - income)[deep_threshold > income]
396
+ return gaps.sum()
397
+
398
+ @get_args_as_micro_series()
399
+ def squared_poverty_gap(self, income: str, threshold: str) -> float:
400
+ """Calculate squared poverty gap, i.e., the total squared gap between
401
+ income and poverty thresholds for all people in poverty. Also known as
402
+ the poverty severity index.
403
+
404
+ :param income: Column indicating income.
405
+ :type income: str
406
+ :param threshold: Column indicating threshold.
407
+ :type threshold: str
408
+ :return: Squared poverty gap.
409
+ :rtype: float
410
+ """
411
+ gaps = (threshold - income)[threshold > income]
412
+ squared_gaps = gaps**2
413
+ return squared_gaps.sum()
414
+
415
+ @get_args_as_micro_series()
416
+ def poverty_count(
417
+ self,
418
+ income: Union[MicroSeries, str],
419
+ threshold: Union[MicroSeries, str],
420
+ ) -> int:
421
+ """Calculates the number of entities with income below a poverty
422
+ threshold.
423
+
424
+ :param income: income array or column name
425
+ :type income: Union[MicroSeries, str]
426
+
427
+ :param threshold: threshold array or column name
428
+ :type threshold: Union[MicroSeries, str]
429
+
430
+ return: number of entities in poverty
431
+ rtype: int
432
+ """
433
+ in_poverty = income < threshold
434
+ return in_poverty.sum()
435
+
436
+ def astype(
437
+ self,
438
+ dtype,
439
+ copy: Optional[bool] = True,
440
+ errors: Optional[str] = "raise",
441
+ ) -> "MicroDataFrame":
442
+ """Convert MicroDataFrame to specified data type while preserving
443
+ weights.
444
+
445
+ :param dtype: Data type to convert to. Can be numpy dtype, Python type,
446
+ or dict.
447
+ :param copy: Whether to make a copy of the data (default True).
448
+ :param errors: How to handle conversion errors (default "raise").
449
+ :return: New MicroDataFrame with converted data types and preserved
450
+ weights.
451
+ """
452
+ converted_df = super().astype(dtype, copy=copy, errors=errors)
453
+ return MicroDataFrame(
454
+ converted_df, weights=self.weights.copy() if copy else self.weights
455
+ )
456
+
457
+ def __repr__(self) -> str:
458
+ df = pd.DataFrame(self)
459
+ df["weight"] = self.weights
460
+ return df[[df.columns[-1]] + list(df.columns[:-1])].__repr__()
461
+
462
+
463
+ class MicroDataFrameGroupBy(pd.core.groupby.generic.DataFrameGroupBy):
464
+ def _init(self, by: Union[str, List]):
465
+ self.columns = list(self.obj.columns)
466
+ if isinstance(by, list):
467
+ for column in by:
468
+ self.columns.remove(column)
469
+ elif isinstance(by, str):
470
+ self.columns.remove(by)
471
+ self.columns.remove("__tmp_weights")
472
+ for fn_name in MicroSeries.SCALAR_FUNCTIONS:
473
+
474
+ def get_fn(name):
475
+ def fn(*args, **kwargs):
476
+ return MicroDataFrame(
477
+ {
478
+ col: getattr(getattr(self, col), name)(
479
+ *args, **kwargs
480
+ )
481
+ for col in self.columns
482
+ }
483
+ )
484
+
485
+ return fn
486
+
487
+ setattr(self, fn_name, get_fn(fn_name))
488
+ for fn_name in MicroSeries.VECTOR_FUNCTIONS:
489
+
490
+ def get_fn(name) -> Callable:
491
+ def fn(*args, **kwargs) -> Union[pd.Series, pd.DataFrame]:
492
+ return MicroDataFrame(
493
+ {
494
+ col: getattr(getattr(self, col), name)(
495
+ *args, **kwargs
496
+ )
497
+ for col in self.columns
498
+ }
499
+ )
500
+
501
+ return fn
502
+
503
+ setattr(self, fn_name, get_fn(fn_name))