Skip to content

Commit 700e83f

Browse files
wip
1 parent ed48215 commit 700e83f

File tree

6 files changed

+161
-24
lines changed

6 files changed

+161
-24
lines changed

packages/python/plotly/plotly/express/__init__.py

Lines changed: 4 additions & 0 deletions
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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,121 @@ def histogram(
489489
)
490490

491491

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

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,9 @@ def build_dataframe(args, constructor):
13121312
wide_cross_name = None # will likely be "index" in wide_mode
13131313
value_name = None # will likely be "value" in wide_mode
13141314
hist2d_types = [go.Histogram2d, go.Histogram2dContour]
1315+
hist1d_orientation = (
1316+
constructor == go.Histogram or "complementary" in args or "kernel" in args
1317+
)
13151318
if constructor in cartesians:
13161319
if wide_x and wide_y:
13171320
raise ValueError(
@@ -1346,7 +1349,7 @@ def build_dataframe(args, constructor):
13461349
df_provided and var_name in df_input
13471350
):
13481351
var_name = "variable"
1349-
if constructor == go.Histogram:
1352+
if hist1d_orientation:
13501353
wide_orientation = "v" if wide_x else "h"
13511354
else:
13521355
wide_orientation = "v" if wide_y else "h"
@@ -1360,7 +1363,10 @@ def build_dataframe(args, constructor):
13601363
var_name = _escape_col_name(df_input, var_name, [])
13611364

13621365
missing_bar_dim = None
1363-
if constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types:
1366+
if (
1367+
constructor in [go.Scatter, go.Bar, go.Funnel] + hist2d_types
1368+
and not hist1d_orientation
1369+
):
13641370
if not wide_mode and (no_x != no_y):
13651371
for ax in ["x", "y"]:
13661372
if args.get(ax) is None:
@@ -1457,14 +1463,22 @@ def build_dataframe(args, constructor):
14571463
df_output[var_name] = df_output[var_name].astype(str)
14581464
orient_v = wide_orientation == "v"
14591465

1460-
if constructor in [go.Scatter, go.Funnel] + hist2d_types:
1466+
if hist1d_orientation:
1467+
args["x" if orient_v else "y"] = value_name
1468+
if wide_cross_name is None and constructor == go.Scatter:
1469+
args["y" if orient_v else "x"] = count_name
1470+
df_output[count_name] = 1
1471+
else:
1472+
args["y" if orient_v else "x"] = wide_cross_name
1473+
args["color"] = args["color"] or var_name
1474+
elif constructor in [go.Scatter, go.Funnel] + hist2d_types:
14611475
args["x" if orient_v else "y"] = wide_cross_name
14621476
args["y" if orient_v else "x"] = value_name
14631477
if constructor != go.Histogram2d:
14641478
args["color"] = args["color"] or var_name
14651479
if "line_group" in args:
14661480
args["line_group"] = args["line_group"] or var_name
1467-
if constructor == go.Bar:
1481+
elif constructor == go.Bar:
14681482
if _is_continuous(df_output, value_name):
14691483
args["x" if orient_v else "y"] = wide_cross_name
14701484
args["y" if orient_v else "x"] = value_name
@@ -1474,13 +1488,9 @@ def build_dataframe(args, constructor):
14741488
args["y" if orient_v else "x"] = count_name
14751489
df_output[count_name] = 1
14761490
args["color"] = args["color"] or var_name
1477-
if constructor in [go.Violin, go.Box]:
1491+
elif constructor in [go.Violin, go.Box]:
14781492
args["x" if orient_v else "y"] = wide_cross_name or var_name
14791493
args["y" if orient_v else "x"] = value_name
1480-
if constructor == go.Histogram:
1481-
args["x" if orient_v else "y"] = value_name
1482-
args["y" if orient_v else "x"] = wide_cross_name
1483-
args["color"] = args["color"] or var_name
14841494
if no_color:
14851495
args["color"] = None
14861496
args["data_frame"] = df_output
@@ -1984,11 +1994,11 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
19841994
trace_spec != trace_specs[0]
19851995
and (
19861996
trace_spec.constructor in [go.Violin, go.Box]
1987-
and m.variable in ["symbol", "pattern"]
1997+
and m.variable in ["symbol", "pattern", "dash"]
19881998
)
19891999
or (
19902000
trace_spec.constructor in [go.Histogram]
1991-
and m.variable in ["symbol"]
2001+
and m.variable in ["symbol", "dash"]
19922002
)
19932003
):
19942004
pass
@@ -2047,6 +2057,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
20472057
):
20482058
trace.update(marker=dict(color=trace.line.color))
20492059

2060+
if "complementary" in args: # ECDF
2061+
base = args["x"] if args["orientation"] == "v" else args["y"]
2062+
var = args["x"] if args["orientation"] == "h" else args["y"]
2063+
group = group.sort_values(by=base)
2064+
group[var] = group[var].cumsum()
2065+
20502066
patch, fit_results = make_trace_kwargs(
20512067
args, trace_spec, group, mapping_labels.copy(), sizeref
20522068
)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,10 +573,17 @@
573573
"Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the"
574574
"levels in the hierarchy.",
575575
],
576+
norm=["TODO"],
577+
complementary=["TODO"],
578+
kernel=["TODO"],
579+
bw_method=["TODO"],
580+
bw_adjust=["TODO"],
576581
)
577582

578583

579-
def make_docstring(fn, override_dict={}, append_dict={}):
584+
def make_docstring(fn, override_dict=None, append_dict=None):
585+
override_dict = {} if override_dict is None else override_dict
586+
append_dict = {} if append_dict is None else append_dict
580587
tw = TextWrapper(width=75, initial_indent=" ", subsequent_indent=" ")
581588
result = (fn.__doc__ or "") + "\nParameters\n----------\n"
582589
for param in getfullargspec(fn)[0]:

packages/python/plotly/plotly/tests/test_optional/test_px/test_facets.py

Lines changed: 6 additions & 11 deletions
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

Lines changed: 1 addition & 1 deletion
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)