ValueError
Чтобы ответить на вопрос о ValueError: причина, по которой вы получаете ошибку, а пример не в том, что вы изначально создаете массив с формой (100,1)
(как в примере)линейная модель соответствует df.data.tolist()
, которая имеет форму (100,)
.Это можно исправить, изменив 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')