Skip to content

Commit fa9500b

Browse files
authored
accelerate plotly JSON encoder for numpy arrays without nans (#2880)
* accelerate plotly JSON encoder for numpy arrays without nans * limit change to numerical data types * solution that works for 99% of cases but lists with composite types won't work * other method: brute force string matching of Infinity or NaN * black * added test * black * removed ununsed variable * changelog entry * NaN tested before Infinity
1 parent a1a625e commit fa9500b

File tree

3 files changed

+46
-3
lines changed

3 files changed

+46
-3
lines changed

Diff for: CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4646

4747
### Updated
4848

49+
- The JSON serialization of plotly figures had been accelerated by handling
50+
differently figures with and without NaN and Inf values ([#2880](https://github.com/plotly/plotly.py/pull/2880)).
51+
52+
### Updated
53+
4954
- Updated Plotly.js to version 1.55.2. See the [plotly.js CHANGELOG](https://github.com/plotly/plotly.js/blob/v1.55.2/CHANGELOG.md) for more information. These changes are reflected in the auto-generated `plotly.graph_objects` module.
5055
- `px.imshow` has a new `binary_string` boolean argument, which passes the
5156
image data as a b64 binary string when True. Using binary strings allow for

Diff for: packages/python/plotly/_plotly_utils/utils.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,14 @@ def encode(self, o):
5555
Note that setting invalid separators will cause a failure at this step.
5656
5757
"""
58-
5958
# this will raise errors in a normal-expected way
6059
encoded_o = super(PlotlyJSONEncoder, self).encode(o)
61-
60+
# Brute force guessing whether NaN or Infinity values are in the string
61+
# We catch false positive cases (e.g. strings such as titles, labels etc.)
62+
# but this is ok since the intention is to skip the decoding / reencoding
63+
# step when it's completely safe
64+
if not ("NaN" in encoded_o or "Infinity" in encoded_o):
65+
return encoded_o
6266
# now:
6367
# 1. `loads` to switch Infinity, -Infinity, NaN to None
6468
# 2. `dumps` again so you get 'null' instead of extended JSON

Diff for: packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import absolute_import
22

3-
from inspect import getargspec
43
from unittest import TestCase
54

65
import json as _json
76

87
from plotly.utils import PlotlyJSONEncoder, get_by_path, node_generator
8+
from time import time
9+
import numpy as np
10+
import plotly.graph_objects as go
911

1012

1113
class TestJSONEncoder(TestCase):
@@ -19,6 +21,38 @@ def test_invalid_encode_exception(self):
1921
with self.assertRaises(TypeError):
2022
_json.dumps({"a": {1}}, cls=PlotlyJSONEncoder)
2123

24+
def test_fast_track_finite_arrays(self):
25+
# if NaN or Infinity is found in the json dump
26+
# of a figure, it is decoded and re-encoded to replace these values
27+
# with null. This test checks that NaN and Infinity values are
28+
# indeed converted to null, and that the encoding of figures
29+
# without inf or nan is faster (because we can avoid decoding
30+
# and reencoding).
31+
z = np.random.randn(100, 100)
32+
x = np.arange(100.0)
33+
fig_1 = go.Figure(go.Heatmap(z=z, x=x))
34+
t1 = time()
35+
json_str_1 = _json.dumps(fig_1, cls=PlotlyJSONEncoder)
36+
t2 = time()
37+
x[0] = np.nan
38+
x[1] = np.inf
39+
fig_2 = go.Figure(go.Heatmap(z=z, x=x))
40+
t3 = time()
41+
json_str_2 = _json.dumps(fig_2, cls=PlotlyJSONEncoder)
42+
t4 = time()
43+
assert t2 - t1 < t4 - t3
44+
assert "null" in json_str_2
45+
assert "NaN" not in json_str_2
46+
assert "Infinity" not in json_str_2
47+
x = np.arange(100.0)
48+
fig_3 = go.Figure(go.Heatmap(z=z, x=x))
49+
fig_3.update_layout(title_text="Infinity")
50+
t5 = time()
51+
json_str_3 = _json.dumps(fig_3, cls=PlotlyJSONEncoder)
52+
t6 = time()
53+
assert t2 - t1 < t6 - t5
54+
assert "Infinity" in json_str_3
55+
2256

2357
class TestGetByPath(TestCase):
2458
def test_get_by_path(self):

0 commit comments

Comments
 (0)