Skip to content

Commit

Permalink
Fix boxplot stat unscaling
Browse files Browse the repository at this point in the history
  • Loading branch information
mwaskom committed Aug 20, 2023
1 parent 472003b commit 44d4418
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 8 deletions.
11 changes: 8 additions & 3 deletions seaborn/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,12 @@ def get_props(element, artist=mpl.lines.Line2D):
capwidth = plot_kws.get("capwidths", 0.5 * data["width"])

self._invert_scale(ax, data)
linear_scale = getattr(ax, f"get_{self.orient}scale")() == "linear"
_, inv = _get_transform_functions(ax, value_var)
for stat in ["mean", "med", "q1", "q3", "cilo", "cihi", "whislo", "whishi"]:
stats[stat] = inv(stats[stat])
stats["fliers"] = stats["fliers"].map(inv)

linear_orient_scale = getattr(ax, f"get_{self.orient}scale")() == "linear"

maincolor = self._hue_map(sub_vars["hue"]) if "hue" in sub_vars else color
if fill:
Expand All @@ -652,7 +657,7 @@ def get_props(element, artist=mpl.lines.Line2D):
bxpstats=stats.to_dict("records"),
positions=data[self.orient],
# Set width to 0 to avoid going out of domain
widths=data["width"] if linear_scale else 0,
widths=data["width"] if linear_orient_scale else 0,
patch_artist=fill,
vert=self.orient == "x",
manage_ticks=False,
Expand All @@ -674,7 +679,7 @@ def get_props(element, artist=mpl.lines.Line2D):
# Reset artist widths after adding so everything stays positive
ori_idx = ["x", "y"].index(self.orient)

if not linear_scale:
if not linear_orient_scale:
for i, box in enumerate(data.to_dict("records")):
p0 = box["edge"]
p1 = box["edge"] + box["width"]
Expand Down
22 changes: 17 additions & 5 deletions tests/test_categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,13 +806,13 @@ def check_box(self, bxp, data, orient, pos, width=0.8):
p25, p50, p75 = np.percentile(data, [25, 50, 75])

box = self.get_box_verts(bxp.box)
assert box[val_idx].min() == p25
assert box[val_idx].max() == p75
assert box[val_idx].min() == approx(p25, 1e-3)
assert box[val_idx].max() == approx(p75, 1e-3)
assert box[pos_idx].min() == approx(pos - width / 2)
assert box[pos_idx].max() == approx(pos + width / 2)

med = bxp.median.get_xydata().T
assert tuple(med[val_idx]) == (p50, p50)
assert np.allclose(med[val_idx], (p50, p50), rtol=1e-3)
assert np.allclose(med[pos_idx], (pos - width / 2, pos + width / 2))

def check_whiskers(self, bxp, data, orient, pos, capsize=0.4, whis=1.5):
Expand All @@ -831,13 +831,13 @@ def check_whiskers(self, bxp, data, orient, pos, capsize=0.4, whis=1.5):
adj_lo = data[data >= (p25 - iqr * whis)].min()
adj_hi = data[data <= (p75 + iqr * whis)].max()

assert whis_lo[val_idx].max() == p25
assert whis_lo[val_idx].max() == approx(p25, 1e-3)
assert whis_lo[val_idx].min() == approx(adj_lo)
assert np.allclose(whis_lo[pos_idx], (pos, pos))
assert np.allclose(caps_lo[val_idx], (adj_lo, adj_lo))
assert np.allclose(caps_lo[pos_idx], (pos - capsize / 2, pos + capsize / 2))

assert whis_hi[val_idx].min() == p75
assert whis_hi[val_idx].min() == approx(p75, 1e-3)
assert whis_hi[val_idx].max() == approx(adj_hi)
assert np.allclose(whis_hi[pos_idx], (pos, pos))
assert np.allclose(caps_hi[val_idx], (adj_hi, adj_hi))
Expand Down Expand Up @@ -944,6 +944,18 @@ def test_dodge_native_scale_log(self, long_df):
widths.append(np.ptp(coords))
assert np.std(widths) == approx(0)

@pytest.mark.parametrize("orient", ["x", "y"])
def test_log_data_scale(self, long_df, orient):

var = {"x": "y", "y": "x"}[orient]
s = long_df["z"]
ax = mpl.figure.Figure().subplots()
getattr(ax, f"set_{var}scale")("log")
boxplot(**{var: s}, whis=np.inf, ax=ax)
bxp = ax.containers[0][0]
self.check_box(bxp, s, orient, 0)
self.check_whiskers(bxp, s, orient, 0, whis=np.inf)

def test_color(self, long_df):

color = "#123456"
Expand Down

0 comments on commit 44d4418

Please sign in to comment.