Skip to content
Open
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
20 changes: 20 additions & 0 deletions ultraplot/axes/plot.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7114,6 +7114,8 @@ def _apply_hist(
filled=None,
histtype=None,
orientation="vertical",
kde=False,
kde_kw=None,
**kwargs,
):
"""
Expand DownExpand Up@@ -7157,6 +7159,24 @@ def _apply_hist(
if type(sub) is list:
res[i] = cbook.silent_list("Polygon", sub)
self._update_guide(res, **guide_kw)
# add kde line
if not kde:
return obj
from scipy.stats import gaussian_kde
edges = obj[1]
kde_kw = dict(kde_kw or {})
density = kw.get('density', False)
data2d = xs if xs.ndim > 1 else xs[:, None] # (M, N) data
stepsize = kde_kw.pop('stepsize', 300)
for i in range(data2d.shape[1]):
_x = data2d[:, i]
xa = np.linspace(_x.min(), _x.max(), stepsize)
ya = gaussian_kde(_x)(xa)
if not density:
idx = np.clip(np.digitize(xa, edges)-1, 0, len(edges)-2)
ya = ya * len(_x) * np.diff(edges)[idx]
x_line, y_line = (xa, ya) if orientation=="vertical" else (ya, xa)
self._call_native("plot", x_line, y_line, **kde_kw)
return obj

@inputs._preprocess_or_redirect("x", "bins", keywords="weights")
Expand Down
36 changes: 36 additions & 0 deletions ultraplot/tests/test_1dplots.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -243,6 +243,42 @@ def test_histogram_types(rng):
ax.hist(data, ec="k", **kw)
return fig

def test_hist_kde_lines(rng):
"""
Test kde for hist.
"""
data = rng.normal(size=200)
# No kde if no kde is not given
fig, ax = uplt.subplot()
ax.hist(data, bins=20)
assert len(ax.lines) == 0
# No kde if kde=False
ax.hist(data, bins=20, kde=False)
assert len(ax.lines) == 0
# One kde line with
ax.hist(data, bins=20, kde=True)
assert len(ax.lines) == 1
# test step size
line = ax.lines[-1]
# default stepsize is 300
assert line.get_xdata().size == 300
assert line.get_ydata().size == 300
assert line.get_xdata()[0] == pytest.approx(data.min())
assert line.get_xdata()[-1] == pytest.approx(data.max())
# use kde_kw to set stepsize=150
ax.hist(data, bins=20, kde=True, density=True,
kde_kw={'stepsize': 150})
density_line = ax.lines[-1]
assert density_line.get_xdata().size == 150
assert density_line.get_ydata().size == 150
# test density, default is False, but to to test accurate?
assert line.get_ydata().max() > 1.0
assert density_line.get_ydata().max() <= 1.0
# test area==1
area = np.trapezoid(density_line.get_ydata(), density_line.get_xdata())
assert area == pytest.approx(1.0, rel=1e-2)
uplt.close(fig)


@pytest.mark.mpl_image_compare
def test_invalid_plot(rng):
Expand Down