Skip to content
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

Make invocation loc.body() default to all columns and rows #79

Merged
merged 14 commits into from
Dec 12, 2023
Merged
Changes from 9 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
78 changes: 67 additions & 11 deletions great_tables/_locations.py
Original file line number Diff line number Diff line change
@@ -105,8 +105,8 @@ class LocBody(Loc):
LocBody
A LocBody object, which is used for a `locations` argument if specifying the table body.
"""
columns: SelectExpr
rows: list[str] | str
columns: SelectExpr = None
rows: list[str] | str | None = None


@dataclass
@@ -179,20 +179,28 @@ def resolve_vector_l(expr: list[str], candidates: list[str], item_label: str) ->


def resolve_cols_c(
expr: SelectExpr,
data: GTData,
expr: SelectExpr,
strict: bool = True,
excl_stub: bool = True,
excl_group: bool = True,
null_means: Literal["everything", "nothing"] = "everything",
) -> list[str]:
selected = resolve_cols_i(expr, data, strict, excl_stub, excl_group, null_means)
"""Return a list of column names, selected by expr."""
selected = resolve_cols_i(
data=data,
expr=expr,
strict=strict,
excl_stub=excl_stub,
excl_group=excl_group,
null_means=null_means,
)
return [name_pos[0] for name_pos in selected]


def resolve_cols_i(
expr: SelectExpr,
data: GTData | TblData,
expr: SelectExpr,
strict: bool = True,
excl_stub: bool = True,
excl_group: bool = True,
@@ -210,6 +218,49 @@ def resolve_cols_i(

return []

# If expr is None, we want to select everything or nothing depending on
# the value of `null_means`
if expr is None:
if null_means == "everything":
# If `null_means` is "everything", we want to select all columns, perhaps
# excluding the stub and/or group columns depending on the values of
# `excl_stub` and `excl_group`; first, we get the column names and positions
# for all columns

if not excl_stub and not excl_group:
return [(col, ii) for ii, col in enumerate(data._tbl_data.columns)]

# Depending on the value of `excl_stub` and `excl_group`, we want
# to exclude the stub and/or group columns from the selection
if excl_stub and excl_group:
# If `excl_stub` and `excl_group` are both True, exclude both
# the stub and group columns from the selection
cols_excl = stub_var + data._boxhead.vars_from_type(ColInfoTypeEnum.row_group)
return [
(col, ii)
for ii, col in enumerate(data._tbl_data.columns)
if col not in cols_excl
]
elif excl_stub:
# If `excl_stub` is True, exclude the stub column from the selection
cols_excl = stub_var
return [
(col, ii)
for ii, col in enumerate(data._tbl_data.columns)
if col not in cols_excl
]
elif excl_group:
# If `excl_group` is True, exclude the group column from the selection
cols_excl = data._boxhead.vars_from_type(ColInfoTypeEnum.row_group)
return [
(col, ii)
for ii, col in enumerate(data._tbl_data.columns)
if col not in cols_excl
]

else:
return []

if not excl_stub:
# In most cases we would want to exclude the column that
# represents the stub but that isn't always the case (e.g.,
@@ -245,9 +296,9 @@ def resolve_cols_i(


def resolve_rows_i(
expr: list[str | int],
data: GTData | list[str],
null_means: Literal["everything", "nothing"] = "nothing",
expr: list[str | int] | None = None,
null_means: Literal["everything", "nothing"] = "everything",
) -> list[tuple[str, int]]:
"""Return matching row numbers, based on expr

@@ -257,10 +308,15 @@ def resolve_rows_i(

Unlike tidyselect::eval_select, this function returns names in
the order they appear in the data (rather than ordered by selectors).

"""

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

row_names = [row.rowname for row in data._stub]
else:
row_names = data
@@ -281,7 +337,7 @@ def resolve_rows_i(
elif isinstance(expr, PlExpr):
# TODO: decide later on the name supplied to `name`
result = data._tbl_data.with_row_count(name="__row_number__").filter(expr)
print([(row_names[ii], ii) for ii in result["__row_number__"]])
# print([(row_names[ii], ii) for ii in result["__row_number__"]])
return [(row_names[ii], ii) for ii in result["__row_number__"]]

# TODO: identify filter-like selectors using some backend check
@@ -314,8 +370,8 @@ def _(loc: LocColumnSpanners, spanners: Spanners) -> LocColumnSpanners:

@resolve.register
def _(loc: LocBody, data: GTData) -> List[CellPos]:
cols = resolve_cols_i(loc.columns, data)
rows = resolve_rows_i(loc.rows, data)
cols = resolve_cols_i(data=data, expr=loc.columns)
rows = resolve_rows_i(data=data, expr=loc.rows)

# TODO: dplyr arranges by `Var1`, and does distinct (since you can tidyselect the same
# thing multiple times
12 changes: 6 additions & 6 deletions great_tables/_spanners.py
Original file line number Diff line number Diff line change
@@ -138,7 +138,7 @@ def tab_spanner(
# TODO: null_means is unimplemented
raise NotImplementedError("columns must be specified")

selected_column_names = resolve_cols_c(columns, data, null_means="nothing")
selected_column_names = resolve_cols_c(data=data, expr=columns, null_means="nothing")

# select spanner ids ----
# TODO: this supports tidyselect
@@ -245,9 +245,9 @@ def cols_move(data: GTData, columns: SelectExpr, after: str) -> GTData:
if isinstance(columns, str):
columns = [columns]

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

sel_after = resolve_cols_c([after], data)
sel_after = resolve_cols_c(data=data, expr=[after])

vars = [col.var for col in data._boxhead]

@@ -327,7 +327,7 @@ def cols_move_to_start(data: GTSelf, columns: SelectExpr) -> GTSelf:
if isinstance(columns, str):
columns = [columns]

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

vars = [col.var for col in data._boxhead]

@@ -393,7 +393,7 @@ def cols_move_to_end(data: GTSelf, columns: SelectExpr) -> GTSelf:
if isinstance(columns, str):
columns = [columns]

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

vars = [col.var for col in data._boxhead]

@@ -446,7 +446,7 @@ def cols_hide(data: GTData, columns: SelectExpr) -> GTData:
if isinstance(columns, str):
columns = [columns]

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

vars = [col.var for col in data._boxhead]

110 changes: 109 additions & 1 deletion tests/__snapshots__/test_utils_render_html.ambr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_format_snap
# name: test_source_notes_snap
'''
<tfoot class="gt_sourcenotes">

@@ -25,3 +25,111 @@
</tfoot>
'''
# ---
# name: test_styling_data_1
'''
<tbody class="gt_table_body">
<tr>
<td style="color: red;" class="gt_row gt_right">0.1111</td>
<td style="color: red;" class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td style="color: red;" class="gt_row gt_right">2.222</td>
<td style="color: red;" class="gt_row gt_left">banana</td>
</tr>
<tr>
<td style="color: red;" class="gt_row gt_right">33.33</td>
<td style="color: red;" class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
# name: test_styling_data_2
'''
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_right">0.1111</td>
<td style="color: red;" class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td class="gt_row gt_right">2.222</td>
<td style="color: red;" class="gt_row gt_left">banana</td>
</tr>
<tr>
<td class="gt_row gt_right">33.33</td>
<td style="color: red;" class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
# name: test_styling_data_3
'''
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_right">0.1111</td>
<td style="color: red;" class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td class="gt_row gt_right">2.222</td>
<td class="gt_row gt_left">banana</td>
</tr>
<tr>
<td class="gt_row gt_right">33.33</td>
<td style="color: red;" class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
# name: test_styling_data_4
'''
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_right">0.1111</td>
<td class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td class="gt_row gt_right">2.222</td>
<td class="gt_row gt_left">banana</td>
</tr>
<tr>
<td class="gt_row gt_right">33.33</td>
<td class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
# name: test_styling_data_5
'''
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_right">0.1111</td>
<td class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td class="gt_row gt_right">2.222</td>
<td class="gt_row gt_left">banana</td>
</tr>
<tr>
<td class="gt_row gt_right">33.33</td>
<td class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
# name: test_styling_data_6
'''
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_right">0.1111</td>
<td class="gt_row gt_left">apricot</td>
</tr>
<tr>
<td class="gt_row gt_right">2.222</td>
<td class="gt_row gt_left">banana</td>
</tr>
<tr>
<td class="gt_row gt_right">33.33</td>
<td class="gt_row gt_left">coconut</td>
</tr>
</tbody>
'''
# ---
12 changes: 6 additions & 6 deletions tests/test_locations.py
Original file line number Diff line number Diff line change
@@ -20,30 +20,30 @@ def test_resolve_vector_i():

def test_resolve_cols_i_gt_data():
gt = GT(pd.DataFrame(columns=["a", "b", "x"]))
assert resolve_cols_i(["x", "a"], gt) == [("x", 2), ("a", 0)]
assert resolve_cols_i(gt, ["x", "a"]) == [("x", 2), ("a", 0)]


def test_resolve_cols_i_strings():
df = pd.DataFrame(columns=["a", "b", "x"])
assert resolve_cols_i(["x", "a"], df) == [("x", 2), ("a", 0)]
assert resolve_cols_i(df, ["x", "a"]) == [("x", 2), ("a", 0)]


def test_resolve_cols_i_ints():
df = pd.DataFrame(columns=["a", "b", "x"])
assert resolve_cols_i([-1, 0], df) == [("x", 2), ("a", 0)]
assert resolve_cols_i(df, [-1, 0]) == [("x", 2), ("a", 0)]


def test_resolve_rows_i_gt_data():
gt = GT(pd.DataFrame({"x": ["a", "b", "c"]}), rowname_col="x")
assert resolve_rows_i(["b", "a"], gt) == [("a", 0), ("b", 1)]
assert resolve_rows_i(gt, ["b", "a"]) == [("a", 0), ("b", 1)]


def test_resolve_rows_i_strings():
assert resolve_rows_i(["x", "a"], ["a", "x", "a", "b"]) == [("a", 0), ("x", 1), ("a", 2)]
assert resolve_rows_i(["a", "x", "a", "b"], ["x", "a"]) == [("a", 0), ("x", 1), ("a", 2)]


def test_resolve_rows_i_ints():
assert resolve_rows_i([0, -1], ["a", "x", "a", "b"]) == [("a", 0), ("b", 3)]
assert resolve_rows_i(["a", "x", "a", "b"], [0, -1]) == [("a", 0), ("b", 3)]


def test_resolve_loc_body():
72 changes: 65 additions & 7 deletions tests/test_utils_render_html.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from typing import Union
from great_tables import GT, exibble, md, html, style, loc
from great_tables._utils_render_html import create_source_notes_component_h, create_body_component_h

import re

from great_tables import GT, exibble, md, html
from great_tables._utils_render_html import create_source_notes_component_h
from great_tables._source_notes import tab_source_note
small_exibble = exibble[["num", "char"]].head(3)


def assert_rendered_source_notes(snapshot, gt):
@@ -14,7 +11,14 @@ def assert_rendered_source_notes(snapshot, gt):
assert snapshot == source_notes


def test_format_snap(snapshot):
def assert_rendered_body(snapshot, gt):
built = gt._build_data("html")
body = create_body_component_h(built)

assert snapshot == body


def test_source_notes_snap(snapshot):
new_gt = (
GT(exibble)
.tab_source_note(md("An **important** note."))
@@ -24,3 +28,57 @@ def test_format_snap(snapshot):
)

assert_rendered_source_notes(snapshot, new_gt)


def test_styling_data_1(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(),
)

assert_rendered_body(snapshot, new_gt)


def test_styling_data_2(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(columns=["char"]),
)

assert_rendered_body(snapshot, new_gt)


def test_styling_data_3(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(columns="char", rows=[0, 2]),
)

assert_rendered_body(snapshot, new_gt)


def test_styling_data_4(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(columns=[], rows=[0, 2]),
)

assert_rendered_body(snapshot, new_gt)


def test_styling_data_5(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(columns="char", rows=[]),
)

assert_rendered_body(snapshot, new_gt)


def test_styling_data_6(snapshot):
new_gt = GT(small_exibble).tab_style(
style=style.text(color="red"),
locations=loc.body(columns=[], rows=[]),
)

assert_rendered_body(snapshot, new_gt)