Skip to content

Commit 7d1e663

Browse files
wip
1 parent 45ddf75 commit 7d1e663

File tree

6 files changed

+160
-23
lines changed

6 files changed

+160
-23
lines changed

Diff for: packages/python/plotly/plotly/express/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
box,
3535
strip,
3636
histogram,
37+
ecdf,
38+
kde,
3739
scatter_matrix,
3840
parallel_coordinates,
3941
parallel_categories,
@@ -88,6 +90,8 @@
8890
"box",
8991
"strip",
9092
"histogram",
93+
"ecdf",
94+
"kde",
9195
"choropleth",
9296
"choropleth_mapbox",
9397
"pie",

Diff for: packages/python/plotly/plotly/express/_chart_types.py

+115
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,121 @@ def histogram(
471471
)
472472

473473

474+
def ecdf(
475+
data_frame=None,
476+
x=None,
477+
y=None,
478+
color=None,
479+
line_dash=None,
480+
facet_row=None,
481+
facet_col=None,
482+
facet_col_wrap=0,
483+
facet_row_spacing=None,
484+
facet_col_spacing=None,
485+
hover_name=None,
486+
hover_data=None,
487+
animation_frame=None,
488+
animation_group=None,
489+
category_orders=None,
490+
labels=None,
491+
color_discrete_sequence=None,
492+
color_discrete_map=None,
493+
line_dash_sequence=None,
494+
line_dash_map=None,
495+
marginal=None,
496+
opacity=None,
497+
orientation=None,
498+
line_shape=None,
499+
norm=None, # TODO use this
500+
complementary=None, # TODO use this
501+
log_x=False,
502+
log_y=False,
503+
range_x=None,
504+
range_y=None,
505+
title=None,
506+
template=None,
507+
width=None,
508+
height=None,
509+
):
510+
"""
511+
In a Empirical Cumulative Distribution Function (ECDF) plot, rows of `data_frame`
512+
are sorted by the value `x` (or `y` if `orientation` is `'h'`) and their cumulative
513+
count (or the cumulative sum of `y` if supplied and `orientation` is `h`) is drawn
514+
as a line.
515+
"""
516+
return make_figure(args=locals(), constructor=go.Scatter)
517+
518+
519+
ecdf.__doc__ = make_docstring(
520+
ecdf,
521+
append_dict=dict(
522+
x=[
523+
"If `orientation` is `'h'`, the cumulative sum of this argument is plotted rather than the cumulative count."
524+
]
525+
+ _wide_mode_xy_append,
526+
y=[
527+
"If `orientation` is `'v'`, the cumulative sum of this argument is plotted rather than the cumulative count."
528+
]
529+
+ _wide_mode_xy_append,
530+
),
531+
)
532+
533+
534+
def kde(
535+
data_frame=None,
536+
x=None,
537+
y=None,
538+
color=None,
539+
line_dash=None,
540+
facet_row=None,
541+
facet_col=None,
542+
facet_col_wrap=0,
543+
facet_row_spacing=None,
544+
facet_col_spacing=None,
545+
hover_name=None,
546+
hover_data=None,
547+
animation_frame=None,
548+
animation_group=None,
549+
category_orders=None,
550+
labels=None,
551+
color_discrete_sequence=None,
552+
color_discrete_map=None,
553+
line_dash_sequence=None,
554+
line_dash_map=None,
555+
marginal=None,
556+
opacity=None,
557+
orientation=None,
558+
norm=None, # TODO use this
559+
kernel=None, # TODO use this
560+
bw_method=None, # TODO use this
561+
bw_adjust=None, # TODO use this
562+
log_x=False,
563+
log_y=False,
564+
range_x=None,
565+
range_y=None,
566+
title=None,
567+
template=None,
568+
width=None,
569+
height=None,
570+
):
571+
"""
572+
In a Kernel Density Estimation (KDE) plot, rows of `data_frame`
573+
are used as inputs to a KDE smoothing function which is rendered as a line.
574+
"""
575+
return make_figure(args=locals(), constructor=go.Scatter)
576+
577+
578+
kde.__doc__ = make_docstring(
579+
kde,
580+
append_dict=dict(
581+
x=["If `orientation` is `'h'`, this argument is used as KDE weights."]
582+
+ _wide_mode_xy_append,
583+
y=["If `orientation` is `'v'`, this argument is used as KDE weights."]
584+
+ _wide_mode_xy_append,
585+
),
586+
)
587+
588+
474589
def violin(
475590
data_frame=None,
476591
x=None,

Diff for: packages/python/plotly/plotly/express/_core.py

+26-10
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,9 @@ def build_dataframe(args, constructor):
12851285
wide_cross_name = None # will likely be "index" in wide_mode
12861286
value_name = None # will likely be "value" in wide_mode
12871287
hist2d_types = [go.Histogram2d, go.Histogram2dContour]
1288+
hist1d_orientation = (
1289+
constructor == go.Histogram or "complementary" in args or "kernel" in args
1290+
)
12881291
if constructor in cartesians:
12891292
if wide_x and wide_y:
12901293
raise ValueError(
@@ -1319,7 +1322,7 @@ def build_dataframe(args, constructor):
13191322
df_provided and var_name in df_input
13201323
):
13211324
var_name = "variable"
1322-
if constructor == go.Histogram:
1325+
if hist1d_orientation:
13231326
wide_orientation = "v" if wide_x else "h"
13241327
else:
13251328
wide_orientation = "v" if wide_y else "h"
@@ -1333,7 +1336,10 @@ def build_dataframe(args, constructor):
13331336
var_name = _escape_col_name(df_input, var_name, [])
13341337

13351338
missing_bar_dim = None
1336-
if constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types:
1339+
if (
1340+
constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types
1341+
and not hist1d_orientation
1342+
):
13371343
if not wide_mode and (no_x != no_y):
13381344
for ax in ["x", "y"]:
13391345
if args.get(ax, None) is None:
@@ -1430,14 +1436,22 @@ def build_dataframe(args, constructor):
14301436
df_output[var_name] = df_output[var_name].astype(str)
14311437
orient_v = wide_orientation == "v"
14321438

1433-
if constructor in [go.Scatter, go.Funnel] + hist2d_types:
1439+
if hist1d_orientation:
1440+
args["x" if orient_v else "y"] = value_name
1441+
if wide_cross_name is None and constructor == go.Scatter:
1442+
args["y" if orient_v else "x"] = count_name
1443+
df_output[count_name] = 1
1444+
else:
1445+
args["y" if orient_v else "x"] = wide_cross_name
1446+
args["color"] = args["color"] or var_name
1447+
elif constructor in [go.Scatter, go.Funnel] + hist2d_types:
14341448
args["x" if orient_v else "y"] = wide_cross_name
14351449
args["y" if orient_v else "x"] = value_name
14361450
if constructor != go.Histogram2d:
14371451
args["color"] = args["color"] or var_name
14381452
if "line_group" in args:
14391453
args["line_group"] = args["line_group"] or var_name
1440-
if constructor == go.Bar:
1454+
elif constructor == go.Bar:
14411455
if _is_continuous(df_output, value_name):
14421456
args["x" if orient_v else "y"] = wide_cross_name
14431457
args["y" if orient_v else "x"] = value_name
@@ -1447,13 +1461,9 @@ def build_dataframe(args, constructor):
14471461
args["y" if orient_v else "x"] = count_name
14481462
df_output[count_name] = 1
14491463
args["color"] = args["color"] or var_name
1450-
if constructor in [go.Violin, go.Box]:
1464+
elif constructor in [go.Violin, go.Box]:
14511465
args["x" if orient_v else "y"] = wide_cross_name or var_name
14521466
args["y" if orient_v else "x"] = value_name
1453-
if constructor == go.Histogram:
1454-
args["x" if orient_v else "y"] = value_name
1455-
args["y" if orient_v else "x"] = wide_cross_name
1456-
args["color"] = args["color"] or var_name
14571467
if no_color:
14581468
args["color"] = None
14591469
args["data_frame"] = df_output
@@ -1943,7 +1953,7 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
19431953
if (
19441954
trace_spec != trace_specs[0]
19451955
and trace_spec.constructor in [go.Violin, go.Box, go.Histogram]
1946-
and m.variable == "symbol"
1956+
and m.variable in ["symbol", "dash"]
19471957
):
19481958
pass
19491959
elif (
@@ -2004,6 +2014,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
20042014
):
20052015
trace.update(marker=dict(color=trace.line.color))
20062016

2017+
if "complementary" in args: # ECDF
2018+
base = args["x"] if args["orientation"] == "v" else args["y"]
2019+
var = args["x"] if args["orientation"] == "h" else args["y"]
2020+
group = group.sort_values(by=base)
2021+
group[var] = group[var].cumsum()
2022+
20072023
patch, fit_results = make_trace_kwargs(
20082024
args, trace_spec, group, mapping_labels.copy(), sizeref
20092025
)

Diff for: packages/python/plotly/plotly/express/_doc.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -541,10 +541,17 @@
541541
"Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the"
542542
"levels in the hierarchy.",
543543
],
544+
norm=["TODO"],
545+
complementary=["TODO"],
546+
kernel=["TODO"],
547+
bw_method=["TODO"],
548+
bw_adjust=["TODO"],
544549
)
545550

546551

547-
def make_docstring(fn, override_dict={}, append_dict={}):
552+
def make_docstring(fn, override_dict=None, append_dict=None):
553+
override_dict = {} if override_dict is None else override_dict
554+
append_dict = {} if append_dict is None else append_dict
548555
tw = TextWrapper(width=75, initial_indent=" ", subsequent_indent=" ")
549556
result = (fn.__doc__ or "") + "\nParameters\n----------\n"
550557
for param in getfullargspec(fn)[0]:

Diff for: packages/python/plotly/plotly/tests/test_core/test_px/test_facets.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import plotly
21
import pandas as pd
32
import plotly.express as px
43
from pytest import approx
@@ -112,25 +111,21 @@ def bad_facet_spacing_df():
112111
def test_bad_facet_spacing_eror(bad_facet_spacing_df):
113112
df = bad_facet_spacing_df
114113
with pytest.raises(
115-
ValueError, match="Use the facet_row_spacing argument to adjust this spacing\."
114+
ValueError, match="Use the facet_row_spacing argument to adjust this spacing."
116115
):
117-
fig = px.scatter(
118-
df, x="x", y="y", facet_row="category", facet_row_spacing=0.01001
119-
)
116+
px.scatter(df, x="x", y="y", facet_row="category", facet_row_spacing=0.01001)
120117
with pytest.raises(
121-
ValueError, match="Use the facet_col_spacing argument to adjust this spacing\."
118+
ValueError, match="Use the facet_col_spacing argument to adjust this spacing."
122119
):
123-
fig = px.scatter(
124-
df, x="x", y="y", facet_col="category", facet_col_spacing=0.01001
125-
)
120+
px.scatter(df, x="x", y="y", facet_col="category", facet_col_spacing=0.01001)
126121
# Check error is not raised when the spacing is OK
127122
try:
128-
fig = px.scatter(df, x="x", y="y", facet_row="category", facet_row_spacing=0.01)
123+
px.scatter(df, x="x", y="y", facet_row="category", facet_row_spacing=0.01)
129124
except ValueError:
130125
# Error shouldn't be raised, so fail if it is
131126
assert False
132127
try:
133-
fig = px.scatter(df, x="x", y="y", facet_col="category", facet_col_spacing=0.01)
128+
px.scatter(df, x="x", y="y", facet_col="category", facet_col_spacing=0.01)
134129
except ValueError:
135130
# Error shouldn't be raised, so fail if it is
136131
assert False

Diff for: packages/python/plotly/plotly/tests/test_core/test_px/test_marginals.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def test_xy_marginals(px_fn, marginal_x, marginal_y):
1414
assert len(fig.data) == 1 + (marginal_x is not None) + (marginal_y is not None)
1515

1616

17-
@pytest.mark.parametrize("px_fn", [px.histogram])
17+
@pytest.mark.parametrize("px_fn", [px.histogram, px.ecdf, px.kde])
1818
@pytest.mark.parametrize("marginal", [None, "rug", "histogram", "box", "violin"])
1919
@pytest.mark.parametrize("orientation", ["h", "v"])
2020
def test_single_marginals(px_fn, marginal, orientation):

0 commit comments

Comments
 (0)