我认为您将噪声与异常值混淆了,请参阅:噪声与异常值之间的基本差异。 您可以尝试通过许多不同的方法删除异常值,例如使用 z 分数:
df = df.mask(np.abs(stats.zscore(df)) < 2) # here we are setting limit on z-score on 2 - you can experiment with values best suited to your data
重要提示:您应该在从数据中删除趋势后执行此操作。
让我们重新创建一个数据集:
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats, signal, optimize
np.random.seed(12345)
def model(x, a, b, c):
return a*np.exp(-b*x) + c
x = np.linspace(0, 350, 200)
y = model(x, 100, 0.01, 75)
n = np.random.normal(size=x.size)
yn = y + n
yn[20] *= 0.75
yn[21] *= 0.5
yn[22] *= 1.75
yn[23] *= 0.25
yn[24] *= 0.20
yn[25] *= 0.75
yn[100] *= 0.5
yn[101] *= 1.75
如果异常值不是太强或太多,我们可以通过用异常值拟合曲线来估计趋势:
popt1, pcov1 = optimize.curve_fit(model, x, yn)
yhat1 = model(x, *popt1)
# (array([9.27557251e+01, 1.02647524e-02, 7.64660389e+01]),
# array([[ 1.94284082e+01, 7.21272130e-04, -3.70396525e+00],
# [ 7.21272130e-04, 1.80489353e-06, 3.75303063e-03],
# [-3.70396525e+00, 3.75303063e-03, 1.05002199e+01]]))
这已经接近最佳参数,但受异常值支配(参见协方差)。
或者我们按照您的建议使用一些过滤器平滑曲线:
yhat1 = signal.savgol_filter(yn, 150, 3)
然后,正如 @Matmozaur 所建议的,z 分数是过滤异常值的一个很好的标准:
zs = stats.zscore(yhat1 - yn)
mask = np.abs(zs) <= 2
现在我们已经识别出异常值,我们可以在没有它们的情况下拟合函数:
popt2, pcov2 = optimize.curve_fit(model, x[mask], yn[mask])
yhat2 = model(x, *popt2)
# (array([9.90714297e+01, 1.01604158e-02, 7.54550734e+01]),
# array([[ 5.81279449e-01, 1.70129801e-05, -1.13880755e-01],
# [ 1.70129801e-05, 4.43252922e-08, 1.00312909e-04],
# [-1.13880755e-01, 1.00312909e-04, 3.04817515e-01]]))
对于这种设置来说,这是相当可以接受的。