-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
more flexible type of input arguments for px functions #1768
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
Changes from 13 commits
152f87f
fe4eda3
86937ea
1ad0f5c
7d0e985
915a5a1
5d5ab81
ea0fa6a
e5f6953
29d7e18
4a028a2
ab35b42
e4b8835
3b0d21a
2a6ff71
72b5d1c
19431dd
7297a7f
dadd645
2cb9dba
b09dc58
91e6cda
33db488
e58c82e
9d65a07
64010de
20bd812
a7241a5
dc1fc0a
61678c2
71756a3
40ba5b9
749db6c
cd8574a
299fdde
8753797
ed24d8b
2da2b21
102b89f
0a50f6a
58c9eba
0a92537
372bd8f
3d0aff0
5a329ad
e0e0dd5
6bced73
bcc41a2
382e768
82c6592
9caeee1
adc68d3
5dc0ab1
f5d056c
bedf74f
074342b
ead8430
db10967
ef29378
5e40653
78564cc
2534ca9
c88660f
b5adbcf
b375f91
c59a4d5
1774ade
16d95af
5c95786
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
from _plotly_utils.basevalidators import ColorscaleValidator | ||
from .colors import qualitative, sequential | ||
import math | ||
import pandas | ||
import pandas as pd | ||
import numpy as np | ||
|
||
from plotly.subplots import ( | ||
|
@@ -754,6 +754,90 @@ def apply_default_cascade(args): | |
args["marginal_x"] = None | ||
|
||
|
||
def build_or_augment_dataframe(args, attrables, array_attrables, constructor): | ||
""" | ||
Constructs an implicit dataframe and modifies `args` in-place. | ||
`attrables` is a list of keys into `args`, all of whose corresponding | ||
values are converted into columns of a dataframe. | ||
Used to be support calls to plotting function that elide a dataframe | ||
argument; for example `scatter(x=[1,2], y=[3,4])`. | ||
""" | ||
|
||
# We start from an empty DataFrame except for the case of functions which | ||
# implicitely need all dimensions: Splom, Parcats, Parcoords | ||
# This could be refined when dimensions is given | ||
if constructor in [go.Splom, go.Parcats, go.Parcoords]: # we take all dimensions | ||
df = args["data_frame"] | ||
emmanuelle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else: | ||
df = pd.DataFrame() | ||
|
||
# Retrieve labels (to change column names) | ||
labels = args.get("labels") # labels or None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it important to move the (re-)labelling logic into this function? Right now it all mostly happens later with various helper functions. If we're moving this logic into this function then should we refactor it out of where it is right now...? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it's better to leave the labelling logic later as it was before, this should be resolved now. |
||
|
||
# Valid column names | ||
df_columns = ( | ||
args["data_frame"].columns if args.get("data_frame") is not None else None | ||
) | ||
group_attrs = ["symbol", "line_dash"] | ||
for group_attr in group_attrs: | ||
if group_attr in args: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this case is already handled below, so you may as well just add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok done |
||
attrables += [group_attr] | ||
|
||
# Loop over possible arguments | ||
for field_name in attrables: | ||
argument_list = ( | ||
[args.get(field_name)] | ||
if field_name not in array_attrables | ||
else args.get(field_name) | ||
) | ||
if argument_list is None: # argument not specified, continue | ||
continue | ||
nicolaskruchten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Argument name: field_name if the argument is a list | ||
# Else we give names like ["hover_data_0, hover_data_1"] etc. | ||
field_list = ( | ||
[field_name] | ||
if field_name not in array_attrables | ||
else [field_name + "_" + str(i) for i in range(len(argument_list))] | ||
) | ||
# argument_list and field_list ready, iterate over them | ||
for i, (argument, field) in enumerate(zip(argument_list, field_list)): | ||
if argument is None: | ||
continue | ||
elif isinstance(argument, str): # just a column name | ||
# Check validity of column name | ||
try: | ||
nicolaskruchten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
df[argument] = args["data_frame"][argument] | ||
continue | ||
except KeyError: | ||
raise ValueError( | ||
"Value of '%s' is not the name of a column in 'data_frame'. " | ||
"Expected one of %s but received: %s" | ||
% (field, str(list(df_columns)), argument) | ||
) | ||
# Case of index | ||
elif isinstance(argument, pd.core.indexes.range.RangeIndex): | ||
col_name = argument.name if argument.name else "index" | ||
col_name = labels[field] if labels and labels.get(field) else col_name | ||
try: | ||
df.insert(0, col_name, argument) | ||
except ValueError: # if col named index already exists, replace | ||
df[col_name] = argument | ||
# Case of numpy array or df column | ||
nicolaskruchten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else: | ||
try: | ||
col_name = argument.name # pandas df | ||
nicolaskruchten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
except AttributeError: | ||
col_name = labels[field] if labels and labels.get(field) else field | ||
df[col_name] = argument | ||
# Update argument with column name now that column exists | ||
if field_name not in array_attrables: | ||
args[field_name] = col_name | ||
else: | ||
args[field_name][i] = col_name | ||
args["data_frame"] = df | ||
return args | ||
|
||
|
||
def infer_config(args, constructor, trace_patch): | ||
# Declare all supported attributes, across all plot types | ||
attrables = ( | ||
|
@@ -766,27 +850,8 @@ def infer_config(args, constructor, trace_patch): | |
array_attrables = ["dimensions", "custom_data", "hover_data"] | ||
group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"] | ||
|
||
# Validate that the strings provided as attribute values reference columns | ||
# in the provided data_frame | ||
df_columns = args["data_frame"].columns | ||
|
||
for attr in attrables + group_attrables + ["color"]: | ||
if attr in args and args[attr] is not None: | ||
maybe_col_list = [args[attr]] if attr not in array_attrables else args[attr] | ||
for maybe_col in maybe_col_list: | ||
try: | ||
in_cols = maybe_col in df_columns | ||
except TypeError: | ||
in_cols = False | ||
if not in_cols: | ||
value_str = ( | ||
"Element of value" if attr in array_attrables else "Value" | ||
) | ||
raise ValueError( | ||
"%s of '%s' is not the name of a column in 'data_frame'. " | ||
"Expected one of %s but received: %s" | ||
% (value_str, attr, str(list(df_columns)), str(maybe_col)) | ||
) | ||
all_attrables = attrables + group_attrables + ["color"] | ||
build_or_augment_dataframe(args, all_attrables, array_attrables, constructor) | ||
|
||
attrs = [k for k in attrables if k in args] | ||
grouped_attrs = [] | ||
|
@@ -1095,7 +1160,7 @@ def make_figure(args, constructor, trace_patch={}, layout_patch={}): | |
fig.layout.update(layout_patch) | ||
fig.frames = frame_list if len(frames) > 1 else [] | ||
|
||
fig._px_trendlines = pandas.DataFrame(trendline_rows) | ||
fig._px_trendlines = pd.DataFrame(trendline_rows) | ||
|
||
configure_axes(args, constructor, fig, orders) | ||
configure_animation_controls(args, constructor, fig) | ||
|
Uh oh!
There was an error while loading. Please reload this page.