diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index f7204ceb9d412..08159d6a1d1d4 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -131,7 +131,7 @@ Other enhancements - Disallow :class:`DataFrame` indexer for ``iloc`` for :meth:`Series.__getitem__` and :meth:`DataFrame.__getitem__`, (:issue:`39004`) - :meth:`Series.apply` can now accept list-like or dictionary-like arguments that aren't lists or dictionaries, e.g. ``ser.apply(np.array(["sum", "mean"]))``, which was already the case for :meth:`DataFrame.apply` (:issue:`39140`) - :meth:`DataFrame.plot.scatter` can now accept a categorical column as the argument to ``c`` (:issue:`12380`, :issue:`31357`) -- :meth:`.Styler.set_tooltips` allows on hover tooltips to be added to styled HTML dataframes (:issue:`35643`, :issue:`21266`, :issue:`39317`) +- :meth:`.Styler.set_tooltips` allows on hover tooltips to be added to styled HTML dataframes (:issue:`35643`, :issue:`21266`, :issue:`39317`, :issue:`39708`) - :meth:`.Styler.set_tooltips_class` and :meth:`.Styler.set_table_styles` amended to optionally allow certain css-string input arguments (:issue:`39564`) - :meth:`.Styler.apply` now more consistently accepts ndarray function returns, i.e. in all cases for ``axis`` is ``0, 1 or None`` (:issue:`39359`) - :meth:`.Styler.apply` and :meth:`.Styler.applymap` now raise errors if wrong format CSS is passed on render (:issue:`39660`) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index e50f5986098d3..dcae87f9d6d48 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -785,16 +785,29 @@ def _copy(self, deepcopy: bool = False) -> Styler: self.data, precision=self.precision, caption=self.caption, - uuid=self.uuid, - table_styles=self.table_styles, + table_attributes=self.table_attributes, + cell_ids=self.cell_ids, na_rep=self.na_rep, ) + + styler.uuid = self.uuid + styler.hidden_index = self.hidden_index + if deepcopy: styler.ctx = copy.deepcopy(self.ctx) styler._todo = copy.deepcopy(self._todo) + styler.table_styles = copy.deepcopy(self.table_styles) + styler.hidden_columns = copy.copy(self.hidden_columns) + styler.cell_context = copy.deepcopy(self.cell_context) + styler.tooltips = copy.deepcopy(self.tooltips) else: styler.ctx = self.ctx styler._todo = self._todo + styler.table_styles = self.table_styles + styler.hidden_columns = self.hidden_columns + styler.cell_context = self.cell_context + styler.tooltips = self.tooltips + return styler def __copy__(self) -> Styler: diff --git a/pandas/tests/io/formats/style/test_style.py b/pandas/tests/io/formats/style/test_style.py index f0d1090899043..09e14d06f4d9b 100644 --- a/pandas/tests/io/formats/style/test_style.py +++ b/pandas/tests/io/formats/style/test_style.py @@ -72,28 +72,101 @@ def test_update_ctx_flatten_multi_and_trailing_semi(self): } assert self.styler.ctx == expected - def test_copy(self): - s2 = copy.copy(self.styler) - assert self.styler is not s2 - assert self.styler.ctx is s2.ctx # shallow - assert self.styler._todo is s2._todo - - self.styler._update_ctx(self.attrs) - self.styler.highlight_max() - assert self.styler.ctx == s2.ctx - assert self.styler._todo == s2._todo - - def test_deepcopy(self): - s2 = copy.deepcopy(self.styler) - assert self.styler is not s2 - assert self.styler.ctx is not s2.ctx - assert self.styler._todo is not s2._todo + @pytest.mark.parametrize("do_changes", [True, False]) + @pytest.mark.parametrize("do_render", [True, False]) + def test_copy(self, do_changes, do_render): + # Updated in GH39708 + # Change some defaults (to check later if the new values are copied) + if do_changes: + self.styler.set_table_styles( + [{"selector": "th", "props": [("foo", "bar")]}] + ) + self.styler.set_table_attributes('class="foo" data-bar') + self.styler.hidden_index = not self.styler.hidden_index + self.styler.hide_columns("A") + classes = pd.DataFrame( + [["favorite-val red", ""], [None, "blue my-val"]], + index=self.df.index, + columns=self.df.columns, + ) + self.styler.set_td_classes(classes) + ttips = pd.DataFrame( + data=[["Favorite", ""], [np.nan, "my"]], + columns=self.df.columns, + index=self.df.index, + ) + self.styler.set_tooltips(ttips) + self.styler.cell_ids = not self.styler.cell_ids + + if do_render: + self.styler.render() + + s_copy = copy.copy(self.styler) + s_deepcopy = copy.deepcopy(self.styler) + + assert self.styler is not s_copy + assert self.styler is not s_deepcopy + + # Check for identity + assert self.styler.ctx is s_copy.ctx + assert self.styler._todo is s_copy._todo + assert self.styler.table_styles is s_copy.table_styles + assert self.styler.hidden_columns is s_copy.hidden_columns + assert self.styler.cell_context is s_copy.cell_context + assert self.styler.tooltips is s_copy.tooltips + if do_changes: # self.styler.tooltips is not None + assert self.styler.tooltips.tt_data is s_copy.tooltips.tt_data + assert ( + self.styler.tooltips.class_properties + is s_copy.tooltips.class_properties + ) + assert self.styler.tooltips.table_styles is s_copy.tooltips.table_styles + + # Check for non-identity + assert self.styler.ctx is not s_deepcopy.ctx + assert self.styler._todo is not s_deepcopy._todo + assert self.styler.hidden_columns is not s_deepcopy.hidden_columns + assert self.styler.cell_context is not s_deepcopy.cell_context + if do_changes: # self.styler.table_style is not None + assert self.styler.table_styles is not s_deepcopy.table_styles + if do_changes: # self.styler.tooltips is not None + assert self.styler.tooltips is not s_deepcopy.tooltips + assert self.styler.tooltips.tt_data is not s_deepcopy.tooltips.tt_data + assert ( + self.styler.tooltips.class_properties + is not s_deepcopy.tooltips.class_properties + ) + assert ( + self.styler.tooltips.table_styles + is not s_deepcopy.tooltips.table_styles + ) self.styler._update_ctx(self.attrs) self.styler.highlight_max() - assert self.styler.ctx != s2.ctx - assert s2._todo == [] - assert self.styler._todo != s2._todo + assert self.styler.ctx == s_copy.ctx + assert self.styler.ctx != s_deepcopy.ctx + assert self.styler._todo == s_copy._todo + assert self.styler._todo != s_deepcopy._todo + assert s_deepcopy._todo == [] + + equal_attributes = [ + "table_styles", + "table_attributes", + "cell_ids", + "hidden_index", + "hidden_columns", + "cell_context", + ] + for s2 in [s_copy, s_deepcopy]: + for att in equal_attributes: + assert self.styler.__dict__[att] == s2.__dict__[att] + if do_changes: # self.styler.tooltips is not None + tm.assert_frame_equal(self.styler.tooltips.tt_data, s2.tooltips.tt_data) + assert ( + self.styler.tooltips.class_properties + == s2.tooltips.class_properties + ) + assert self.styler.tooltips.table_styles == s2.tooltips.table_styles def test_clear(self): # updated in GH 39396