使用PyPlot绘制平滑曲线

176
我有以下简单的脚本,用于绘制图表:

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()

目前,这条线路直接连接每个点,看起来还不错,但我认为可以更好。我想要的是平滑连接这些点的曲线。在Gnuplot中,我会使用smooth cplines绘制。

在PyPlot中有简单的方法实现吗?我找到了一些教程,但它们似乎都很复杂。

8个回答

225
您可以使用scipy.interpolate.spline自己来平滑您的数据:
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在scipy 0.19.0版本已被弃用,请使用BSpline类。

spline切换到BSpline不是一件简单的复制/粘贴工作,需要进行一些微调:

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()

之前: screenshot 1 之后: screenshot 2

7
如果T没有排序,这将无法运行。同时,如果函数(T)不是一对一的,则也无法运行。 - Rahat Zaman
您可能希望将“#BSpline对象”注释为类型提示,例如spl = make_interp_spline(T,power,k = 3) #type:BSpline对象,以便导入BSpline可以更有效地使用...或者它还需要用于其他任何事情吗?我在这里提醒一下 :) (此外,将注释稍微更改为PEP8风格也没有坏处,毕竟这是“公开代码”。) 但总的来说,感谢您的示例! - brezniczky
3
“k = 3”是什么意思? - Amin Guermazi
4
@AminGuermazi, k=3 是样条插值的插值度数:https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html 。如果您使用更高的数字例如 k=6 ,曲线应该会更加平滑。 - Jānis Lazovskis
有人知道怎么做吗,当 x 的值为字符串时? - Ramon
由于平滑度k = 3没有像https://dev59.com/NFYN5IYBdhLWcg3w9MRJ#46634139(`splrep,splev`)那样产生相同的效果,因此我最终使用了那个看似更旧的版本 - 即使有一条评论说它已被弃用,并引用这个BSpline作为更近期的版本。 - questionto42

68

对于这个例子,样条线运行良好,但是如果函数本身不平滑,而你想要平滑版本的话,也可以尝试:

from scipy.ndimage.filters import gaussian_filter1d

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

如果你增加 sigma,你可以得到一个更平滑的函数。

对于这个操作要小心谨慎。它会修改原始值,可能不是你想要的结果。


17
请谨慎操作此功能。它会更改原始数值,可能不符合您的意愿。 - tartaruga_casco_mole
1
并不是很有效,会将整个函数压缩并停止遵循任何点... - Maciek Woźniak
@MaciekWoźniak,这个“工作良好”的程度完全取决于您想要用数据实现什么目标,以及您选择的sigma值。如果您需要一条平滑的线来插值原始数据,那么当然,这并不“工作良好”。在其他应用中,插值原始数据可能是不合适的(例如,如果原始数据非常嘈杂或在每个位置有多个值)。 - Ceph
scipy.ndimage.filtersе·Іиў«ејғз”ЁпјҢжӮЁеҸҜд»ҘдҪҝз”Ёд»ҘдёӢжӣҝд»Јпјҡfrom scipy.ndimage import gaussian_filter1d - Nathan Dai

23

请参阅scipy.interpolate文档以查看一些示例。

下面的示例演示了线性插值和三次样条插值的用法:

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()

这里输入图片描述

为了更易读,略微修改。


17

我发现最简单的实现之一是使用Tensorboard使用的指数移动平均值:

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), x_labels, train_data)

enter image description here


2
这是一个很好的建议,尽管我认为图例中的曲线标签被交换了。 - Danilo

11

这里是一个有关日期的简单解决方案:

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


10

我猜你的问题是指曲线拟合而不是抗锯齿。PyPlot没有内置支持,但你可以轻松地自己实现一些基本的曲线拟合,就像在这里看到的代码,或者如果你使用GuiQwt,它有一个曲线拟合模块。(你也可以从SciPy中窃取代码来完成这个任务)。


1
谢谢。我尝试了十种不同的方程式,其中[使用径向基函数进行平滑/插值][1] rbf = Rbf(x,y),fi = rbf(xi) 是最好的选择。 [1]: https://scipy-cookbook.readthedocs.io/items/RadialBasisFunctions.html - Cloud Cho

2
值得花些时间查看 seaborn 以绘制平滑线。 seaborn的lmplot函数将绘制数据和回归模型拟合结果。
以下示例展示了多项式和lowess拟合:
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

“order = 4”的多项式拟合过度适应了这个玩具数据集。我没有展示,但是“order = 2”和“order = 3”的结果更差。”

enter image description here

"lowess = True" 拟合在这个小数据集上欠拟合,但在更大的数据集上可能会得到更好的结果。
请查看seaborn回归教程以获取更多示例。

1

另一种方法是根据您使用的参数略微修改函数:

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

那个回答比其他回答更适合我的具体应用场景。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接