如何检测 DataFrame 中数据线性变化的连续跨度?
How to detect contiguous spans in which data changes linearly within a DataFrame?
我正在尝试检测相关变量在 DataFrame 中的某些数据内线性变化的连续跨度。数据中可能有许多跨度可以满足这一点。我在 Robust linear model estimation using RANSAC 的基础上使用 ransac
开始了我的方法。但是,我在使用示例数据时遇到问题。
Objective
检测相关变量在数据中线性变化的连续跨度。待检测的跨度由20多个连续的数据点组成。所需的输出将是放置连续跨度的范围日期。
玩具示例
在下面的玩具示例代码中,我生成随机数据,然后设置数据的两个部分以创建线性变化的连续跨度。然后我尝试将线性回归模型拟合到数据中。我使用的其余代码(此处未显示)只是 Robust linear model estimation using 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: ... Reshape your
data either using array.reshape(-1, 1) if your data has a single
feature or array.reshape(1, -1) if it contains a single sample.
我希望能够在此示例中检测到相关变量线性变化的两个连续跨度(line1
和 line2
)。但我无法实施 ransac code example.
中所述的示例
问题
我应该在我的代码中修改什么才能继续?并且,可能有更好的方法来检测相关变量线性变化的连续跨度?
要继续拟合线性回归,您必须执行以下操作:
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(但这只会丢弃那些没有很好对齐的术语,所以如果有一些元素打破了跨度,你仍然会检测到它是一个跨度)。一切都取决于你到底对什么感兴趣。
值错误
要回答有关 ValueError 的问题:您收到错误而示例不是的原因是,当您最初创建形状为 (100,1)
的数组(如示例)时,线性模型适合具有形状 (100,)
的 df.data.tolist()
。这可以通过 X = X.reshape(-1,1)
将 X
重塑为 2D 来解决。下一个错误是 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()
。但是,这只会 return 一个跨度,而您需要检测所有跨度。这意味着您需要重复跨度检测,同时在每次检测后删除跨度,以免再次检测到它们。应重复此操作,直到检测到的跨度中的点数小于 20。
RANSAC 拟合的残差阈值需要非常小,以免拾取范围外的点。如果真实数据中有任何噪音,可以更改 residual_threshold
。然而,这并不总是足够的,并且很可能会发现错误的内点,这将影响记录的跨度范围。
错误的内点
由于 RANSAC 不检查跨度内点是否连续,因此异常值可能会错误地包含在跨度中。为了防止这种情况,标记为跨度内的点如果被异常值包围,则应更改为异常值。最快的方法是将 lr.inlier_mask_
与 [1,1,1]
进行卷积。任何单独的 "inliers" 在卷积后的值为 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')
我正在尝试检测相关变量在 DataFrame 中的某些数据内线性变化的连续跨度。数据中可能有许多跨度可以满足这一点。我在 Robust linear model estimation using RANSAC 的基础上使用 ransac
开始了我的方法。但是,我在使用示例数据时遇到问题。
Objective
检测相关变量在数据中线性变化的连续跨度。待检测的跨度由20多个连续的数据点组成。所需的输出将是放置连续跨度的范围日期。
玩具示例
在下面的玩具示例代码中,我生成随机数据,然后设置数据的两个部分以创建线性变化的连续跨度。然后我尝试将线性回归模型拟合到数据中。我使用的其余代码(此处未显示)只是 Robust linear model estimation using 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: ... Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
我希望能够在此示例中检测到相关变量线性变化的两个连续跨度(line1
和 line2
)。但我无法实施 ransac code example.
问题
我应该在我的代码中修改什么才能继续?并且,可能有更好的方法来检测相关变量线性变化的连续跨度?
要继续拟合线性回归,您必须执行以下操作:
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(但这只会丢弃那些没有很好对齐的术语,所以如果有一些元素打破了跨度,你仍然会检测到它是一个跨度)。一切都取决于你到底对什么感兴趣。
值错误
要回答有关 ValueError 的问题:您收到错误而示例不是的原因是,当您最初创建形状为 (100,1)
的数组(如示例)时,线性模型适合具有形状 (100,)
的 df.data.tolist()
。这可以通过 X = X.reshape(-1,1)
将 X
重塑为 2D 来解决。下一个错误是 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()
。但是,这只会 return 一个跨度,而您需要检测所有跨度。这意味着您需要重复跨度检测,同时在每次检测后删除跨度,以免再次检测到它们。应重复此操作,直到检测到的跨度中的点数小于 20。
RANSAC 拟合的残差阈值需要非常小,以免拾取范围外的点。如果真实数据中有任何噪音,可以更改 residual_threshold
。然而,这并不总是足够的,并且很可能会发现错误的内点,这将影响记录的跨度范围。
错误的内点
由于 RANSAC 不检查跨度内点是否连续,因此异常值可能会错误地包含在跨度中。为了防止这种情况,标记为跨度内的点如果被异常值包围,则应更改为异常值。最快的方法是将 lr.inlier_mask_
与 [1,1,1]
进行卷积。任何单独的 "inliers" 在卷积后的值为 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')