I have this histogram:
I want to have a resulting plot/waveform as like in the picture below? What code or python process to used. Gaussian Distribution is what I'm thinking to use.
Here are some of the codes I've been using:
def gaus(x,a,x0,sigma):
return a*np.exp(-(x-x0)**2/(2*sigma**2))
mean = np.mean(y)
sigma = np.std(y)
# y is histogram list
popt, pcov = curve_fit(gaus, x, y, p0=[1, mean, sigma])
plt.plot(x, y, 'b+:', label='data')
z = gaus(x, *popt)
plt.plot(x, z, 'ro:', label='fit')
plt.show()
EDIT / UPDATE:
I have edited the histogram plot so as it does not look continuous. The horizontal data/axis is the bin edges(they are measured voltages, measurement done to greater 5000 times). So the number of counts those voltages being hit is the vertical axis. What i wanted for my resulting plot/waveform would be to have two peaks. 1 peak is the maximum number of count and the 2nd peak is the 2nd number of count. How do i upload a list(.txt) in here so I can give you the whole data measurements?
Related
I plotted a graph for entropy. Now I want to do a curve fitting but I am not able to understand how to initiate the process. I tried it using the curve_fit module of scipy but what I get is just a straight line rather than a Gaussian curve. Here is the output:
from scipy.optimize import curve_fit
x = [18,23,95,142,154,156,157,158,258,318,367,382,484,501,522,574,681,832,943,1071,1078,1101,1133,1153,1174,1264]
y = [0.179,0.179,0.692,0.574,0.669,0.295,0.295,0.295,0.387,0.179,0.179,0.462,0.179,0.179,0.179,0.179,0.179,0.179,0.179,0.179,0.179,0.462,0.179,0.387,0.179,0.295]
x = np.asarray(x)
y = np.asarray(y)
def Gauss(x, A, B):
y = A*np.exp(-1*B*x**2)
return y
parameters_g, covariance_g = curve_fit(Gauss, x, y)
fit_A = parameters_g[0]
fit_B = parameters_g[1]
print(fit_A)
print(fit_B)
fit_y = Gauss(x, fit_A, fit_B)
plt.figure(figsize=(18,10))
plt.plot(x, y, 'o', label='data')
plt.plot(x, fit_y, '-', label='fit')
plt.legend()
plt.show()
I just connected the values using spline and got this kind of curve:
Can anyone suggest to me how to fit this curve into a Gaussian curve?
Edit 1: Now I have the barplot (sort of) where I have the y value for the corresponding x values. My x-axis ranges from 0 to 1273 and the y-axis from 0 to 1. How can I do a curve fitting and what will be the right curve over here? I was trying to fit a bimodal curve distribution for the given data. You can find the data from here.
Bar plot image: https://i.stack.imgur.com/1Awt5.png
Data : https://drive.google.com/file/d/1_uiweIWRWgzy5wNVLOvn4WN25jteu8rQ/view?usp=sharing
You dont have a line you indeed have a Gaussian curve (centered around zero because of your definition of Gauss). You can clearly see that when you plot the function on a different scale:
x_arr = np.linspace(-2,2,100)
fit_y = Gauss(x_arr, fit_A, fit_B)
plt.figure(figsize=(18,10))
plt.plot(x_arr, fit_y, '-', label='fit')
plt.legend()
plt.show()
That image was made with the estimated fit_A == fit_B == 1 from your code.
You can add a different initial guess which leads to different result via:
parameters_g, covariance_g = curve_fit(Gauss, x, y, p0=(1,1/1000))
but I would say that your data is not that well described via a Gaussian curve.
One thing I would allays recommend is setting those values by hand to see the effect it has on the plot. That way you can get a feeling if the task you try to automated is realistic in the first place.
I'm using seaborn for plotting data. Everything is fine until my mentor asked me how the plot is made in the following code for example.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
x = np.random.normal(size=100)
sns.distplot(x)
plt.show()
The result of this code is:
My questions:
How does distplot manage to plot this?
Why does the plot start at -3 and end at 4?
Is there any parametric function or any specific mathematical function that distplot uses to plot the data like this?
I use distplot and kind='kde' to plot my data, but I would like to know what is the maths behind those functions.
Here is some code trying to illustrate how the kde curve is drawn.
The code starts with a random sample of 100 xs.
These xs are shown in a histogram. With density=True the histogram is normalized so that it's full area would be 1. (Standard, the bars of the histogram grow with the number of points. Internally, the complete area is calculated and each bar's height is divided by that area.)
To draw the kde, a gaussian "bell" curve is drawn around each of the N samples. These curves are summed, and normalized by dividing by N.
The sigma of these curves is a free parameter. Default it is calculated by Scott's rule (N ** (-1/5) or 0.4 for 100 points, the green curve in the example plot).
The code below shows the result for different choices of sigma. Smaller sigmas enclose the given data stronger, larger sigmas appear more smooth. There is no perfect choice for sigma, it depends strongly on the data and what is known (or guessed) about the underlying distribution.
import matplotlib.pyplot as plt
import numpy as np
def gauss(x, mu, sigma):
return np.exp(-((x - mu) / sigma) ** 2 / 2) / (sigma * np.sqrt(2 * np.pi))
N = 100
xs = np.random.normal(0, 1, N)
plt.hist(xs, density=True, label='Histogram', alpha=.4, ec='w')
x = np.linspace(xs.min() - 1, xs.max() + 1, 100)
for sigma in np.arange(.2, 1.2, .2):
plt.plot(x, sum(gauss(x, xi, sigma) for xi in xs) / N, label=f'$\\sigma = {sigma:.1f}$')
plt.xlim(x[0], x[-1])
plt.legend()
plt.show()
PS: Instead of a histogram or a kde, other ways to visualize 100 random numbers are a set of short lines:
plt.plot(np.repeat(xs, 3), np.tile((0, -0.05, np.nan), N), lw=1, c='k', alpha=0.5)
plt.ylim(ymin=-0.05)
or dots (jittered, so they don't overlap):
plt.scatter(xs, -np.random.rand(N)/10, s=1, color='crimson')
plt.ylim(ymin=-0.099)
I have an array of velocity data in directions V_x and V_y. I've plotted a histogram for the velocity norm using the code below,
plt.hist(V_norm_hist, bins=60, density=True, rwidth=0.95)
which gives the following figure:
Now I also want to add a Rayleigh Distribution curve on top of this, but I can't get it to work. I've been trying different combinations using scipy.stats.rayleigh but the scipy homepage isn't really intuative so I can't get it to function properly...
What exactly does the lines
mean, var, skew, kurt = rayleigh.stats(moments='mvsk')
and
x = np.linspace(rayleigh.ppf(0.01),rayleigh.ppf(0.99), 100)
ax.plot(x, rayleigh.pdf(x),'r-', lw=5, alpha=0.6, label='rayleigh pdf')
do?
You might need to first follow the link to rv_continuous, from which rayleigh is subclassed. And from there to the ppf to find out that ppf is the 'Percent point function'. x0 = ppf(0.01) tells at which spot everything less than x0 has accumulated 1% of its total 'weight' and similarly x1 = ppf(0.99) is where 99% of the 'weight' is accumulated. np.linspace(x0, x1, 100) divides the space from x0 to x1 in 100 short intervals. As a continuous distribution can be infinite, these x0 and x1 limits are needed to only show the interesting interval.
rayleigh.pdf(x) gives the pdf at x. So, an indication of how probable each x is.
rayleigh.stats(moments='mvsk') where moments is composed of letters [‘mvsk’] defines which moments to compute: ‘m’ = mean, ‘v’ = variance, ‘s’ = (Fisher’s) skew, ‘k’ = (Fisher’s) kurtosis.
To plot both the histogram and the distribution on the same plot, we need to know the parameters of Raleigh that correspond to your sample (loc and scale). Furthermore, both the pdf and the histogram would need the same x and same y. For the x we can take the limits of the histogram bins. For the y, we can scale up the pdf, knowing that the total area of the pdf is supposed to be 1. And the histogram bins are proportional to the number of entries.
If you do know the loc is 0 but don't know the scale, the wikipedia article gives a formula that connects the scale to the mean of your samples:
estimated_rayleigh_scale = samples.mean() / np.sqrt(np.pi / 2)
Supposing a loc of 0 and a scale of 0.08 the code would look like:
from matplotlib import pyplot as plt
import numpy as np
from scipy.stats import rayleigh
N = 1000
# V = np.random.uniform(0, 0.1, 2*N).reshape((N,2))
# V_norm = (np.linalg.norm(V, axis=1))
scale = 0.08
V_norm_hist = scale * np.sqrt( -2* np.log (np.random.uniform(0, 1, N)))
fig, ax = plt.subplots(1, 1)
num_bins = 60
_binvalues, bins, _patches = plt.hist(V_norm_hist, bins=num_bins, density=False, rwidth=1, ec='white', label='Histogram')
x = np.linspace(bins[0], bins[-1], 100)
binwidth = (bins[-1] - bins[0]) / num_bins
scale = V_norm_hist.mean() / np.sqrt(np.pi / 2)
plt.plot(x, rayleigh(loc=0, scale=scale).pdf(x)*len(V_norm_hist)*binwidth, lw=5, alpha=0.6, label=f'Rayleigh pdf (s={scale:.3f})')
plt.legend()
plt.show()
I'm tryng to fit a histogram but the fit only works with normalised data, i.e. with option normed=True in the histogram. Is there a way of doing this with scipy stats (or other method)? Here is a MWE using a uniform distribution:
import matplotlib.pyplot as plt
import numpy as np
import random
from scipy.stats import uniform
data = []
for i in range(1000):
data.append(random.uniform(-1,1))
loc, scale = uniform.fit(data)
x = np.linspace(-1,1, 1000)
y = uniform.pdf(x, loc, scale)
plt.hist(data, bins=100, normed=False)
plt.plot(x, y, 'r-')
plt.show()
I also tried defining my own function (below) but I'm getting a bad fit.
import matplotlib.pyplot as plt
import numpy as np
import random
from scipy import optimize
data = []
for i in range(1000):
data.append(random.uniform(-1,1))
def unif(x,avg,sig):
return avg*x + sig
y, base = np.histogram(data,bins=100)
x = [0.5 * (base[i] + base[i+1]) for i in xrange(len(base)-1)]
popt, pcov = optimize.curve_fit(unif, x, y)
x_fit = np.linspace(x[0], x[-1], 100)
y_fit = unif(x_fit, *popt)
plt.hist(data, bins=100, normed=False)
plt.plot(x_fit, y_fit, 'r-')
plt.show()
Note that it is generally a bad idea to fit a distribution to the histogram. Compared to the raw data the histogram contains less information so the fit will most likely be worse. Thus, the first MWE in the question actually contains the best approach. Simply normalize the histogram and it will match the distribution of the data: plt.hist(data, bins=100, normed=True).
However, it seems you actually want to work with the unnormalized histogram. In that case take the normalization that the histogram would normally use and apply it inverted to the fitted distribution. The documentation describes the normalization as
n/(len(x)`dbin)
which is verbose for saying dividing by the number of observations times the bin width.
Multiplying the distribution by this value results in the expected counts per bin:
loc, scale = uniform.fit(data)
x = np.linspace(-1,1, 1000)
y = uniform.pdf(x, loc, scale)
n_bins = 100
bin_width = np.ptp(data) / n_bins
plt.hist(data, bins=n_bins, normed=False)
plt.plot(x, y * len(data) * bin_width, 'r-')
The second MWE is interesting because you describe the line a a bad fit, but actually it is a very good fit :). You simply overfit the histogram because although you expect a horizontal line (one degree of freedom) you fit an arbitrary line (two degrees of freedom).
So if you want a horizontal line fit a horizontal line and don't be surprised to get something else if you fit something else...
def unif(x, sig):
return 0 * x + sig # slope is zero -> horizontal line
However, there is a much simpler way of obtaining the height of the unnormalized uniform distribution. Just average the histogram over all bins:
y, base = np.histogram(data,bins=100)
y_hat = np.mean(y)
print(y_hat)
# 10.0
Or, even simpler use the theoretical value of len(data) / n_bins == 10.
I am working on fitting some data to a gaussian curve using lmfit module for Python. The fit was easy enough following some online tutorial, but I would also like to plot the error bars for this curve. Does anyone know how to accomplish this? If your interested the data represents counts of radioactive decay per 4 seconds from Cesium-137 (x-axis represents number of counts, y-axis represents the frequency that those counts occurred). Here is some of my code,
data = loadtxt('CS137gaussian.txt')
x = data[:, 0]
y = data[:, 1]
def gaussian(x, amp, mu, sigma):
"1-d gaussian: gaussian(x, amp, mu, sigma)"
return (amp/(sqrt(2*pi)*sigma)) * exp(-(x-mu)**2 /(2*sigma**2))
gmod = Model(gaussian)
result = gmod.fit(y, x=x, amp=20, mu=300, sigma=1)
plt.plot(x, y, 'bo')
plt.plot(x, result.init_fit, 'k--')
plt.plot(x, result.best_fit, 'r-')