Skip to content

ENH: Add custom descriptors (such as dtype, nunique, etc.) to Styler output #43894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 96 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
9743099
ignore hidden rows in loop
attack68 Sep 18, 2021
8a0253e
add latex 43644 test
attack68 Sep 18, 2021
74c418e
add latex 43644 test
attack68 Sep 18, 2021
70535c5
Merge remote-tracking branch 'upstream/master' into bug_styler_multii…
attack68 Sep 21, 2021
2fbe569
clean up code
attack68 Sep 21, 2021
7903723
clean up code
attack68 Sep 21, 2021
6a2793c
row, col and level css
attack68 Sep 21, 2021
d227914
whats new
attack68 Sep 21, 2021
7ca5002
tests and user guide
attack68 Sep 21, 2021
f27f7ed
merge
attack68 Sep 22, 2021
a1000a7
merge
attack68 Sep 22, 2021
c44dcda
move private methods
attack68 Sep 22, 2021
c4c9aaa
Merge branch 'clean_styler_css_classes' into bug_styler_multiindex_hi…
attack68 Sep 22, 2021
c22cf0d
refactor methods
attack68 Sep 22, 2021
0e0b46e
add test for 43703
attack68 Sep 22, 2021
1f3bbec
add test for 43703
attack68 Sep 22, 2021
021bc26
docs
attack68 Sep 22, 2021
4ba3dff
docs
attack68 Sep 22, 2021
7fee05d
more explicit test
attack68 Sep 22, 2021
baa3233
more explicit test
attack68 Sep 22, 2021
f4ad390
fix checks
attack68 Sep 23, 2021
22b03e3
fix checks
attack68 Sep 23, 2021
db214d8
fix checks
attack68 Sep 23, 2021
771d056
Merge remote-tracking branch 'upstream/master' into bug_styler_multii…
attack68 Sep 24, 2021
24952ae
fix checks
attack68 Sep 24, 2021
1d47d0f
Merge remote-tracking branch 'upstream/master' into clean_styler_css_…
attack68 Sep 24, 2021
566738d
fix checks
attack68 Sep 24, 2021
230138a
fix checks
attack68 Sep 24, 2021
b9ba9ea
refactor to get tests to pass
attack68 Sep 25, 2021
ea2bba1
refactor to get tests to pass
attack68 Sep 25, 2021
75de033
Merge branch 'clean_styler_css_classes' into bug_styler_multiindex_hi…
attack68 Sep 25, 2021
4c34bc2
try filter
attack68 Sep 25, 2021
68ee832
docs methods
attack68 Sep 25, 2021
ca70491
correct css classes in set_table_styles
attack68 Sep 25, 2021
7a1994e
Merge remote-tracking branch 'upstream/master' into clean_styler_css_…
attack68 Sep 29, 2021
91320f8
add version added
attack68 Sep 29, 2021
92c1941
add version added
attack68 Sep 29, 2021
aad0e16
checks fix
attack68 Sep 29, 2021
61b24ed
add tests allow none
attack68 Sep 29, 2021
d4c5715
fix checks
attack68 Sep 29, 2021
aa0172f
Merge remote-tracking branch 'upstream/master' into clean_styler_css_…
attack68 Oct 2, 2021
84a814f
rename `css` `css_class_names`
attack68 Oct 2, 2021
f06b727
rename `css` `css_class_names`
attack68 Oct 2, 2021
131070f
rename `css` `css_class_names`
attack68 Oct 2, 2021
a048122
update arg name
attack68 Oct 2, 2021
4931f32
Merge remote-tracking branch 'upstream/master' into clean_styler_css_…
attack68 Oct 3, 2021
e9716f2
fix line length
attack68 Oct 3, 2021
d983464
black
attack68 Oct 3, 2021
c08ef82
add vars
attack68 Oct 5, 2021
0f11a62
Merge branch 'clean_styler_css_classes' into bug_styler_multiindex_hi…
attack68 Oct 5, 2021
5c52957
Merge branch 'bug_styler_multiindex_hiding' into describe_styler
attack68 Oct 5, 2021
17d181c
add descriptor rendering
attack68 Oct 5, 2021
e5b4d3e
mypy
attack68 Oct 5, 2021
f98ba9f
fix merge
attack68 Oct 19, 2021
0cc5502
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Oct 23, 2021
c5b75cc
docs
attack68 Oct 23, 2021
ac1384b
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Oct 23, 2021
c948f11
tests edit
attack68 Oct 23, 2021
afa8f10
some tests
attack68 Oct 23, 2021
60efbbb
doc string
attack68 Oct 23, 2021
9ef5b44
add direct test
attack68 Oct 23, 2021
a02b4ac
doc fixes
attack68 Oct 24, 2021
6b51d88
update user guide
attack68 Oct 24, 2021
814bace
table styles doc
attack68 Oct 24, 2021
33c35a4
version identifiers
attack68 Oct 24, 2021
91d8680
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Oct 31, 2021
260d356
merge master
attack68 Nov 5, 2021
46856fb
add tests
attack68 Nov 5, 2021
a4dce4d
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Nov 12, 2021
fefb420
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Nov 16, 2021
f2c8f0e
whats new
attack68 Nov 16, 2021
a945da5
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Nov 29, 2021
b3c0f96
Merge branch 'master' into describe_styler
attack68 Dec 11, 2021
f36d4fd
formatting
attack68 Dec 11, 2021
e78907f
fdoc string validate
attack68 Dec 13, 2021
cd085df
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Dec 15, 2021
45f5879
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Jan 4, 2022
6f64e80
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Jan 5, 2022
1c5cf53
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Jan 6, 2022
58aa894
push to 1.5.0
attack68 Jan 6, 2022
bd2fe7d
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Jan 7, 2022
02aeae5
cleaer comment
attack68 Jan 7, 2022
5541396
Merge remote-tracking branch 'upstream/master' into describe_styler
attack68 Jan 9, 2022
beb2bc1
fix formatter
attack68 Jan 13, 2022
7d62bb3
Merge remote-tracking branch 'upstream/main' into describe_styler
attack68 Jan 13, 2022
6bfb5a9
fix formatter
attack68 Jan 14, 2022
9554f89
Merge remote-tracking branch 'upstream/main' into describe_styler
attack68 Jan 25, 2022
d8e11c8
Merge remote-tracking branch 'upstream/main' into describe_styler
attack68 Jan 26, 2022
4f935c4
is_integer replacemnet
attack68 Jan 26, 2022
7031897
Merge remote-tracking branch 'upstream/main' into describe_styler
attack68 Jan 28, 2022
0c034a6
bastage req: refactor _element
attack68 Jan 28, 2022
6128ccb
bashtage req: is_float / complex
attack68 Jan 28, 2022
bb8201e
bashtage req: is_float / complex
attack68 Jan 28, 2022
44204e0
bashtage req: __name__ and typing
attack68 Jan 28, 2022
ebec1d6
bashtage req: doc updates for __name__
attack68 Jan 28, 2022
c205c38
doc fix
attack68 Jan 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/source/_static/style/des_mean.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/source/reference/style.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Style application
Styler.set_td_classes
Styler.set_table_styles
Styler.set_table_attributes
Styler.set_descriptors
Styler.set_tooltips
Styler.set_caption
Styler.set_sticky
Expand Down
44 changes: 43 additions & 1 deletion doc/source/user_guide/style.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,47 @@
"weather_df.loc[\"2021-01-04\":\"2021-01-08\"].style.pipe(make_pretty)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Describing Data\n",
"\n",
"The data can also be explored with the ability to add header level calculations. The [.set_descriptors()][descriptors] method is used here. We begin with a large DataFrame and reconfigure the `pandas.options` to reduce the rendered size, whilst adding descriptors we wish to calculate on the data.\n",
"\n",
"[descriptors]: ../reference/api/pandas.io.formats.style.Styler.set_descriptors.rst"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pd.options.styler.render.max_rows = 5\n",
"df_described = pd.DataFrame({\"A\": np.random.randn(1000), \n",
" \"B\": np.random.randint(low=-10, high=10, size=1000, dtype=\"int64\")})\n",
"df_described.style.set_descriptors([\n",
" \"mean\",\n",
" (\"mean 2dp\", lambda s: f\"{s.mean():.2f}\"),\n",
" (\"std\", pd.Series.std),\n",
" \"nunique\",\n",
" lambda s: s.dtype,\n",
"])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"nbsphinx": "hidden"
},
"outputs": [],
"source": [
"# Hidden cell to reset pandas options \n",
"pd.options.styler.render.max_rows = None"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1661,6 +1702,7 @@
" + `col<n>`, where `n` is the numeric position of the cell.\n",
"- Blank cells include `blank`\n",
"- Trimmed cells include `col_trim` or `row_trim`\n",
"- Descriptor name cells include `descriptor_name`, descriptor value cells include `descriptor_value` and both also include `descriptor<j>`, where `j` is the numeric index of the list of descriptors.\n",
"\n",
"The structure of the `id` is `T_uuid_level<k>_row<m>_col<n>` where `level<k>` is used only on headings, and headings will only have either `row<m>` or `col<n>` whichever is needed. By default we've also prepended each row/column identifier with a UUID unique to each DataFrame so that the style from one doesn't collide with the styling from another within the same notebook or page. You can read more about the use of UUIDs in [Optimization](#Optimization).\n",
"\n",
Expand All @@ -1675,7 +1717,7 @@
"metadata": {},
"outputs": [],
"source": [
"print(pd.DataFrame([[1,2],[3,4]], index=['i1', 'i2'], columns=['c1', 'c2']).style.to_html())"
"print(pd.DataFrame([[1,2],[3,4]], index=['i1', 'i2'], columns=['c1', 'c2']).style.set_descriptors([\"mean\"]).to_html())"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Styler
^^^^^^

- New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`)
- Added a new method :meth:`.Styler.set_descriptors` which allows adding customised header rows to explore and make calculations on the data, e.g. totals and counts etc. (:issue:`43875`)
- Various bug fixes, see below.

.. _whatsnew_150.enhancements.enhancement2:
Expand Down
63 changes: 62 additions & 1 deletion pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from pandas.io.formats.style_render import (
CSSProperties,
CSSStyles,
Descriptor,
ExtFormatter,
StylerRenderer,
Subset,
Expand Down Expand Up @@ -1435,6 +1436,7 @@ def _copy(self, deepcopy: bool = False) -> Styler:
]
deep = [ # nested lists or dicts
"css",
"descriptors",
"_display_funcs",
"_display_funcs_index",
"_display_funcs_columns",
Expand Down Expand Up @@ -1977,6 +1979,9 @@ def export(self) -> dict[str, Any]:

Can be applied to a second Styler with ``Styler.use``.

.. versionchanged:: 1.5.0
Adds ``descriptors`` to the exported items.

Returns
-------
styles : dict
Expand All @@ -1998,6 +2003,7 @@ def export(self) -> dict[str, Any]:
- Whether axes and names are hidden from the display, if unambiguous.
- Table attributes
- Table styles
- Descriptors

The following attributes are considered data dependent and therefore not
exported:
Expand Down Expand Up @@ -2027,6 +2033,7 @@ def export(self) -> dict[str, Any]:
"hide_index_names": self.hide_index_names,
"hide_column_names": self.hide_column_names,
"css": copy.copy(self.css),
"descriptors": copy.copy(self.descriptors),
}

def use(self, styles: dict[str, Any]) -> Styler:
Expand All @@ -2035,6 +2042,9 @@ def use(self, styles: dict[str, Any]) -> Styler:

Possibly uses styles from ``Styler.export``.

.. versionchanged:: 1.5.0
Adds ``descriptors`` to the used items.

Parameters
----------
styles : dict(str, Any)
Expand All @@ -2052,6 +2062,8 @@ def use(self, styles: dict[str, Any]) -> Styler:
- "hide_index_names": whether index names are hidden.
- "hide_column_names": whether column header names are hidden.
- "css": the css class names used.
- "descriptors": list of descriptors, typically added with
``set_descriptors``.

Returns
-------
Expand Down Expand Up @@ -2094,6 +2106,8 @@ def use(self, styles: dict[str, Any]) -> Styler:
self.hide_column_names = styles.get("hide_column_names", False)
if styles.get("css"):
self.css = styles.get("css") # type: ignore[assignment]
if styles.get("descriptors"):
self.set_descriptors(styles.get("descriptors"))
return self

def set_uuid(self, uuid: str) -> Styler:
Expand Down Expand Up @@ -2352,7 +2366,10 @@ def set_table_styles(
"row_trim": "row_trim",
"level": "level",
"data": "data",
"blank": "blank}
"blank": "blank",
"descriptor": "descriptor",
"descriptor_name": "descriptor_name",
"descriptor_value": "descriptor_value"}

Examples
--------
Expand Down Expand Up @@ -2423,6 +2440,50 @@ def set_table_styles(
self.table_styles = table_styles
return self

def set_descriptors(
self, descriptors: list[Descriptor | tuple[str, Descriptor]] | None = None
) -> Styler:
"""
Add header-level calculations to the output which describes the data.

.. versionadded:: 1.5.0

Parameters
----------
descriptors : list of str, callables or 2-tuples of str and callable
If a string is given must be a valid Series method, e.g. "mean" invokes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you some additional details about the callable. Specifically it must (or should) return a scalar quantity. Is the callable allowed to throw and exception? If so, what happens? I would be good to clarify this.

Series.mean().

If a callable is given must accept a Series and return a scalar.

If a 2-tuple, must be a string used as the name of the row and a
callable or string as above.

Returns
-------
self : Styler

Examples
--------

>>> df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"])
>>> def udf_func(s):
... return s.mean()
>>> styler = df.style.set_descriptors([
... "mean",
... Series.mean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screen show shows no lable on this line? Was it not possible to have .__name__ as a default test?

... ("my-text", "mean"),
... ("my-text2", Series.mean),
... ("my-func", lambda s: s.sum()/2),
... lambda s: s.sum()/2,
... udf_func,
... ]) # doctest: +SKIP

.. figure:: ../../_static/style/des_mean.png
"""
self.descriptors = descriptors if descriptors is not None else []
return self

def set_na_rep(self, na_rep: str) -> StylerRenderer:
"""
Set the missing data representation on a ``Styler``.
Expand Down
Loading