From 54531554e4491ab022235016e3697a6ced2ccbc6 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 8 Jul 2021 09:51:19 -0400 Subject: [PATCH 1/3] Manually revert 6cea61db9280b33d4870ff527be9dcafeed45bd3 Allow a figure to store numpy arrays with Object dtype again --- .../plotly/_plotly_utils/basevalidators.py | 69 +++++++++++-------- .../validators/test_dataarray_validator.py | 18 +---- .../validators/test_enumerated_validator.py | 2 +- .../validators/test_pandas_series_input.py | 28 +++++--- .../tests/validators/test_string_validator.py | 7 +- .../tests/validators/test_xarray_input.py | 7 +- .../tests/test_optional/test_px/test_px.py | 8 +-- .../test_px/test_px_functions.py | 6 +- .../test_optional/test_px/test_px_input.py | 2 +- 9 files changed, 82 insertions(+), 65 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/basevalidators.py b/packages/python/plotly/_plotly_utils/basevalidators.py index da0d21db7c..66e7b15dc8 100644 --- a/packages/python/plotly/_plotly_utils/basevalidators.py +++ b/packages/python/plotly/_plotly_utils/basevalidators.py @@ -53,7 +53,7 @@ def to_scalar_or_list(v): return v -def copy_to_readonly_numpy_array_or_list(v, kind=None, force_numeric=False): +def copy_to_readonly_numpy_array(v, kind=None, force_numeric=False): """ Convert an array-like value into a read-only numpy array @@ -94,7 +94,6 @@ def copy_to_readonly_numpy_array_or_list(v, kind=None, force_numeric=False): "i": "int32", "f": "float64", "O": "object", - "U": "U", } # Handle pandas Series and Index objects @@ -119,12 +118,18 @@ def copy_to_readonly_numpy_array_or_list(v, kind=None, force_numeric=False): if not isinstance(v, np.ndarray): # v has its own logic on how to convert itself into a numpy array if is_numpy_convertable(v): - return copy_to_readonly_numpy_array_or_list( + return copy_to_readonly_numpy_array( np.array(v), kind=kind, force_numeric=force_numeric ) else: # v is not homogenous array - return [to_scalar_or_list(e) for e in v] + v_list = [to_scalar_or_list(e) for e in v] + + # Lookup dtype for requested kind, if any + dtype = kind_default_dtypes.get(first_kind, None) + + # construct new array from list + new_v = np.array(v_list, order="C", dtype=dtype) elif v.dtype.kind in numeric_kinds: # v is a homogenous numeric array if kind and v.dtype.kind not in kind: @@ -135,12 +140,6 @@ def copy_to_readonly_numpy_array_or_list(v, kind=None, force_numeric=False): else: # Either no kind was requested or requested kind is satisfied new_v = np.ascontiguousarray(v.copy()) - elif v.dtype.kind == "O": - if kind: - dtype = kind_default_dtypes.get(first_kind, None) - return np.array(v, dtype=dtype) - else: - return v.tolist() else: # v is a non-numeric homogenous array new_v = v.copy() @@ -155,12 +154,12 @@ def copy_to_readonly_numpy_array_or_list(v, kind=None, force_numeric=False): if "U" not in kind: # Force non-numeric arrays to have object type # -------------------------------------------- - # Here we make sure that non-numeric arrays become lists - # This works around cases like np.array([1, 2, '3']) where + # Here we make sure that non-numeric arrays have the object + # datatype. This works around cases like np.array([1, 2, '3']) where # numpy converts the integers to strings and returns array of dtype # 'sepal_length=%{y}
petal_length=%{customdata[2]}
petal_width=%{customdata[3]}
species_id=%{customdata[0]}" diff --git a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py index fad79b5ded..a75a45f43d 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_functions.py @@ -229,9 +229,9 @@ def test_sunburst_treemap_with_path_color(): df["hover"] = [el.lower() for el in vendors] fig = px.sunburst(df, path=path, color="calls", hover_data=["hover"]) custom = fig.data[0].customdata - assert [el[0] for el in custom[:8]] == df["hover"].tolist() - assert [el[0] for el in custom[8:]] == ["(?)"] * 7 - assert [el[1] for el in custom[:8]] == df["calls"].tolist() + assert np.all(custom[:8, 0] == df["hover"]) + assert np.all(custom[8:, 0] == "(?)") + assert np.all(custom[:8, 1] == df["calls"]) # Discrete color fig = px.sunburst(df, path=path, color="vendors") diff --git a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py index 0dce0ae663..477e7dbcb0 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py @@ -126,7 +126,7 @@ def test_repeated_name(): hover_data=["petal_length", "petal_width", "species_id"], custom_data=["species_id", "species"], ) - assert len(fig.data[0].customdata[0]) == 4 + assert fig.data[0].customdata.shape[1] == 4 def test_arrayattrable_numpy(): From dba2bee238eb7e096faf0df62ff13c6d22f1a042 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 8 Jul 2021 09:56:52 -0400 Subject: [PATCH 2/3] Add JSON test for serializing customdata as numpy array --- .../plotly/tests/test_io/test_to_from_plotly_json.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py b/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py index 70d6803f1a..63283803e6 100644 --- a/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py +++ b/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py @@ -1,6 +1,7 @@ import pytest import plotly.io.json as pio import plotly.graph_objects as go +import plotly.express as px import numpy as np import pandas as pd import json @@ -202,3 +203,9 @@ def to_str(v): expected = build_test_dict_string(array_str) assert result == expected check_roundtrip(result, engine=engine, pretty=pretty) + + +def test_object_array(engine, pretty): + fig = px.scatter(px.data.tips(), x="total_bill", y="tip", custom_data=["sex"]) + result = fig.to_plotly_json() + check_roundtrip(result, engine=engine, pretty=pretty) From a921ab5917df692b085bd10bade0591f3976b618 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 8 Jul 2021 14:20:07 -0400 Subject: [PATCH 3/3] Add CHANGELOG.md entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c842a380..8e9df6bc6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## UNRELEASED +### Fixed + - Fixed regression introduced in version 5.0.0 where pandas/numpy arrays with `dtype` of Object were being converted to `list` values when added to a Figure ([#3292](https://github.com/plotly/plotly.py/issues/3292), [#3293](https://github.com/plotly/plotly.py/pull/3293)) ## [5.1.0] - 2021-06-28