diff --git a/doc/source/whatsnew/v1.3.1.rst b/doc/source/whatsnew/v1.3.1.rst
index 33f286fccccef..70bfc349607da 100644
--- a/doc/source/whatsnew/v1.3.1.rst
+++ b/doc/source/whatsnew/v1.3.1.rst
@@ -37,6 +37,7 @@ Bug fixes
~~~~~~~~~
- Fixed bug in :meth:`DataFrame.transpose` dropping values when the DataFrame had an Extension Array dtype and a duplicate index (:issue:`42380`)
- Fixed bug in :meth:`DataFrame.to_xml` raising ``KeyError`` when called with ``index=False`` and an offset index (:issue:`42458`)
+- Fixed bug in :meth:`.Styler.set_sticky` not handling index names correctly for single index columns case (:issue:`42537`)
- Fixed bug in :meth:`DataFrame.copy` failing to consolidate blocks in the result (:issue:`42579`)
.. ---------------------------------------------------------------------------
diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py
index cb56ea33acad8..91a301b665f7c 100644
--- a/pandas/io/formats/style.py
+++ b/pandas/io/formats/style.py
@@ -1450,7 +1450,8 @@ def set_sticky(
Whether to make the index or column headers sticky.
pixel_size : int, optional
Required to configure the width of index cells or the height of column
- header cells when sticking a MultiIndex. Defaults to 75 and 25 respectively.
+ header cells when sticking a MultiIndex (or with a named Index).
+ Defaults to 75 and 25 respectively.
levels : list of int
If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
stick all levels.
@@ -1458,6 +1459,16 @@ def set_sticky(
Returns
-------
self : Styler
+
+ Notes
+ -----
+ This method uses the CSS 'position: sticky;' property to display. It is
+ designed to work with visible axes, therefore both:
+
+ - `styler.set_sticky(axis="index").hide_index()`
+ - `styler.set_sticky(axis="columns").hide_columns()`
+
+ may produce strange behaviour due to CSS controls with missing elements.
"""
if axis in [0, "index"]:
axis, obj, tag, pos = 0, self.data.index, "tbody", "left"
@@ -1469,15 +1480,42 @@ def set_sticky(
raise ValueError("`axis` must be one of {0, 1, 'index', 'columns'}")
if not isinstance(obj, pd.MultiIndex):
- return self.set_table_styles(
- [
+ # handling MultiIndexes requires different CSS
+ props = "position:sticky; background-color:white;"
+
+ if axis == 1:
+ # stick the first
of and, if index names, the second
+ # if self._hide_columns then no here will exist: no conflict
+ styles: CSSStyles = [
{
- "selector": f"{tag} th",
- "props": f"position:sticky; {pos}:0px; background-color:white;",
+ "selector": "thead tr:first-child",
+ "props": props + "top:0px; z-index:2;",
}
- ],
- overwrite=False,
- )
+ ]
+ if not self.index.names[0] is None:
+ styles[0]["props"] = (
+ props + f"top:0px; z-index:2; height:{pixel_size}px;"
+ )
+ styles.append(
+ {
+ "selector": "thead tr:nth-child(2)",
+ "props": props
+ + f"top:{pixel_size}px; z-index:2; height:{pixel_size}px; ",
+ }
+ )
+ else:
+ # stick the first of each |
in both and
+ # if self._hide_index then no will exist in | : no conflict
+ # but will exist in : conflict with initial element
+ styles = [
+ {
+ "selector": "tr th:first-child",
+ "props": props + "left:0px; z-index:1;",
+ }
+ ]
+
+ return self.set_table_styles(styles, overwrite=False)
+
else:
range_idx = list(range(obj.nlevels))
diff --git a/pandas/tests/io/formats/style/test_html.py b/pandas/tests/io/formats/style/test_html.py
index 495dc82f0e7bd..4e71cb4c46626 100644
--- a/pandas/tests/io/formats/style/test_html.py
+++ b/pandas/tests/io/formats/style/test_html.py
@@ -272,17 +272,35 @@ def test_caption_as_sequence(styler):
@pytest.mark.parametrize("index", [False, True])
@pytest.mark.parametrize("columns", [False, True])
-def test_sticky_basic(styler, index, columns):
+@pytest.mark.parametrize("index_name", [True, False])
+def test_sticky_basic(styler, index, columns, index_name):
+ if index_name:
+ styler.index.name = "some text"
if index:
styler.set_sticky(axis=0)
if columns:
styler.set_sticky(axis=1)
res = styler.set_uuid("").to_html()
- cs1 = "tbody th {\n position: sticky;\n left: 0px;\n background-color: white;\n}"
- assert (cs1 in res) is index
- cs2 = "thead th {\n position: sticky;\n top: 0px;\n background-color: white;\n}"
- assert (cs2 in res) is columns
+
+ css_for_index = (
+ "tr th:first-child {\n position: sticky;\n background-color: white;\n "
+ "left: 0px;\n z-index: 1;\n}"
+ )
+ assert (css_for_index in res) is index
+
+ css_for_cols_1 = (
+ "thead tr:first-child {\n position: sticky;\n background-color: white;\n "
+ "top: 0px;\n z-index: 2;\n"
+ )
+ css_for_cols_1 += " height: 25px;\n}" if index_name else "}"
+ assert (css_for_cols_1 in res) is columns
+
+ css_for_cols_2 = (
+ "thead tr:nth-child(2) {\n position: sticky;\n background-color: white;\n "
+ "top: 25px;\n z-index: 2;\n height: 25px;\n}"
+ )
+ assert (css_for_cols_2 in res) is (index_name and columns)
@pytest.mark.parametrize("index", [False, True])
|