Skip to content

Commit e8365bf

Browse files
wip
1 parent c67f800 commit e8365bf

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
@@ -485,6 +485,121 @@ def histogram(
485485
)
486486

487487

488+
def ecdf(
489+
data_frame=None,
490+
x=None,
491+
y=None,
492+
color=None,
493+
line_dash=None,
494+
facet_row=None,
495+
facet_col=None,
496+
facet_col_wrap=0,
497+
facet_row_spacing=None,
498+
facet_col_spacing=None,
499+
hover_name=None,
500+
hover_data=None,
501+
animation_frame=None,
502+
animation_group=None,
503+
category_orders=None,
504+
labels=None,
505+
color_discrete_sequence=None,
506+
color_discrete_map=None,
507+
line_dash_sequence=None,
508+
line_dash_map=None,
509+
marginal=None,
510+
opacity=None,
511+
orientation=None,
512+
line_shape=None,
513+
norm=None, # TODO use this
514+
complementary=None, # TODO use this
515+
log_x=False,
516+
log_y=False,
517+
range_x=None,
518+
range_y=None,
519+
title=None,
520+
template=None,
521+
width=None,
522+
height=None,
523+
):
524+
"""
525+
In a Empirical Cumulative Distribution Function (ECDF) plot, rows of `data_frame`
526+
are sorted by the value `x` (or `y` if `orientation` is `'h'`) and their cumulative
527+
count (or the cumulative sum of `y` if supplied and `orientation` is `h`) is drawn
528+
as a line.
529+
"""
530+
return make_figure(args=locals(), constructor=go.Scatter)
531+
532+
533+
ecdf.__doc__ = make_docstring(
534+
ecdf,
535+
append_dict=dict(
536+
x=[
537+
"If `orientation` is `'h'`, the cumulative sum of this argument is plotted rather than the cumulative count."
538+
]
539+
+ _wide_mode_xy_append,
540+
y=[
541+
"If `orientation` is `'v'`, the cumulative sum of this argument is plotted rather than the cumulative count."
542+
]
543+
+ _wide_mode_xy_append,
544+
),
545+
)
546+
547+
548+
def kde(
549+
data_frame=None,
550+
x=None,
551+
y=None,
552+
color=None,
553+
line_dash=None,
554+
facet_row=None,
555+
facet_col=None,
556+
facet_col_wrap=0,
557+
facet_row_spacing=None,
558+
facet_col_spacing=None,
559+
hover_name=None,
560+
hover_data=None,
561+
animation_frame=None,
562+
animation_group=None,
563+
category_orders=None,
564+
labels=None,
565+
color_discrete_sequence=None,
566+
color_discrete_map=None,
567+
line_dash_sequence=None,
568+
line_dash_map=None,
569+
marginal=None,
570+
opacity=None,
571+
orientation=None,
572+
norm=None, # TODO use this
573+
kernel=None, # TODO use this
574+
bw_method=None, # TODO use this
575+
bw_adjust=None, # TODO use this
576+
log_x=False,
577+
log_y=False,
578+
range_x=None,
579+
range_y=None,
580+
title=None,
581+
template=None,
582+
width=None,
583+
height=None,
584+
):
585+
"""
586+
In a Kernel Density Estimation (KDE) plot, rows of `data_frame`
587+
are used as inputs to a KDE smoothing function which is rendered as a line.
588+
"""
589+
return make_figure(args=locals(), constructor=go.Scatter)
590+
591+
592+
kde.__doc__ = make_docstring(
593+
kde,
594+
append_dict=dict(
595+
x=["If `orientation` is `'h'`, this argument is used as KDE weights."]
596+
+ _wide_mode_xy_append,
597+
y=["If `orientation` is `'v'`, this argument is used as KDE weights."]
598+
+ _wide_mode_xy_append,
599+
),
600+
)
601+
602+
488603
def violin(
489604
data_frame=None,
490605
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
@@ -1978,11 +1988,11 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
19781988
trace_spec != trace_specs[0]
19791989
and (
19801990
trace_spec.constructor in [go.Violin, go.Box]
1981-
and m.variable in ["symbol", "pattern"]
1991+
and m.variable in ["symbol", "pattern", "dash"]
19821992
)
19831993
or (
19841994
trace_spec.constructor in [go.Histogram]
1985-
and m.variable in ["symbol"]
1995+
and m.variable in ["symbol", "dash"]
19861996
)
19871997
):
19881998
pass
@@ -2041,6 +2051,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
20412051
):
20422052
trace.update(marker=dict(color=trace.line.color))
20432053

2054+
if "complementary" in args: # ECDF
2055+
base = args["x"] if args["orientation"] == "v" else args["y"]
2056+
var = args["x"] if args["orientation"] == "h" else args["y"]
2057+
group = group.sort_values(by=base)
2058+
group[var] = group[var].cumsum()
2059+
20442060
patch, fit_results = make_trace_kwargs(
20452061
args, trace_spec, group, mapping_labels.copy(), sizeref
20462062
)

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

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

564569

565-
def make_docstring(fn, override_dict={}, append_dict={}):
570+
def make_docstring(fn, override_dict=None, append_dict=None):
571+
override_dict = {} if override_dict is None else override_dict
572+
append_dict = {} if append_dict is None else append_dict
566573
tw = TextWrapper(width=75, initial_indent=" ", subsequent_indent=" ")
567574
result = (fn.__doc__ or "") + "\nParameters\n----------\n"
568575
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)