6

我正在尝试检测相关变量在 DataFrame 中的某些数据内线性变化的连续跨度。满足这一点的数据中可能有许多跨度。我开始使用ransac基于使用 RANSAC 的稳健线性模型估计的方法。但是,我在将示例用于我的数据时遇到了问题。

客观的

检测相关变量在数据中线性变化的连续跨度。要检测的跨度由20多个连续的数据点组成。所需的输出将是放置连续跨度的范围日期。

玩具示例

在下面的玩具示例代码中,我生成随机数据,然后设置数据的两个部分以创建线性变化的连续跨度。然后我尝试将线性回归模型拟合到数据中。我使用的其余代码(此处未显示)只是使用 RANSAC页面的稳健线性模型估计中的其余代码。但是我知道我需要更改剩余的代码才能达到目标。

import pandas as pd
import matplotlib.pyplot as plt
from sklearn import linear_model, datasets
import numpy as np

## 1. Generate random data for toy sample
times = pd.date_range('2016-08-10', periods=100, freq='15min')
df = pd.DataFrame(np.random.randint(0,100,size=(100, 1)), index=times, columns=["data"])

## 2. Set line1 within random data
date_range1_start = "2016-08-10 08:15"
date_range1_end = "2016-08-10 15:00"
line1 = df.data[date_range1_start:date_range1_end]
value_start1 = 10
values1 = range(value_start1,value_start1+len(line1))
df.data[date_range1_start:date_range1_end] = values1

## 3. Set line2 within random data
date_range2_start = "2016-08-10 17:00"
date_range2_end = "2016-08-10 22:30"
value_start2 = 90
line2 = df.data[date_range2_start:date_range2_end]
values2 = range(value_start2,value_start2-len(line2),-1)
df.data[date_range2_start:date_range2_end] = values2

## 4. Plot data
df.plot()
plt.show()

## 5. Create arrays
X = np.asarray(df.index)
y = np.asarray(df.data.tolist())

## 6. Fit line using all data
lr = linear_model.LinearRegression()
lr.fit(X, y)

对于这个玩具示例代码,所需的输出(我还不能编码)将是这样的 DataFrame:

>>> out
              start               end
0  2016-08-10 08:15  2016-08-10 15:00
1  2016-08-10 17:00  2016-08-10 22:30

生成的图表如下所示: 生成的数据

错误代码

但是,当执行第 6 步时,出现以下错误:

ValueError: Expected 2D array, got 1D array instead: ... 重塑您的数据,如果您的数据具有单个特征,则使用 array.reshape(-1, 1) 或 array.reshape(1, -1) 如果它包含单个特征样本。

我希望能够在这个例子中检测到相关变量线性变化的两个连续跨度(line1line2)。但我无法实现ransac 代码示例中所述的示例

问题

我应该在我的代码中修改什么才能继续?而且,是否有更好的方法来检测相关变量线性变化的连续跨度

4

2 回答 2

5

值错误

要回答有关 ValueError 的问题:您收到错误而示例没有出现的原因是,虽然您最初创建了一个具有 shape 的数组(100,1)(如示例),但线性模型适合df.data.tolist()具有 shape (100,)。这可以通过将 2D 重塑X为 2D 来解决X = X.reshape(-1,1)。下一个错误将是这些X值不能采用datetime64格式。然后可以通过将时间转换为秒来解决此问题。例如,要使用的标准时期是1970-01-01T00:00Z,然后所有数据点都是自该日期和时间以来的秒数。这种转换可以通过以下方式完成:

X = (X - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's')

这是显示下图中线性拟合的完整代码:

import pandas as pd
import matplotlib.pyplot as plt
from sklearn import linear_model, datasets
import numpy as np

## 1. Generate random data for toy sample
times = pd.date_range('2016-08-10', periods=100, freq='15min')
df = pd.DataFrame(np.random.randint(0,100,size=(100, 1)), index=times, columns=["data"])

## 2. Set line1 within random data
date_range1_start = "2016-08-10 08:15"
date_range1_end = "2016-08-10 15:00"
line1 = df.data[date_range1_start:date_range1_end]
value_start1 = 10
values1 = range(value_start1,value_start1+len(line1))
df.data[date_range1_start:date_range1_end] = values1

## 3. Set line2 within random data
date_range2_start = "2016-08-10 17:00"
date_range2_end = "2016-08-10 22:30"
value_start2 = 90
line2 = df.data[date_range2_start:date_range2_end]
values2 = range(value_start2,value_start2-len(line2),-1)
df.data[date_range2_start:date_range2_end] = values2


## 4. Create arrays
X = np.asarray(df.index)
X = ( X - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's')
X = X.reshape(-1,1)
y = np.asarray(df.data.tolist())

## 5. Fit line using all data
lr = linear_model.LinearRegression()
lr.fit(X, y)

## 6. Predict values
z = lr.predict(X)
df['linear fit'] = z

## 7. Plot
df.plot()
plt.show()

在此处输入图像描述

检测连续跨度

如您所述,要检测线性数据的跨度,RANSAC 是一种很好的使用方法。为此,线性模型将更改为lr = linear_model.RANSACRegressor()。但是,这只会返回一个跨度,而您需要检测所有跨度。这意味着您需要重复跨度检测,同时在每次检测后删除跨度,以免再次检测到它们。应重复此操作,直到检测到的跨度中的点数少于 20。

RANSAC 拟合的残差阈值需要非常小,以免拾取跨度之外的点。如果实际数据中有任何噪声,residual_threshold则可以更改。然而,这并不总是足够的,并且可能会发现错误的内点,这将影响记录的跨度范围。

虚假内点

由于 RANSAC 不检查跨度内点是否连续,因此异常值可能被错误地包含在跨度中。为了防止这种情况,如果标记为跨度内的点被异常值包围,则应将其更改为异常值。最快的方法是与 进行lr.inlier_mask_卷积[1,1,1]。任何单独的“内点”在卷积之后的值为 1(因此是真正的异常值),而作为跨度运行一部分的点将为 2 或 3。因此,以下将修复错误的内点:

lr.inlier_mask_ = np.convolve(lr.inlier_mask_.astype(int), [1,1,1], mode='same') > 1

代码

import pandas as pd
import matplotlib.pyplot as plt
from sklearn import linear_model, datasets
import numpy as np

## 1. Generate random data for toy sample
times = pd.date_range('2016-08-10', periods=100, freq='15min')
df = pd.DataFrame(np.random.randint(0,100,size=(100, 1)), index=times, columns=["data"])

## 2. Set line1 within random data
date_range1_start = "2016-08-10 08:15"
date_range1_end = "2016-08-10 15:00"
line1 = df.data[date_range1_start:date_range1_end]
value_start1 = 10
values1 = range(value_start1,value_start1+len(line1))
df.data[date_range1_start:date_range1_end] = values1

## 3. Set line2 within random data
date_range2_start = "2016-08-10 17:00"
date_range2_end = "2016-08-10 22:30"
value_start2 = 90
line2 = df.data[date_range2_start:date_range2_end]
values2 = range(value_start2,value_start2-len(line2),-1)
df.data[date_range2_start:date_range2_end] = values2

## 4. Create arrays
X = np.asarray(df.index)
X = ( X - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's')
X = X.reshape(-1,1)
y = np.asarray(df.data.tolist())

## 5. Fit line using all data
lr = linear_model.RANSACRegressor(residual_threshold=0.001)
lr.fit(X, y)

# Placeholders for start/end times
start_times = []
end_times = []

# Repeat fit and check if number of span inliers is greater than 20
while np.sum(lr.inlier_mask_) > 20:

    # Remove false inliers
    lr.inlier_mask_ = np.convolve(lr.inlier_mask_.astype(int), [1,1,1], mode='same') > 1

    # Store start/end times
    in_span = np.squeeze(np.where(lr.inlier_mask_))
    start_times.append(str(times[in_span[0]]))
    end_times.append(str(times[in_span[-1]]))

    # Get outlier and check for another span
    outliers = np.logical_not(lr.inlier_mask_)
    X = X[outliers]
    y = y[outliers]
    times = times[outliers]

    # Fit to remaining points
    lr.fit(X, y)

out = pd.DataFrame({'start':start_times, 'end':end_times}, columns=['start','end'])
out.sort_values('start')

这是out数据框:

在此处输入图像描述

您还可以绘制跨度以进行验证。

plt.plot(df['data'],c='b')

for idx,row in out.iterrows():
    x0 = np.datetime64(row['start'])
    y0 = df.loc[x0]['data']
    x1 = np.datetime64(row['end'])
    y1 = df.loc[x1]['data']
    plt.plot([x0,x1],[y0,y1],c='r')

在此处输入图像描述

于 2018-11-26T21:20:45.790 回答
2

要继续并拟合您的线性回归,您必须执行以下操作:

lr.fit(X.reshape(-1,1), y)

这是因为sklearn正在等待一个二维值数组,每一行都是一行特征。

因此,在此之后,您是否希望为许多不同范围拟合模型并查看是否找到线性变化的跨度?

如果您正在寻找精确的线性范围(例如,可以在整数的情况下检测到,但对于浮点数则不行),那么我会做类似的事情:

dff = df.diff()
dff['block'] = (dff.data.shift(1) != dff.data).astype(int).cumsum()
out = pd.DataFrame(list(dff.reset_index().groupby('block')['index'].apply(lambda x: \
    [x.min(), x.max()] if len(x) > 20 else None).dropna()))

输出将是:

>>> out
                    0                   1
0 2016-08-10 08:30:00 2016-08-10 15:00:00
1 2016-08-10 17:15:00 2016-08-10 22:30:00

如果您尝试做类似的事情,但对于浮点数据,我会使用diff相同的方式做一些事情,然后指定某种可接受的错误或类似的。请让我知道这是否是您想要实现的目标。或者在这里你也可以在不同的范围内使用 RANSAC(但这只会丢弃没有很好对齐的术语,所以如果有一些元素破坏了跨度,你仍然会检测到它是一个跨度)。一切都取决于你到底对什么感兴趣。

于 2018-11-26T20:46:45.283 回答