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