whyvalue 0.1.0__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.
@@ -0,0 +1,1032 @@
1
+ import uuid
2
+
3
+ import pandas as pd
4
+ from pandas.core.groupby.generic import DataFrameGroupBy, SeriesGroupBy
5
+
6
+ from ..history import history
7
+
8
+
9
+ _original_add = None
10
+ _original_radd = None
11
+ _original_mul = None
12
+ _original_rmul = None
13
+ _original_sub = None
14
+ _original_rsub = None
15
+ _original_truediv = None
16
+ _original_rtruediv = None
17
+
18
+ _original_gt = None
19
+ _original_ge = None
20
+ _original_lt = None
21
+ _original_le = None
22
+ _original_eq = None
23
+ _original_ne = None
24
+ _original_and = None
25
+ _original_or = None
26
+
27
+ _original_fillna = None
28
+ _original_astype = None
29
+ _original_round = None
30
+ _original_map = None
31
+ _original_apply = None
32
+ _original_dropna = None
33
+ _original_rename = None
34
+ _original_copy = None
35
+ _original_merge = None
36
+ _original_groupby = None
37
+ _original_groupby_getitem = None
38
+ _original_series_groupby_sum = None
39
+ _original_series_groupby_mean = None
40
+ _original_series_groupby_count = None
41
+ _original_series_groupby_min = None
42
+ _original_series_groupby_max = None
43
+ _original_setitem = None
44
+ _original_getitem = None
45
+
46
+ _enabled = False
47
+
48
+
49
+ def _get_dataframe_id(df):
50
+ """Get or create a stable WhyValue ID for a DataFrame."""
51
+ dataframe_id = df.attrs.get("_whyvalue_id")
52
+
53
+ if dataframe_id is None:
54
+ dataframe_id = uuid.uuid4().hex
55
+ df.attrs["_whyvalue_id"] = dataframe_id
56
+
57
+ return dataframe_id
58
+
59
+
60
+ def _add_filter_info(result, series, operator, value):
61
+ """Attach filter information to a boolean Series."""
62
+ result.attrs["_whyvalue_filter"] = {
63
+ "column": series.name,
64
+ "operator": operator,
65
+ "value": value,
66
+ }
67
+
68
+ return result
69
+
70
+
71
+ def enable():
72
+ global _original_add
73
+ global _original_radd
74
+ global _original_mul
75
+ global _original_rmul
76
+ global _original_sub
77
+ global _original_rsub
78
+ global _original_truediv
79
+ global _original_rtruediv
80
+
81
+ global _original_gt
82
+ global _original_ge
83
+ global _original_lt
84
+ global _original_le
85
+ global _original_eq
86
+ global _original_ne
87
+ global _original_and
88
+ global _original_or
89
+
90
+ global _original_fillna
91
+ global _original_astype
92
+ global _original_round
93
+ global _original_map
94
+ global _original_apply
95
+ global _original_dropna
96
+ global _original_rename
97
+ global _original_copy
98
+ global _original_merge
99
+ global _original_groupby
100
+ global _original_groupby_getitem
101
+ global _original_series_groupby_sum
102
+ global _original_series_groupby_mean
103
+ global _original_series_groupby_count
104
+ global _original_series_groupby_min
105
+ global _original_series_groupby_max
106
+ global _original_setitem
107
+ global _original_getitem
108
+ global _enabled
109
+
110
+ if _enabled:
111
+ return
112
+
113
+ _original_add = pd.Series.__add__
114
+ _original_radd = pd.Series.__radd__
115
+ _original_mul = pd.Series.__mul__
116
+ _original_rmul = pd.Series.__rmul__
117
+ _original_sub = pd.Series.__sub__
118
+ _original_rsub = pd.Series.__rsub__
119
+ _original_truediv = pd.Series.__truediv__
120
+ _original_rtruediv = pd.Series.__rtruediv__
121
+
122
+ _original_gt = pd.Series.__gt__
123
+ _original_ge = pd.Series.__ge__
124
+ _original_lt = pd.Series.__lt__
125
+ _original_le = pd.Series.__le__
126
+ _original_eq = pd.Series.__eq__
127
+ _original_ne = pd.Series.__ne__
128
+ _original_and = pd.Series.__and__
129
+ _original_or = pd.Series.__or__
130
+
131
+ _original_fillna = pd.Series.fillna
132
+ _original_astype = pd.Series.astype
133
+ _original_round = pd.Series.round
134
+ _original_map = pd.Series.map
135
+ _original_apply = pd.Series.apply
136
+ _original_dropna = pd.DataFrame.dropna
137
+ _original_rename = pd.DataFrame.rename
138
+ _original_copy = pd.DataFrame.copy
139
+ _original_merge = pd.DataFrame.merge
140
+ _original_groupby = pd.DataFrame.groupby
141
+ _original_groupby_getitem = DataFrameGroupBy.__getitem__
142
+ _original_series_groupby_sum = SeriesGroupBy.sum
143
+ _original_series_groupby_mean = SeriesGroupBy.mean
144
+ _original_series_groupby_count = SeriesGroupBy.count
145
+ _original_series_groupby_min = SeriesGroupBy.min
146
+ _original_series_groupby_max = SeriesGroupBy.max
147
+ _original_setitem = pd.DataFrame.__setitem__
148
+ _original_getitem = pd.DataFrame.__getitem__
149
+
150
+ def whyvalue_add(left, right):
151
+ result = _original_add(left, right)
152
+
153
+ left_df_id = left.attrs.get("_whyvalue_dataframe_id") or left.attrs.get(
154
+ "_whyvalue_id"
155
+ )
156
+
157
+ if isinstance(right, pd.Series):
158
+ right_df_id = right.attrs.get("_whyvalue_dataframe_id") or right.attrs.get(
159
+ "_whyvalue_id"
160
+ )
161
+ info = {
162
+ "operation": "add",
163
+ "left": left.name,
164
+ "right": right.name,
165
+ "right_type": "column",
166
+ }
167
+ if left_df_id:
168
+ info["left_dataframe_id"] = left_df_id
169
+ if right_df_id:
170
+ info["right_dataframe_id"] = right_df_id
171
+ result.attrs["_whyvalue"] = info
172
+ else:
173
+ info = {
174
+ "operation": "add",
175
+ "left": left.name,
176
+ "right": right,
177
+ "right_type": "scalar",
178
+ }
179
+ if left_df_id:
180
+ info["left_dataframe_id"] = left_df_id
181
+ result.attrs["_whyvalue"] = info
182
+
183
+ return result
184
+
185
+ def whyvalue_radd(series, other):
186
+ result = _original_radd(series, other)
187
+
188
+ series_df_id = series.attrs.get("_whyvalue_dataframe_id") or series.attrs.get(
189
+ "_whyvalue_id"
190
+ )
191
+
192
+ info = {
193
+ "operation": "add",
194
+ "left": other,
195
+ "left_type": "scalar",
196
+ "right": series.name,
197
+ "right_type": "column",
198
+ }
199
+ if series_df_id:
200
+ info["right_dataframe_id"] = series_df_id
201
+
202
+ result.attrs["_whyvalue"] = info
203
+
204
+ return result
205
+
206
+ def whyvalue_mul(left, right):
207
+ result = _original_mul(left, right)
208
+
209
+ left_df_id = left.attrs.get("_whyvalue_dataframe_id") or left.attrs.get(
210
+ "_whyvalue_id"
211
+ )
212
+
213
+ if isinstance(right, pd.Series):
214
+ right_df_id = right.attrs.get("_whyvalue_dataframe_id") or right.attrs.get(
215
+ "_whyvalue_id"
216
+ )
217
+ info = {
218
+ "operation": "multiply",
219
+ "left": left.name,
220
+ "right": right.name,
221
+ "right_type": "column",
222
+ }
223
+ if left_df_id:
224
+ info["left_dataframe_id"] = left_df_id
225
+ if right_df_id:
226
+ info["right_dataframe_id"] = right_df_id
227
+ result.attrs["_whyvalue"] = info
228
+ else:
229
+ info = {
230
+ "operation": "multiply",
231
+ "left": left.name,
232
+ "right": right,
233
+ "right_type": "scalar",
234
+ }
235
+ if left_df_id:
236
+ info["left_dataframe_id"] = left_df_id
237
+ result.attrs["_whyvalue"] = info
238
+
239
+ return result
240
+
241
+ def whyvalue_rmul(series, other):
242
+ result = _original_rmul(series, other)
243
+
244
+ series_df_id = series.attrs.get("_whyvalue_dataframe_id") or series.attrs.get(
245
+ "_whyvalue_id"
246
+ )
247
+
248
+ info = {
249
+ "operation": "multiply",
250
+ "left": other,
251
+ "left_type": "scalar",
252
+ "right": series.name,
253
+ "right_type": "column",
254
+ }
255
+ if series_df_id:
256
+ info["right_dataframe_id"] = series_df_id
257
+
258
+ result.attrs["_whyvalue"] = info
259
+
260
+ return result
261
+
262
+ def whyvalue_sub(left, right):
263
+ result = _original_sub(left, right)
264
+
265
+ left_df_id = left.attrs.get("_whyvalue_dataframe_id") or left.attrs.get(
266
+ "_whyvalue_id"
267
+ )
268
+
269
+ if isinstance(right, pd.Series):
270
+ right_df_id = right.attrs.get("_whyvalue_dataframe_id") or right.attrs.get(
271
+ "_whyvalue_id"
272
+ )
273
+ info = {
274
+ "operation": "subtract",
275
+ "left": left.name,
276
+ "right": right.name,
277
+ "right_type": "column",
278
+ }
279
+ if left_df_id:
280
+ info["left_dataframe_id"] = left_df_id
281
+ if right_df_id:
282
+ info["right_dataframe_id"] = right_df_id
283
+ result.attrs["_whyvalue"] = info
284
+ else:
285
+ info = {
286
+ "operation": "subtract",
287
+ "left": left.name,
288
+ "right": right,
289
+ "right_type": "scalar",
290
+ }
291
+ if left_df_id:
292
+ info["left_dataframe_id"] = left_df_id
293
+ result.attrs["_whyvalue"] = info
294
+
295
+ return result
296
+
297
+ def whyvalue_rsub(series, other):
298
+ result = _original_rsub(series, other)
299
+
300
+ series_df_id = series.attrs.get("_whyvalue_dataframe_id") or series.attrs.get(
301
+ "_whyvalue_id"
302
+ )
303
+
304
+ info = {
305
+ "operation": "subtract",
306
+ "left": other,
307
+ "left_type": "scalar",
308
+ "right": series.name,
309
+ "right_type": "column",
310
+ }
311
+ if series_df_id:
312
+ info["right_dataframe_id"] = series_df_id
313
+
314
+ result.attrs["_whyvalue"] = info
315
+
316
+ return result
317
+
318
+ def whyvalue_truediv(left, right):
319
+ result = _original_truediv(left, right)
320
+
321
+ left_df_id = left.attrs.get("_whyvalue_dataframe_id") or left.attrs.get(
322
+ "_whyvalue_id"
323
+ )
324
+
325
+ if isinstance(right, pd.Series):
326
+ right_df_id = right.attrs.get("_whyvalue_dataframe_id") or right.attrs.get(
327
+ "_whyvalue_id"
328
+ )
329
+ info = {
330
+ "operation": "divide",
331
+ "left": left.name,
332
+ "right": right.name,
333
+ "right_type": "column",
334
+ }
335
+ if left_df_id:
336
+ info["left_dataframe_id"] = left_df_id
337
+ if right_df_id:
338
+ info["right_dataframe_id"] = right_df_id
339
+ result.attrs["_whyvalue"] = info
340
+ else:
341
+ info = {
342
+ "operation": "divide",
343
+ "left": left.name,
344
+ "right": right,
345
+ "right_type": "scalar",
346
+ }
347
+ if left_df_id:
348
+ info["left_dataframe_id"] = left_df_id
349
+ result.attrs["_whyvalue"] = info
350
+
351
+ return result
352
+
353
+ def whyvalue_rtruediv(series, other):
354
+ result = _original_rtruediv(series, other)
355
+
356
+ series_df_id = series.attrs.get("_whyvalue_dataframe_id") or series.attrs.get(
357
+ "_whyvalue_id"
358
+ )
359
+
360
+ info = {
361
+ "operation": "divide",
362
+ "left": other,
363
+ "left_type": "scalar",
364
+ "right": series.name,
365
+ "right_type": "column",
366
+ }
367
+ if series_df_id:
368
+ info["right_dataframe_id"] = series_df_id
369
+
370
+ result.attrs["_whyvalue"] = info
371
+
372
+ return result
373
+
374
+ def whyvalue_gt(series, other):
375
+ result = _original_gt(series, other)
376
+
377
+ return _add_filter_info(
378
+ result,
379
+ series,
380
+ ">",
381
+ other,
382
+ )
383
+
384
+ def whyvalue_ge(series, other):
385
+ result = _original_ge(series, other)
386
+
387
+ return _add_filter_info(
388
+ result,
389
+ series,
390
+ ">=",
391
+ other,
392
+ )
393
+
394
+ def whyvalue_lt(series, other):
395
+ result = _original_lt(series, other)
396
+
397
+ return _add_filter_info(
398
+ result,
399
+ series,
400
+ "<",
401
+ other,
402
+ )
403
+
404
+ def whyvalue_le(series, other):
405
+ result = _original_le(series, other)
406
+
407
+ return _add_filter_info(
408
+ result,
409
+ series,
410
+ "<=",
411
+ other,
412
+ )
413
+
414
+ def whyvalue_eq(series, other):
415
+ result = _original_eq(series, other)
416
+
417
+ return _add_filter_info(
418
+ result,
419
+ series,
420
+ "==",
421
+ other,
422
+ )
423
+
424
+ def whyvalue_ne(series, other):
425
+ result = _original_ne(series, other)
426
+
427
+ return _add_filter_info(
428
+ result,
429
+ series,
430
+ "!=",
431
+ other,
432
+ )
433
+
434
+ def whyvalue_and(left, right):
435
+ result = _original_and(left, right)
436
+
437
+ left_info = left.attrs.get("_whyvalue_filter")
438
+ right_info = (
439
+ right.attrs.get("_whyvalue_filter")
440
+ if isinstance(right, pd.Series)
441
+ else None
442
+ )
443
+
444
+ if left_info and right_info:
445
+ result.attrs["_whyvalue_filter"] = {
446
+ "type": "combined_filter",
447
+ "logic": "and",
448
+ "conditions": [left_info, right_info],
449
+ }
450
+
451
+ else:
452
+ result.attrs.pop("_whyvalue_filter", None)
453
+
454
+ return result
455
+
456
+ def whyvalue_or(left, right):
457
+ result = _original_or(left, right)
458
+
459
+ left_info = left.attrs.get("_whyvalue_filter")
460
+ right_info = (
461
+ right.attrs.get("_whyvalue_filter")
462
+ if isinstance(right, pd.Series)
463
+ else None
464
+ )
465
+
466
+ if left_info and right_info:
467
+ result.attrs["_whyvalue_filter"] = {
468
+ "type": "combined_filter",
469
+ "logic": "or",
470
+ "conditions": [left_info, right_info],
471
+ }
472
+
473
+ else:
474
+ result.attrs.pop("_whyvalue_filter", None)
475
+
476
+ return result
477
+
478
+ def whyvalue_fillna(series, value=None, *args, **kwargs):
479
+ missing_rows = series[series.isna()].index.tolist()
480
+
481
+ result = _original_fillna(series, value=value, *args, **kwargs)
482
+
483
+ result.attrs["_whyvalue"] = {
484
+ "operation": "fillna",
485
+ "source": series.name,
486
+ "value": value,
487
+ "missing_rows": missing_rows,
488
+ }
489
+
490
+ return result
491
+
492
+ def whyvalue_astype(series, dtype, *args, **kwargs):
493
+ result = _original_astype(series, dtype, *args, **kwargs)
494
+
495
+ result.attrs["_whyvalue"] = {
496
+ "operation": "astype",
497
+ "source": series.name,
498
+ "dtype": str(dtype),
499
+ }
500
+
501
+ return result
502
+
503
+ def whyvalue_round(series, decimals=0, *args, **kwargs):
504
+ result = _original_round(series, decimals, *args, **kwargs)
505
+
506
+ result.attrs["_whyvalue"] = {
507
+ "operation": "round",
508
+ "source": series.name,
509
+ "decimals": decimals,
510
+ }
511
+
512
+ return result
513
+
514
+ def whyvalue_map(series, arg, *args, **kwargs):
515
+ result = _original_map(series, arg, *args, **kwargs)
516
+
517
+ if isinstance(arg, dict):
518
+ result.attrs["_whyvalue"] = {
519
+ "operation": "map",
520
+ "source": series.name,
521
+ "mapping": dict(arg),
522
+ }
523
+
524
+ else:
525
+ result.attrs.pop("_whyvalue", None)
526
+
527
+ return result
528
+
529
+ def whyvalue_apply(series, func, *args, **kwargs):
530
+ result = _original_apply(series, func, *args, **kwargs)
531
+
532
+ func_name = getattr(func, "__name__", None)
533
+ if not func_name:
534
+ func_name = type(func).__name__
535
+
536
+ if isinstance(result, pd.Series):
537
+ result.attrs["_whyvalue"] = {
538
+ "operation": "apply",
539
+ "source": series.name,
540
+ "function": func_name,
541
+ }
542
+
543
+ return result
544
+
545
+ def whyvalue_dropna(df, *args, **kwargs):
546
+ source_id = _get_dataframe_id(df)
547
+ before = df.copy(deep=False)
548
+
549
+ result = _original_dropna(df, *args, **kwargs)
550
+ is_inplace = result is None or kwargs.get("inplace", False)
551
+
552
+ if is_inplace:
553
+ result_id = source_id
554
+ remaining = df
555
+ else:
556
+ result_id = uuid.uuid4().hex
557
+ remaining = result
558
+ remaining.attrs["_whyvalue_id"] = result_id
559
+
560
+ if len(remaining) < len(before):
561
+ # Use positions to retain original labels, even with ignore_index
562
+ # or duplicate row labels. Let pandas determine which rows survive.
563
+ original_index = before.index
564
+ before.index = pd.RangeIndex(len(before))
565
+ tracking_kwargs = dict(kwargs, inplace=False, ignore_index=False)
566
+ kept = _original_dropna(before, *args, **tracking_kwargs)
567
+ removed_rows = original_index[~before.index.isin(kept.index)].tolist()
568
+
569
+ subset = kwargs.get("subset")
570
+ if subset is None:
571
+ subset = before.columns.tolist()
572
+ elif pd.api.types.is_list_like(subset):
573
+ subset = list(subset)
574
+ else:
575
+ subset = [subset]
576
+
577
+ history.add(
578
+ {
579
+ "type": "dropna",
580
+ "dataframe_id": result_id,
581
+ "source_dataframe_id": source_id,
582
+ "subset": subset,
583
+ "removed_rows": removed_rows,
584
+ }
585
+ )
586
+
587
+ return result
588
+
589
+ def whyvalue_rename(df, *args, **kwargs):
590
+ source_id = _get_dataframe_id(df)
591
+ original_columns = df.columns.copy()
592
+
593
+ result = _original_rename(df, *args, **kwargs)
594
+ is_inplace = result is None or kwargs.get("inplace", False)
595
+
596
+ if is_inplace:
597
+ result_id = source_id
598
+ renamed = df
599
+ else:
600
+ result_id = uuid.uuid4().hex
601
+ renamed = result
602
+ renamed.attrs["_whyvalue_id"] = result_id
603
+
604
+ columns = kwargs.get("columns")
605
+ if (
606
+ columns is not None
607
+ and hasattr(columns, "items")
608
+ and not original_columns.equals(renamed.columns)
609
+ ):
610
+ history.add(
611
+ {
612
+ "type": "rename",
613
+ "dataframe_id": result_id,
614
+ "source_dataframe_id": source_id,
615
+ "columns": dict(columns.items()),
616
+ }
617
+ )
618
+
619
+ return result
620
+
621
+ def whyvalue_copy(df, *args, **kwargs):
622
+ result = _original_copy(df, *args, **kwargs)
623
+
624
+ if isinstance(result, pd.DataFrame):
625
+ result.attrs["_whyvalue_id"] = uuid.uuid4().hex
626
+
627
+ return result
628
+
629
+ def whyvalue_merge(self, right, *args, **kwargs):
630
+ left_dataframe_id = _get_dataframe_id(self)
631
+ right_dataframe_id = _get_dataframe_id(right)
632
+
633
+ result = _original_merge(self, right, *args, **kwargs)
634
+
635
+ new_dataframe_id = uuid.uuid4().hex
636
+ result.attrs["_whyvalue_id"] = new_dataframe_id
637
+
638
+ how = kwargs.get("how")
639
+ if how is None:
640
+ if len(args) >= 1:
641
+ how = args[0]
642
+ else:
643
+ how = "inner"
644
+
645
+ on = kwargs.get("on")
646
+ if on is None and len(args) >= 2:
647
+ on = args[1]
648
+
649
+ history.add(
650
+ {
651
+ "type": "merge",
652
+ "dataframe_id": new_dataframe_id,
653
+ "left_dataframe_id": left_dataframe_id,
654
+ "right_dataframe_id": right_dataframe_id,
655
+ "on": on,
656
+ "how": how,
657
+ }
658
+ )
659
+
660
+ return result
661
+
662
+ def whyvalue_groupby(df, by, *args, **kwargs):
663
+ dataframe_id = _get_dataframe_id(df)
664
+ gb = _original_groupby(df, by, *args, **kwargs)
665
+
666
+ gb._whyvalue_info = {
667
+ "dataframe_id": dataframe_id,
668
+ "group_by": by,
669
+ }
670
+
671
+ return gb
672
+
673
+ def whyvalue_groupby_getitem(gb, key):
674
+ sgb = _original_groupby_getitem(gb, key)
675
+
676
+ if hasattr(gb, "_whyvalue_info"):
677
+ sgb._whyvalue_info = dict(gb._whyvalue_info)
678
+ sgb._whyvalue_info["source"] = key
679
+
680
+ return sgb
681
+
682
+ def whyvalue_series_groupby_sum(sgb, *args, **kwargs):
683
+ result = _original_series_groupby_sum(sgb, *args, **kwargs)
684
+
685
+ if hasattr(sgb, "_whyvalue_info"):
686
+ info = sgb._whyvalue_info
687
+ result_id = uuid.uuid4().hex
688
+ result.attrs["_whyvalue_id"] = result_id
689
+
690
+ history.add(
691
+ {
692
+ "type": "groupby",
693
+ "dataframe_id": result_id,
694
+ "source_dataframe_id": info["dataframe_id"],
695
+ "group_by": info["group_by"],
696
+ "source": info["source"],
697
+ "aggregation": "sum",
698
+ }
699
+ )
700
+
701
+ return result
702
+
703
+ def whyvalue_series_groupby_mean(sgb, *args, **kwargs):
704
+ result = _original_series_groupby_mean(sgb, *args, **kwargs)
705
+
706
+ if hasattr(sgb, "_whyvalue_info"):
707
+ info = sgb._whyvalue_info
708
+ result_id = uuid.uuid4().hex
709
+ result.attrs["_whyvalue_id"] = result_id
710
+
711
+ history.add(
712
+ {
713
+ "type": "groupby",
714
+ "dataframe_id": result_id,
715
+ "source_dataframe_id": info["dataframe_id"],
716
+ "group_by": info["group_by"],
717
+ "source": info["source"],
718
+ "aggregation": "mean",
719
+ }
720
+ )
721
+
722
+ return result
723
+
724
+ def whyvalue_series_groupby_count(sgb, *args, **kwargs):
725
+ result = _original_series_groupby_count(sgb, *args, **kwargs)
726
+
727
+ if hasattr(sgb, "_whyvalue_info"):
728
+ info = sgb._whyvalue_info
729
+ result_id = uuid.uuid4().hex
730
+ result.attrs["_whyvalue_id"] = result_id
731
+
732
+ history.add(
733
+ {
734
+ "type": "groupby",
735
+ "dataframe_id": result_id,
736
+ "source_dataframe_id": info["dataframe_id"],
737
+ "group_by": info["group_by"],
738
+ "source": info["source"],
739
+ "aggregation": "count",
740
+ }
741
+ )
742
+
743
+ return result
744
+
745
+ def whyvalue_series_groupby_min(sgb, *args, **kwargs):
746
+ result = _original_series_groupby_min(sgb, *args, **kwargs)
747
+
748
+ if hasattr(sgb, "_whyvalue_info"):
749
+ info = sgb._whyvalue_info
750
+ result_id = uuid.uuid4().hex
751
+ result.attrs["_whyvalue_id"] = result_id
752
+
753
+ history.add(
754
+ {
755
+ "type": "groupby",
756
+ "dataframe_id": result_id,
757
+ "source_dataframe_id": info["dataframe_id"],
758
+ "group_by": info["group_by"],
759
+ "source": info["source"],
760
+ "aggregation": "min",
761
+ }
762
+ )
763
+
764
+ return result
765
+
766
+ def whyvalue_series_groupby_max(sgb, *args, **kwargs):
767
+ result = _original_series_groupby_max(sgb, *args, **kwargs)
768
+
769
+ if hasattr(sgb, "_whyvalue_info"):
770
+ info = sgb._whyvalue_info
771
+ result_id = uuid.uuid4().hex
772
+ result.attrs["_whyvalue_id"] = result_id
773
+
774
+ history.add(
775
+ {
776
+ "type": "groupby",
777
+ "dataframe_id": result_id,
778
+ "source_dataframe_id": info["dataframe_id"],
779
+ "group_by": info["group_by"],
780
+ "source": info["source"],
781
+ "aggregation": "max",
782
+ }
783
+ )
784
+
785
+ return result
786
+
787
+ def whyvalue_setitem(df, key, value):
788
+ dataframe_id = _get_dataframe_id(df)
789
+
790
+ _original_setitem(df, key, value)
791
+
792
+ if isinstance(value, pd.Series):
793
+ info = value.attrs.get("_whyvalue")
794
+
795
+ if info:
796
+ if info["operation"] == "fillna":
797
+ history.add(
798
+ {
799
+ "type": "column_filled",
800
+ "dataframe_id": dataframe_id,
801
+ "column": key,
802
+ "source": info["source"],
803
+ "operation": "fillna",
804
+ "value": info["value"],
805
+ "missing_rows": info["missing_rows"],
806
+ }
807
+ )
808
+
809
+ elif info["operation"] == "astype":
810
+ history.add(
811
+ {
812
+ "type": "astype",
813
+ "dataframe_id": dataframe_id,
814
+ "column": key,
815
+ "source": info["source"],
816
+ "dtype": info["dtype"],
817
+ }
818
+ )
819
+
820
+ elif info["operation"] == "round":
821
+ history.add(
822
+ {
823
+ "type": "round",
824
+ "dataframe_id": dataframe_id,
825
+ "column": key,
826
+ "source": info["source"],
827
+ "decimals": info["decimals"],
828
+ }
829
+ )
830
+
831
+ elif info["operation"] == "map":
832
+ history.add(
833
+ {
834
+ "type": "map",
835
+ "dataframe_id": dataframe_id,
836
+ "column": key,
837
+ "source": info["source"],
838
+ "mapping": info["mapping"],
839
+ }
840
+ )
841
+
842
+ elif info["operation"] == "apply":
843
+ history.add(
844
+ {
845
+ "type": "apply",
846
+ "dataframe_id": dataframe_id,
847
+ "column": key,
848
+ "source": info["source"],
849
+ "function": info["function"],
850
+ }
851
+ )
852
+
853
+ else:
854
+ event = {
855
+ "type": "column_created",
856
+ "dataframe_id": dataframe_id,
857
+ "column": key,
858
+ "operation": info["operation"],
859
+ "left": info["left"],
860
+ "right": info["right"],
861
+ }
862
+
863
+ if "left_type" in info:
864
+ event["left_type"] = info["left_type"]
865
+
866
+ if "right_type" in info:
867
+ event["right_type"] = info["right_type"]
868
+
869
+ if "left_dataframe_id" in info:
870
+ event["left_dataframe_id"] = info["left_dataframe_id"]
871
+
872
+ if "right_dataframe_id" in info:
873
+ event["right_dataframe_id"] = info["right_dataframe_id"]
874
+
875
+ history.add(event)
876
+ else:
877
+ source_dataframe_id = value.attrs.get(
878
+ "_whyvalue_dataframe_id"
879
+ ) or value.attrs.get("_whyvalue_id")
880
+ if source_dataframe_id and source_dataframe_id != dataframe_id:
881
+ source_name = value.name if value.name is not None else key
882
+ history.add(
883
+ {
884
+ "type": "column_created",
885
+ "dataframe_id": dataframe_id,
886
+ "column": key,
887
+ "operation": "copy",
888
+ "source": source_name,
889
+ "source_dataframe_id": source_dataframe_id,
890
+ }
891
+ )
892
+
893
+ def whyvalue_getitem(df, key):
894
+ dataframe_id = _get_dataframe_id(df)
895
+
896
+ result = _original_getitem(df, key)
897
+
898
+ if isinstance(result, pd.Series):
899
+ result.attrs["_whyvalue_dataframe_id"] = dataframe_id
900
+
901
+ if isinstance(key, pd.Series):
902
+ info = key.attrs.get("_whyvalue_filter")
903
+
904
+ if info and info.get("type") == "combined_filter":
905
+ result_id = uuid.uuid4().hex
906
+ if isinstance(result, pd.DataFrame):
907
+ result.attrs["_whyvalue_id"] = result_id
908
+
909
+ mask = key.reindex(df.index).fillna(False)
910
+ history.add(
911
+ {
912
+ "type": "combined_filter",
913
+ "dataframe_id": result_id,
914
+ "source_dataframe_id": dataframe_id,
915
+ "logic": info["logic"],
916
+ "conditions": info["conditions"],
917
+ "removed_rows": df.index[~mask].tolist(),
918
+ }
919
+ )
920
+
921
+ return result
922
+
923
+ if info:
924
+ result_id = uuid.uuid4().hex
925
+ if isinstance(result, pd.DataFrame):
926
+ result.attrs["_whyvalue_id"] = result_id
927
+
928
+ removed_rows = df.index[~key].tolist()
929
+
930
+ removed_values = {
931
+ row: df.loc[row, info["column"]] for row in removed_rows
932
+ }
933
+
934
+ history.add(
935
+ {
936
+ "type": "filter",
937
+ "dataframe_id": result_id,
938
+ "source_dataframe_id": dataframe_id,
939
+ "column": info["column"],
940
+ "operator": info["operator"],
941
+ "value": info["value"],
942
+ "removed_rows": removed_rows,
943
+ "removed_values": removed_values,
944
+ }
945
+ )
946
+
947
+ return result
948
+
949
+ pd.Series.__add__ = whyvalue_add
950
+ pd.Series.__radd__ = whyvalue_radd
951
+ pd.Series.__mul__ = whyvalue_mul
952
+ pd.Series.__rmul__ = whyvalue_rmul
953
+ pd.Series.__sub__ = whyvalue_sub
954
+ pd.Series.__rsub__ = whyvalue_rsub
955
+ pd.Series.__truediv__ = whyvalue_truediv
956
+ pd.Series.__rtruediv__ = whyvalue_rtruediv
957
+
958
+ pd.Series.__gt__ = whyvalue_gt
959
+ pd.Series.__ge__ = whyvalue_ge
960
+ pd.Series.__lt__ = whyvalue_lt
961
+ pd.Series.__le__ = whyvalue_le
962
+ pd.Series.__eq__ = whyvalue_eq
963
+ pd.Series.__ne__ = whyvalue_ne
964
+ pd.Series.__and__ = whyvalue_and
965
+ pd.Series.__or__ = whyvalue_or
966
+
967
+ pd.Series.fillna = whyvalue_fillna
968
+ pd.Series.astype = whyvalue_astype
969
+ pd.Series.round = whyvalue_round
970
+ pd.Series.map = whyvalue_map
971
+ pd.Series.apply = whyvalue_apply
972
+ pd.DataFrame.dropna = whyvalue_dropna
973
+ pd.DataFrame.rename = whyvalue_rename
974
+ pd.DataFrame.copy = whyvalue_copy
975
+ pd.DataFrame.merge = whyvalue_merge
976
+ pd.DataFrame.groupby = whyvalue_groupby
977
+ DataFrameGroupBy.__getitem__ = whyvalue_groupby_getitem
978
+ SeriesGroupBy.sum = whyvalue_series_groupby_sum
979
+ SeriesGroupBy.mean = whyvalue_series_groupby_mean
980
+ SeriesGroupBy.count = whyvalue_series_groupby_count
981
+ SeriesGroupBy.min = whyvalue_series_groupby_min
982
+ SeriesGroupBy.max = whyvalue_series_groupby_max
983
+ pd.DataFrame.__setitem__ = whyvalue_setitem
984
+ pd.DataFrame.__getitem__ = whyvalue_getitem
985
+
986
+ _enabled = True
987
+
988
+
989
+ def disable():
990
+ global _enabled
991
+
992
+ if not _enabled:
993
+ return
994
+
995
+ pd.Series.__add__ = _original_add
996
+ pd.Series.__radd__ = _original_radd
997
+ pd.Series.__mul__ = _original_mul
998
+ pd.Series.__rmul__ = _original_rmul
999
+ pd.Series.__sub__ = _original_sub
1000
+ pd.Series.__rsub__ = _original_rsub
1001
+ pd.Series.__truediv__ = _original_truediv
1002
+ pd.Series.__rtruediv__ = _original_rtruediv
1003
+
1004
+ pd.Series.__gt__ = _original_gt
1005
+ pd.Series.__ge__ = _original_ge
1006
+ pd.Series.__lt__ = _original_lt
1007
+ pd.Series.__le__ = _original_le
1008
+ pd.Series.__eq__ = _original_eq
1009
+ pd.Series.__ne__ = _original_ne
1010
+ pd.Series.__and__ = _original_and
1011
+ pd.Series.__or__ = _original_or
1012
+
1013
+ pd.Series.fillna = _original_fillna
1014
+ pd.Series.astype = _original_astype
1015
+ pd.Series.round = _original_round
1016
+ pd.Series.map = _original_map
1017
+ pd.Series.apply = _original_apply
1018
+ pd.DataFrame.dropna = _original_dropna
1019
+ pd.DataFrame.rename = _original_rename
1020
+ pd.DataFrame.copy = _original_copy
1021
+ pd.DataFrame.merge = _original_merge
1022
+ pd.DataFrame.groupby = _original_groupby
1023
+ DataFrameGroupBy.__getitem__ = _original_groupby_getitem
1024
+ SeriesGroupBy.sum = _original_series_groupby_sum
1025
+ SeriesGroupBy.mean = _original_series_groupby_mean
1026
+ SeriesGroupBy.count = _original_series_groupby_count
1027
+ SeriesGroupBy.min = _original_series_groupby_min
1028
+ SeriesGroupBy.max = _original_series_groupby_max
1029
+ pd.DataFrame.__setitem__ = _original_setitem
1030
+ pd.DataFrame.__getitem__ = _original_getitem
1031
+
1032
+ _enabled = False