Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ quartodoc:
- GT.cols_move
- GT.cols_move_to_start
- GT.cols_move_to_end
- GT.cols_reorder
- GT.cols_hide
- title: Location Targeting and Styling Classes
desc: >
Expand Down
2 changes: 1 addition & 1 deletion great_tables/_gt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ def vars_from_type(self, type: ColInfoTypeEnum) -> list[str]:
def reorder(self, vars: list[str]) -> Self:
boxh_vars = [col.var for col in self]
if set(vars) != set(boxh_vars):
raise ValueError("Reordering vars must contain all boxhead vars.")
raise ValueError("Column reordering must include all columns in the table.")

new_order = [boxh_vars.index(var) for var in vars]

Expand Down
55 changes: 55 additions & 0 deletions great_tables/_spanners.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,3 +753,58 @@
curr_boxhead = curr_boxhead._set_column_width(col, width)

return self._replace(_boxhead=curr_boxhead)


def cols_reorder(self: GTSelf, columns: SelectExpr) -> GTSelf:
"""Reordering the columns as specified.

We can easily reorder columns by specifying `columns`. While this can be done upstream of
**Great Tables**, `cols_reorder()` is simpler and reduces the chance of errors. The order of
`columns` is preserved.

Note that all columns specified in `columns` must exist in the table, and the list must contain
the same number of columns as the original table.

Parameters
----------
columns
The columns to target, specified as a list of column names.

Returns
-------
GT
The GT object is returned. This is the same object that the method is called on so that we
can facilitate method chaining.

Examples
--------
Let's use the `exibble` dataset to create a new table.
```{python}
from great_tables import GT
from great_tables.data import exibble

gt = GT(exibble)
gt
```
Suppose you want to move the `fctr` column to the beginning of the table and position the `char`
and `num` columns as the last two. While this can be accomplished using `cols_move()`,
`cols_move_to_start()`, and `cols_move_to_end()`, **Great Tables** offers a more convenient
function: `cols_reorder()`. By passing a list of column names to the `columns=` parameter,
the table will be reordered accordingly:
```{python}
num_col, char_col, fctr_col, *cols = exibble.columns
gt.cols_reorder([fctr_col, *cols, char_col, num_col])
```
"""

if isinstance(columns, str):
columns = [columns]

Check warning on line 802 in great_tables/_spanners.py

View check run for this annotation

Codecov / codecov/patch

great_tables/_spanners.py#L802

Added line #L802 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should raise here, if only to provide a more user-friendly message. The user will instead see "Reordering vars must contain all boxhead vars." if not providing all columns, which is good for a developer-facing error message but it exposes some of the underlying implementation that the user might not know about.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great suggestion! Should we raise the exception inside the function or modify the error message to be more user-friendly rather than developer-focused?

Copy link
Member

Choose a reason for hiding this comment

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

Good question. I’m for slightly tweaking the wording in the existing location. Just switch vars with columns and avoid the use of boxhead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How about "Column reordering must include all columns in the table."?

Copy link
Member

Choose a reason for hiding this comment

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

I like it!

sel_cols = resolve_cols_c(data=self, expr=columns)

col_vars = [col.var for col in self._boxhead]

_validate_sel_cols(sel_cols, col_vars)

new_boxhead = self._boxhead.reorder(sel_cols)
return self._replace(_boxhead=new_boxhead)
2 changes: 2 additions & 0 deletions great_tables/gt.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
cols_move,
cols_move_to_end,
cols_move_to_start,
cols_reorder,
cols_width,
tab_spanner,
)
Expand Down Expand Up @@ -256,6 +257,7 @@ def __init__(
cols_move = cols_move
cols_move_to_start = cols_move_to_start
cols_move_to_end = cols_move_to_end
cols_reorder = cols_reorder
cols_hide = cols_hide

tab_header = tab_header
Expand Down
29 changes: 29 additions & 0 deletions tests/test_spanners.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
cols_move,
cols_move_to_end,
cols_move_to_start,
cols_reorder,
empty_spanner_matrix,
spanners_print_matrix,
tab_spanner,
Expand Down Expand Up @@ -358,6 +359,34 @@ def test_cols_move_to_end_multi_cols(DF, columns):
assert [col.var for col in new_gt._boxhead] == ["b", "d", "a", "c"]


@pytest.mark.parametrize(
"DF, columns",
[
(pd.DataFrame, ["a", "d", "c", "b"]),
(pl.DataFrame, pl.col("a", "d", "c", "b")),
(pl.DataFrame, [pl.col("a", "d", "c"), "b"]),
],
)
def test_cols_reorder(DF, columns):
df = DF({"a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8]})
src_gt = GT(df)
new_gt = cols_reorder(src_gt, columns=columns)
assert [col.var for col in new_gt._boxhead] == ["a", "d", "c", "b"]


@pytest.mark.parametrize(
"DF, columns",
[(pd.DataFrame, ["a", "d"]), (pl.DataFrame, ["a", "d", "c"])],
)
def test_cols_reorder_raises(DF, columns):
df = DF({"a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8]})
src_gt = GT(df)
with pytest.raises(Exception) as exc_info:
new_gt = cols_reorder(src_gt, columns=columns)

assert "Column reordering must include all columns in the table." in exc_info.value.args[0]


@pytest.mark.parametrize("DF, columns", [(pd.DataFrame, "c"), (pl.DataFrame, cs.starts_with("c"))])
def test_cols_hide_single_col(DF, columns):
df = DF({"a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8]})
Expand Down