$ pip install joypyPulls inmatplotlib,pandas,scipy,numpy.import joypy★The whole library — one function inside.import pandas as pdjoypy is built around DataFrames.from matplotlib import cmFor colormaps likecm.viridis,cm.magma.import matplotlib.pyplot as pltTo save / show the returned figure.
fig, axes = joypy.joyplot(df)★That's the whole thing — everything else is optional.# returns (fig, axes)A Matplotlib figure and a list of axes.# one ridge per numeric columnNon-numeric columns are quietly skipped.len(axes) == n + 1noten rows + 1 global axis (axes[-1]).# density via scipy gaussian_kdeSmooth curves out of the box.
joyplot(df)★A DataFrame → one ridge per numeric column.joyplot(series)A single pandas Series.joyplot({"a": arr1, "b": arr2})A dict of arrays (keys become labels).joyplot([arr1, arr2])A plain list of arrays.joyplot(df.groupby("g"))A pandas GroupBy object.
joyplot(df, by="group")★One ridge per group value.joyplot(df, column="value")Restrict to one variable.joyplot(df, by="g", column="v")★Group × one variable — the classic form.joyplot(df, by="g", column=["v1","v2"])Several densities overlaid per group.# by = groups rows · column = picks valuesTwo independent axes of splitting.
joyplot(df)★Smooth KDE curves (the default).joyplot(df, hist=True)★Stepped histograms instead of KDE.bins=20Histogram bin count (default 10).joyplot(df, hist=True, bins=30)Finer histograms.# KDE uses scipy.stats.gaussian_kdeGreat for continuous data.
kind="kde"Default smooth density.kind="counts"Raw histogram counts.kind="normalized_counts"Counts ÷ n.kind="values"Plot raw values — setx_rangetoo.bw_method=0.2KDE smoothing bandwidth (passed to scipy).normalize=TrueNormalise each density (default).
overlap=1★How much rows overlap (default 1).overlap=0Rows fully separated — no overlap.overlap=2Heavy overlap — the dramatic look.ylim="max"★All rows share one y-scale (default).ylim="own"Each row scales to its own peak.figsize=(8, 6)Figure size in inches.
range_style="all"Every row shares the x-range (default).range_style="own"Each row uses its own data range.range_style="group"Range spans the whole group.x_range=np.linspace(0, 100, 200)Explicit x positions for the density.tails=0.2Extra padding beyond the data on each side.
colormap=cm.viridis★Colour rows along a colormap.colormap=cm.magma · cm.BluesAny Matplotlib colormap object.color="steelblue"One flat colour for every row.color=["r", "g", "b"]An explicit per-row list.fade=True★Fade opacity down the stack.# colormap wants an object, not a namenotecm.viridis, not"viridis".
fill=True★Fill under each curve (default).fill=FalseOutlines only — pure ridgelines.linecolor="k"Curve outline colour (auto black when filled).background="black"Figure background — the Unknown Pleasures look.alpha=0.6 · linewidth=2Passed straight through to Matplotlib.
labels=["A", "B", "C"]Custom per-row labels.ylabels=FalseHide the row labels entirely.xlabels=TrueShow the shared x tick labels (default).title="Distributions by year"Figure title.xlabelsize=10 · ylabelsize=12Tick / label font sizes.xrot=45 · yrot=0Rotate tick labels.
legend=TrueShow a legend (needs sub-labels).loc="upper right"Legend position.grid=TrueTurn on gridlines.grid="y" · "x" · "both"Grid on a specific axis.# legend appears with by= + multiple columnsSub-labels drive the legend entries.
fig, axes = joypy.joyplot(df)★Capture both to save or post-edit.fig.savefig("joy.png", dpi=300)★Save the figure.plt.savefig("joy.png", bbox_inches="tight")Trim surrounding whitespace.axes[-1]The global axis — style the whole figure here.axes[i].set_xlim(..)Reach into any single ridge's axis.plt.show()Display interactively.
basic: joyplot(df)One ridge per numeric column.grouped: joyplot(df, by="g", column="v")Compare a variable across groups.over time: joyplot(df, by="month", column="temp")Distribution shifts across a dimension.histogram: joyplot(df, hist=True, bins=20)Binned instead of smoothed.unknown pleasures: colormap=cm.magma, background="k", overlap=2The album-cover aesthetic.airy: joyplot(df, fade=True, overlap=3, fill=False)Faded, overlapping outlines.