数据有两个趋势。如何提取独立的趋势线?


34

我有一组数据,这些数据不是以任何特定的方式排序的,但是在绘制时显然具有两个不同的趋势。由于两个系列之间有明显区别,因此简单的线性回归在此实际上并不足够。是否有一种简单的方法来获取两个独立的线性趋势线?

作为记录,我使用Python,并且对编程和数据分析(包括机器学习)相当满意,但在绝对必要的情况下愿意跳到R。

在此处输入图片说明


6
到目前为止,我最好的答案是将其打印在方格纸上,并使用铅笔,尺子和计算器……
jbbiomed

也许您可以计算成对的斜率并将它们分组为两个“ slope-clusters”。但是,如果您有两个平行趋势,这将失败。
Thomas Jungblut 2012年

1
我没有任何个人经验,但是我认为statsmodels应该值得一试。从统计上讲,线性回归与组间的相互作用就足够了(除非您说您有未分组的数据,在这种情况下,这有点毛茸茸...)
Matt Parker

1
不幸的是,这不是影响数据,而是使用情况数据,并且显然来自两个单独系统的使用情况混合到了同一数据集中。我希望能够描述这两种使用模式,但是我无法返回并重新收集数据,因为这代表了客户收集了大约6年的信息。
jbbiomed 2012年

2
只是为了确保:您的客户没有任何其他数据可以表明哪些度量来自哪个人口?这是您或您的客户拥有或可以找到的数据的100%。同样,2012年看起来您的数据收集崩溃了,或者您的一个或两个系统崩溃了。让我想知道趋势线到那时是否有多大作用。
韦恩2012年

Answers:


30

为了解决您的问题,一个好的方法是定义一个与您的数据集假设相匹配的概率模型。在您的情况下,您可能需要混合使用线性回归模型。通过将不同的数据点与不同的混合成分相关联,可以创建类似于高斯混合模型的“回归混合”模型。

我已包含一些代码来帮助您入门。该代码为两个回归变量的混合实现了EM算法(扩展到较大的混合相对容易)。对于随机数据集,该代码似乎相当健壮。但是,与线性回归不同,混合模型具有非凸面目标,因此对于真实数据集,您可能需要使用不同的随机起点进行一些试验。

import numpy as np
import matplotlib.pyplot as plt 
import scipy.linalg as lin

#generate some random data
N=100
x=np.random.rand(N,2)
x[:,1]=1

w=np.random.rand(2,2)
y=np.zeros(N)

n=int(np.random.rand()*N)
y[:n]=np.dot(x[:n,:],w[0,:])+np.random.normal(size=n)*.01
y[n:]=np.dot(x[n:,:],w[1,:])+np.random.normal(size=N-n)*.01


rx=np.ones( (100,2) )
r=np.arange(0,1,.01)
rx[:,0]=r

#plot the random dataset
plt.plot(x[:,0],y,'.b')
plt.plot(r,np.dot(rx,w[0,:]),':k',linewidth=2)
plt.plot(r,np.dot(rx,w[1,:]),':k',linewidth=2)

# regularization parameter for the regression weights
lam=.01

def em():
    # mixture weights
    rpi=np.zeros( (2) )+.5

    # expected mixture weights for each data point
    pi=np.zeros( (len(x),2) )+.5

    #the regression weights
    w1=np.random.rand(2)
    w2=np.random.rand(2)

    #precision term for the probability of the data under the regression function 
    eta=100

    for _ in xrange(100):
        if 0:
            plt.plot(r,np.dot(rx,w1),'-r',alpha=.5)
            plt.plot(r,np.dot(rx,w2),'-g',alpha=.5)

        #compute lhood for each data point
        err1=y-np.dot(x,w1)
        err2=y-np.dot(x,w2)
        prbs=np.zeros( (len(y),2) )
        prbs[:,0]=-.5*eta*err1**2
        prbs[:,1]=-.5*eta*err2**2

        #compute expected mixture weights
        pi=np.tile(rpi,(len(x),1))*np.exp(prbs)
        pi/=np.tile(np.sum(pi,1),(2,1)).T

        #max with respect to the mixture probabilities
        rpi=np.sum(pi,0)
        rpi/=np.sum(rpi)

        #max with respect to the regression weights
        pi1x=np.tile(pi[:,0],(2,1)).T*x
        xp1=np.dot(pi1x.T,x)+np.eye(2)*lam/eta
        yp1=np.dot(pi1x.T,y)
        w1=lin.solve(xp1,yp1)

        pi2x=np.tile(pi[:,1],(2,1)).T*x
        xp2=np.dot(pi2x.T,x)+np.eye(2)*lam/eta
        yp2=np.dot(pi[:,1]*y,x)
        w2=lin.solve(xp2,yp2)

        #max wrt the precision term
        eta=np.sum(pi)/np.sum(-prbs/eta*pi)

        #objective function - unstable as the pi's become concentrated on a single component
        obj=np.sum(prbs*pi)-np.sum(pi[pi>1e-50]*np.log(pi[pi>1e-50]))+np.sum(pi*np.log(np.tile(rpi,(len(x),1))))+np.log(eta)*np.sum(pi)
        print obj,eta,rpi,w1,w2

        try:
            if np.isnan(obj): break
            if np.abs(obj-oldobj)<1e-2: break
        except:
            pass

        oldobj=obj

    return w1,w2


#run the em algorithm and plot the solution
rw1,rw2=em()
plt.plot(r,np.dot(rx,rw1),'-r')
plt.plot(r,np.dot(rx,rw2),'-g')

plt.show()

25

在此线程的其他地方,user1149913提供了很好的建议(定义概率模型)和代码,以提供功能强大的方法(EM估计)。 有两个问题有待解决:

  1. 如何应对概率模型的偏离(这在2011-2012年的数据中非常明显,而在不太倾斜的点的波动中则很明显)。

  2. 如何为EM算法(或任何其他算法)识别良好的起始值。

要解决#2,请考虑使用霍夫变换。这是一种特征检测算法,可以找到特征的线性延伸,并可以有效地将其计算为Radon变换

XÿXÿ在霍夫变换中。当原始图中的特征沿着一条公共线落下或接近一条公共线时,则它们在霍夫变换中生成的曲线的集合往往会具有与该公共线相对应的公共交点。通过在霍夫变换中找到这些强度最大的点,我们可以读出原始问题的良好解决方案。

为了开始使用这些数据,我首先裁剪了辅助内容(轴,刻度线和标签),并很好地裁剪了右下角明显偏远的点,并沿底轴进行了撒布。(当这些东西没有被裁剪出来时,该过程仍然可以很好地工作,但是它也可以检测轴,框架,刻度线的线性序列,标签的线性序列,甚至偶数地位于底轴上的点!)

img = Import["http://i.stack.imgur.com/SkEm3.png"]
i = ColorNegate[Binarize[img]]
crop2 = ImageCrop[ImageCrop[i, {694, 531}, {Left, Bottom}], {565, 467}, {Right, Top}]

(此代码和其余代码在Mathematica中。)

裁剪图像

该图像中的每个点对应霍夫变换中的狭窄曲线范围,在此处可见。它们是正弦波:

hough2 = Radon[crop2, Method -> "Hough"]  // ImageAdjust

霍夫变换

这从视觉上体现了问题是线聚类问题的意义:霍夫变换将其简化为点聚类问题,我们可以将其应用任何喜欢的聚类方法。

在这种情况下,聚类是如此清晰,以至于霍夫变换的简单后处理就足够了。为了确定变换中强度最大的位置,我增加了对比度,并在大约1%的半径范围内模糊了变换:这与原始图像中绘图点的直径相当。

blur = ImageAdjust[Blur[ImageAdjust[hough2, {1, 0}], 8]]

模糊变换

对结果进行阈值处理会将其范围缩小到两个微小的斑点,这些斑点的质心可以合理地识别出最大强度的点:这些点估计拟合线。

comp = MorphologicalComponents[blur, 0.777]) // Colorize

0.777

阈值二值化变换

图像的左侧对应于0度(水平)的方向,并且当我们从左到右看时,该角度线性增加到180度。内插,我计算出两个斑点分别以19度和57.1度为中心。我们还可以从斑点的垂直位置读取截距。此信息产生初始拟合:

width = ImageDimensions[blur][[1]];
slopes =  Module[{x, y, z}, ComponentMeasurements[comp, "Centroid"] /. 
          Rule[x_, {y_, z_}] :>  Round[((y - 1/2)/(width - 1))  180., 0.1]
  ]

{19.,57.1}

可以类似的方式计算与这些斜率相对应的截距,从而得出以下拟合值:

拟合线

(红线对应于上一张照片中的粉红色小点,蓝线对应于较大的水色斑点。)

这种方法在很大程度上自动解决了第一个问题:线性度偏差会抹去强度最大的点,但通常不会使它们发生太大的移动。坦率地说,外围点将在整个Hough变换中产生低电平噪声,该噪声在后处理过程中将消失。

在这一点上,可以将这些估计值作为EM算法或似然最小化器的起始值(如果给出了良好的估计值,它们将迅速收敛)。不过,更好的方法是使用健壮的回归估算器,例如迭代地加权最小二乘法。它能够为每个点提供回归权重。低权重表示一个点不“属于”一条线。如果需要,可以利用这些权重将每个点分配给其适当的线。然后,在对点进行分类之后,可以分别在两组点上使用普通最小二乘法(或任何其他回归过程)。


1
图片讲出一千个单词,您有5个单词。这是我为回答这个问题而制作的快速图表中不可思议的工作!荣誉!
jbbiomed

2
霍夫变换在计算机视觉领域被广泛使用,以识别图像中的直线。为什么也不能在统计中使用它?;)
卢卡斯·里斯

Xÿ

是。例如,想象一下,比较两个图像以检测它们是否来自同一对象时所涉及的异常值。而且,最重要的是,想象一下必须实时进行。“速度”是计算机视觉中非常重要的因素,而在统计中则不是那么重要。
卢卡斯·里斯

@RoyalTS感谢您指出需要修复其中一个代码段的问题。到我发现您建议的更改时,该更改已被拒绝(正确的是,因为它不太正确,但请不要介意:非常感谢您注意到有错误)。我通过删除对的引用来修复它,该引用rotation最初设置为零,因此没有区别。
ub

15

我发现这个问题与另一个问题有关。我实际上对这种问题进行了学术研究。请检查我的答案“最小平方根”是否合适?具有多个最小值的拟合方法,以获取更多详细信息。

whuber基于Hough变换的方法对于您所给出的简单场景是一个非常好的解决方案。我处理了具有更复杂数据的方案,例如:

数据关联问题-Candy数据集

我和我的合著者将此称为“数据关联”问题。当您尝试解决它时,主要问题通常是组合的,因为可能的数据组合呈指数级增长。

我们有出版物“ 数据关联问题的高斯过程的重叠混合物 ”,我们用迭代技术解决了N曲线的一般问题,给出了很好的结果。您可以在本文中找到链接的Matlab代码。

[更新]可以在GPClust库中找到OMGP技术的Python实现。

我还有另一篇论文,我们放宽了问题以获得凸优化问题,但是尚未被发表。它专用于2条曲线,因此可以完美地处理您的数据。让我知道你是否有兴趣。


1
我很伤心地看到,在两年内没有其他人upvoted这种原始的和有价值的回复。同时,您提到的最后一篇论文是否被接受?
ub

1
几个月前,该论文确实已经被接受。您可以在此处下载它gtas.unican.es/pub/378。这实际上是一个非常罕见的问题(这可能解释了它缺乏流行性),但是我们仍然设法找到了一些有趣的应用程序。如果愿意,请看本文结尾处的实验。
2014年

2

user1149913有一个很好的答案(+1),但在我看来,您的数据收集在2011年末分崩离析,因此您必须先剪掉那部分数据,然后以不同的随机性运行几次起始系数,看看你能得到什么。

一种简单的处理方法是用眼睛将数据分成两组,然后使用您惯用的任何线性模型技术。在R中,它将是lm函数。

或通过眼睛拟合两行。在R中,您将使用abline此功能。

数据杂乱无章,有异常值,并且在最后分散开来,但肉眼有两条相当明显的线,因此我不确定哪种花哨的方法值得。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.