Skip to content

Commit

Permalink
Merge pull request #484 from posit-dev/fix-locations
Browse files Browse the repository at this point in the history
Fix locations: row and group selection
  • Loading branch information
machow authored Oct 4, 2024
2 parents 58e9663 + fb5709f commit e3acf4e
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 22 deletions.
13 changes: 9 additions & 4 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,24 @@ website:
- get-started/basic-header.qmd
- get-started/basic-stub.qmd
- get-started/basic-column-labels.qmd
- section: Format and Style
- section: Format
contents:
- get-started/basic-formatting.qmd
- get-started/nanoplots.qmd
- section: Style
contents:
- get-started/basic-styling.qmd
- get-started/targeted-styles.qmd
- get-started/colorizing-with-data.qmd
- section: Theming
contents:
- get-started/table-theme-options.qmd
- get-started/table-theme-premade.qmd
- section: Extra Topics
- section: Selecting table parts
contents:
- get-started/column-selection.qmd
- get-started/row-selection.qmd
- get-started/nanoplots.qmd
- get-started/targeted-styles.qmd
- get-started/loc-selection.qmd

format:
html:
Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/basic-styling.qmd
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Stying the Table Body
title: Styling the Table Body
jupyter: python3
html-table-processing: none
---
Expand Down
179 changes: 179 additions & 0 deletions docs/get-started/loc-selection.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
---
title: Location selection
jupyter: python3
---

Great Tables uses the `loc` module to specify locations for styling in `tab_style()`. Some location specifiers also allow selecting specific columns and rows of data.

For example, you might style a particular row name, group, column, or spanner label.

The table below shows the different location specifiers, along with the types of column or row selection they allow.

```{python}
# | echo: false
import polars as pl
from great_tables import GT
data = [
["header", "loc.header()", "composite"],
["", "loc.title()", ""],
["", "loc.subtitle()", ""],
["boxhead", "loc.column_header()", "composite"],
["", "loc.spanner_labels()", "columns"],
["", "loc.column_labels()", "columns"],
["row stub", "loc.stub()", "rows"],
["", "loc.row_groups()", "rows"],
["table body", "loc.body()", "columns and rows"],
["footer", "loc.footer()", "composite"],
["", "loc.source_notes()", ""],
]
df = pl.DataFrame(data, schema=["table part", "name", "selection"], orient="row")
GT(df)
```

Note that composite specifiers are ones that target multiple locations. For example, `loc.header()` specifies both `loc.title()` and `loc.subtitle()`.

## Setting up data

The examples below will use this small dataset to show selecting different locations, as well as specific rows and columns within a location (where supported).

```{python}
import polars as pl
import polars.selectors as cs
from great_tables import GT, loc, style, exibble
pl_exibble = pl.from_pandas(exibble)[[0, 1, 4], ["num", "char", "group"]]
pl_exibble
```

## Simple locations

Simple locations don't take any arguments.

For example, styling the title uses `loc.title()`.

```{python}
(
GT(pl_exibble)
.tab_header("A title", "A subtitle")
.tab_style(
style.fill("yellow"),
loc.title(),
)
)
```

## Composite locations

Composite locations target multiple simple locations.

For example, `loc.header()` includes both `loc.title()` and `loc.subtitle()`.

```{python}
(
GT(pl_exibble)
.tab_header("A title", "A subtitle")
.tab_style(
style.fill("yellow"),
loc.header(),
)
)
```

## Body columns and rows

Use `loc.body()` to style specific cells in the table body.

```{python}
(
GT(pl_exibble).tab_style(
style.fill("yellow"),
loc.body(
columns=cs.starts_with("cha"),
rows=pl.col("char").str.contains("a"),
),
)
)
```

This is discussed in detail in [Styling the Table Body](./basic-styling.qmd).

## Column labels

Locations like `loc.spanner_labels()` and `loc.column_labels()` can select specific column and spanner labels.

You can use name strings, index position, or polars selectors.

```{python}
GT(pl_exibble).tab_style(
style.fill("yellow"),
loc.column_labels(
cs.starts_with("cha"),
),
)
```

However, note that `loc.spanner_labels()` currently only accepts list of string names.

## Row and group names

Row and group names in `loc.stub()` and `loc.row_groups()` may be specified three ways:

* by name
* by index
* by polars expression

```{python}
gt = GT(pl_exibble).tab_stub(
rowname_col="char",
groupname_col="group",
)
gt.tab_style(style.fill("yellow"), loc.stub())
```


```{python}
gt.tab_style(style.fill("yellow"), loc.stub("banana"))
```

```{python}
gt.tab_style(style.fill("yellow"), loc.stub(["apricot", 2]))
```

### Groups by name and position

Note that for specifying row groups, the group corresponding to the group name or row number in the original data is used.

For example, the code below styles the group corresponding to the row at index 1 (i.e. the second row) in the data.

```{python}
gt.tab_style(
style.fill("yellow"),
loc.row_groups(1),
)
```

Since the second row (starting with "banana") is in "grp_a", that is the group that gets styled.

This means you can use a polars expression to select groups:

```{python}
gt.tab_style(
style.fill("yellow"),
loc.row_groups(pl.col("group") == "grp_b"),
)
```

You can also specify group names using a string (or list of strings).

```{python}
gt.tab_style(
style.fill("yellow"),
loc.row_groups("grp_b"),
)
```
3 changes: 1 addition & 2 deletions docs/get-started/table-theme-options.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jupyter: python3

Great Tables exposes options to customize the appearance of tables via two methods:

* [](`~great_tables.GT.tab_style`) - targeted styles (e.g. color a specific cell of data).
* [](`~great_tables.GT.tab_style`) - targeted styles (e.g. color a specific cell of data, or a specific group label).
* [](`~great_tables.GT.tab_options`) - broad styles (e.g. color the header and source notes).

Both methods target parts of the table, as shown in the diagram below.
Expand All @@ -14,7 +14,6 @@ Both methods target parts of the table, as shown in the diagram below.

This page covers how to style and theme your table using `GT.tab_options()`,
which is meant to quickly set a broad range of styles.
In the future, even more granular options will become available via `GT.tab_style()`.

We'll use the basic GT object below for most examples, since it marks some of the table parts.

Expand Down
4 changes: 2 additions & 2 deletions docs/get-started/targeted-styles.qmd
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: Targeted styles
title: Styling the whole table
jupyter: python3
---

In [Styling the Table Body](./basic-styling), we discussed styling table data with `.tab_style()`.
In this article we'll cover how the same method can be used to style many other parts of the table, like the header, specific spanner labels, the footer, and more.

:::{.callout-warning}
This feature is currently a work in progress, and not yet released. Great Tables must be installed from github in order to try it.
This feature is new, and this page of documentation is still in development.
:::


Expand Down
23 changes: 11 additions & 12 deletions great_tables/_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ def resolve_rows_i(
data: GTData | list[str],
expr: RowSelectExpr = None,
null_means: Literal["everything", "nothing"] = "everything",
row_name_attr: Literal["rowname", "group_id"] = "rowname",
) -> list[tuple[str, int]]:
"""Return matching row numbers, based on expr
Expand All @@ -766,13 +767,13 @@ def resolve_rows_i(
expr: list[str | int] = [expr]

if isinstance(data, GTData):
row_names = [row.rowname for row in data._stub]
row_names = [getattr(row, row_name_attr) for row in data._stub]
else:
row_names = data

if expr is None:
if null_means == "everything":
return [(row.rowname, ii) for ii, row in enumerate(data._stub)]
return [(name, ii) for ii, name in enumerate(row_names)]
else:
return []

Expand Down Expand Up @@ -844,18 +845,17 @@ def _(loc: LocSpannerLabels, spanners: Spanners) -> LocSpannerLabels:


@resolve.register
def _(loc: LocColumnLabels, data: GTData) -> list[CellPos]:
cols = resolve_cols_i(data=data, expr=loc.columns)
cell_pos = [CellPos(col[1], 0, colname=col[0]) for col in cols]
return cell_pos
def _(loc: LocColumnLabels, data: GTData) -> list[tuple[str, int]]:
name_pos = resolve_cols_i(data=data, expr=loc.columns)
return name_pos


@resolve.register
def _(loc: LocRowGroups, data: GTData) -> set[int]:
# TODO: what are the rules for matching row groups?
# TODO: resolve_rows_i will match a list expr to row names (not group names)
group_pos = set(pos for _, pos in resolve_rows_i(data, loc.rows))
return list(group_pos)
group_pos = set(name for name, _ in resolve_rows_i(data, loc.rows, row_name_attr="group_id"))
return group_pos


@resolve.register
Expand Down Expand Up @@ -939,17 +939,16 @@ def _(

@set_style.register
def _(loc: LocColumnLabels, data: GTData, style: list[CellStyle]) -> GTData:
positions: list[CellPos] = resolve(loc, data)
selected = resolve(loc, data)

# evaluate any column expressions in styles
styles = [entry._evaluate_expressions(data._tbl_data) for entry in style]

all_info: list[StyleInfo] = []
for col_pos in positions:
for name, pos in selected:
crnt_info = StyleInfo(
locname=loc,
colname=col_pos.colname,
rownum=col_pos.row,
colname=name,
styles=styles,
)
all_info.append(crnt_info)
Expand Down
6 changes: 5 additions & 1 deletion great_tables/_utils_render_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,11 @@ def create_body_component_h(data: GTData) -> str:
"gt_empty_group_heading" if group_label == "" else "gt_group_heading_row"
)

_styles = [style for style in styles_row_group_label if i in style.grpname]
_styles = [
style
for style in styles_row_group_label
if group_info.group_id in style.grpname
]
group_styles = _flatten_styles(_styles, wrap=True)
group_row = f""" <tr class="{group_class}">
<th class="gt_group_heading" colspan="{colspan_value}"{group_styles}>{group_label}</th>
Expand Down
Loading

0 comments on commit e3acf4e

Please sign in to comment.