1"""
2Templating for ops docstrings
3"""
4
5from __future__ import annotations
6
7
8def make_flex_doc(op_name: str, typ: str) -> str:
9 """
10 Make the appropriate substitutions for the given operation and class-typ
11 into either _flex_doc_SERIES or _flex_doc_FRAME to return the docstring
12 to attach to a generated method.
13
14 Parameters
15 ----------
16 op_name : str {'__add__', '__sub__', ... '__eq__', '__ne__', ...}
17 typ : str {series, 'dataframe']}
18
19 Returns
20 -------
21 doc : str
22 """
23 op_name = op_name.replace("__", "")
24 op_desc = _op_descriptions[op_name]
25
26 op_desc_op = op_desc["op"]
27 assert op_desc_op is not None # for mypy
28 if op_name.startswith("r"):
29 equiv = f"other {op_desc_op} {typ}"
30 elif op_name == "divmod":
31 equiv = f"{op_name}({typ}, other)"
32 else:
33 equiv = f"{typ} {op_desc_op} other"
34
35 if typ == "series":
36 base_doc = _flex_doc_SERIES
37 if op_desc["reverse"]:
38 base_doc += _see_also_reverse_SERIES.format(
39 reverse=op_desc["reverse"], see_also_desc=op_desc["see_also_desc"]
40 )
41 doc_no_examples = base_doc.format(
42 desc=op_desc["desc"],
43 op_name=op_name,
44 equiv=equiv,
45 series_returns=op_desc["series_returns"],
46 )
47 ser_example = op_desc["series_examples"]
48 if ser_example:
49 doc = doc_no_examples + ser_example
50 else:
51 doc = doc_no_examples
52 elif typ == "dataframe":
53 if op_name in ["eq", "ne", "le", "lt", "ge", "gt"]:
54 base_doc = _flex_comp_doc_FRAME
55 doc = _flex_comp_doc_FRAME.format(
56 op_name=op_name,
57 desc=op_desc["desc"],
58 )
59 else:
60 base_doc = _flex_doc_FRAME
61 doc = base_doc.format(
62 desc=op_desc["desc"],
63 op_name=op_name,
64 equiv=equiv,
65 reverse=op_desc["reverse"],
66 )
67 else:
68 raise AssertionError("Invalid typ argument.")
69 return doc
70
71
72_common_examples_algebra_SERIES = """
73Examples
74--------
75>>> a = pd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd'])
76>>> a
77a 1.0
78b 1.0
79c 1.0
80d NaN
81dtype: float64
82>>> b = pd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e'])
83>>> b
84a 1.0
85b NaN
86d 1.0
87e NaN
88dtype: float64"""
89
90_common_examples_comparison_SERIES = """
91Examples
92--------
93>>> a = pd.Series([1, 1, 1, np.nan, 1], index=['a', 'b', 'c', 'd', 'e'])
94>>> a
95a 1.0
96b 1.0
97c 1.0
98d NaN
99e 1.0
100dtype: float64
101>>> b = pd.Series([0, 1, 2, np.nan, 1], index=['a', 'b', 'c', 'd', 'f'])
102>>> b
103a 0.0
104b 1.0
105c 2.0
106d NaN
107f 1.0
108dtype: float64"""
109
110_add_example_SERIES = (
111 _common_examples_algebra_SERIES
112 + """
113>>> a.add(b, fill_value=0)
114a 2.0
115b 1.0
116c 1.0
117d 1.0
118e NaN
119dtype: float64
120"""
121)
122
123_sub_example_SERIES = (
124 _common_examples_algebra_SERIES
125 + """
126>>> a.subtract(b, fill_value=0)
127a 0.0
128b 1.0
129c 1.0
130d -1.0
131e NaN
132dtype: float64
133"""
134)
135
136_mul_example_SERIES = (
137 _common_examples_algebra_SERIES
138 + """
139>>> a.multiply(b, fill_value=0)
140a 1.0
141b 0.0
142c 0.0
143d 0.0
144e NaN
145dtype: float64
146"""
147)
148
149_div_example_SERIES = (
150 _common_examples_algebra_SERIES
151 + """
152>>> a.divide(b, fill_value=0)
153a 1.0
154b inf
155c inf
156d 0.0
157e NaN
158dtype: float64
159"""
160)
161
162_floordiv_example_SERIES = (
163 _common_examples_algebra_SERIES
164 + """
165>>> a.floordiv(b, fill_value=0)
166a 1.0
167b inf
168c inf
169d 0.0
170e NaN
171dtype: float64
172"""
173)
174
175_divmod_example_SERIES = (
176 _common_examples_algebra_SERIES
177 + """
178>>> a.divmod(b, fill_value=0)
179(a 1.0
180 b inf
181 c inf
182 d 0.0
183 e NaN
184 dtype: float64,
185 a 0.0
186 b NaN
187 c NaN
188 d 0.0
189 e NaN
190 dtype: float64)
191"""
192)
193
194_mod_example_SERIES = (
195 _common_examples_algebra_SERIES
196 + """
197>>> a.mod(b, fill_value=0)
198a 0.0
199b NaN
200c NaN
201d 0.0
202e NaN
203dtype: float64
204"""
205)
206_pow_example_SERIES = (
207 _common_examples_algebra_SERIES
208 + """
209>>> a.pow(b, fill_value=0)
210a 1.0
211b 1.0
212c 1.0
213d 0.0
214e NaN
215dtype: float64
216"""
217)
218
219_ne_example_SERIES = (
220 _common_examples_algebra_SERIES
221 + """
222>>> a.ne(b, fill_value=0)
223a False
224b True
225c True
226d True
227e True
228dtype: bool
229"""
230)
231
232_eq_example_SERIES = (
233 _common_examples_algebra_SERIES
234 + """
235>>> a.eq(b, fill_value=0)
236a True
237b False
238c False
239d False
240e False
241dtype: bool
242"""
243)
244
245_lt_example_SERIES = (
246 _common_examples_comparison_SERIES
247 + """
248>>> a.lt(b, fill_value=0)
249a False
250b False
251c True
252d False
253e False
254f True
255dtype: bool
256"""
257)
258
259_le_example_SERIES = (
260 _common_examples_comparison_SERIES
261 + """
262>>> a.le(b, fill_value=0)
263a False
264b True
265c True
266d False
267e False
268f True
269dtype: bool
270"""
271)
272
273_gt_example_SERIES = (
274 _common_examples_comparison_SERIES
275 + """
276>>> a.gt(b, fill_value=0)
277a True
278b False
279c False
280d False
281e True
282f False
283dtype: bool
284"""
285)
286
287_ge_example_SERIES = (
288 _common_examples_comparison_SERIES
289 + """
290>>> a.ge(b, fill_value=0)
291a True
292b True
293c False
294d False
295e True
296f False
297dtype: bool
298"""
299)
300
301_returns_series = """Series\n The result of the operation."""
302
303_returns_tuple = """2-Tuple of Series\n The result of the operation."""
304
305_op_descriptions: dict[str, dict[str, str | None]] = {
306 # Arithmetic Operators
307 "add": {
308 "op": "+",
309 "desc": "Addition",
310 "reverse": "radd",
311 "series_examples": _add_example_SERIES,
312 "series_returns": _returns_series,
313 },
314 "sub": {
315 "op": "-",
316 "desc": "Subtraction",
317 "reverse": "rsub",
318 "series_examples": _sub_example_SERIES,
319 "series_returns": _returns_series,
320 },
321 "mul": {
322 "op": "*",
323 "desc": "Multiplication",
324 "reverse": "rmul",
325 "series_examples": _mul_example_SERIES,
326 "series_returns": _returns_series,
327 "df_examples": None,
328 },
329 "mod": {
330 "op": "%",
331 "desc": "Modulo",
332 "reverse": "rmod",
333 "series_examples": _mod_example_SERIES,
334 "series_returns": _returns_series,
335 },
336 "pow": {
337 "op": "**",
338 "desc": "Exponential power",
339 "reverse": "rpow",
340 "series_examples": _pow_example_SERIES,
341 "series_returns": _returns_series,
342 "df_examples": None,
343 },
344 "truediv": {
345 "op": "/",
346 "desc": "Floating division",
347 "reverse": "rtruediv",
348 "series_examples": _div_example_SERIES,
349 "series_returns": _returns_series,
350 "df_examples": None,
351 },
352 "floordiv": {
353 "op": "//",
354 "desc": "Integer division",
355 "reverse": "rfloordiv",
356 "series_examples": _floordiv_example_SERIES,
357 "series_returns": _returns_series,
358 "df_examples": None,
359 },
360 "divmod": {
361 "op": "divmod",
362 "desc": "Integer division and modulo",
363 "reverse": "rdivmod",
364 "series_examples": _divmod_example_SERIES,
365 "series_returns": _returns_tuple,
366 "df_examples": None,
367 },
368 # Comparison Operators
369 "eq": {
370 "op": "==",
371 "desc": "Equal to",
372 "reverse": None,
373 "series_examples": _eq_example_SERIES,
374 "series_returns": _returns_series,
375 },
376 "ne": {
377 "op": "!=",
378 "desc": "Not equal to",
379 "reverse": "eq",
380 "series_examples": _ne_example_SERIES,
381 "series_returns": _returns_series,
382 },
383 "lt": {
384 "op": "<",
385 "desc": "Less than",
386 "reverse": None,
387 "series_examples": _lt_example_SERIES,
388 "series_returns": _returns_series,
389 },
390 "le": {
391 "op": "<=",
392 "desc": "Less than or equal to",
393 "reverse": None,
394 "series_examples": _le_example_SERIES,
395 "series_returns": _returns_series,
396 },
397 "gt": {
398 "op": ">",
399 "desc": "Greater than",
400 "reverse": "lt",
401 "series_examples": _gt_example_SERIES,
402 "series_returns": _returns_series,
403 },
404 "ge": {
405 "op": ">=",
406 "desc": "Greater than or equal to",
407 "reverse": "le",
408 "series_examples": _ge_example_SERIES,
409 "series_returns": _returns_series,
410 },
411}
412
413_py_num_ref = """see
414 `Python documentation
415 <https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types>`_
416 for more details"""
417_op_names = list(_op_descriptions.keys())
418for key in _op_names:
419 reverse_op = _op_descriptions[key]["reverse"]
420 if reverse_op is not None:
421 _op_descriptions[reverse_op] = _op_descriptions[key].copy()
422 _op_descriptions[reverse_op]["reverse"] = key
423 _op_descriptions[key]["see_also_desc"] = (
424 f"Reverse of the {_op_descriptions[key]['desc']} operator, {_py_num_ref}"
425 )
426 _op_descriptions[reverse_op]["see_also_desc"] = (
427 f"Element-wise {_op_descriptions[key]['desc']}, {_py_num_ref}"
428 )
429
430_flex_doc_SERIES = """
431Return {desc} of series and other, element-wise (binary operator `{op_name}`).
432
433Equivalent to ``{equiv}``, but with support to substitute a fill_value for
434missing data in either one of the inputs.
435
436Parameters
437----------
438other : object
439 When a Series is provided, will align on indexes. For all other types,
440 will behave the same as ``==`` but with possibly different results due
441 to the other arguments.
442level : int or name
443 Broadcast across a level, matching Index values on the
444 passed MultiIndex level.
445fill_value : None or float value, default None (NaN)
446 Fill existing missing (NaN) values, and any new element needed for
447 successful Series alignment, with this value before computation.
448 If data in both corresponding Series locations is missing
449 the result of filling (at that location) will be missing.
450axis : {{0 or 'index'}}
451 Unused. Parameter needed for compatibility with DataFrame.
452
453Returns
454-------
455{series_returns}
456"""
457
458_see_also_reverse_SERIES = """
459See Also
460--------
461Series.{reverse} : {see_also_desc}.
462"""
463
464_flex_doc_FRAME = """
465Get {desc} of dataframe and other, element-wise (binary operator `{op_name}`).
466
467Equivalent to ``{equiv}``, but with support to substitute a fill_value
468for missing data in one of the inputs. With reverse version, `{reverse}`.
469
470Among flexible wrappers (`add`, `sub`, `mul`, `div`, `floordiv`, `mod`, `pow`) to
471arithmetic operators: `+`, `-`, `*`, `/`, `//`, `%`, `**`.
472
473Parameters
474----------
475other : scalar, sequence, Series, dict or DataFrame
476 Any single or multiple element data structure, or list-like object.
477axis : {{0 or 'index', 1 or 'columns'}}
478 Whether to compare by the index (0 or 'index') or columns.
479 (1 or 'columns'). For Series input, axis to match Series index on.
480level : int or label
481 Broadcast across a level, matching Index values on the
482 passed MultiIndex level.
483fill_value : float or None, default None
484 Fill existing missing (NaN) values, and any new element needed for
485 successful DataFrame alignment, with this value before computation.
486 If data in both corresponding DataFrame locations is missing
487 the result will be missing.
488
489Returns
490-------
491DataFrame
492 Result of the arithmetic operation.
493
494See Also
495--------
496DataFrame.add : Add DataFrames.
497DataFrame.sub : Subtract DataFrames.
498DataFrame.mul : Multiply DataFrames.
499DataFrame.div : Divide DataFrames (float division).
500DataFrame.truediv : Divide DataFrames (float division).
501DataFrame.floordiv : Divide DataFrames (integer division).
502DataFrame.mod : Calculate modulo (remainder after division).
503DataFrame.pow : Calculate exponential power.
504
505Notes
506-----
507Mismatched indices will be unioned together.
508
509Examples
510--------
511>>> df = pd.DataFrame({{'angles': [0, 3, 4],
512... 'degrees': [360, 180, 360]}},
513... index=['circle', 'triangle', 'rectangle'])
514>>> df
515 angles degrees
516circle 0 360
517triangle 3 180
518rectangle 4 360
519
520Add a scalar with operator version which return the same
521results.
522
523>>> df + 1
524 angles degrees
525circle 1 361
526triangle 4 181
527rectangle 5 361
528
529>>> df.add(1)
530 angles degrees
531circle 1 361
532triangle 4 181
533rectangle 5 361
534
535Divide by constant with reverse version.
536
537>>> df.div(10)
538 angles degrees
539circle 0.0 36.0
540triangle 0.3 18.0
541rectangle 0.4 36.0
542
543>>> df.rdiv(10)
544 angles degrees
545circle inf 0.027778
546triangle 3.333333 0.055556
547rectangle 2.500000 0.027778
548
549Subtract a list and Series by axis with operator version.
550
551>>> df - [1, 2]
552 angles degrees
553circle -1 358
554triangle 2 178
555rectangle 3 358
556
557>>> df.sub([1, 2], axis='columns')
558 angles degrees
559circle -1 358
560triangle 2 178
561rectangle 3 358
562
563>>> df.sub(pd.Series([1, 1, 1], index=['circle', 'triangle', 'rectangle']),
564... axis='index')
565 angles degrees
566circle -1 359
567triangle 2 179
568rectangle 3 359
569
570Multiply a dictionary by axis.
571
572>>> df.mul({{'angles': 0, 'degrees': 2}})
573 angles degrees
574circle 0 720
575triangle 0 360
576rectangle 0 720
577
578>>> df.mul({{'circle': 0, 'triangle': 2, 'rectangle': 3}}, axis='index')
579 angles degrees
580circle 0 0
581triangle 6 360
582rectangle 12 1080
583
584Multiply a DataFrame of different shape with operator version.
585
586>>> other = pd.DataFrame({{'angles': [0, 3, 4]}},
587... index=['circle', 'triangle', 'rectangle'])
588>>> other
589 angles
590circle 0
591triangle 3
592rectangle 4
593
594>>> df * other
595 angles degrees
596circle 0 NaN
597triangle 9 NaN
598rectangle 16 NaN
599
600>>> df.mul(other, fill_value=0)
601 angles degrees
602circle 0 0.0
603triangle 9 0.0
604rectangle 16 0.0
605
606Divide by a MultiIndex by level.
607
608>>> df_multindex = pd.DataFrame({{'angles': [0, 3, 4, 4, 5, 6],
609... 'degrees': [360, 180, 360, 360, 540, 720]}},
610... index=[['A', 'A', 'A', 'B', 'B', 'B'],
611... ['circle', 'triangle', 'rectangle',
612... 'square', 'pentagon', 'hexagon']])
613>>> df_multindex
614 angles degrees
615A circle 0 360
616 triangle 3 180
617 rectangle 4 360
618B square 4 360
619 pentagon 5 540
620 hexagon 6 720
621
622>>> df.div(df_multindex, level=1, fill_value=0)
623 angles degrees
624A circle NaN 1.0
625 triangle 1.0 1.0
626 rectangle 1.0 1.0
627B square 0.0 0.0
628 pentagon 0.0 0.0
629 hexagon 0.0 0.0
630
631>>> df_pow = pd.DataFrame({{'A': [2, 3, 4, 5],
632... 'B': [6, 7, 8, 9]}})
633>>> df_pow.pow(2)
634 A B
6350 4 36
6361 9 49
6372 16 64
6383 25 81
639"""
640
641_flex_comp_doc_FRAME = """
642Get {desc} of dataframe and other, element-wise (binary operator `{op_name}`).
643
644Among flexible wrappers (`eq`, `ne`, `le`, `lt`, `ge`, `gt`) to comparison
645operators.
646
647Equivalent to `==`, `!=`, `<=`, `<`, `>=`, `>` with support to choose axis
648(rows or columns) and level for comparison.
649
650Parameters
651----------
652other : scalar, sequence, Series, or DataFrame
653 Any single or multiple element data structure, or list-like object.
654axis : {{0 or 'index', 1 or 'columns'}}, default 'columns'
655 Whether to compare by the index (0 or 'index') or columns
656 (1 or 'columns').
657level : int or label
658 Broadcast across a level, matching Index values on the passed
659 MultiIndex level.
660
661Returns
662-------
663DataFrame of bool
664 Result of the comparison.
665
666See Also
667--------
668DataFrame.eq : Compare DataFrames for equality elementwise.
669DataFrame.ne : Compare DataFrames for inequality elementwise.
670DataFrame.le : Compare DataFrames for less than inequality
671 or equality elementwise.
672DataFrame.lt : Compare DataFrames for strictly less than
673 inequality elementwise.
674DataFrame.ge : Compare DataFrames for greater than inequality
675 or equality elementwise.
676DataFrame.gt : Compare DataFrames for strictly greater than
677 inequality elementwise.
678
679Notes
680-----
681Mismatched indices will be unioned together.
682`NaN` values are considered different (i.e. `NaN` != `NaN`).
683
684Examples
685--------
686>>> df = pd.DataFrame({{'cost': [250, 150, 100],
687... 'revenue': [100, 250, 300]}},
688... index=['A', 'B', 'C'])
689>>> df
690 cost revenue
691A 250 100
692B 150 250
693C 100 300
694
695Comparison with a scalar, using either the operator or method:
696
697>>> df == 100
698 cost revenue
699A False True
700B False False
701C True False
702
703>>> df.eq(100)
704 cost revenue
705A False True
706B False False
707C True False
708
709When `other` is a :class:`Series`, the columns of a DataFrame are aligned
710with the index of `other` and broadcast:
711
712>>> df != pd.Series([100, 250], index=["cost", "revenue"])
713 cost revenue
714A True True
715B True False
716C False True
717
718Use the method to control the broadcast axis:
719
720>>> df.ne(pd.Series([100, 300], index=["A", "D"]), axis='index')
721 cost revenue
722A True False
723B True True
724C True True
725D True True
726
727When comparing to an arbitrary sequence, the number of columns must
728match the number elements in `other`:
729
730>>> df == [250, 100]
731 cost revenue
732A True True
733B False False
734C False False
735
736Use the method to control the axis:
737
738>>> df.eq([250, 250, 100], axis='index')
739 cost revenue
740A True False
741B False True
742C True False
743
744Compare to a DataFrame of different shape.
745
746>>> other = pd.DataFrame({{'revenue': [300, 250, 100, 150]}},
747... index=['A', 'B', 'C', 'D'])
748>>> other
749 revenue
750A 300
751B 250
752C 100
753D 150
754
755>>> df.gt(other)
756 cost revenue
757A False False
758B False False
759C False True
760D False False
761
762Compare to a MultiIndex by level.
763
764>>> df_multindex = pd.DataFrame({{'cost': [250, 150, 100, 150, 300, 220],
765... 'revenue': [100, 250, 300, 200, 175, 225]}},
766... index=[['Q1', 'Q1', 'Q1', 'Q2', 'Q2', 'Q2'],
767... ['A', 'B', 'C', 'A', 'B', 'C']])
768>>> df_multindex
769 cost revenue
770Q1 A 250 100
771 B 150 250
772 C 100 300
773Q2 A 150 200
774 B 300 175
775 C 220 225
776
777>>> df.le(df_multindex, level=1)
778 cost revenue
779Q1 A True True
780 B True True
781 C True True
782Q2 A False True
783 B True False
784 C True False
785"""