Plot smooth line with PyPlot
Asked Answered
R

8

180

I've got the following simple script that plots a graph:

import matplotlib.pyplot as plt
import numpy as np

T = np.array([6, 7, 8, 9, 10, 11, 12])
power = np.array([1.53E+03, 5.92E+02, 2.04E+02, 7.24E+01, 2.72E+01, 1.10E+01, 4.70E+00])

plt.plot(T,power)
plt.show()

As it is now, the line goes straight from point to point which looks ok, but could be better in my opinion. What I want is to smooth the line between the points. In Gnuplot I would have plotted with smooth cplines.

Is there an easy way to do this in PyPlot? I've found some tutorials, but they all seem rather complex.

Radiolocation answered 12/3, 2011 at 16:6 Comment(0)
T
225

You could use scipy.interpolate.spline to smooth out your data yourself:

from scipy.interpolate import spline

# 300 represents number of points to make between T.min and T.max
xnew = np.linspace(T.min(), T.max(), 300)  

power_smooth = spline(T, power, xnew)

plt.plot(xnew,power_smooth)
plt.show()

spline is deprecated in scipy 0.19.0, use BSpline class instead.

Switching from spline to BSpline isn't a straightforward copy/paste and requires a little tweaking:

from scipy.interpolate import make_interp_spline, BSpline

# 300 represents number of points to make between T.min and T.max
xnew = np.linspace(T.min(), T.max(), 300) 

spl = make_interp_spline(T, power, k=3)  # type: BSpline
power_smooth = spl(xnew)

plt.plot(xnew, power_smooth)
plt.show()

Before: screenshot 1

After: screenshot 2

Twibill answered 12/3, 2011 at 17:9 Comment(6)
This will not work if the T is not sorted. And also if the functiton(T) is not one-to-one.Obligate
You may have wanted to make the #BSpline object comment a type hint such as spl = make_interp_spline(T, power, k=3) # type: BSpline object so that the import of BSpline leads to a slightly more effective use ... or was it otherwise needed for anything? I'm here to remind :) (Plus there's no harm in making the coments a bit more PEP8 style, after all it's "exposed code".) But in general: thanks for the example!Crissman
What's the k = 3 ??Galinagalindo
@AminGuermazi the k=3 is the degree of the interpolation of the spline: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html . So if you use a higher number like k=6, the curve should be smoother.Ashlynashman
Does someone knows how to do it when x values are strings?Armoured
Since the smoothing k=3 did not have the same effect as with https://mcmap.net/q/137901/-smoothing-out-a-curve (splrep, splev), I ended up using that seemingly older version - even if a comment said that it was deprecated, referring to this BSpline as the more recent one.Accelerando
A
69

For this example spline works well, but if the function is not smooth inherently and you want to have smoothed version you can also try:

from scipy.ndimage.filters import gaussian_filter1d

ysmoothed = gaussian_filter1d(y, sigma=2)
plt.plot(x, ysmoothed)
plt.show()

if you increase sigma you can get a more smoothed function.

Proceed with caution with this one. It modifies the original values and may not be what you want.

Ambroseambrosi answered 25/11, 2018 at 23:14 Comment(4)
Proceed with caution with this one. It modifies the original values and may not be what you want.Deiform
doesnt really work well, really flatten the whole function and stops following the points at all...Leyva
@MaciekWoźniak, whether this "works well" depends entirely on what you are trying to accomplish with your data, and also on your choice of sigma value. If you need a smooth line that interpolates the original data, then sure, this does not "work well". In other applications, interpolating the original data would be inappropriate (e.g. if the original data is very noisy, or has multiple values at each location).Dallis
scipy.ndimage.filters is deprecated, and you can use the following instead: from scipy.ndimage import gaussian_filter1dLuciana
T
26

See the scipy.interpolate documentation for some examples.

The following example demonstrates its use, for linear and cubic spline interpolation:

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d

# Define x, y, and xnew to resample at.
x = np.linspace(0, 10, num=11, endpoint=True)
y = np.cos(-x**2/9.0)
xnew = np.linspace(0, 10, num=41, endpoint=True)

# Define interpolators.
f_linear = interp1d(x, y)
f_cubic = interp1d(x, y, kind='cubic')

# Plot.
plt.plot(x, y, 'o', label='data')
plt.plot(xnew, f_linear(xnew), '-', label='linear')
plt.plot(xnew, f_cubic(xnew), '--', label='cubic')
plt.legend(loc='best')
plt.show()

enter image description here

Slightly modified for increased readability.

Tungstic answered 4/6, 2020 at 19:6 Comment(0)
H
23

One of the easiest implementations I found was to use that Exponential Moving Average the Tensorboard uses:

def smooth(scalars: List[float], weight: float) -> List[float]:  # Weight between 0 and 1
    last = scalars[0]  # First value in the plot (first timestep)
    smoothed = list()
    for point in scalars:
        smoothed_val = last * weight + (1 - weight) * point  # Calculate smoothed value
        smoothed.append(smoothed_val)                        # Save it
        last = smoothed_val                                  # Anchor the last smoothed value
        
    return smoothed


ax.plot(x_labels, smooth(train_data, .9), label="Train Smooth")  # label="Train") # uncomment to reproduce figure below
ax.plot(x_labels, train_data, label="Train")  # label="Train Smooth") # uncomment to reproduce figure below

enter image description here

Hindu answered 24/7, 2021 at 14:2 Comment(1)
That is an excellent suggestion, although I believe the curves have their labels swapped in the legend.Gudgeon
P
12

Here is a simple solution for dates:

from scipy.interpolate import make_interp_spline
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as dates
from datetime import datetime

data = {
    datetime(2016, 9, 26, 0, 0): 26060, datetime(2016, 9, 27, 0, 0): 23243,
    datetime(2016, 9, 28, 0, 0): 22534, datetime(2016, 9, 29, 0, 0): 22841,
    datetime(2016, 9, 30, 0, 0): 22441, datetime(2016, 10, 1, 0, 0): 23248 
}
#create data
date_np = np.array(list(data.keys()))
value_np = np.array(list(data.values()))
date_num = dates.date2num(date_np)
# smooth
date_num_smooth = np.linspace(date_num.min(), date_num.max(), 100) 
spl = make_interp_spline(date_num, value_np, k=3)
value_np_smooth = spl(date_num_smooth)
# print
plt.plot(date_np, value_np)
plt.plot(dates.num2date(date_num_smooth), value_np_smooth)
plt.show()

example

Park answered 7/11, 2020 at 10:31 Comment(0)
Y
10

I presume you mean curve-fitting and not anti-aliasing from the context of your question. PyPlot doesn't have any built-in support for this, but you can easily implement some basic curve-fitting yourself, like the code seen here, or if you're using GuiQwt it has a curve fitting module. (You could probably also steal the code from SciPy to do this as well).

Yaws answered 12/3, 2011 at 17:4 Comment(1)
thanks. I tried ten different equations and [Using radial basis functions for smoothing/interpolation][1] rbf = Rbf(x, y), fi = rbf(xi) was best among them. [1]: scipy-cookbook.readthedocs.io/items/RadialBasisFunctions.html,Tetrarch
R
2

It's worth your time looking at seaborn for plotting smoothed lines.

The seaborn lmplot function will plot data and regression model fits.

The following illustrates both polynomial and lowess fits:

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

T = np.array([6, 7, 8, 9, 10, 11, 12])
power = np.array([1.53E+03, 5.92E+02, 2.04E+02, 7.24E+01, 2.72E+01, 1.10E+01, 4.70E+00])

df = pd.DataFrame(data = {'T': T, 'power': power})
    
sns.lmplot(x='T', y='power', data=df, ci=None, order=4, truncate=False)
sns.lmplot(x='T', y='power', data=df, ci=None, lowess=True, truncate=False)

enter image description here

The order = 4 polynomial fit is overfitting this toy dataset. I don't show it here but order = 2 and order = 3 gave worse results.

enter image description here

The lowess = True fit is underfitting this tiny dataset but may give better results on larger datasets.

Check the seaborn regression tutorial for more examples.

Riesman answered 2/7, 2021 at 9:48 Comment(0)
P
1

Another way to go, which slightly modifies the function depending on the parameters you use:

from statsmodels.nonparametric.smoothers_lowess import lowess

def smoothing(x, y):
    lowess_frac = 0.15  # size of data (%) for estimation =~ smoothing window
    lowess_it = 0
    x_smooth = x
    y_smooth = lowess(y, x, is_sorted=False, frac=lowess_frac, it=lowess_it, return_sorted=False)
    return x_smooth, y_smooth

That was better suited than other answers for my specific application case.

Pate answered 11/3, 2021 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.