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