# How to visualize an evolution of a distribution in time?

Suppose you have a record of distribution for each day in some period. For example, some distribution which depends on a parameter which evolves over time. Suppose we have dozens or hundreds of days. How would you visualize the change of this distribution? I want the plot to contain as much info as possible.

One way I could think of is: proxy the density function and plot these curves's evolution in 2D. It will be some form of homotopy: the initial distribution converging to the final with some smooth step. Of course here I assume smoothness.

The question is theoretical in nature but I am aiming for python realization so implementations or suggestions for libraries are also welcome.

• probably better placed in stackoverflow. matplotlib.org/3.1.0/api/animation_api.html Commented Jun 13, 2019 at 10:19
• this might be very close to what you're looking for: kaggle.com/meykds/leaderboard-progression-animation Commented Jun 13, 2019 at 10:26
• You should give some more context-is this empirical distributions, or a modeled distribution? Commented Jun 14, 2019 at 14:18
• @kjetilbhalvorsen empirical distributions. Commented Jun 17, 2019 at 15:55

EDIT Didn't see it was a python question. I think the idea stands, but not sure if python implementation readily available.

You might want to look into so called ridge-plots using R package ggridges (and gganimate for animation). Below is an example (obviously doesn't have to be an animation):

Code is available in this gist.

• This looks pretty cool. Anybody knows about a Python implementaion? Commented Mar 7, 2023 at 17:59

Always think about the nature of your situation: Who will be the audience for this plot? What are my goals for it? What are my data?

You state that you have a "distribution which depends on a parameter which evolves over time". If your audience is fairly sophisticated, and this is a known, studied distribution (e.g., a Weibull), then you could estimate the changing parameter for each day, plot it on a scatterplot, and smooth it with something simple like a LOWESS line.

Here's an example. These are coded in R, but they are intended to be easy to follow by people who don't use R, and should be possible to translate to Python.

library(fitdistrplus)      # we'll use this package
set.seed(9234)             # this makes the example exactly reproducible

day = rep(1:16, each=100)  # 1 through 16, each repeated 100x
y   = c()                  # an empty vector to hold the data
for(i in 1:16){            # generates data from Weibull distributions w/ the
y = c(y,                 #  shape parameter increasing (but decelerating) by day
rweibull(n=100, shape=(.5 + .1*i - .002*(i^2)), scale=1))
}
# estimate Weibull shape & scale parameters by MLE for each day
d = t(sapply(split(y, day), function(x){ fitdist(x, distr="weibull")$$estimate })) d = data.frame(day=1:16, d) d # day shape scale # 1 1 0.6311143 0.9871380 # 2 2 0.7501392 1.0168905 # 3 3 0.7510426 0.8853516 # 4 4 0.8142484 0.8701132 # 5 5 0.9081937 1.1098466 # 6 6 0.9679144 1.0668120 # 7 7 1.0347746 1.0638731 # 8 8 1.1496184 0.9775989 # 9 9 1.1724681 1.0758072 # 10 10 1.2396152 0.9975250 # 11 11 1.2519313 0.8847656 # 12 12 1.4648643 1.0801915 # 13 13 1.3258313 0.9113326 # 14 14 1.4301392 0.9699252 # 15 15 1.5494493 1.0448072 # 16 16 1.5989056 1.0133831 windows() plot(shape~day, d) lines(lowess(d$$day, d\$shape), col="red")


If the data weren't from a known distribution, you could plot multiple lines tracing fixed quantiles over time.

dq = t(sapply(split(y, day), function(x){ quantile(x, probs=c(0.25, 0.5, 0.75, 0.95)) }))
dq = data.frame(day=1:16, dq)
names(dq) = c("day", "25%", "50%", "75%", "95%")
dq
#    day       25%       50%      75%
# 1    1 0.1447001 0.5212207 1.628061
# 2    2 0.2318992 0.6657878 1.394435
# 3    3 0.1868559 0.5122787 1.618891
# 4    4 0.1665822 0.6402280 1.259112
# 5    5 0.3038764 0.6778831 1.418966
# 6    6 0.2508331 0.7469482 1.447055
# 7    7 0.2759569 0.7411599 1.527585
# 8    8 0.2774774 0.7496496 1.421123
# 9    9 0.3630679 0.9203537 1.343523
# 10  10 0.3788195 0.6613015 1.263599
# 11  11 0.3514467 0.6411170 1.110531
# 12  12 0.4697239 0.8562416 1.253663
# 13  13 0.3281270 0.6732758 1.113507
# 14  14 0.4498140 0.7440592 1.143489
# 15  15 0.4391240 0.8440031 1.371128
# 16  16 0.4726235 0.8386493 1.210914
windows()
plot(1,1, xlim=c(1,16), ylim=c(0, 7), xlab="day", ylab="value", type="n")
lines(dq$$day, dq$$25%, col="red",    lty=2)
lines(dq$$day, dq$$50%, col="black",  lty=1)
lines(dq$$day, dq$$75%, col="blue",   lty=3)
lines(dq$$day, dq$$95%, col="purple", lty=4)
legend("topright", legend=c("95%", "75%", "50%", "25%"), lty=c(4,3,1,2),
col=c("purple", "blue", "black", "red"))


If your audience won't be as sophisticated, and wouldn't know what a "Weibull" is or be thrown off by trying to follow the idea of the "75th percentile", and you want something with more pizzazz, make a panel of kernel density plots. (Admittedly, adibender's plot has more pizzazz than this.)

windows()
par(mfrow=c(4,4))
for(i in 1:16){
plot(density(y[day==i]), main=paste("day", i), xlab="", xlim=range(y),
ylim=c(0, .8), ylab="", axes=FALSE);  box()
}


Ridge plots can be done with Python Seaborn. I found this beautiful example in their documentation and will repost it here.

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)})

# Create random data
rs = np.random.RandomState(1979)
x = rs.randn(500)
g = np.tile(list("ABCDEFGHIJ"), 50)
df = pd.DataFrame(dict(x=x, g=g))
m = df.g.map(ord)
df["x"] += m

# Initialize the FacetGrid object
pal = sns.cubehelix_palette(10, rot=-.25, light=.7)
g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal)

# Draw the densities in a few steps
g.map(sns.kdeplot, "x",
fill=True, alpha=1, linewidth=1.5)
g.map(sns.kdeplot, "x", clip_on=False, color="w", lw=2, bw_adjust=.5)

# passing color=None to refline() uses the hue mapping
g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False)

# Define and use a simple function to label the plot in axes coordinates
def label(x, color, label):
ax = plt.gca()
ax.text(0, .2, label, fontweight="bold", color=color,
ha="left", va="center", transform=ax.transAxes)

g.map(label, "x")

# Set the subplots to overlap