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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed
- Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#3441](https://github.com/plotly/plotly.py/issues/3441)], with thanks to @hb1915 for the contribution!
- Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5765](https://github.com/plotly/plotly.py/issues/5765)], with thanks to @Irahan2 for the contribution!


## [7.1.0] - 2026-09-15
Expand Down
11 changes: 9 additions & 2 deletions plotly/express/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1958,7 +1958,12 @@ def process_dataframe_hierarchy(args):
_check_dataframe_all_leaves(df[path[::-1]])
discrete_color = not _is_continuous(df, args["color"]) if args["color"] else False

df = df.lazy()
# Keep track of the original row order, so that the sectors can be sorted by
# first appearance after each group_by (Polars' group_by does not keep order).
# TODO: drop this column and use group_by(maintain_order=True) once narwhals
# exposes it, see #5769 and narwhals-dev/narwhals#3309.
row_index_colname = _generate_temporary_column_name(n_bytes=16, columns=df.columns)
df = df.with_row_index(row_index_colname).lazy()

new_path = [col_name + "_path_copy" for col_name in path]
df = df.with_columns(
Expand Down Expand Up @@ -1997,6 +2002,7 @@ def process_dataframe_hierarchy(args):
# Since count_colname is always in agg_f, it can be used later to normalize color
# in the continuous case after some gymnastic
agg_f[count_colname] = nw.sum(count_colname)
agg_f[row_index_colname] = nw.min(row_index_colname)

discrete_aggs = []
continuous_aggs = []
Expand Down Expand Up @@ -2049,7 +2055,7 @@ def process_dataframe_hierarchy(args):
agg_f[args["color"]] = nw.sum(args["color"])

# Other columns (for color, hover_data, custom_data etc.)
cols = list(set(df.collect_schema().names()).difference(path))
cols = list(set(df.collect_schema().names()).difference([*path, row_index_colname]))
df = df.with_columns(nw.col(c).cast(nw.String()) for c in cols if c not in agg_f)

for col in cols: # for hover_data, custom_data etc.
Expand Down Expand Up @@ -2092,6 +2098,7 @@ def post_agg(dframe: nw.LazyFrame, continuous_aggs, discrete_aggs) -> nw.LazyFra
dfg = (
df.group_by(path[i:], drop_null_keys=True)
.agg(**agg_f)
.sort(row_index_colname)
.pipe(post_agg, continuous_aggs, discrete_aggs)
)

Expand Down
31 changes: 31 additions & 0 deletions tests/test_optional/test_px/test_px_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,37 @@ def test_sunburst_treemap_with_path(constructor):
assert fig.data[0].values[-1] == 8


@pytest.mark.parametrize("px_fn", [px.sunburst, px.treemap, px.icicle])
def test_sunburst_treemap_with_path_order(constructor, px_fn):
if _pandas_version_at_least("3.0.0") and constructor == pandas_pyarrow_constructor:
pytest.skip(
"known issue with pandas 3 + pandas_pyarrow_constructor + px hierarchy charts (https://github.com/plotly/plotly.py/issues/5571)"
)

# Sectors should follow the order of first appearance in the data, whatever

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's a previously known issue biting us here. We skip it up above in test_sunburst_treemap_with_path so we should do the same thing here:

Suggested change
# Sectors should follow the order of first appearance in the data, whatever
if _pandas_version_at_least("3.0.0") and constructor == pandas_pyarrow_constructor:
pytest.skip(
"known issue with pandas 3 + pandas_pyarrow_constructor + px hierarchy charts (https://github.com/plotly/plotly.py/issues/5571)"
)
# Sectors should follow the order of first appearance in the data, whatever

# the dataframe backend (Polars' group_by does not keep the row order).
df = constructor(
dict(
regions=["South", "North", "South", "West", "North", "West"],
sectors=["Tech", "Finance", "Finance", "Tech", "Tech", "Finance"],
values=[1, 2, 3, 4, 5, 6],
)
)
fig = px_fn(df, path=["regions", "sectors"], values="values")
assert list(fig.data[0].ids) == [
"South/Tech",
"North/Finance",
"South/Finance",
"West/Tech",
"North/Tech",
"West/Finance",
"South",
"North",
"West",
]
assert list(fig.data[0].values) == [1, 2, 3, 4, 5, 6, 4, 7, 10]


def test_sunburst_treemap_with_path_and_hover(backend):
df = px.data.tips(return_type=backend)
fig = px.sunburst(
Expand Down