Skip to content
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

Extra tests, bugfix and finish histnorm labelling #3014

Merged
merged 2 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions packages/python/plotly/plotly/express/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,36 @@ def _is_continuous(df, col_name):


def get_decorated_label(args, column, role):
label = get_label(args, column)
original_label = label = get_label(args, column)
if "histfunc" in args and (
(role == "z")
or (role == "x" and "orientation" in args and args["orientation"] == "h")
or (role == "y" and "orientation" in args and args["orientation"] == "v")
):
if label:
label = "%s of %s" % (args["histfunc"] or "count", label)
histfunc = args["histfunc"] or "count"
if histfunc != "count":
label = "%s of %s" % (histfunc, label)
else:
label = "count"

if "histnorm" in args and args["histnorm"] is not None:
label = "%s of %s" % (args["histnorm"], label)
if label == "count":
label = args["histnorm"]
else:
histnorm = args["histnorm"]
if histfunc == "sum":
if histnorm == "probability":
label = "%s of %s" % ("fraction", label)
elif histnorm == "percent":
label = "%s of %s" % (histnorm, label)
else:
label = "%s weighted by %s" % (histnorm, original_label)
elif histnorm == "probability":
label = "%s of sum of %s" % ("fraction", label)
elif histnorm == "percent":
label = "%s of sum of %s" % ("percent", label)
else:
label = "%s of %s" % (histnorm, label)

if "barnorm" in args and args["barnorm"] is not None:
label = "%s (normalized as %s)" % (label, args["barnorm"])
Expand Down Expand Up @@ -924,13 +941,6 @@ def apply_default_cascade(args):
"longdashdot",
]

# If both marginals and faceting are specified, faceting wins
if args.get("facet_col", None) is not None and args.get("marginal_y", None):
args["marginal_y"] = None

if args.get("facet_row", None) is not None and args.get("marginal_x", None):
args["marginal_x"] = None


def _check_name_not_reserved(field_name, reserved_names):
if field_name not in reserved_names:
Expand Down Expand Up @@ -1765,6 +1775,14 @@ def infer_config(args, constructor, trace_patch, layout_patch):
args[position] = args["marginal"]
args[other_position] = None

# If both marginals and faceting are specified, faceting wins
if args.get("facet_col", None) is not None and args.get("marginal_y", None):
args["marginal_y"] = None

if args.get("facet_row", None) is not None and args.get("marginal_x", None):
args["marginal_x"] = None

# facet_col_wrap only works if no marginals or row faceting is used
if (
args.get("marginal_x", None) is not None
or args.get("marginal_y", None) is not None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,53 @@ def test_facets():
assert fig.layout.yaxis4.domain[0] - fig.layout.yaxis.domain[1] == approx(0.08)


def test_facets_with_marginals():
df = px.data.tips()

fig = px.histogram(df, x="total_bill", facet_col="sex", marginal="rug")
assert len(fig.data) == 4
fig = px.histogram(df, x="total_bill", facet_row="sex", marginal="rug")
assert len(fig.data) == 2

fig = px.histogram(df, y="total_bill", facet_col="sex", marginal="rug")
assert len(fig.data) == 2
fig = px.histogram(df, y="total_bill", facet_row="sex", marginal="rug")
assert len(fig.data) == 4

fig = px.scatter(df, x="total_bill", y="tip", facet_col="sex", marginal_x="rug")
assert len(fig.data) == 4
fig = px.scatter(
df, x="total_bill", y="tip", facet_col="day", facet_col_wrap=2, marginal_x="rug"
)
assert len(fig.data) == 8 # ignore the wrap when marginal is used
fig = px.scatter(df, x="total_bill", y="tip", facet_col="sex", marginal_y="rug")
assert len(fig.data) == 2 # ignore the marginal in the facet direction

fig = px.scatter(df, x="total_bill", y="tip", facet_row="sex", marginal_x="rug")
assert len(fig.data) == 2 # ignore the marginal in the facet direction
fig = px.scatter(df, x="total_bill", y="tip", facet_row="sex", marginal_y="rug")
assert len(fig.data) == 4

fig = px.scatter(
df, x="total_bill", y="tip", facet_row="sex", marginal_y="rug", marginal_x="rug"
)
assert len(fig.data) == 4 # ignore the marginal in the facet direction
fig = px.scatter(
df, x="total_bill", y="tip", facet_col="sex", marginal_y="rug", marginal_x="rug"
)
assert len(fig.data) == 4 # ignore the marginal in the facet direction
fig = px.scatter(
df,
x="total_bill",
y="tip",
facet_row="sex",
facet_col="sex",
marginal_y="rug",
marginal_x="rug",
)
assert len(fig.data) == 2 # ignore all marginals


@pytest.fixture
def bad_facet_spacing_df():
NROWS = 101
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import plotly.express as px
import pytest


@pytest.mark.parametrize("px_fn", [px.scatter, px.density_heatmap, px.density_contour])
@pytest.mark.parametrize("marginal_x", [None, "histogram", "box", "violin"])
@pytest.mark.parametrize("marginal_y", [None, "rug"])
def test_xy_marginals(px_fn, marginal_x, marginal_y):
df = px.data.tips()

fig = px_fn(
df, x="total_bill", y="tip", marginal_x=marginal_x, marginal_y=marginal_y
)
assert len(fig.data) == 1 + (marginal_x is not None) + (marginal_y is not None)


@pytest.mark.parametrize("px_fn", [px.histogram])
@pytest.mark.parametrize("marginal", [None, "rug", "histogram", "box", "violin"])
@pytest.mark.parametrize("orientation", ["h", "v"])
def test_single_marginals(px_fn, marginal, orientation):
df = px.data.tips()

fig = px_fn(
df, x="total_bill", y="total_bill", marginal=marginal, orientation=orientation
)
assert len(fig.data) == 1 + (marginal is not None)
Original file line number Diff line number Diff line change
Expand Up @@ -379,18 +379,73 @@ def test_parcats_dimensions_max():
assert [d.label for d in fig.data[0].dimensions] == ["sex", "smoker", "day", "size"]


def test_histfunc_hoverlabels():
@pytest.mark.parametrize("histfunc,y", [(None, None), ("count", "tip")])
def test_histfunc_hoverlabels_univariate(histfunc, y):
def check_label(label, fig):
assert fig.layout.yaxis.title.text == label
assert label + "=" in fig.data[0].hovertemplate

df = px.data.tips()
fig = px.histogram(df, x="total_bill")
label = "count"
assert fig.layout.yaxis.title.text == label
assert label + "=" in fig.data[0].hovertemplate

fig = px.histogram(df, x="total_bill", y="tip")
label = "sum of tip"
assert fig.layout.yaxis.title.text == label
assert label + "=" in fig.data[0].hovertemplate
# base case, just "count" (note count(tip) is same as count())
fig = px.histogram(df, x="total_bill", y=y, histfunc=histfunc)
check_label("count", fig)

# without y, label is just histnorm
for histnorm in ["probability", "percent", "density", "probability density"]:
fig = px.histogram(
df, x="total_bill", y=y, histfunc=histfunc, histnorm=histnorm
)
check_label(histnorm, fig)

for histnorm in ["probability", "percent", "density", "probability density"]:
for barnorm in ["percent", "fraction"]:
fig = px.histogram(
df,
x="total_bill",
y=y,
histfunc=histfunc,
histnorm=histnorm,
barnorm=barnorm,
)
check_label("%s (normalized as %s)" % (histnorm, barnorm), fig)


def test_histfunc_hoverlabels_bivariate():
def check_label(label, fig):
assert fig.layout.yaxis.title.text == label
assert label + "=" in fig.data[0].hovertemplate

df = px.data.tips()

# with y, should be same as forcing histfunc to sum
fig = px.histogram(df, x="total_bill", y="tip")
check_label("sum of tip", fig)

# change probability to fraction when histfunc is sum
fig = px.histogram(df, x="total_bill", y="tip", histnorm="probability")
check_label("fraction of sum of tip", fig)

# percent is percent
fig = px.histogram(df, x="total_bill", y="tip", histnorm="percent")
check_label("percent of sum of tip", fig)

# the other two are "weighted by"
for histnorm in ["density", "probability density"]:
fig = px.histogram(df, x="total_bill", y="tip", histnorm=histnorm)
check_label("%s weighted by tip" % histnorm, fig)

# check a few "normalized by"
for histnorm in ["density", "probability density"]:
for barnorm in ["fraction", "percent"]:
fig = px.histogram(
df, x="total_bill", y="tip", histnorm=histnorm, barnorm=barnorm
)
check_label(
"%s weighted by tip (normalized as %s)" % (histnorm, barnorm), fig
)

# these next two are weird but OK...
fig = px.histogram(
df,
x="total_bill",
Expand All @@ -399,9 +454,21 @@ def test_histfunc_hoverlabels():
histnorm="probability",
barnorm="percent",
)
label = "probability of min of tip (normalized as percent)"
assert fig.layout.yaxis.title.text == label
assert label + "=" in fig.data[0].hovertemplate
check_label("fraction of sum of min of tip (normalized as percent)", fig)

fig = px.histogram(
df,
x="total_bill",
y="tip",
histfunc="avg",
histnorm="percent",
barnorm="fraction",
)
check_label("percent of sum of avg of tip (normalized as fraction)", fig)

# this next one is basically "never do this" but needs a defined behaviour
fig = px.histogram(df, x="total_bill", y="tip", histfunc="max", histnorm="density")
check_label("density of max of tip", fig)


def test_timeline():
Expand Down