We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ENH] Add `softmax` to math submodule
In reviewing the `math` module, I'd like to add `softmax` to the functions. This will complete the "activation" functions :) as we have sigmoids but no softmax.
[ENH] Add `softmax` to math submodule
In reviewing the `math` module, I'd like to add `softmax` to the functions. This will complete the "activation" functions :) as we have sigmoids but no softmax.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `janitor/math.py`
Content:
```
1 """ Miscellaneous mathematical operators. """
2 
3 import warnings
4 from typing import Tuple
5 
6 import numpy as np
7 import pandas as pd
8 import pandas_flavor as pf
9 from pandas.api.types import is_numeric_dtype
10 from scipy.special import expit
11 from scipy.stats import norm
12 
13 
14 @pf.register_series_method
15 def log(s: pd.Series, error: str = "warn") -> pd.Series:
16     """
17     Take natural logarithm of the Series.
18 
19     :param s: Input Series.
20     :param error: Determines behavior when taking the log of nonpositive
21         entries. If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`,
22         then a `RuntimeError` is thrown. Otherwise, nothing is thrown and
23         log of nonpositive values is `np.nan`; defaults to `'warn'`.
24     :raises RuntimeError: Raised when there are nonpositive values in the
25         Series and `error='raise'`.
26     :return: Transformed Series.
27     """
28     s = s.copy()
29     nonpositive = s <= 0
30     if (nonpositive).any():
31         msg = f"Log taken on {nonpositive.sum()} nonpositive value(s)"
32         if error.lower() == "warn":
33             warnings.warn(msg, RuntimeWarning)
34         if error.lower() == "raise":
35             raise RuntimeError(msg)
36         else:
37             pass
38     s[nonpositive] = np.nan
39     return np.log(s)
40 
41 
42 @pf.register_series_method
43 def exp(s: pd.Series) -> pd.Series:
44     """
45     Take the exponential transform of the series.
46 
47     :param s: Input Series.
48     :return: Transformed Series.
49     """
50     return np.exp(s)
51 
52 
53 @pf.register_series_method
54 def sigmoid(s: pd.Series) -> pd.Series:
55     """
56     Take the sigmoid transform of the series where:
57 
58     ```python
59     sigmoid(x) = 1 / (1 + exp(-x))
60     ```
61 
62     :param s: Input Series.
63     :return: Transformed Series.
64     """
65     return expit(s)
66 
67 
68 @pf.register_series_method
69 def logit(s: pd.Series, error: str = "warn") -> pd.Series:
70     """
71     Take logit transform of the Series where:
72 
73     ```python
74     logit(p) = log(p/(1-p))
75     ```
76 
77     :param s: Input Series.
78     :param error: Determines behavior when `s / (1-s)` is outside of `(0, 1)`.
79         If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then a
80         `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`
81         is returned for the problematic entries; defaults to `'warn'`.
82     :return: Transformed Series.
83     :raises RuntimeError: if `error` is set to `'raise'`.
84     """
85     s = s.copy()
86     odds_ratio = s / (1 - s)
87     outside_support = (odds_ratio <= 0) | (odds_ratio >= 1)
88     if (outside_support).any():
89         msg = f"Odds ratio for {outside_support.sum()} value(s) \
90 are outside of (0, 1)"
91         if error.lower() == "warn":
92             warnings.warn(msg, RuntimeWarning)
93         if error.lower() == "raise":
94             raise RuntimeError(msg)
95         else:
96             pass
97     odds_ratio[outside_support] = np.nan
98     return odds_ratio.log(error="ignore")
99 
100 
101 @pf.register_series_method
102 def normal_cdf(s: pd.Series) -> pd.Series:
103     """
104     Transforms the Series via the CDF of the Normal distribution.
105 
106     :param s: Input Series.
107     :return: Transformed Series.
108     """
109     return pd.Series(norm.cdf(s), index=s.index)
110 
111 
112 @pf.register_series_method
113 def probit(s: pd.Series, error: str = "warn") -> pd.Series:
114     """
115     Transforms the Series via the inverse CDF of the Normal distribution.
116 
117     :param s: Input Series.
118     :param error: Determines behavior when `s` is outside of `(0, 1)`.
119         If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then
120         a `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`
121         is returned for the problematic entries; defaults to `'warn'`.
122     :raises RuntimeError: Raised when there are problematic values
123         in the Series and `error='raise'`.
124     :return: Transformed Series
125     """
126     s = s.copy()
127     outside_support = (s <= 0) | (s >= 1)
128     if (outside_support).any():
129         msg = f"{outside_support.sum()} value(s) are outside of (0, 1)"
130         if error.lower() == "warn":
131             warnings.warn(msg, RuntimeWarning)
132         if error.lower() == "raise":
133             raise RuntimeError(msg)
134         else:
135             pass
136     s[outside_support] = np.nan
137     with np.errstate(all="ignore"):
138         out = pd.Series(norm.ppf(s), index=s.index)
139     return out
140 
141 
142 @pf.register_series_method
143 def z_score(
144     s: pd.Series,
145     moments_dict: dict = None,
146     keys: Tuple[str, str] = ("mean", "std"),
147 ) -> pd.Series:
148     """
149     Transforms the Series into z-scores where:
150 
151     ```python
152     z = (s - s.mean()) / s.std()
153     ```
154 
155     :param s: Input Series.
156     :param moments_dict: If not `None`, then the mean and standard
157         deviation used to compute the z-score transformation is
158         saved as entries in `moments_dict` with keys determined by
159         the `keys` argument; defaults to `None`.
160     :param keys: Determines the keys saved in `moments_dict`
161         if moments are saved; defaults to (`'mean'`, `'std'`).
162     :return: Transformed Series.
163     """
164     mean = s.mean()
165     std = s.std()
166     if std == 0:
167         return 0
168     if moments_dict is not None:
169         moments_dict[keys[0]] = mean
170         moments_dict[keys[1]] = std
171     return (s - mean) / std
172 
173 
174 @pf.register_series_method
175 def ecdf(s: pd.Series) -> Tuple[np.ndarray, np.ndarray]:
176     """
177     Return cumulative distribution of values in a series.
178 
179     Intended to be used with the following pattern:
180 
181     ```python
182     df = pd.DataFrame(...)
183 
184     # Obtain ECDF values to be plotted
185     x, y = df["column_name"].ecdf()
186 
187     # Plot ECDF values
188     plt.scatter(x, y)
189     ```
190 
191     Null values must be dropped from the series,
192     otherwise a `ValueError` is raised.
193 
194     Also, if the `dtype` of the series is not numeric,
195     a `TypeError` is raised.
196 
197     :param s: A pandas series. `dtype` should be numeric.
198     :returns: `(x, y)`.
199         `x`: sorted array of values.
200         `y`: cumulative fraction of data points with value `x` or lower.
201     :raises TypeError: if series is not numeric.
202     :raises ValueError: if series contains nulls.
203     """
204     if not is_numeric_dtype(s):
205         raise TypeError(f"series {s.name} must be numeric!")
206     if not s.isna().sum() == 0:
207         raise ValueError(f"series {s.name} contains nulls. Please drop them.")
208 
209     n = len(s)
210     x = np.sort(s)
211     y = np.arange(1, n + 1) / n
212 
213     return x, y
214
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/janitor/math.py b/janitor/math.py
--- a/janitor/math.py
+++ b/janitor/math.py
@@ -8,6 +8,7 @@
 import pandas_flavor as pf
 from pandas.api.types import is_numeric_dtype
 from scipy.special import expit
+from scipy.special import softmax as scipy_softmax
 from scipy.stats import norm
 
 
@@ -65,6 +66,27 @@
     return expit(s)
 
 
+@pf.register_series_method
+def softmax(s: pd.Series) -> pd.Series:
+    """
+    Take the softmax transform of the series.
+
+    The softmax function transforms each element of a collection by
+    computing the exponential of each element divided by the sum of the
+    exponentials of all the elements.
+
+    That is, if x is a one-dimensional numpy array or pandas Series:
+
+    ```python
+    softmax(x) = exp(x)/sum(exp(x))
+    ```
+
+    :param s: Input Series.
+    :return: Transformed Series.
+    """
+    return scipy_softmax(s)
+
+
 @pf.register_series_method
 def logit(s: pd.Series, error: str = "warn") -> pd.Series:
     """

{"golden_diff": "diff --git a/janitor/math.py b/janitor/math.py\n--- a/janitor/math.py\n+++ b/janitor/math.py\n@@ -8,6 +8,7 @@\n import pandas_flavor as pf\n from pandas.api.types import is_numeric_dtype\n from scipy.special import expit\n+from scipy.special import softmax as scipy_softmax\n from scipy.stats import norm\n \n \n@@ -65,6 +66,27 @@\n     return expit(s)\n \n \n+@pf.register_series_method\n+def softmax(s: pd.Series) -> pd.Series:\n+    \"\"\"\n+    Take the softmax transform of the series.\n+\n+    The softmax function transforms each element of a collection by\n+    computing the exponential of each element divided by the sum of the\n+    exponentials of all the elements.\n+\n+    That is, if x is a one-dimensional numpy array or pandas Series:\n+\n+    ```python\n+    softmax(x) = exp(x)/sum(exp(x))\n+    ```\n+\n+    :param s: Input Series.\n+    :return: Transformed Series.\n+    \"\"\"\n+    return scipy_softmax(s)\n+\n+\n @pf.register_series_method\n def logit(s: pd.Series, error: str = \"warn\") -> pd.Series:\n     \"\"\"\n", "issue": "[ENH] Add `softmax` to math submodule\nIn reviewing the `math` module, I'd like to add `softmax` to the functions. This will complete the \"activation\" functions :) as we have sigmoids but no softmax.\n[ENH] Add `softmax` to math submodule\nIn reviewing the `math` module, I'd like to add `softmax` to the functions. This will complete the \"activation\" functions :) as we have sigmoids but no softmax.\n", "before_files": [{"content": "\"\"\" Miscellaneous mathematical operators. \"\"\"\n\nimport warnings\nfrom typing import Tuple\n\nimport numpy as np\nimport pandas as pd\nimport pandas_flavor as pf\nfrom pandas.api.types import is_numeric_dtype\nfrom scipy.special import expit\nfrom scipy.stats import norm\n\n\n@pf.register_series_method\ndef log(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Take natural logarithm of the Series.\n\n    :param s: Input Series.\n    :param error: Determines behavior when taking the log of nonpositive\n        entries. If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`,\n        then a `RuntimeError` is thrown. Otherwise, nothing is thrown and\n        log of nonpositive values is `np.nan`; defaults to `'warn'`.\n    :raises RuntimeError: Raised when there are nonpositive values in the\n        Series and `error='raise'`.\n    :return: Transformed Series.\n    \"\"\"\n    s = s.copy()\n    nonpositive = s <= 0\n    if (nonpositive).any():\n        msg = f\"Log taken on {nonpositive.sum()} nonpositive value(s)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    s[nonpositive] = np.nan\n    return np.log(s)\n\n\n@pf.register_series_method\ndef exp(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Take the exponential transform of the series.\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return np.exp(s)\n\n\n@pf.register_series_method\ndef sigmoid(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Take the sigmoid transform of the series where:\n\n    ```python\n    sigmoid(x) = 1 / (1 + exp(-x))\n    ```\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return expit(s)\n\n\n@pf.register_series_method\ndef logit(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Take logit transform of the Series where:\n\n    ```python\n    logit(p) = log(p/(1-p))\n    ```\n\n    :param s: Input Series.\n    :param error: Determines behavior when `s / (1-s)` is outside of `(0, 1)`.\n        If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then a\n        `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`\n        is returned for the problematic entries; defaults to `'warn'`.\n    :return: Transformed Series.\n    :raises RuntimeError: if `error` is set to `'raise'`.\n    \"\"\"\n    s = s.copy()\n    odds_ratio = s / (1 - s)\n    outside_support = (odds_ratio <= 0) | (odds_ratio >= 1)\n    if (outside_support).any():\n        msg = f\"Odds ratio for {outside_support.sum()} value(s) \\\nare outside of (0, 1)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    odds_ratio[outside_support] = np.nan\n    return odds_ratio.log(error=\"ignore\")\n\n\n@pf.register_series_method\ndef normal_cdf(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Transforms the Series via the CDF of the Normal distribution.\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return pd.Series(norm.cdf(s), index=s.index)\n\n\n@pf.register_series_method\ndef probit(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Transforms the Series via the inverse CDF of the Normal distribution.\n\n    :param s: Input Series.\n    :param error: Determines behavior when `s` is outside of `(0, 1)`.\n        If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then\n        a `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`\n        is returned for the problematic entries; defaults to `'warn'`.\n    :raises RuntimeError: Raised when there are problematic values\n        in the Series and `error='raise'`.\n    :return: Transformed Series\n    \"\"\"\n    s = s.copy()\n    outside_support = (s <= 0) | (s >= 1)\n    if (outside_support).any():\n        msg = f\"{outside_support.sum()} value(s) are outside of (0, 1)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    s[outside_support] = np.nan\n    with np.errstate(all=\"ignore\"):\n        out = pd.Series(norm.ppf(s), index=s.index)\n    return out\n\n\n@pf.register_series_method\ndef z_score(\n    s: pd.Series,\n    moments_dict: dict = None,\n    keys: Tuple[str, str] = (\"mean\", \"std\"),\n) -> pd.Series:\n    \"\"\"\n    Transforms the Series into z-scores where:\n\n    ```python\n    z = (s - s.mean()) / s.std()\n    ```\n\n    :param s: Input Series.\n    :param moments_dict: If not `None`, then the mean and standard\n        deviation used to compute the z-score transformation is\n        saved as entries in `moments_dict` with keys determined by\n        the `keys` argument; defaults to `None`.\n    :param keys: Determines the keys saved in `moments_dict`\n        if moments are saved; defaults to (`'mean'`, `'std'`).\n    :return: Transformed Series.\n    \"\"\"\n    mean = s.mean()\n    std = s.std()\n    if std == 0:\n        return 0\n    if moments_dict is not None:\n        moments_dict[keys[0]] = mean\n        moments_dict[keys[1]] = std\n    return (s - mean) / std\n\n\n@pf.register_series_method\ndef ecdf(s: pd.Series) -> Tuple[np.ndarray, np.ndarray]:\n    \"\"\"\n    Return cumulative distribution of values in a series.\n\n    Intended to be used with the following pattern:\n\n    ```python\n    df = pd.DataFrame(...)\n\n    # Obtain ECDF values to be plotted\n    x, y = df[\"column_name\"].ecdf()\n\n    # Plot ECDF values\n    plt.scatter(x, y)\n    ```\n\n    Null values must be dropped from the series,\n    otherwise a `ValueError` is raised.\n\n    Also, if the `dtype` of the series is not numeric,\n    a `TypeError` is raised.\n\n    :param s: A pandas series. `dtype` should be numeric.\n    :returns: `(x, y)`.\n        `x`: sorted array of values.\n        `y`: cumulative fraction of data points with value `x` or lower.\n    :raises TypeError: if series is not numeric.\n    :raises ValueError: if series contains nulls.\n    \"\"\"\n    if not is_numeric_dtype(s):\n        raise TypeError(f\"series {s.name} must be numeric!\")\n    if not s.isna().sum() == 0:\n        raise ValueError(f\"series {s.name} contains nulls. Please drop them.\")\n\n    n = len(s)\n    x = np.sort(s)\n    y = np.arange(1, n + 1) / n\n\n    return x, y\n", "path": "janitor/math.py"}], "after_files": [{"content": "\"\"\" Miscellaneous mathematical operators. \"\"\"\n\nimport warnings\nfrom typing import Tuple\n\nimport numpy as np\nimport pandas as pd\nimport pandas_flavor as pf\nfrom pandas.api.types import is_numeric_dtype\nfrom scipy.special import expit\nfrom scipy.special import softmax as scipy_softmax\nfrom scipy.stats import norm\n\n\n@pf.register_series_method\ndef log(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Take natural logarithm of the Series.\n\n    :param s: Input Series.\n    :param error: Determines behavior when taking the log of nonpositive\n        entries. If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`,\n        then a `RuntimeError` is thrown. Otherwise, nothing is thrown and\n        log of nonpositive values is `np.nan`; defaults to `'warn'`.\n    :raises RuntimeError: Raised when there are nonpositive values in the\n        Series and `error='raise'`.\n    :return: Transformed Series.\n    \"\"\"\n    s = s.copy()\n    nonpositive = s <= 0\n    if (nonpositive).any():\n        msg = f\"Log taken on {nonpositive.sum()} nonpositive value(s)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    s[nonpositive] = np.nan\n    return np.log(s)\n\n\n@pf.register_series_method\ndef exp(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Take the exponential transform of the series.\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return np.exp(s)\n\n\n@pf.register_series_method\ndef sigmoid(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Take the sigmoid transform of the series where:\n\n    ```python\n    sigmoid(x) = 1 / (1 + exp(-x))\n    ```\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return expit(s)\n\n\n@pf.register_series_method\ndef softmax(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Take the softmax transform of the series.\n\n    The softmax function transforms each element of a collection by\n    computing the exponential of each element divided by the sum of the\n    exponentials of all the elements.\n\n    That is, if x is a one-dimensional numpy array or pandas Series:\n\n    ```python\n    softmax(x) = exp(x)/sum(exp(x))\n    ```\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return scipy_softmax(s)\n\n\n@pf.register_series_method\ndef logit(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Take logit transform of the Series where:\n\n    ```python\n    logit(p) = log(p/(1-p))\n    ```\n\n    :param s: Input Series.\n    :param error: Determines behavior when `s / (1-s)` is outside of `(0, 1)`.\n        If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then a\n        `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`\n        is returned for the problematic entries; defaults to `'warn'`.\n    :return: Transformed Series.\n    :raises RuntimeError: if `error` is set to `'raise'`.\n    \"\"\"\n    s = s.copy()\n    odds_ratio = s / (1 - s)\n    outside_support = (odds_ratio <= 0) | (odds_ratio >= 1)\n    if (outside_support).any():\n        msg = f\"Odds ratio for {outside_support.sum()} value(s) \\\nare outside of (0, 1)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    odds_ratio[outside_support] = np.nan\n    return odds_ratio.log(error=\"ignore\")\n\n\n@pf.register_series_method\ndef normal_cdf(s: pd.Series) -> pd.Series:\n    \"\"\"\n    Transforms the Series via the CDF of the Normal distribution.\n\n    :param s: Input Series.\n    :return: Transformed Series.\n    \"\"\"\n    return pd.Series(norm.cdf(s), index=s.index)\n\n\n@pf.register_series_method\ndef probit(s: pd.Series, error: str = \"warn\") -> pd.Series:\n    \"\"\"\n    Transforms the Series via the inverse CDF of the Normal distribution.\n\n    :param s: Input Series.\n    :param error: Determines behavior when `s` is outside of `(0, 1)`.\n        If `'warn'` then a `RuntimeWarning` is thrown. If `'raise'`, then\n        a `RuntimeError` is thrown. Otherwise, nothing is thrown and `np.nan`\n        is returned for the problematic entries; defaults to `'warn'`.\n    :raises RuntimeError: Raised when there are problematic values\n        in the Series and `error='raise'`.\n    :return: Transformed Series\n    \"\"\"\n    s = s.copy()\n    outside_support = (s <= 0) | (s >= 1)\n    if (outside_support).any():\n        msg = f\"{outside_support.sum()} value(s) are outside of (0, 1)\"\n        if error.lower() == \"warn\":\n            warnings.warn(msg, RuntimeWarning)\n        if error.lower() == \"raise\":\n            raise RuntimeError(msg)\n        else:\n            pass\n    s[outside_support] = np.nan\n    with np.errstate(all=\"ignore\"):\n        out = pd.Series(norm.ppf(s), index=s.index)\n    return out\n\n\n@pf.register_series_method\ndef z_score(\n    s: pd.Series,\n    moments_dict: dict = None,\n    keys: Tuple[str, str] = (\"mean\", \"std\"),\n) -> pd.Series:\n    \"\"\"\n    Transforms the Series into z-scores where:\n\n    ```python\n    z = (s - s.mean()) / s.std()\n    ```\n\n    :param s: Input Series.\n    :param moments_dict: If not `None`, then the mean and standard\n        deviation used to compute the z-score transformation is\n        saved as entries in `moments_dict` with keys determined by\n        the `keys` argument; defaults to `None`.\n    :param keys: Determines the keys saved in `moments_dict`\n        if moments are saved; defaults to (`'mean'`, `'std'`).\n    :return: Transformed Series.\n    \"\"\"\n    mean = s.mean()\n    std = s.std()\n    if std == 0:\n        return 0\n    if moments_dict is not None:\n        moments_dict[keys[0]] = mean\n        moments_dict[keys[1]] = std\n    return (s - mean) / std\n\n\n@pf.register_series_method\ndef ecdf(s: pd.Series) -> Tuple[np.ndarray, np.ndarray]:\n    \"\"\"\n    Return cumulative distribution of values in a series.\n\n    Intended to be used with the following pattern:\n\n    ```python\n    df = pd.DataFrame(...)\n\n    # Obtain ECDF values to be plotted\n    x, y = df[\"column_name\"].ecdf()\n\n    # Plot ECDF values\n    plt.scatter(x, y)\n    ```\n\n    Null values must be dropped from the series,\n    otherwise a `ValueError` is raised.\n\n    Also, if the `dtype` of the series is not numeric,\n    a `TypeError` is raised.\n\n    :param s: A pandas series. `dtype` should be numeric.\n    :returns: `(x, y)`.\n        `x`: sorted array of values.\n        `y`: cumulative fraction of data points with value `x` or lower.\n    :raises TypeError: if series is not numeric.\n    :raises ValueError: if series contains nulls.\n    \"\"\"\n    if not is_numeric_dtype(s):\n        raise TypeError(f\"series {s.name} must be numeric!\")\n    if not s.isna().sum() == 0:\n        raise ValueError(f\"series {s.name} contains nulls. Please drop them.\")\n\n    n = len(s)\n    x = np.sort(s)\n    y = np.arange(1, n + 1) / n\n\n    return x, y\n", "path": "janitor/math.py"}]}