Skip to content

Commit 6f69432

Browse files
committed
Improve px error for unknown column names in a list passed to x or y
When x or y is a list of column names and one of them is not a column of data_frame (e.g. a typo), Plotly Express does not switch to wide mode and treats the list as data, which then fails with a misleading "All arguments should have the same length" error. In that case, raise an error naming the missing column(s) instead. Lists of strings whose length matches the other arguments are still treated as data. Closes #4040
1 parent 0de8a38 commit 6f69432

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2121
- Fix `convert_dash` mapping dotted lines (`linestyle=':'`) to invalid `'circle'` in Plotly, correctly converting them to `'dot'` and preserving custom dash patterns as pixel lists, [[#5719](https://github.com/plotly/plotly.py/pull/5719)] with thanks to @robertoffmoura for the contribution!
2222
- 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 [[#5766](https://github.com/plotly/plotly.py/pull/5766)], with thanks to @Irahan2 for the contribution!
2323
- Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution!
24+
- Fix the misleading "All arguments should have the same length" error raised by Plotly Express when a list of column names passed to `x` or `y` contains a name that is not a column of `data_frame` (e.g. a typo); the error now names the missing column(s) and lists the available ones [[#5840](https://github.com/plotly/plotly.py/pull/5840)]
2425

2526
## [7.1.0] - 2026-09-15
2627

‎plotly/express/_core.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,39 @@ def _is_col_list(columns, arg, is_pd_like, native_namespace):
11881188
return True
11891189

11901190

1191+
def _check_col_list_typos(field, argument, df_input, length):
1192+
"""Raise a helpful error if `argument` looks like a list of column names of
1193+
`df_input` (e.g. `y=["a", "b"]`) but some of them are not columns, instead of
1194+
letting it be treated as a list of values with a confusing length error.
1195+
`length` is the length of the previously-processed arguments (0 if none).
1196+
"""
1197+
if (
1198+
not isinstance(argument, (list, tuple))
1199+
or len(argument) == 0
1200+
or not all(isinstance(c, str) for c in argument)
1201+
):
1202+
return
1203+
missing = [c for c in argument if c not in df_input.columns]
1204+
if not missing or len(argument) == len(df_input):
1205+
return
1206+
# Only raise where a length error would be raised anyway, so that lists of
1207+
# strings used as data keep working even if some of them match column names
1208+
if length and len(argument) != length:
1209+
raise ValueError(
1210+
"Value of '%s' looks like a list of column names, but %s %s in "
1211+
"'data_frame'. Expected one of %s but received: %s"
1212+
% (
1213+
field,
1214+
", ".join(repr(c) for c in missing),
1215+
"is not the name of a column"
1216+
if len(missing) == 1
1217+
else "are not names of columns",
1218+
str(list(df_input.columns)),
1219+
list(argument),
1220+
)
1221+
)
1222+
1223+
11911224
def _isinstance_listlike(x):
11921225
"""Returns True if x is an iterable which can be transformed into a pandas Series,
11931226
False for the other types of possible values of a `hover_data` dict.
@@ -1421,6 +1454,9 @@ def process_args_into_dataframe(
14211454
if col_name is None: # numpy array, list...
14221455
col_name = _check_name_not_reserved(field, reserved_names)
14231456

1457+
if df_provided and field_name in ("x", "y"):
1458+
_check_col_list_typos(field, argument, df_input, length)
1459+
14241460
if length and (len_arg := len(argument)) != length:
14251461
raise ValueError(
14261462
"All arguments should have the same length. "

‎tests/test_optional/test_px/test_px_input.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,3 +756,36 @@ def test_no_futurewarning():
756756
warn for warn in warn_list if issubclass(warn.category, FutureWarning)
757757
]
758758
assert len(future_warnings) == 0, "FutureWarning(s) raised!"
759+
760+
761+
def test_wrong_column_name_in_list(constructor): # issue 4040
762+
df = constructor(dict(x=[1, 2, 3], data1=[1, 4, 9], data2=[1, 8, 27]))
763+
# a typo in a list of column names used to raise a misleading
764+
# "All arguments should have the same length" error
765+
with pytest.raises(ValueError) as err_msg:
766+
px.line(df, x="x", y=["data1", "Data2"])
767+
msg = str(err_msg.value)
768+
assert "Value of 'y' looks like a list of column names" in msg
769+
assert "'Data2' is not the name of a column in 'data_frame'" in msg
770+
assert "same length" not in msg
771+
if isinstance(df, pd.DataFrame):
772+
# without x, y is compared with the length of the index
773+
with pytest.raises(ValueError, match="'Data2' is not the name of a column"):
774+
px.line(df, y=["data1", "Data2"])
775+
# all names wrong
776+
with pytest.raises(ValueError, match="'Data1', 'Data2' are not names of columns"):
777+
px.line(df, x="x", y=["Data1", "Data2"])
778+
779+
780+
def test_list_of_strings_as_values_with_data_frame():
781+
# lists of strings whose length matches are still treated as data
782+
df = pd.DataFrame(dict(a=[1, 2, 3], b=[4, 5, 6]))
783+
fig = px.scatter(df, x=["u", "v", "w"], y="b")
784+
assert list(fig.data[0].x) == ["u", "v", "w"]
785+
fig = px.scatter(df, x=["u", "v"], y=["w", "z"])
786+
assert list(fig.data[0].y) == ["w", "z"]
787+
# even if some of the strings are also column names
788+
fig = px.scatter(df, x=["a", "u"], y=[1, 2])
789+
assert list(fig.data[0].x) == ["a", "u"]
790+
fig = px.histogram(df, x=["a", "u"])
791+
assert list(fig.data[0].x) == ["a", "u"]

0 commit comments

Comments
 (0)