When one knows that the underlying distribution is a mixture of a known number of normal distributions, then what @SvyatoslavKorneev gives works great. However, with any reasonable sample size, there is no reason to have to rely on the assumption that the underlying bumps are the result of a number of normal distributions with the number of distributions matching what you think you see in a histogram.
A nonparametric kernel density estimate is the way to go. Mathematica has the SmoothKernelDistribution
function to do so:
(* Define mixture distribution *)
mixtureDist =
MixtureDistribution[{0.4, 0.3, 0.3}, {NormalDistribution[4, 1],
NormalDistribution[10, 2], NormalDistribution[20, 3]}];
(* Sample size *)
n = 10^5;
(* Generate a random sample *)
tbl = RandomVariate[mixtureDist, n];
(* Get adaptive kernel estimate of density function *)
skd = SmoothKernelDistribution[
tbl, {"Adaptive", Automatic, Automatic}];
(* Plot true density and estimated density *)
Plot[{PDF[mixtureDist, x], PDF[skd, x]}, {x, Min[tbl], Max[tbl]},
PlotStyle -> {{LightGray, Thickness[0.02]}, Red}]
The thick gray line is the true density and the red line is the estimated density.
Yes, a kernel density estimate does not result in a nice compact formula but it also relies on fewer potentially unjustified assumptions (and likely fits better). (And, yes, I'm not answering the actual question asked.)
Note that "larger" sample sizes are needed if one can't assume a known number of normal distributions as that assumption essentially provides a whole lot of information. Here's an example with the same underlying mixture distribution but with just 100 samples:
Update
I found a comment by @AndyRoss that allows for the finding of peaks which is something else that a kernel density estimate can provide (besides means, variances, semi-interquartile ranges, etc.). That comment suggests to replace SmoothKernelDistribution
with KernelMixtureDistribution
. The output object allows one to estimate both the first and second derivative of the density function.
Below is the updated code that finds peaks (if any) that the data seems to support:
(* Get adaptive kernel estimate of density function *)
kmd = KernelMixtureDistribution[
tbl, {"Adaptive", Automatic, Automatic}];
(* Plot true density and estimated density *)
Plot[{PDF[mixtureDist, x], PDF[kmd, x]}, {x, Min[tbl], Max[tbl]},
PlotStyle -> {{LightGray, Thickness[0.02]}, Red}, PlotRange -> All]
(* Find peaks *)
(* First and second derivatives *)
d1[x_] := D[PDF[kmd, y], y] /. y -> x
d2[x_] := D[d1[y], y] /. y -> x
(* Find where first derivative is zero *)
xmin = Min[tbl];
xmax = Max[tbl];
sol = NSolve[{d1[x] == 0, xmin < x < xmax}, x]
(* Horizontal and vertical axes values corresponding to peaks *)
xPeaks = Pick[x /. sol, Negative[d2[x /. sol]]]
yPeaks = PDF[kmd, xPeaks]
FindFit
and/orNonlinearModelFit
. $\endgroup$