Skip to content

Commit 88084f1

Browse files
wip
1 parent ce1bf36 commit 88084f1

File tree

6 files changed

+161
-24
lines changed

6 files changed

+161
-24
lines changed

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,
@@ -89,6 +91,8 @@
8991
"box",
9092
"strip",
9193
"histogram",
94+
"ecdf",
95+
"kde",
9296
"choropleth",
9397
"choropleth_mapbox",
9498
"pie",

packages/python/plotly/plotly/express/_chart_types.py

+115
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,121 @@ def histogram(
477477
)
478478

479479

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

packages/python/plotly/plotly/express/_core.py

+27-11
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,9 @@ def build_dataframe(args, constructor):
13161316
wide_cross_name = None # will likely be "index" in wide_mode
13171317
value_name = None # will likely be "value" in wide_mode
13181318
hist2d_types = [go.Histogram2d, go.Histogram2dContour]
1319+
hist1d_orientation = (
1320+
constructor == go.Histogram or "complementary" in args or "kernel" in args
1321+
)
13191322
if constructor in cartesians:
13201323
if wide_x and wide_y:
13211324
raise ValueError(
@@ -1350,7 +1353,7 @@ def build_dataframe(args, constructor):
13501353
df_provided and var_name in df_input
13511354
):
13521355
var_name = "variable"
1353-
if constructor == go.Histogram:
1356+
if hist1d_orientation:
13541357
wide_orientation = "v" if wide_x else "h"
13551358
else:
13561359
wide_orientation = "v" if wide_y else "h"
@@ -1364,7 +1367,10 @@ def build_dataframe(args, constructor):
13641367
var_name = _escape_col_name(df_input, var_name, [])
13651368

13661369
missing_bar_dim = None
1367-
if constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types:
1370+
if (
1371+
constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types
1372+
and not hist1d_orientation
1373+
):
13681374
if not wide_mode and (no_x != no_y):
13691375
for ax in ["x", "y"]:
13701376
if args.get(ax) is None:
@@ -1461,14 +1467,22 @@ def build_dataframe(args, constructor):
14611467
df_output[var_name] = df_output[var_name].astype(str)
14621468
orient_v = wide_orientation == "v"
14631469

1464-
if constructor in [go.Scatter, go.Funnel] + hist2d_types:
1470+
if hist1d_orientation:
1471+
args["x" if orient_v else "y"] = value_name
1472+
if wide_cross_name is None and constructor == go.Scatter:
1473+
args["y" if orient_v else "x"] = count_name
1474+
df_output[count_name] = 1
1475+
else:
1476+
args["y" if orient_v else "x"] = wide_cross_name
1477+
args["color"] = args["color"] or var_name
1478+
elif constructor in [go.Scatter, go.Funnel] + hist2d_types:
14651479
args["x" if orient_v else "y"] = wide_cross_name
14661480
args["y" if orient_v else "x"] = value_name
14671481
if constructor != go.Histogram2d:
14681482
args["color"] = args["color"] or var_name
14691483
if "line_group" in args:
14701484
args["line_group"] = args["line_group"] or var_name
1471-
if constructor == go.Bar:
1485+
elif constructor == go.Bar:
14721486
if _is_continuous(df_output, value_name):
14731487
args["x" if orient_v else "y"] = wide_cross_name
14741488
args["y" if orient_v else "x"] = value_name
@@ -1478,13 +1492,9 @@ def build_dataframe(args, constructor):
14781492
args["y" if orient_v else "x"] = count_name
14791493
df_output[count_name] = 1
14801494
args["color"] = args["color"] or var_name
1481-
if constructor in [go.Violin, go.Box]:
1495+
elif constructor in [go.Violin, go.Box]:
14821496
args["x" if orient_v else "y"] = wide_cross_name or var_name
14831497
args["y" if orient_v else "x"] = value_name
1484-
if constructor == go.Histogram:
1485-
args["x" if orient_v else "y"] = value_name
1486-
args["y" if orient_v else "x"] = wide_cross_name
1487-
args["color"] = args["color"] or var_name
14881498
if no_color:
14891499
args["color"] = None
14901500
args["data_frame"] = df_output
@@ -1973,11 +1983,11 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
19731983
trace_spec != trace_specs[0]
19741984
and (
19751985
trace_spec.constructor in [go.Violin, go.Box]
1976-
and m.variable in ["symbol", "pattern"]
1986+
and m.variable in ["symbol", "pattern", "dash"]
19771987
)
19781988
or (
19791989
trace_spec.constructor in [go.Histogram]
1980-
and m.variable in ["symbol"]
1990+
and m.variable in ["symbol", "dash"]
19811991
)
19821992
):
19831993
pass
@@ -2036,6 +2046,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
20362046
):
20372047
trace.update(marker=dict(color=trace.line.color))
20382048

2049+
if "complementary" in args: # ECDF
2050+
base = args["x"] if args["orientation"] == "v" else args["y"]
2051+
var = args["x"] if args["orientation"] == "h" else args["y"]
2052+
group = group.sort_values(by=base)
2053+
group[var] = group[var].cumsum()
2054+
20392055
patch, fit_results = make_trace_kwargs(
20402056
args, trace_spec, group, mapping_labels.copy(), sizeref
20412057
)

packages/python/plotly/plotly/express/_doc.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -558,10 +558,17 @@
558558
"Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the"
559559
"levels in the hierarchy.",
560560
],
561+
norm=["TODO"],
562+
complementary=["TODO"],
563+
kernel=["TODO"],
564+
bw_method=["TODO"],
565+
bw_adjust=["TODO"],
561566
)
562567

563568

564-
def make_docstring(fn, override_dict={}, append_dict={}):
569+
def make_docstring(fn, override_dict=None, append_dict=None):
570+
override_dict = {} if override_dict is None else override_dict
571+
append_dict = {} if append_dict is None else append_dict
565572
tw = TextWrapper(width=75, initial_indent=" ", subsequent_indent=" ")
566573
result = (fn.__doc__ or "") + "\nParameters\n----------\n"
567574
for param in getfullargspec(fn)[0]:

packages/python/plotly/plotly/tests/test_optional/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

packages/python/plotly/plotly/tests/test_optional/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)