From 8c3535e2d9fd1c7335e40dad299516f06320be45 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 11:08:58 -0500 Subject: [PATCH 01/26] Adds ability to store tooltips as title attribute through pandas styler --- pandas/io/formats/style.py | 25 +++++-- pandas/io/formats/style_render.py | 68 ++++++++++++------- pandas/tests/io/formats/style/test_tooltip.py | 46 +++++++++++++ 3 files changed, 112 insertions(+), 27 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index c85c6c3ef0ff7..056c162ac92b3 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -419,6 +419,7 @@ def set_tooltips( ttips: DataFrame, props: CSSProperties | None = None, css_class: str | None = None, + as_title_attribute: bool = False, ) -> Styler: """ Set the DataFrame of strings on ``Styler`` generating ``:hover`` tooltips. @@ -442,6 +443,9 @@ def set_tooltips( Name of the tooltip class used in CSS, should conform to HTML standards. Only useful if integrating tooltips with external CSS. If ``None`` uses the internal default value 'pd-t'. + as_title_attribute: bool, default False + Add the tooltip text as title attribute to resultant element. If True + then props and css_class arguments are ignored. Returns ------- @@ -470,6 +474,12 @@ def set_tooltips( additional HTML for larger tables, since they also require that ``cell_ids`` is forced to `True`. + If multiline tooltips are required, or if styling is not required and/or + space is of concern, then utilizing as_title_attribute as True will store + the tooltip on the title attribute. This will cause no CSS + to be generated nor will the the elements. Storing tooltips through + the title attribute will mean that styling effects do not apply. + Examples -------- Basic application @@ -490,6 +500,10 @@ def set_tooltips( ... ttips, css_class='tt-add', ... props='visibility:hidden; position:absolute; z-index:1;') ... # doctest: +SKIP + + Multiline tooltips with smaller size footprint + + >>> df.style.set_tooltips(ttips, as_title_attribute=True) # doctest: +SKIP """ if not self.cell_ids: # tooltips not optimised for individual cell check. requires reasonable @@ -504,10 +518,13 @@ def set_tooltips( if self.tooltips is None: # create a default instance if necessary self.tooltips = Tooltips() self.tooltips.tt_data = ttips - if props: - self.tooltips.class_properties = props - if css_class: - self.tooltips.class_name = css_class + if not as_title_attribute: + if props: + self.tooltips.class_properties = props + if css_class: + self.tooltips.class_name = css_class + else: + self.tooltips.as_title_attribute = as_title_attribute return self diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 38afde1eb762b..b05dcfbe5dff6 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1978,6 +1978,11 @@ class Tooltips: tooltips: DataFrame, default empty DataFrame of strings aligned with underlying Styler data for tooltip display. + as_title_attribute: bool, default False + Flag to use title attribute based tooltips (True) or based + tooltips (False). + Add the tooltip text as title attribute to resultant element. If + True, no CSS is generated and styling effects do not apply. Notes ----- @@ -2006,11 +2011,13 @@ def __init__( ], css_name: str = "pd-t", tooltips: DataFrame = DataFrame(), + as_title_attribute: bool = False, ) -> None: self.class_name = css_name self.class_properties = css_props self.tt_data = tooltips self.table_styles: CSSStyles = [] + self.as_title_attribute = as_title_attribute @property def _class_styles(self): @@ -2098,35 +2105,50 @@ def _translate(self, styler: StylerRenderer, d: dict): if self.tt_data.empty: return d - name = self.class_name mask = (self.tt_data.isna()) | (self.tt_data.eq("")) # empty string = no ttip - self.table_styles = [ - style - for sublist in [ - self._pseudo_css(styler.uuid, name, i, j, str(self.tt_data.iloc[i, j])) - for i in range(len(self.tt_data.index)) - for j in range(len(self.tt_data.columns)) - if not ( - mask.iloc[i, j] - or i in styler.hidden_rows - or j in styler.hidden_columns - ) + if not self.as_title_attribute: + name = self.class_name + self.table_styles = [ + style + for sublist in [ + self._pseudo_css( + styler.uuid, name, i, j, str(self.tt_data.iloc[i, j]) + ) + for i in range(len(self.tt_data.index)) + for j in range(len(self.tt_data.columns)) + if not ( + mask.iloc[i, j] + or i in styler.hidden_rows + or j in styler.hidden_columns + ) + ] + for style in sublist ] - for style in sublist - ] - if self.table_styles: - # add span class to every cell only if at least 1 non-empty tooltip + if self.table_styles: + # add span class to every cell only if at least 1 non-empty tooltip + for row in d["body"]: + for item in row: + if item["type"] == "td": + item["display_value"] = ( + str(item["display_value"]) + + f'' + ) + d["table_styles"].extend(self._class_styles) + d["table_styles"].extend(self.table_styles) + else: + id_regex = re.compile("row(\d+)_col(\d+)") for row in d["body"]: for item in row: if item["type"] == "td": - item["display_value"] = ( - str(item["display_value"]) - + f'' - ) - d["table_styles"].extend(self._class_styles) - d["table_styles"].extend(self.table_styles) - + td_id = item["id"] + i, j = (int(x) for x in id_regex.match(td_id).groups()) + if ( + not mask.iloc[i, j] + or i in styler.hidden_rows + or j in styler.hidden_columns + ): + item["attributes"] += f'title="{self.tt_data.iloc[i, j]}"' return d diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index c49a0e05c6700..aeeef72c85beb 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -64,6 +64,7 @@ def test_tooltip_ignored(styler): result = styler.to_html() # no set_tooltips() creates no assert '' in result assert '' not in result + assert 'title="' not in result def test_tooltip_css_class(styler): @@ -83,3 +84,48 @@ def test_tooltip_css_class(styler): props="color:green;color:red;", ).to_html() assert "#T_ .another-class {\n color: green;\n color: red;\n}" in result + + +@pytest.mark.parametrize( + "ttips", + [ + DataFrame( # Test basic reindex and ignoring blank + data=[["Min", "Max"], [np.nan, ""]], + columns=["A", "C"], + index=["x", "y"], + ), + DataFrame( # Test non-referenced columns, reversed col names, short index + data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] + ), + ], +) +def test_tooltip_render_as_title(ttips, styler): + # GH 21266 + result = styler.set_tooltips(ttips, as_title_attribute=True).to_html() + + # test css not added + assert "#T_ .pd-t {\n visibility: hidden;\n" not in result + + # test 'Min' tooltip added as title attribute and css does not exist + assert "#T_ #T__row0_col0:hover .pd-t {\n visibility: visible;\n}" not in result + assert '#T_ #T__row0_col0 .pd-t::after {\n content: "Min";\n}' not in result + assert 'class="data row0 col0" title="Min">0' in result + + # test 'Max' tooltip added as title attribute and css does not exist + assert "#T_ #T__row0_col2:hover .pd-t {\n visibility: visible;\n}" not in result + assert '#T_ #T__row0_col2 .pd-t::after {\n content: "Max";\n}' not in result + assert 'class="data row0 col2" title="Max">2' in result + + # test Nan, empty string and bad column ignored + assert "#T_ #T__row1_col0:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row1_col1:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row0_col1:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row1_col2:hover .pd-t {\n visibility: visible;\n}" not in result + assert "Bad-Col" not in result + assert 'class="data row0 col1" >1' in result + assert 'class="data row1 col0" >3' in result + assert 'class="data row1 col1" >4' in result + assert 'class="data row1 col2" >5' in result + assert 'class="data row2 col0" >6' in result + assert 'class="data row2 col1" >7' in result + assert 'class="data row2 col2" >8' in result From 9eec2544ae470851bdfae16c9b16b2b5152abbaa Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 11:15:43 -0500 Subject: [PATCH 02/26] Adds entry to whatsnew --- doc/source/whatsnew/v2.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 629b044f24f90..9d82004750795 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -208,7 +208,7 @@ ExtensionArray Styler ^^^^^^ -- +- Enh in :meth:`Styler.set_tooltips` allowing users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at losing the ability to style the tooltip text. - Other From 17f54f8fc9694e66d8250b998ac6c21aa023c8bb Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 11:19:41 -0500 Subject: [PATCH 03/26] Updates GH issue number comment --- pandas/tests/io/formats/style/test_tooltip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index aeeef72c85beb..aa4980edc803f 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -100,7 +100,7 @@ def test_tooltip_css_class(styler): ], ) def test_tooltip_render_as_title(ttips, styler): - # GH 21266 + # GH 56605 result = styler.set_tooltips(ttips, as_title_attribute=True).to_html() # test css not added From a2abc1069c97cb1f43bc02dbbecf87020dc8b604 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 11:26:40 -0500 Subject: [PATCH 04/26] Fixes trailing whitespace precommit check --- doc/source/whatsnew/v2.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 9d82004750795..720deea460cad 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -208,7 +208,7 @@ ExtensionArray Styler ^^^^^^ -- Enh in :meth:`Styler.set_tooltips` allowing users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at losing the ability to style the tooltip text. +- Enh in :meth:`Styler.set_tooltips` allowing users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at losing the ability to style the tooltip text. - Other From ccaae3925ff922e746b0d8be2257712abe4feb83 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 11:39:15 -0500 Subject: [PATCH 05/26] Performs ruff to pass precommit checks --- pandas/io/formats/style_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index b05dcfbe5dff6..309d83d868156 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2137,7 +2137,7 @@ def _translate(self, styler: StylerRenderer, d: dict): d["table_styles"].extend(self._class_styles) d["table_styles"].extend(self.table_styles) else: - id_regex = re.compile("row(\d+)_col(\d+)") + id_regex = re.compile(r"row(\d+)_col(\d+)") for row in d["body"]: for item in row: if item["type"] == "td": From ce0bc5ae74043cc4fd43fbbbd15b41dc6c9448f0 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 13:06:25 -0500 Subject: [PATCH 06/26] Resolves mypy errors --- pandas/io/formats/style.py | 4 ++-- pandas/io/formats/style_render.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 056c162ac92b3..15b4e8aebd5d5 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -443,7 +443,7 @@ def set_tooltips( Name of the tooltip class used in CSS, should conform to HTML standards. Only useful if integrating tooltips with external CSS. If ``None`` uses the internal default value 'pd-t'. - as_title_attribute: bool, default False + as_title_attribute : bool, default False Add the tooltip text as title attribute to resultant element. If True then props and css_class arguments are ignored. @@ -503,7 +503,7 @@ def set_tooltips( Multiline tooltips with smaller size footprint - >>> df.style.set_tooltips(ttips, as_title_attribute=True) # doctest: +SKIP + >>> df.style.set_tooltips(ttips, as_title_attribute=True) # doctest: +SKIP """ if not self.cell_ids: # tooltips not optimised for individual cell check. requires reasonable diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 309d83d868156..5c37a16cd2a79 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2142,13 +2142,17 @@ def _translate(self, styler: StylerRenderer, d: dict): for item in row: if item["type"] == "td": td_id = item["id"] - i, j = (int(x) for x in id_regex.match(td_id).groups()) - if ( - not mask.iloc[i, j] - or i in styler.hidden_rows - or j in styler.hidden_columns - ): - item["attributes"] += f'title="{self.tt_data.iloc[i, j]}"' + match = id_regex.match(td_id) + if match is not None: + i, j = (int(x) for x in match.groups()) + if ( + not mask.iloc[i, j] + or i in styler.hidden_rows + or j in styler.hidden_columns + ): + item[ + "attributes" + ] += f'title="{self.tt_data.iloc[i, j]}"' return d From eba47192b2930bcebeed194b77acf57cbf15c025 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 13:39:41 -0500 Subject: [PATCH 07/26] Fixes grammar in whatsnew --- doc/source/whatsnew/v2.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 720deea460cad..e915599bee663 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -208,7 +208,7 @@ ExtensionArray Styler ^^^^^^ -- Enh in :meth:`Styler.set_tooltips` allowing users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at losing the ability to style the tooltip text. +- Enh in :meth:`Styler.set_tooltips` allows users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at the cost of losing the ability to style the tooltip text. - Other From 67c563db45f983b115175334f0a47e080a3b20ff Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 20 Jan 2024 13:41:49 -0500 Subject: [PATCH 08/26] Another grammar fix in whatsnew --- doc/source/whatsnew/v2.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index e915599bee663..7a4e7c3b7916b 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -208,7 +208,7 @@ ExtensionArray Styler ^^^^^^ -- Enh in :meth:`Styler.set_tooltips` allows users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and size footprint at the cost of losing the ability to style the tooltip text. +- Enh in :meth:`Styler.set_tooltips` allows users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and reduced size footprint at the cost of losing the ability to style the tooltip text. - Other From 55e5af25d48c7778cad6d08930beb52b6f9f6112 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 27 Jan 2024 19:48:37 -0500 Subject: [PATCH 09/26] Changes loop to not utilize regex --- asv_bench/benchmarks/indexing.py | 4 +++- asv_bench/benchmarks/io/style.py | 2 +- pandas/_libs/hashtable.pyi | 5 +---- pandas/_testing/_hypothesis.py | 8 ++++++-- pandas/core/arrays/base.py | 4 +++- pandas/io/formats/style_render.py | 30 ++++++++++++++---------------- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 86da26bead64d..9ad1f5b31016d 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -84,7 +84,9 @@ def time_loc_slice(self, index, index_structure): class NumericMaskedIndexing: monotonic_list = list(range(10**6)) - non_monotonic_list = list(range(50)) + [54, 53, 52, 51] + list(range(55, 10**6 - 1)) + non_monotonic_list = ( + list(range(50)) + [54, 53, 52, 51] + list(range(55, 10**6 - 1)) + ) params = [ ("Int64", "UInt64", "Float64"), diff --git a/asv_bench/benchmarks/io/style.py b/asv_bench/benchmarks/io/style.py index 24fd8a0d20aba..51ee294c0dd39 100644 --- a/asv_bench/benchmarks/io/style.py +++ b/asv_bench/benchmarks/io/style.py @@ -77,7 +77,7 @@ def _style_format(self): # subset is flexible but hinders vectorised solutions self.st = self.df.style.format( "{:,.3f}", - subset=IndexSlice["row_1" : f"row_{ir}", "float_1" : f"float_{ic}"], + subset=IndexSlice["row_1":f"row_{ir}", "float_1":f"float_{ic}"], ) def _style_apply_format_hide(self): diff --git a/pandas/_libs/hashtable.pyi b/pandas/_libs/hashtable.pyi index 3725bfa3362d9..a3f7d122ff44b 100644 --- a/pandas/_libs/hashtable.pyi +++ b/pandas/_libs/hashtable.pyi @@ -204,10 +204,7 @@ class HashTable: *, return_inverse: Literal[False] = ..., mask: npt.NDArray[np.bool_], - ) -> tuple[ - np.ndarray, - npt.NDArray[np.bool_], - ]: ... # np.ndarray[subclass-specific] + ) -> tuple[np.ndarray, npt.NDArray[np.bool_],]: ... # np.ndarray[subclass-specific] def factorize( self, values: np.ndarray, # np.ndarray[subclass-specific] diff --git a/pandas/_testing/_hypothesis.py b/pandas/_testing/_hypothesis.py index f9f653f636c4c..084ca9c306d19 100644 --- a/pandas/_testing/_hypothesis.py +++ b/pandas/_testing/_hypothesis.py @@ -54,8 +54,12 @@ DATETIME_NO_TZ = st.datetimes() DATETIME_JAN_1_1900_OPTIONAL_TZ = st.datetimes( - min_value=pd.Timestamp(1900, 1, 1).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] - max_value=pd.Timestamp(1900, 1, 1).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] + min_value=pd.Timestamp( + 1900, 1, 1 + ).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] + max_value=pd.Timestamp( + 1900, 1, 1 + ).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] timezones=st.one_of(st.none(), dateutil_timezones(), pytz_timezones()), ) diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 58264f2aef6f3..47660d8b32af9 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -1492,7 +1492,9 @@ def factorize( uniques_ea = self._from_factorized(uniques, self) return codes, uniques_ea - _extension_array_shared_docs["repeat"] = """ + _extension_array_shared_docs[ + "repeat" + ] = """ Repeat elements of a %(klass)s. Returns a new %(klass)s where each element of the current %(klass)s diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 5c37a16cd2a79..4fa22fde791f3 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2137,22 +2137,20 @@ def _translate(self, styler: StylerRenderer, d: dict): d["table_styles"].extend(self._class_styles) d["table_styles"].extend(self.table_styles) else: - id_regex = re.compile(r"row(\d+)_col(\d+)") - for row in d["body"]: - for item in row: - if item["type"] == "td": - td_id = item["id"] - match = id_regex.match(td_id) - if match is not None: - i, j = (int(x) for x in match.groups()) - if ( - not mask.iloc[i, j] - or i in styler.hidden_rows - or j in styler.hidden_columns - ): - item[ - "attributes" - ] += f'title="{self.tt_data.iloc[i, j]}"' + index_offset = self.tt_data.index.nlevels + body = d['body'] + for i in range(len(self.tt_data.index)): + for j in range(len(self.tt_data.columns)): + if ( + not mask.iloc[i, j] + or i in styler.hidden_rows + or j in styler.hidden_columns + ): + row = body[i] + item = row[j + index_offset] + item[ + "attributes" + ] += f'title="{self.tt_data.iloc[i, j]}"' return d From 80eeb57c410b9d10f62bf78bcb8daa0ec9e1a33e Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 27 Jan 2024 19:53:11 -0500 Subject: [PATCH 10/26] Formatting, and simplified whatsnew --- doc/source/whatsnew/v2.3.0.rst | 2 +- pandas/io/formats/style_render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 7a4e7c3b7916b..e910df3b20d12 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -208,7 +208,7 @@ ExtensionArray Styler ^^^^^^ -- Enh in :meth:`Styler.set_tooltips` allows users to store tooltips as title attribute on elements. Benefits include multiline tooltip support and reduced size footprint at the cost of losing the ability to style the tooltip text. +- Enh in :meth:`Styler.set_tooltips` provides alternative method to storing tooltips by using title attribute of td elements. - Other diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 4fa22fde791f3..887000de6acc4 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2138,7 +2138,7 @@ def _translate(self, styler: StylerRenderer, d: dict): d["table_styles"].extend(self.table_styles) else: index_offset = self.tt_data.index.nlevels - body = d['body'] + body = d["body"] for i in range(len(self.tt_data.index)): for j in range(len(self.tt_data.columns)): if ( From 9ffebeec62adb8094e9f43fcb0547daedb8d4cc8 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 27 Jan 2024 20:31:23 -0500 Subject: [PATCH 11/26] Adds check for types of quotes used --- pandas/io/formats/style_render.py | 8 +++++--- pandas/tests/io/formats/style/test_tooltip.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 887000de6acc4..6a6675987baf3 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2148,9 +2148,11 @@ def _translate(self, styler: StylerRenderer, d: dict): ): row = body[i] item = row[j + index_offset] - item[ - "attributes" - ] += f'title="{self.tt_data.iloc[i, j]}"' + value = self.tt_data.iloc[i, j] + if '"' in value: + item["attributes"] += f" title='{value}'" + else: + item["attributes"] += f' title="{value}"' return d diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index aa4980edc803f..c998bdc4106b9 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -109,12 +109,12 @@ def test_tooltip_render_as_title(ttips, styler): # test 'Min' tooltip added as title attribute and css does not exist assert "#T_ #T__row0_col0:hover .pd-t {\n visibility: visible;\n}" not in result assert '#T_ #T__row0_col0 .pd-t::after {\n content: "Min";\n}' not in result - assert 'class="data row0 col0" title="Min">0' in result + assert 'class="data row0 col0" title="Min">0' in result # test 'Max' tooltip added as title attribute and css does not exist assert "#T_ #T__row0_col2:hover .pd-t {\n visibility: visible;\n}" not in result assert '#T_ #T__row0_col2 .pd-t::after {\n content: "Max";\n}' not in result - assert 'class="data row0 col2" title="Max">2' in result + assert 'class="data row0 col2" title="Max">2' in result # test Nan, empty string and bad column ignored assert "#T_ #T__row1_col0:hover .pd-t {\n visibility: visible;\n}" not in result From a0ba24b2f09ca90b9609bc84b34bfe246385cc6f Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 28 Jan 2024 10:23:30 -0500 Subject: [PATCH 12/26] Removes quotation mark check --- pandas/io/formats/style_render.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 6a6675987baf3..0cb18a541b653 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2137,6 +2137,7 @@ def _translate(self, styler: StylerRenderer, d: dict): d["table_styles"].extend(self._class_styles) d["table_styles"].extend(self.table_styles) else: + if not styler.hide_index_ index_offset = self.tt_data.index.nlevels body = d["body"] for i in range(len(self.tt_data.index)): @@ -2149,10 +2150,7 @@ def _translate(self, styler: StylerRenderer, d: dict): row = body[i] item = row[j + index_offset] value = self.tt_data.iloc[i, j] - if '"' in value: - item["attributes"] += f" title='{value}'" - else: - item["attributes"] += f' title="{value}"' + item["attributes"] += f' title="{value}"' return d From 942b089f381d9b51c8556d8ab43f8041090fbf2d Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 3 Feb 2024 12:05:38 -0500 Subject: [PATCH 13/26] Adds additional test to convince myself of functionality --- pandas/io/formats/style_render.py | 1 - pandas/tests/io/formats/style/test_tooltip.py | 93 +++++++++++++------ 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0cb18a541b653..384f099baf7e2 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2137,7 +2137,6 @@ def _translate(self, styler: StylerRenderer, d: dict): d["table_styles"].extend(self._class_styles) d["table_styles"].extend(self.table_styles) else: - if not styler.hide_index_ index_offset = self.tt_data.index.nlevels body = d["body"] for i in range(len(self.tt_data.index)): diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index c998bdc4106b9..cb72b2e47aa42 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from pandas import DataFrame +from pandas import DataFrame, MultiIndex pytest.importorskip("jinja2") from pandas.io.formats.style import Styler @@ -21,19 +21,23 @@ def styler(df): return Styler(df, uuid_len=0) -@pytest.mark.parametrize( - "ttips", - [ - DataFrame( # Test basic reindex and ignoring blank - data=[["Min", "Max"], [np.nan, ""]], - columns=["A", "C"], - index=["x", "y"], - ), - DataFrame( # Test non-referenced columns, reversed col names, short index - data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] - ), - ], -) +ttips_dfs = [ + DataFrame( # Test basic reindex and ignoring blank + data=[["Min", "Max"], [np.nan, ""]], + columns=["A", "C"], + index=["x", "y"], + ), + DataFrame( # Test non-referenced columns, reversed col names, short index + data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] + ), +] + + +@pytest.fixture(params=ttips_dfs) +def ttips(request): + return request.param + + def test_tooltip_render(ttips, styler): # GH 21266 result = styler.set_tooltips(ttips).to_html() @@ -86,20 +90,57 @@ def test_tooltip_css_class(styler): assert "#T_ .another-class {\n color: green;\n color: red;\n}" in result -@pytest.mark.parametrize( - "ttips", - [ - DataFrame( # Test basic reindex and ignoring blank - data=[["Min", "Max"], [np.nan, ""]], - columns=["A", "C"], - index=["x", "y"], +def test_tooltip_render_as_title(ttips, styler): + # GH 56605 + result = styler.set_tooltips(ttips, as_title_attribute=True).to_html() + + # test css not added + assert "#T_ .pd-t {\n visibility: hidden;\n" not in result + + # test 'Min' tooltip added as title attribute and css does not exist + assert "#T_ #T__row0_col0:hover .pd-t {\n visibility: visible;\n}" not in result + assert '#T_ #T__row0_col0 .pd-t::after {\n content: "Min";\n}' not in result + assert 'class="data row0 col0" title="Min">0' in result + + # test 'Max' tooltip added as title attribute and css does not exist + assert "#T_ #T__row0_col2:hover .pd-t {\n visibility: visible;\n}" not in result + assert '#T_ #T__row0_col2 .pd-t::after {\n content: "Max";\n}' not in result + assert 'class="data row0 col2" title="Max">2' in result + + # test Nan, empty string and bad column ignored + assert "#T_ #T__row1_col0:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row1_col1:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row0_col1:hover .pd-t {\n visibility: visible;\n}" not in result + assert "#T_ #T__row1_col2:hover .pd-t {\n visibility: visible;\n}" not in result + assert "Bad-Col" not in result + assert 'class="data row0 col1" >1' in result + assert 'class="data row1 col0" >3' in result + assert 'class="data row1 col1" >4' in result + assert 'class="data row1 col2" >5' in result + assert 'class="data row2 col0" >6' in result + assert 'class="data row2 col1" >7' in result + assert 'class="data row2 col2" >8' in result + + +def test_tooltip_render_as_title_with_hidden_index_level(): + df = DataFrame( + data=[[0, 1, 2], [3, 4, 5], [6, 7, 8]], + columns=["A", "B", "C"], + index=MultiIndex.from_arrays( + [["x", "y", "z"], [1, 2, 3], ["aa", "bb", "cc"]], + names=["alpha", "num", "char"], ), - DataFrame( # Test non-referenced columns, reversed col names, short index - data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] + ) + ttips = DataFrame( + # Test basic reindex and ignoring blank, and hide level 2 (num) from index + data=[["Min", "Max"], [np.nan, ""]], + columns=["A", "C"], + index=MultiIndex.from_arrays( + [["x", "y"], [1, 2], ["aa", "bb"]], names=["alpha", "num", "char"] ), - ], -) -def test_tooltip_render_as_title(ttips, styler): + ) + styler = Styler(df, uuid_len=0) + styler = styler.hide(axis=0, level=-1, names=True) # GH 56605 result = styler.set_tooltips(ttips, as_title_attribute=True).to_html() From c248e4d4073b62c1bb005768f62e2dd475cdfcd0 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 3 Feb 2024 18:55:25 -0500 Subject: [PATCH 14/26] Update pandas/io/formats/style_render.py Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- pandas/io/formats/style_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 384f099baf7e2..0483f225a52f9 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2136,6 +2136,7 @@ def _translate(self, styler: StylerRenderer, d: dict): ) d["table_styles"].extend(self._class_styles) d["table_styles"].extend(self.table_styles) + # this conditional adds tooltips as extra "title" attribute on a element else: index_offset = self.tt_data.index.nlevels body = d["body"] From cd28f5963640e205c0a4e0b14333de7c125ebbbe Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sat, 3 Feb 2024 18:55:45 -0500 Subject: [PATCH 15/26] Update pandas/io/formats/style_render.py Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- pandas/io/formats/style_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0483f225a52f9..f6dbd4c190e7f 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2106,6 +2106,7 @@ def _translate(self, styler: StylerRenderer, d: dict): return d mask = (self.tt_data.isna()) | (self.tt_data.eq("")) # empty string = no ttip + # this conditional adds tooltips via pseudo css and elements. if not self.as_title_attribute: name = self.class_name self.table_styles = [ From 2d8c292323d112bd7740ad93c19ae5a610eaa792 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 4 Feb 2024 08:05:03 -0500 Subject: [PATCH 16/26] Resolves grammar issue --- pandas/io/formats/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 15b4e8aebd5d5..9046800cc9029 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -478,7 +478,7 @@ def set_tooltips( space is of concern, then utilizing as_title_attribute as True will store the tooltip on the title attribute. This will cause no CSS to be generated nor will the the elements. Storing tooltips through - the title attribute will mean that styling effects do not apply. + the title attribute will mean that tooltip styling effects do not apply. Examples -------- From 78c448e5a84aa9b825a5009c5f5dbbddc23b7a8b Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 4 Feb 2024 08:10:44 -0500 Subject: [PATCH 17/26] Isort to pass precommit --- pandas/tests/io/formats/style/test_tooltip.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index cb72b2e47aa42..c110ff34f34f3 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -1,7 +1,10 @@ import numpy as np import pytest -from pandas import DataFrame, MultiIndex +from pandas import ( + DataFrame, + MultiIndex, +) pytest.importorskip("jinja2") from pandas.io.formats.style import Styler From 743dc9359f24bfe192bceafb8ffbe0d80fcc0c0d Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 4 Feb 2024 08:34:48 -0500 Subject: [PATCH 18/26] Ruff format on merged files --- asv_bench/benchmarks/indexing.py | 4 +--- asv_bench/benchmarks/io/style.py | 2 +- pandas/_libs/hashtable.pyi | 5 ++++- pandas/_testing/_hypothesis.py | 8 ++------ pandas/core/apply.py | 2 +- pandas/core/arrays/base.py | 4 +--- pandas/io/parsers/c_parser_wrapper.py | 2 +- pandas/plotting/_matplotlib/core.py | 2 +- scripts/validate_min_versions_in_sync.py | 2 +- 9 files changed, 13 insertions(+), 18 deletions(-) diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 9ad1f5b31016d..86da26bead64d 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -84,9 +84,7 @@ def time_loc_slice(self, index, index_structure): class NumericMaskedIndexing: monotonic_list = list(range(10**6)) - non_monotonic_list = ( - list(range(50)) + [54, 53, 52, 51] + list(range(55, 10**6 - 1)) - ) + non_monotonic_list = list(range(50)) + [54, 53, 52, 51] + list(range(55, 10**6 - 1)) params = [ ("Int64", "UInt64", "Float64"), diff --git a/asv_bench/benchmarks/io/style.py b/asv_bench/benchmarks/io/style.py index 51ee294c0dd39..24fd8a0d20aba 100644 --- a/asv_bench/benchmarks/io/style.py +++ b/asv_bench/benchmarks/io/style.py @@ -77,7 +77,7 @@ def _style_format(self): # subset is flexible but hinders vectorised solutions self.st = self.df.style.format( "{:,.3f}", - subset=IndexSlice["row_1":f"row_{ir}", "float_1":f"float_{ic}"], + subset=IndexSlice["row_1" : f"row_{ir}", "float_1" : f"float_{ic}"], ) def _style_apply_format_hide(self): diff --git a/pandas/_libs/hashtable.pyi b/pandas/_libs/hashtable.pyi index a3f7d122ff44b..3725bfa3362d9 100644 --- a/pandas/_libs/hashtable.pyi +++ b/pandas/_libs/hashtable.pyi @@ -204,7 +204,10 @@ class HashTable: *, return_inverse: Literal[False] = ..., mask: npt.NDArray[np.bool_], - ) -> tuple[np.ndarray, npt.NDArray[np.bool_],]: ... # np.ndarray[subclass-specific] + ) -> tuple[ + np.ndarray, + npt.NDArray[np.bool_], + ]: ... # np.ndarray[subclass-specific] def factorize( self, values: np.ndarray, # np.ndarray[subclass-specific] diff --git a/pandas/_testing/_hypothesis.py b/pandas/_testing/_hypothesis.py index 084ca9c306d19..f9f653f636c4c 100644 --- a/pandas/_testing/_hypothesis.py +++ b/pandas/_testing/_hypothesis.py @@ -54,12 +54,8 @@ DATETIME_NO_TZ = st.datetimes() DATETIME_JAN_1_1900_OPTIONAL_TZ = st.datetimes( - min_value=pd.Timestamp( - 1900, 1, 1 - ).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] - max_value=pd.Timestamp( - 1900, 1, 1 - ).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] + min_value=pd.Timestamp(1900, 1, 1).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] + max_value=pd.Timestamp(1900, 1, 1).to_pydatetime(), # pyright: ignore[reportGeneralTypeIssues] timezones=st.one_of(st.none(), dateutil_timezones(), pytz_timezones()), ) diff --git a/pandas/core/apply.py b/pandas/core/apply.py index ebd714f9c14d4..f4d56c4b6f8f8 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -1798,7 +1798,7 @@ def normalize_keyword_aggregation( def _make_unique_kwarg_list( - seq: Sequence[tuple[Any, Any]] + seq: Sequence[tuple[Any, Any]], ) -> Sequence[tuple[Any, Any]]: """ Uniquify aggfunc name of the pairs in the order list diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 72791272df721..e41a96cfcef7e 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -1492,9 +1492,7 @@ def factorize( uniques_ea = self._from_factorized(uniques, self) return codes, uniques_ea - _extension_array_shared_docs[ - "repeat" - ] = """ + _extension_array_shared_docs["repeat"] = """ Repeat elements of a %(klass)s. Returns a new %(klass)s where each element of the current %(klass)s diff --git a/pandas/io/parsers/c_parser_wrapper.py b/pandas/io/parsers/c_parser_wrapper.py index f24d7a628998e..67f3e5a9f4880 100644 --- a/pandas/io/parsers/c_parser_wrapper.py +++ b/pandas/io/parsers/c_parser_wrapper.py @@ -396,7 +396,7 @@ def _concatenate_chunks(chunks: list[dict[int, ArrayLike]]) -> dict: def ensure_dtype_objs( - dtype: DtypeArg | dict[Hashable, DtypeArg] | None + dtype: DtypeArg | dict[Hashable, DtypeArg] | None, ) -> DtypeObj | dict[Hashable, DtypeObj] | None: """ Ensure we have either None, a dtype object, or a dictionary mapping to diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 6fa75ba5fb12d..1c8cd9a4970c8 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -465,7 +465,7 @@ def _validate_color_args(self, color, colormap): @final @staticmethod def _iter_data( - data: DataFrame | dict[Hashable, Series | DataFrame] + data: DataFrame | dict[Hashable, Series | DataFrame], ) -> Iterator[tuple[Hashable, np.ndarray]]: for col, values in data.items(): # This was originally written to use values.values before EAs diff --git a/scripts/validate_min_versions_in_sync.py b/scripts/validate_min_versions_in_sync.py index f8cce255f532d..14227730d74ce 100755 --- a/scripts/validate_min_versions_in_sync.py +++ b/scripts/validate_min_versions_in_sync.py @@ -105,7 +105,7 @@ def get_operator_from(dependency: str) -> str | None: def get_yaml_map_from( - yaml_dic: list[str | dict[str, list[str]]] + yaml_dic: list[str | dict[str, list[str]]], ) -> dict[str, list[str] | None]: yaml_map: dict[str, list[str] | None] = {} for dependency in yaml_dic: From d083cde62daf1507ba065c57f6a83773d608131c Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:15:24 -0500 Subject: [PATCH 19/26] Update doc/source/whatsnew/v3.0.0.rst Co-authored-by: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> --- doc/source/whatsnew/v3.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 0a9b0838c9e66..2446f3f23887d 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -225,7 +225,7 @@ ExtensionArray Styler ^^^^^^ -- Enh in :meth:`Styler.set_tooltips` provides alternative method to storing tooltips by using title attribute of td elements. +- :meth:`Styler.set_tooltips` provides alternative method to storing tooltips by using title attribute of td elements. (:issue:`56981`) - Other From 6cdce61ca7930df339bf36329ecbe9ec66cc62cc Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:18:11 -0500 Subject: [PATCH 20/26] Changes whatsnew section --- doc/source/whatsnew/v3.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 2446f3f23887d..8493890e4598b 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -30,6 +30,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`) +- :meth:`Styler.set_tooltips` provides alternative method to storing tooltips by using title attribute of td elements. (:issue:`56981`) - .. --------------------------------------------------------------------------- @@ -225,7 +226,6 @@ ExtensionArray Styler ^^^^^^ -- :meth:`Styler.set_tooltips` provides alternative method to storing tooltips by using title attribute of td elements. (:issue:`56981`) - Other From 109586471245b26c0140deff548fdaf7c158b7d3 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:20:38 -0500 Subject: [PATCH 21/26] Resolves grammar error in docstring --- pandas/io/formats/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 3fd7b60ef2de9..a64e7d54de2e7 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -477,7 +477,7 @@ def set_tooltips( If multiline tooltips are required, or if styling is not required and/or space is of concern, then utilizing as_title_attribute as True will store the tooltip on the title attribute. This will cause no CSS - to be generated nor will the the elements. Storing tooltips through + to be generated nor will the elements. Storing tooltips through the title attribute will mean that tooltip styling effects do not apply. Examples From 6f6a0368200404a354569b74fb21a4b027ef2159 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:23:14 -0500 Subject: [PATCH 22/26] Updates comment with suggestion --- pandas/io/formats/style_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index cbb6f4973a449..fcde00e1891d1 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2129,7 +2129,7 @@ def _translate(self, styler: StylerRenderer, d: dict): ] if self.table_styles: - # add span class to every cell only if at least 1 non-empty tooltip + # add span class to every cell since there is at least 1 non-empty tooltip for row in d["body"]: for item in row: if item["type"] == "td": From 1297ea5d998259bc8286276c074f17dec63c87e7 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:36:11 -0500 Subject: [PATCH 23/26] Reverts accidental style formatting --- pandas/core/apply.py | 2 +- pandas/io/parsers/c_parser_wrapper.py | 2 +- pandas/plotting/_matplotlib/core.py | 2 +- scripts/validate_min_versions_in_sync.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/apply.py b/pandas/core/apply.py index f4d56c4b6f8f8..ebd714f9c14d4 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -1798,7 +1798,7 @@ def normalize_keyword_aggregation( def _make_unique_kwarg_list( - seq: Sequence[tuple[Any, Any]], + seq: Sequence[tuple[Any, Any]] ) -> Sequence[tuple[Any, Any]]: """ Uniquify aggfunc name of the pairs in the order list diff --git a/pandas/io/parsers/c_parser_wrapper.py b/pandas/io/parsers/c_parser_wrapper.py index 67f3e5a9f4880..f24d7a628998e 100644 --- a/pandas/io/parsers/c_parser_wrapper.py +++ b/pandas/io/parsers/c_parser_wrapper.py @@ -396,7 +396,7 @@ def _concatenate_chunks(chunks: list[dict[int, ArrayLike]]) -> dict: def ensure_dtype_objs( - dtype: DtypeArg | dict[Hashable, DtypeArg] | None, + dtype: DtypeArg | dict[Hashable, DtypeArg] | None ) -> DtypeObj | dict[Hashable, DtypeObj] | None: """ Ensure we have either None, a dtype object, or a dictionary mapping to diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 1c8cd9a4970c8..6fa75ba5fb12d 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -465,7 +465,7 @@ def _validate_color_args(self, color, colormap): @final @staticmethod def _iter_data( - data: DataFrame | dict[Hashable, Series | DataFrame], + data: DataFrame | dict[Hashable, Series | DataFrame] ) -> Iterator[tuple[Hashable, np.ndarray]]: for col, values in data.items(): # This was originally written to use values.values before EAs diff --git a/scripts/validate_min_versions_in_sync.py b/scripts/validate_min_versions_in_sync.py index 14227730d74ce..f8cce255f532d 100755 --- a/scripts/validate_min_versions_in_sync.py +++ b/scripts/validate_min_versions_in_sync.py @@ -105,7 +105,7 @@ def get_operator_from(dependency: str) -> str | None: def get_yaml_map_from( - yaml_dic: list[str | dict[str, list[str]]], + yaml_dic: list[str | dict[str, list[str]]] ) -> dict[str, list[str] | None]: yaml_map: dict[str, list[str] | None] = {} for dependency in yaml_dic: From 0d03fa09ef9eba46ca0d0faf425739c78000f7ed Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Sun, 11 Feb 2024 19:51:28 -0500 Subject: [PATCH 24/26] Reverts parametrization -> fixture back to parametrization --- pandas/tests/io/formats/style/test_tooltip.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index c110ff34f34f3..55f8301fe55b5 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -24,23 +24,19 @@ def styler(df): return Styler(df, uuid_len=0) -ttips_dfs = [ - DataFrame( # Test basic reindex and ignoring blank - data=[["Min", "Max"], [np.nan, ""]], - columns=["A", "C"], - index=["x", "y"], - ), - DataFrame( # Test non-referenced columns, reversed col names, short index - data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] - ), -] - - -@pytest.fixture(params=ttips_dfs) -def ttips(request): - return request.param - - +@pytest.mark.parametrize( + "ttips", + [ + DataFrame( # Test basic reindex and ignoring blank + data=[["Min", "Max"], [np.nan, ""]], + columns=["A", "C"], + index=["x", "y"], + ), + DataFrame( # Test non-referenced columns, reversed col names, short index + data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] + ), + ], +) def test_tooltip_render(ttips, styler): # GH 21266 result = styler.set_tooltips(ttips).to_html() @@ -93,6 +89,19 @@ def test_tooltip_css_class(styler): assert "#T_ .another-class {\n color: green;\n color: red;\n}" in result +@pytest.mark.parametrize( + "ttips", + [ + DataFrame( # Test basic reindex and ignoring blank + data=[["Min", "Max"], [np.nan, ""]], + columns=["A", "C"], + index=["x", "y"], + ), + DataFrame( # Test non-referenced columns, reversed col names, short index + data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] + ), + ], +) def test_tooltip_render_as_title(ttips, styler): # GH 56605 result = styler.set_tooltips(ttips, as_title_attribute=True).to_html() From f7939ef6fe4a2b2fbae1aec5e6bb25708d233f54 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Mon, 12 Feb 2024 19:24:36 -0500 Subject: [PATCH 25/26] Changes parametrization per request --- pandas/io/formats/style_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index fcde00e1891d1..70c6281f56eec 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2128,8 +2128,8 @@ def _translate(self, styler: StylerRenderer, d: dict): for style in sublist ] + # add span class to every cell since there is at least 1 non-empty tooltip if self.table_styles: - # add span class to every cell since there is at least 1 non-empty tooltip for row in d["body"]: for item in row: if item["type"] == "td": From f52c33a806a0cdbeb0235b3d1e5d26254ec31075 Mon Sep 17 00:00:00 2001 From: Matt Delengowski Date: Mon, 12 Feb 2024 19:25:02 -0500 Subject: [PATCH 26/26] Fixes ruff format error --- pandas/tests/io/formats/style/test_tooltip.py | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/pandas/tests/io/formats/style/test_tooltip.py b/pandas/tests/io/formats/style/test_tooltip.py index 55f8301fe55b5..f1071c949299e 100644 --- a/pandas/tests/io/formats/style/test_tooltip.py +++ b/pandas/tests/io/formats/style/test_tooltip.py @@ -25,19 +25,17 @@ def styler(df): @pytest.mark.parametrize( - "ttips", + "data, columns, index", [ - DataFrame( # Test basic reindex and ignoring blank - data=[["Min", "Max"], [np.nan, ""]], - columns=["A", "C"], - index=["x", "y"], - ), - DataFrame( # Test non-referenced columns, reversed col names, short index - data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] - ), + # Test basic reindex and ignoring blank + ([["Min", "Max"], [np.nan, ""]], ["A", "C"], ["x", "y"]), + # Test non-referenced columns, reversed col names, short index + ([["Max", "Min", "Bad-Col"]], ["C", "A", "D"], ["x"]), ], ) -def test_tooltip_render(ttips, styler): +def test_tooltip_render(data, columns, index, styler): + ttips = DataFrame(data=data, columns=columns, index=index) + # GH 21266 result = styler.set_tooltips(ttips).to_html() @@ -90,19 +88,16 @@ def test_tooltip_css_class(styler): @pytest.mark.parametrize( - "ttips", + "data, columns, index", [ - DataFrame( # Test basic reindex and ignoring blank - data=[["Min", "Max"], [np.nan, ""]], - columns=["A", "C"], - index=["x", "y"], - ), - DataFrame( # Test non-referenced columns, reversed col names, short index - data=[["Max", "Min", "Bad-Col"]], columns=["C", "A", "D"], index=["x"] - ), + # Test basic reindex and ignoring blank + ([["Min", "Max"], [np.nan, ""]], ["A", "C"], ["x", "y"]), + # Test non-referenced columns, reversed col names, short index + ([["Max", "Min", "Bad-Col"]], ["C", "A", "D"], ["x"]), ], ) -def test_tooltip_render_as_title(ttips, styler): +def test_tooltip_render_as_title(data, columns, index, styler): + ttips = DataFrame(data=data, columns=columns, index=index) # GH 56605 result = styler.set_tooltips(ttips, as_title_attribute=True).to_html()