识别图表的上升趋势或下降趋势。

8

我正在尝试使用Python(标准线图)读取数据并将其绘制到图表上。请问如何以编程方式分类某些点在图表中是上升趋势还是下降趋势?哪种方法最优?这肯定是一个已解决的问题,存在数学方程来确定这一点吧?

以下是一些包含上升趋势和下降趋势的样本数据

x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]
y = [2,5,7,9,10,13,16,18,21,22,21,20,19,18,17,14,10,9,7,5,7,9,10,12,13,15,16,17,22,27]

感谢您提前阅读。

1
听起来你只是想要拟合一个一阶多项式,然后查看系数是负数还是正数。这将适用于整个数据集,从问题中不清楚你需要更多什么。 - Greg
抱歉我的无知,您所说的多项式是指最佳拟合直线吗? - godzilla
是的,一阶多项式就是最佳拟合直线。请注意,通常最佳拟合可以适用于任何函数。您能否发布一些您可能感兴趣的样本数据? - Greg
当然,我已经更新了问题,请看一下。 - godzilla
好的,像这样拟合数据实际上是相当困难的,但确实存在一些方法。问题在于,您正在将线性方程 y=mx+c 拟合到数据上,但如何将数据分段以达到最佳拟合并不明显。当您绘制它时,很明显哪一部分是哪一部分,但仅从数据上来看,并不是那么清楚。然后您有几个选择,最简单的是明确告诉计算机要拟合哪些区域(然后这就变得相当琐碎)。我有一个用霍夫变换更普遍地完成它的想法,我会尝试在这里得到一些东西,但可能需要一段时间。 - Greg
谢谢 Greg,非常感激! - godzilla
2个回答

21
一个简单的方法是查看'y相对于x的变化率',也称为导数。这通常对连续(平滑)函数效果更好,因此您可以通过使用n阶多项式对数据进行插值来实现它,如上所示。一个简单的实现看起来像这样:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.misc import derivative

x = np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\
              16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
y = np.array([2,5,7,9,10,13,16,18,21,22,21,20,19,18,\
              17,14,10,9,7,5,7,9,10,12,13,15,16,17,22,27])

# Simple interpolation of x and y    
f = interp1d(x, y)
x_fake = np.arange(1.1, 30, 0.1)

# derivative of y with respect to x
df_dx = derivative(f, x_fake, dx=1e-6)

# Plot
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

ax1.errorbar(x, y, fmt="o", color="blue", label='Input data')
ax1.errorbar(x_fake, f(x_fake), label="Interpolated data", lw=2)
ax1.set_xlabel("x")
ax1.set_ylabel("y")

ax2.errorbar(x_fake, df_dx, lw=2)
ax2.errorbar(x_fake, np.array([0 for i in x_fake]), ls="--", lw=2)
ax2.set_xlabel("x")
ax2.set_ylabel("dy/dx")

leg = ax1.legend(loc=2, numpoints=1,scatterpoints=1)
leg.draw_frame(False)

y的导数图

从上升趋势(正梯度)转变为下降趋势(负梯度)时,导数(dy/dx)从正数变成了负数。这个转变发生在dy/dx = 0处,绿色虚线表示。关于Scipy例程,您可以查看:

http://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.derivative.html

http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html

NumPy的diff/gradient也可以使用而且不需要插值,但我展示了以上内容,以便您理解。要获得微积分的完整数学描述,请参阅维基百科。


4
你不知道我从中学到了多少知识,感激你给我的帮助。如果你住在伦敦,我欠你一杯饮料。非常感谢! - godzilla
这是一个很好的答案,我只有一个关于可能改进的问题。是否有一种方法可以始终获得“统一”的数字范围作为输出?例如,无论我们输入多少个和多大的数字,始终获得第二个图表中-1到1之间的值?这样就可以轻松确定趋势,而不管数字如何。当然,它将相对于给定的数字。 - CaptainCsaba

1
我认为这个话题非常重要和有趣。我想扩展上述答案:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.misc import derivative

x = np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\
              16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
y = np.array([2,5,7,9,10,13,16,18,21,22,21,20,19,18,\
              17,14,10,9,7,5,7,9,10,12,13,15,16,17,22,27])


# Simple interpolation of x and y
f = interp1d(x, y, fill_value="extrapolate")
x_fake = np.arange(1.1, 30, 0.1)

# derivative of y with respect to x
df_dx = derivative(f, x_fake, dx=1e-6)

plt.plot(x,y, label = "Data")
plt.plot(x_fake,df_dx,label = "Trend")
plt.legend()
plt.show()

average = np.average(df_dx)
if average > 0 :
    print("Uptrend", average)
elif average < 0:
    print("Downtrend", average)
elif average == 0:
    print("No trend!", average)

print("Max trend measure is:")
print(np.max(df_dx))
print("min trend measure is:")
print(np.min(df_dx))
print("Overall trend measure:")
print(((np.max(df_dx))-np.min(df_dx)-average)/((np.max(df_dx))-np.min(df_dx)))


extermum_list_y = []
extermum_list_x = []

for i in range(0,df_dx.shape[0]):
    if df_dx[i] < 0.001 and df_dx[i] > -0.001:
        extermum_list_x.append(x_fake[i])
        extermum_list_y.append(df_dx[i])

plt.scatter(extermum_list_x, extermum_list_y, label="Extermum", marker = "o", color = "green")
plt.plot(x,y, label = "Data")
plt.plot(x_fake, df_dx, label="Trend")
plt.legend()
plt.show()

因此,总体趋势是上升的!当您想要找到斜率为零的x时,这种方法也很好;例如,曲线中的极值。局部最小值和最大值点可以以最佳精度和计算时间找到。

enter image description here

enter image description here


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