Skip to content

PX Input upgrades - wide form, constant, identity, auto-orientation etc #2336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 74 commits into from
May 25, 2020
Merged
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ec10b7b
initial cut at wide-form support
nicolaskruchten Mar 31, 2020
2168ec8
wip
nicolaskruchten Apr 2, 2020
79be641
wip
nicolaskruchten Apr 2, 2020
b99c2fa
initial pass at PX auto-orientation
nicolaskruchten Mar 31, 2020
df0b4d1
cleanup
nicolaskruchten Mar 31, 2020
4a2ddd1
fix for odd box/violin spacing when axis matches color
nicolaskruchten Mar 31, 2020
a2ca3c5
clean up default {}
nicolaskruchten Apr 1, 2020
442f3a1
smarter histfunc defaults
nicolaskruchten Apr 1, 2020
453e9e0
px.IdentityMap
nicolaskruchten Mar 29, 2020
c08f0bf
px.Constant
nicolaskruchten Mar 29, 2020
77139b9
PR feedback
nicolaskruchten Mar 30, 2020
ba36fa9
black
nicolaskruchten Mar 30, 2020
dd043d6
satisfy flake8
nicolaskruchten Apr 3, 2020
f952e64
test auto orient box-like
nicolaskruchten Apr 3, 2020
f58c3b5
locking down auto_orientation with tests
nicolaskruchten Apr 3, 2020
12e6aaa
respect column index name and pass through melted names
nicolaskruchten Apr 6, 2020
767c3bd
fix too-narrow type check bug
nicolaskruchten Apr 6, 2020
c06c555
tests wip
nicolaskruchten Apr 7, 2020
e28fbf7
DRY up attrable lists
nicolaskruchten Apr 7, 2020
fa2fe51
testing wip
nicolaskruchten Apr 8, 2020
b174d7f
sort columns for python < 3.6
nicolaskruchten Apr 8, 2020
48e56ef
wide mode special case tests
nicolaskruchten Apr 8, 2020
d57d035
extra comments and another test
nicolaskruchten Apr 8, 2020
e4071a9
px.Range
nicolaskruchten Apr 9, 2020
ea14fc9
smarter x or y behaviour
nicolaskruchten Apr 10, 2020
a376c6d
smarter x or y behaviour
nicolaskruchten Apr 10, 2020
6f3b5c4
scattergl doesn't support orientation
nicolaskruchten Apr 11, 2020
e5b9679
move gl switch
nicolaskruchten Apr 14, 2020
6b28eda
fix Pandas warning, use parameterized tests
nicolaskruchten Apr 15, 2020
b96f8c1
fix Pandas warning, use parameterized tests
nicolaskruchten Apr 15, 2020
7e129b2
align bar and histogram behaviours in wide mode with categorical values
nicolaskruchten Apr 16, 2020
9d45dc8
wip
nicolaskruchten Apr 16, 2020
f2a0079
wip
nicolaskruchten Apr 16, 2020
977dbcf
wip wide_y
nicolaskruchten Apr 17, 2020
553bd0b
wip
nicolaskruchten Apr 17, 2020
9524d94
more tests, more parameterization
nicolaskruchten Apr 19, 2020
7b022f1
funnel is wideable
nicolaskruchten Apr 20, 2020
e8027f0
all cartesians now support wide mode
nicolaskruchten Apr 20, 2020
d8b337e
merging master
nicolaskruchten Apr 28, 2020
a85acf6
get rid of
nicolaskruchten Apr 30, 2020
dff2c11
raise errors on multi-index in column/index in wide mode
nicolaskruchten Apr 30, 2020
f3039ac
parameterize test_px
nicolaskruchten Apr 30, 2020
f733831
manage ugly name collisions
nicolaskruchten Apr 30, 2020
e78cb50
lock down edge cases around name collisions
nicolaskruchten May 1, 2020
02109c8
Merge branch 'master' into wide_form2
nicolaskruchten May 14, 2020
b1bcd08
PR comments
nicolaskruchten May 14, 2020
3a87007
wide-mode docstrings
nicolaskruchten May 15, 2020
3129e63
fix ordering in tests in py < 3.6
nicolaskruchten May 15, 2020
bd551c0
fix tests py2.7
nicolaskruchten May 15, 2020
18276cf
expand docstrings a bit
nicolaskruchten May 15, 2020
b788721
wide datasets
nicolaskruchten May 15, 2020
be36f37
better timeseries dataset
nicolaskruchten May 16, 2020
1882f51
pandas backend
nicolaskruchten May 16, 2020
0b442b1
cleaner pandas backend
nicolaskruchten May 17, 2020
773b669
special case for wide-var=columns
nicolaskruchten May 17, 2020
7745f23
straight equality test
nicolaskruchten May 17, 2020
5dfcd4e
make CI pass for now
nicolaskruchten May 17, 2020
f2e64f4
changelog and more docstring
nicolaskruchten May 18, 2020
58999bf
changelog
nicolaskruchten May 18, 2020
08800a5
Merge branch 'master' into wide_form2
nicolaskruchten May 21, 2020
883f02e
long and wide
nicolaskruchten May 21, 2020
122b3ca
Merge branch 'master' into wide_form2
nicolaskruchten May 21, 2020
b1b2d7c
wide form docs draft
nicolaskruchten May 21, 2020
65822bc
Emma's changelog
nicolaskruchten May 21, 2020
de764ce
Merge branch 'master' into wide_form2
nicolaskruchten May 22, 2020
2eaea98
accept indexes as wide-mode spec
nicolaskruchten May 24, 2020
3adcfd2
reject wide-mode with different types
nicolaskruchten May 24, 2020
f8f0880
make list-like hover_data more robust
nicolaskruchten May 24, 2020
3479c68
Update doc/python/wide-form.md
nicolaskruchten May 24, 2020
4599415
Update doc/python/radar-chart.md
nicolaskruchten May 24, 2020
5430490
PR feedback
nicolaskruchten May 24, 2020
0bc7ba5
PR feedback
nicolaskruchten May 24, 2020
51dbf60
docs
nicolaskruchten May 25, 2020
b19d2c6
bump doc pandas
nicolaskruchten May 25, 2020
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
60 changes: 28 additions & 32 deletions packages/python/plotly/plotly/express/_core.py
Original file line number Diff line number Diff line change
@@ -16,6 +16,22 @@
)


# Declare all supported attributes, across all plot types

attrables = (
["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"]
+ ["custom_data", "hover_name", "hover_data", "text"]
+ ["names", "values", "parents", "ids"]
+ ["error_x", "error_x_minus"]
+ ["error_y", "error_y_minus", "error_z", "error_z_minus"]
+ ["lat", "lon", "locations", "animation_group", "path"]
)
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"]
renameable_group_attrables = ["color", "symbol", "line_dash"]
non_array_attrables = attrables + group_attrables + renameable_group_attrables
array_attrables = ["dimensions", "custom_data", "hover_data", "path"]


class PxDefaults(object):
__slots__ = [
"template",
@@ -850,7 +866,7 @@ def _check_name_not_reserved(field_name, reserved_names):
)


def _get_reserved_col_names(args, attrables, array_attrables):
def _get_reserved_col_names(args):
"""
This function builds a list of columns of the data_frame argument used
as arguments, either as str/int arguments or given as columns
@@ -859,7 +875,7 @@ def _get_reserved_col_names(args, attrables, array_attrables):
df = args["data_frame"]
reserved_names = set()
for field in args:
if field not in attrables:
if field not in non_array_attrables:
continue
names = args[field] if field in array_attrables else [args[field]]
if names is None:
@@ -879,7 +895,7 @@ def _get_reserved_col_names(args, attrables, array_attrables):
return reserved_names


def build_dataframe(args, attrables, array_attrables, constructor):
def build_dataframe(args, constructor):
"""
Constructs a dataframe and modifies `args` in-place.

@@ -891,11 +907,8 @@ def build_dataframe(args, attrables, array_attrables, constructor):
----------
args : OrderedDict
arguments passed to the px function and subsequently modified
attrables : list
list of keys into `args`, all of whose corresponding values are
converted into columns of a dataframe.
array_attrables : list
argument names corresponding to iterables, such as `hover_data`, ...
constructor : graph_object trace class
the trace type selected for this figure
"""

# make copies of all the fields via dict() and list()
@@ -931,7 +944,7 @@ def build_dataframe(args, attrables, array_attrables, constructor):
# Initialize set of column names
# These are reserved names
if df_provided:
reserved_names = _get_reserved_col_names(args, attrables, array_attrables)
reserved_names = _get_reserved_col_names(args)
else:
reserved_names = set()

@@ -947,7 +960,7 @@ def build_dataframe(args, attrables, array_attrables, constructor):
constants = dict()

# Loop over possible arguments
for field_name in attrables:
for field_name in non_array_attrables:
# Massaging variables
argument_list = (
[args.get(field_name)]
@@ -1254,27 +1267,6 @@ def aggfunc_continuous(x):


def infer_config(args, constructor, trace_patch, layout_patch):
# Declare all supported attributes, across all plot types
attrables = (
["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"]
+ ["custom_data", "hover_name", "hover_data", "text"]
+ ["names", "values", "parents", "ids"]
+ ["error_x", "error_x_minus"]
+ ["error_y", "error_y_minus", "error_z", "error_z_minus"]
+ ["lat", "lon", "locations", "animation_group", "path"]
)
array_attrables = ["dimensions", "custom_data", "hover_data", "path"]
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"]
all_attrables = attrables + group_attrables + ["color"]
group_attrs = ["symbol", "line_dash"]
for group_attr in group_attrs:
if group_attr in args:
all_attrables += [group_attr]

args = build_dataframe(args, all_attrables, array_attrables, constructor)
if constructor in [go.Treemap, go.Sunburst] and args["path"] is not None:
args = process_dataframe_hierarchy(args)

attrs = [k for k in attrables if k in args]
grouped_attrs = []

@@ -1473,6 +1465,10 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
layout_patch = layout_patch or {}
apply_default_cascade(args)

args = build_dataframe(args, constructor)
if constructor in [go.Treemap, go.Sunburst] and args["path"] is not None:
args = process_dataframe_hierarchy(args)

trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config(
args, constructor, trace_patch, layout_patch
)
@@ -1651,7 +1647,7 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
frame_list = sorted(
frame_list, key=lambda f: orders[args["animation_frame"]].index(f["name"])
)
layout_patch = layout_patch.copy()

if show_colorbar:
colorvar = "z" if constructor in [go.Histogram2d, go.Densitymapbox] else "color"
range_color = args["range_color"] or [None, None]
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import pandas as pd
import pytest
from plotly.express._core import build_dataframe
from pandas.util.testing import assert_frame_equal

attrables = (
["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"]
+ ["custom_data", "hover_name", "hover_data", "text"]
+ ["error_x", "error_x_minus"]
+ ["error_y", "error_y_minus", "error_z", "error_z_minus"]
+ ["lat", "lon", "locations", "animation_group"]
)
array_attrables = ["dimensions", "custom_data", "hover_data"]
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"]

all_attrables = attrables + group_attrables + ["color"]


def test_numpy():
fig = px.scatter(x=[1, 2, 3], y=[2, 3, 4], color=[1, 3, 9])
@@ -225,7 +214,7 @@ def test_build_df_from_lists():
output = {key: key for key in args}
df = pd.DataFrame(args)
args["data_frame"] = None
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(df.sort_index(axis=1), out["data_frame"].sort_index(axis=1))
out.pop("data_frame")
assert out == output
@@ -235,7 +224,7 @@ def test_build_df_from_lists():
output = {key: key for key in args}
df = pd.DataFrame(args)
args["data_frame"] = None
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(df.sort_index(axis=1), out["data_frame"].sort_index(axis=1))
out.pop("data_frame")
assert out == output
@@ -244,7 +233,7 @@ def test_build_df_from_lists():
def test_build_df_with_index():
tips = px.data.tips()
args = dict(data_frame=tips, x=tips.index, y="total_bill")
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(tips.reset_index()[out["data_frame"].columns], out["data_frame"])


@@ -254,17 +243,17 @@ def test_non_matching_index():
expected = pd.DataFrame(dict(index=["a", "b", "c"], y=[1, 2, 3]))

args = dict(data_frame=df, x=df.index, y="y")
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(expected, out["data_frame"])

expected = pd.DataFrame(dict(x=["a", "b", "c"], y=[1, 2, 3]))

args = dict(data_frame=None, x=df.index, y=df.y)
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(expected, out["data_frame"])

args = dict(data_frame=None, x=["a", "b", "c"], y=df.y)
out = build_dataframe(args, all_attrables, array_attrables, None)
out = build_dataframe(args, go.Scatter)
assert_frame_equal(expected, out["data_frame"])


Original file line number Diff line number Diff line change
@@ -4,18 +4,6 @@
from plotly.express._core import build_dataframe
from pandas.util.testing import assert_frame_equal

attrables = (
["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"]
+ ["custom_data", "hover_name", "hover_data", "text"]
+ ["names", "values", "parents", "ids"]
+ ["error_x", "error_x_minus"]
+ ["error_y", "error_y_minus", "error_z", "error_z_minus"]
+ ["lat", "lon", "locations", "animation_group", "path"]
)
array_attrables = ["dimensions", "custom_data", "hover_data", "path"]
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"]
all_attrables = attrables + group_attrables + ["color"]


def test_wide_mode_external():
df = pd.DataFrame(dict(a=[1, 2, 3], b=[4, 5, 6], c=[7, 8, 9]), index=[11, 12, 13])
@@ -87,7 +75,7 @@ def test_wide_mode_labels_external():
def test_wide_mode_internal():
df_in = pd.DataFrame(dict(a=[1, 2, 3], b=[4, 5, 6]), index=[11, 12, 13])
args_in = dict(data_frame=df_in, color=None)
args_out = build_dataframe(args_in, all_attrables, array_attrables, go.Scatter)
args_out = build_dataframe(args_in, go.Scatter)
df_out = args_out["data_frame"]
df_out_expected = pd.DataFrame(
dict(