Skip to content

Fix Plotly Express mutating lists passed to x or y in wide mode - #5727

Merged
camdecoster merged 4 commits into
plotly:mainfrom
cpruijsen:fix/issue-4117
Oct 9, 2026
Merged

camdecoster merged 4 commits into
plotly:mainfrom
cpruijsen:fix/issue-4117

Conversation

@cpruijsen

Copy link
Copy Markdown
Contributor

In wide mode, Plotly Express mutates the list a caller passes as x or y, converting its values to
strings in place. The caller's own variable changes underneath them, which is surprising on its own
and breaks any later use of that list.

build_dataframe in plotly/express/_core.py assigns the argument to args["wide_variable"] and
then replaces entries in it. The copy that would have prevented this was conditional: it only ran when
the value was a pandas Index, so a plain list was aliased rather than copied and the replacement wrote
through to the caller's object.

The copy is now unconditional. list() over a list is a shallow copy, which is all that is needed
here since only the entries are replaced, and the Index case takes the same path it did before.

The test asserts the caller's list is unchanged after the call, which fails on main.

Changelog entry included, per the repo convention.

Fixes #4117

@camdecoster camdecoster self-assigned this Sep 14, 2026
args['wide_variable'] aliased the user-supplied x/y list, and
process_args_into_dataframe replaces its elements with column-name
strings in place. Copy it into a fresh list (this also accepts tuples,
which previously failed on item assignment).

Fixes plotly#4117

@camdecoster camdecoster left a comment

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.

Thanks for the fix! Once you update the test, I can approve this.

Comment on lines +895 to +905
def test_wide_mode_does_not_mutate_x_or_y():
# https://github.com/plotly/plotly.py/issues/4117
df = pd.DataFrame(dict(a=[1, 2, 3], b=[4, 5, 6], c=[7, 8, 9]))
for arg in ["x", "y"]:
cols = ["a", "b"]
px.bar(df, **{arg: cols})
assert cols == ["a", "b"]
cols = [0, 1]
df_int = pd.DataFrame([[1, 2], [3, 4]])
px.histogram(df_int, x=cols)
assert cols == [0, 1]

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.

Could you update this test to check the case where the column names are integers to match the issue? With string names, the mutation doesn't do anything because "a" is the same as str("a").

Comment thread plotly/express/_core.py Outdated
var_name = columns.name
if is_pd_like and isinstance(args["wide_variable"], native_namespace.Index):
args["wide_variable"] = list(args["wide_variable"])
# copy into a new list so that the list provided by the user for

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.

Suggested change
# copy into a new list so that the list provided by the user for
# copy into a new list so that the object passed by the user for

@cpruijsen

Copy link
Copy Markdown
Contributor Author

Thanks. The test now uses integer column names for both x and y, and it fails without the fix in _core.py. I also applied your comment wording.

@camdecoster camdecoster left a comment

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.

Thanks for the fix!

@camdecoster
camdecoster merged commit 3f8a650 into plotly:main Oct 9, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plotly Express constructor: in wide mode, lists passed to x and y params are mutated

2 participants