Repository: pandas-dev/pandas (python)

Issue:
DOC: fix SA05 errors in docstrings 
Pandas has a script for validating docstrings:

https://github.com/pandas-dev/pandas/blob/1d7aedcd19470f689e4db625271802eb49531e80/ci/code_checks.sh#L145-L206

Currently, some methods fail the SA05 check.

The task here is:

- take 2-4 methods
- run: scripts/validate_docstrings.py --format=actions --errors=SA05 `method-name`
- check if validation docstrings passes for those methods, and if it’s necessary fix the docstrings according to whatever error is reported
- remove those methods from code_checks.sh
- commit, push, open pull request
- Please don't comment `take` as multiple people can work on this issue. You also don't need to ask for permission to work on this, just comment on which methods are you going to work.

If you're new contributor, please check the [contributing guide](https://pandas.pydata.org/docs/dev/development/contributing.html)

Pull request description:
DOC: fix SA05 errors in docstrings for pandas.core.resample.Resampler…
Small code change, removing the two functions above for SA05 ignore functions.
- [ ] xref #57392 
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Patch:
diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index 54e12496cf..5eae5c6b60 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -1870,8 +1870,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
     $BASE_DIR/scripts/validate_docstrings.py --format=actions --errors=SA05 --ignore_functions \
         pandas.core.groupby.SeriesGroupBy.first\
         pandas.core.groupby.SeriesGroupBy.last\
-        pandas.core.resample.Resampler.first\
-        pandas.core.resample.Resampler.last\
         pandas.core.window.expanding.Expanding.aggregate\
         pandas.core.window.rolling.Rolling.aggregate\
         pandas.plotting.bootstrap_plot\
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index ae135fd41d..35769d9c5f 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -31,6 +31,7 @@ Other enhancements
 - :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`)
 - :func:`read_stata` now returns ``datetime64`` resolutions better matching those natively stored in the stata format (:issue:`55642`)
 - Allow dictionaries to be passed to :meth:`pandas.Series.str.replace` via ``pat`` parameter (:issue:`51748`)
+- Support passing a :class:`Series` input to :func:`json_normalize` that retains the :class:`Series` :class:`Index` (:issue:`51452`)
 -
 
 .. ---------------------------------------------------------------------------
diff --git a/pandas/io/json/_normalize.py b/pandas/io/json/_normalize.py
index 49f95430d9..f784004487 100644
--- a/pandas/io/json/_normalize.py
+++ b/pandas/io/json/_normalize.py
@@ -19,7 +19,10 @@ import numpy as np
 from pandas._libs.writers import convert_json_to_lines
 
 import pandas as pd
-from pandas import DataFrame
+from pandas import (
+    DataFrame,
+    Series,
+)
 
 if TYPE_CHECKING:
     from collections.abc import Iterable
@@ -266,7 +269,7 @@ def _simple_json_normalize(
 
 
 def json_normalize(
-    data: dict | list[dict],
+    data: dict | list[dict] | Series,
     record_path: str | list | None = None,
     meta: str | list[str | list[str]] | None = None,
     meta_prefix: str | None = None,
@@ -280,7 +283,7 @@ def json_normalize(
 
     Parameters
     ----------
-    data : dict or list of dicts
+    data : dict, list of dicts, or Series of dicts
         Unserialized JSON objects.
     record_path : str or list of str, default None
         Path in each object to list of records. If not passed, data will be
@@ -365,6 +368,26 @@ def json_normalize(
     1  NaN    Mark Reg             130              60
     2  2.0  Faye Raker             130              60
 
+    >>> data = [
+    ...     {
+    ...         "id": 1,
+    ...         "name": "Cole Volk",
+    ...         "fitness": {"height": 130, "weight": 60},
+    ...     },
+    ...     {"name": "Mark Reg", "fitness": {"height": 130, "weight": 60}},
+    ...     {
+    ...         "id": 2,
+    ...         "name": "Faye Raker",
+    ...         "fitness": {"height": 130, "weight": 60},
+    ...     },
+    ... ]
+    >>> series = pd.Series(data, index=pd.Index(["a", "b", "c"]))
+    >>> pd.json_normalize(series)
+        id        name  fitness.height  fitness.weight
+    a  1.0   Cole Volk             130              60
+    b  NaN    Mark Reg             130              60
+    c  2.0  Faye Raker             130              60
+
     >>> data = [
     ...     {
     ...         "state": "Florida",
@@ -455,6 +478,11 @@ def json_normalize(
                 )
         return result
 
+    if isinstance(data, Series):
+        index = data.index
+    else:
+        index = None
+
     if isinstance(data, list) and not data:
         return DataFrame()
     elif isinstance(data, dict):
@@ -477,7 +505,7 @@ def json_normalize(
         and record_prefix is None
         and max_level is None
     ):
-        return DataFrame(_simple_json_normalize(data, sep=sep))
+        return DataFrame(_simple_json_normalize(data, sep=sep), index=index)
 
     if record_path is None:
         if any([isinstance(x, dict) for x in y.values()] for y in data):
@@ -489,7 +517,7 @@ def json_normalize(
             # TODO: handle record value which are lists, at least error
             #       reasonably
             data = nested_to_record(data, sep=sep, max_level=max_level)
-        return DataFrame(data)
+        return DataFrame(data, index=index)
     elif not isinstance(record_path, list):
         record_path = [record_path]
 
@@ -564,4 +592,6 @@ def json_normalize(
                 values[i] = val
 
         result[k] = values.repeat(lengths)
+    if index is not None:
+        result.index = index.repeat(lengths)
     return result


Test patch:
diff --git a/pandas/tests/io/json/test_normalize.py b/pandas/tests/io/json/test_normalize.py
index 0f33883feb..d83e7b4641 100644
--- a/pandas/tests/io/json/test_normalize.py
+++ b/pandas/tests/io/json/test_normalize.py
@@ -561,6 +561,14 @@ class TestJSONNormalize:
 
         tm.assert_frame_equal(result, expected)
 
+    def test_series_index(self, state_data):
+        idx = Index([7, 8])
+        series = Series(state_data, index=idx)
+        result = json_normalize(series)
+        tm.assert_index_equal(result.index, idx)
+        result = json_normalize(series, "counties")
+        tm.assert_index_equal(result.index, idx.repeat([3, 2]))
+
 
 class TestNestedToRecord:
     def test_flat_stays_flat(self):
@@ -891,6 +899,7 @@ class TestNestedToRecord:
                 "elements.a": [1.0, np.nan, np.nan],
                 "elements.b": [np.nan, 2.0, np.nan],
                 "elements.c": [np.nan, np.nan, 3.0],
-            }
+            },
+            index=[1, 2, 3],
         )
         tm.assert_frame_equal(result, expected)