Skip to content

Commit fadafba

Browse files
committed
be able to add lines for all indices, not just visible ones [fix #59877]
- implement a new suffix for the `clines` option, `-invisible`, doubling the number of available options, specifying that hidden indices should be included when deciding whether to add \clines - add tests for this behaviour
1 parent 23c497b commit fadafba

File tree

2 files changed

+134
-5
lines changed

2 files changed

+134
-5
lines changed

pandas/io/formats/style_render.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -930,12 +930,18 @@ def concatenated_visible_rows(obj):
930930
None,
931931
"all;data",
932932
"all;index",
933+
"all-invisible;data",
934+
"all-invisible;index",
933935
"skip-last;data",
934936
"skip-last;index",
937+
"skip-last-invisible;data",
938+
"skip-last-invisible;index",
935939
]:
936940
raise ValueError(
937941
f"`clines` value of {clines} is invalid. Should either be None or one "
938-
f"of 'all;data', 'all;index', 'skip-last;data', 'skip-last;index'."
942+
f"of 'all;data', 'all;index', 'all-invisible;data', "
943+
f"'all-invisible;index', 'skip-last;data', 'skip-last;index', "
944+
f"'skip-last-invisible;data', 'skip-last-invisible;index'."
939945
)
940946
if clines is not None:
941947
data_len = len(row_body_cells) if "data" in clines and d["body"] else 0
@@ -947,15 +953,25 @@ def concatenated_visible_rows(obj):
947953
visible_index_levels: list[int] = [
948954
i for i in range(index_levels) if not self.hide_index_[i]
949955
]
956+
target_index_levels: list[int] = [
957+
i for i in range(index_levels)
958+
if "invisible" in clines or not self.hide_index_[i]
959+
]
950960
for rn, r in enumerate(visible_row_indexes):
951-
for lvln, lvl in enumerate(visible_index_levels):
961+
lvln = 0
962+
for lvl in target_index_levels:
952963
if lvl == index_levels - 1 and "skip-last" in clines:
953964
continue
954965
idx_len = d["index_lengths"].get((lvl, r), None)
955966
if idx_len is not None: # i.e. not a sparsified entry
956-
d["clines"][rn + idx_len].append(
957-
f"\\cline{{{lvln+1}-{len(visible_index_levels)+data_len}}}"
958-
)
967+
cline_start_col = lvln + 1
968+
cline_end_col = len(visible_index_levels) + data_len
969+
if cline_end_col >= cline_start_col:
970+
d["clines"][rn + idx_len].append(
971+
f"\\cline{{{cline_start_col}-{cline_end_col}}}"
972+
)
973+
if lvl in visible_index_levels:
974+
lvln += 1
959975

960976
def format(
961977
self,

pandas/tests/io/formats/style/test_to_latex.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,8 +896,12 @@ def test_clines_validation(clines, styler):
896896
[
897897
("all;index", "\n\\cline{1-1}"),
898898
("all;data", "\n\\cline{1-2}"),
899+
("all-invisible;index", "\n\\cline{1-1}"),
900+
("all-invisible;data", "\n\\cline{1-2}"),
899901
("skip-last;index", ""),
900902
("skip-last;data", ""),
903+
("skip-last-invisible;index", ""),
904+
("skip-last-invisible;data", ""),
901905
(None, ""),
902906
],
903907
)
@@ -984,6 +988,62 @@ def test_clines_index(clines, exp, env):
984988
"""
985989
),
986990
),
991+
(
992+
"skip-last-invisible;index",
993+
dedent(
994+
"""\
995+
\\multirow[c]{2}{*}{A} & X & 1 \\\\
996+
& Y & 2 \\\\
997+
\\cline{1-2} \\cline{2-2}
998+
\\multirow[c]{2}{*}{B} & X & 3 \\\\
999+
& Y & 4 \\\\
1000+
\\cline{1-2} \\cline{2-2}
1001+
"""
1002+
),
1003+
),
1004+
(
1005+
"skip-last-invisible;data",
1006+
dedent(
1007+
"""\
1008+
\\multirow[c]{2}{*}{A} & X & 1 \\\\
1009+
& Y & 2 \\\\
1010+
\\cline{1-3} \\cline{2-3}
1011+
\\multirow[c]{2}{*}{B} & X & 3 \\\\
1012+
& Y & 4 \\\\
1013+
\\cline{1-3} \\cline{2-3}
1014+
"""
1015+
),
1016+
),
1017+
(
1018+
"all-invisible;index",
1019+
dedent(
1020+
"""\
1021+
\\multirow[c]{2}{*}{A} & X & 1 \\\\
1022+
\\cline{2-2}
1023+
& Y & 2 \\\\
1024+
\\cline{1-2} \\cline{2-2} \\cline{2-2}
1025+
\\multirow[c]{2}{*}{B} & X & 3 \\\\
1026+
\\cline{2-2}
1027+
& Y & 4 \\\\
1028+
\\cline{1-2} \\cline{2-2} \\cline{2-2}
1029+
"""
1030+
),
1031+
),
1032+
(
1033+
"all-invisible;data",
1034+
dedent(
1035+
"""\
1036+
\\multirow[c]{2}{*}{A} & X & 1 \\\\
1037+
\\cline{2-3}
1038+
& Y & 2 \\\\
1039+
\\cline{1-3} \\cline{2-3} \\cline{2-3}
1040+
\\multirow[c]{2}{*}{B} & X & 3 \\\\
1041+
\\cline{2-3}
1042+
& Y & 4 \\\\
1043+
\\cline{1-3} \\cline{2-3} \\cline{2-3}
1044+
"""
1045+
),
1046+
),
9871047
],
9881048
)
9891049
@pytest.mark.parametrize("env", ["table"])
@@ -998,6 +1058,59 @@ def test_clines_multiindex(clines, expected, env):
9981058
assert expected in result
9991059

10001060

1061+
@pytest.mark.parametrize(
1062+
"clines, expected",
1063+
[
1064+
(None, "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1065+
("all;data", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1066+
("all;index", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1067+
("skip-last;data", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1068+
("skip-last;index", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1069+
("all-invisible;index", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1070+
("skip-last-invisible;index", "1 \\\\\n2 \\\\\n3 \\\\\n4 \\\\\n"),
1071+
(
1072+
"all-invisible;data",
1073+
dedent(
1074+
"""\
1075+
1 \\\\
1076+
\\cline{1-1}
1077+
2 \\\\
1078+
\\cline{1-1} \\cline{1-1}
1079+
3 \\\\
1080+
\\cline{1-1}
1081+
4 \\\\
1082+
\\cline{1-1} \\cline{1-1}
1083+
"""
1084+
),
1085+
),
1086+
(
1087+
"skip-last-invisible;data",
1088+
dedent(
1089+
"""\
1090+
1 \\\\
1091+
2 \\\\
1092+
\\cline{1-1}
1093+
3 \\\\
1094+
4 \\\\
1095+
\\cline{1-1}
1096+
"""
1097+
),
1098+
),
1099+
]
1100+
)
1101+
@pytest.mark.parametrize("env", ["table"])
1102+
def test_clines_hiddenindex(clines, expected, env):
1103+
# Make sure that \clines are correctly hidden or shown with all indixes hideen
1104+
midx = MultiIndex.from_product([["A", "-", "B"], ["X", "Y"]])
1105+
df = DataFrame([[1], [2], [99], [99], [3], [4]], index=midx)
1106+
styler = df.style
1107+
styler.hide([("-", "X"), ("-", "Y")])
1108+
styler.hide(axis=0)
1109+
result = styler.to_latex(clines=clines, environment=env)
1110+
assert expected in result
1111+
1112+
1113+
10011114
def test_col_format_len(styler):
10021115
# gh 46037
10031116
result = styler.to_latex(environment="longtable", column_format="lrr{10cm}")

0 commit comments

Comments
 (0)