Как определить непрерывные промежутки, в которых данные изменяются линейно в пределах DataFrame? - PullRequest
0 голосов
/ 22 ноября 2018

Я пытаюсь обнаружить непрерывные промежутки, в которых соответствующая переменная изменяется линейно в определенных данных в 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

Сгенерированный график выглядит так: Data generated

Код ошибки

Однако, когда шаг 6 выполнен, я получаю нижеошибка:

ValueError: Ожидаемый двумерный массив, вместо него получен одномерный массив: ... Измените ваши данные либо с помощью array.reshape (-1, 1), если ваши данные имеют один объект или array.reshape(1, -1), если он содержит одну выборку.

Я хотел бы иметь возможность обнаруживать в этом примере оба смежных диапазона, в которых соответствующая переменная изменяется линейно (line1 и line2).Но я не могу реализовать пример, указанный в примере кода ransac .

Вопрос

Что мне следует изменить в моем кодебыть в состоянии продолжить?И, , возможно, существует лучший подход для обнаружения смежных диапазонов, в которых соответствующая переменная изменяется линейно ?

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

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()

enter image description here

Обнаружение непрерывных диапазонов

Для обнаружения диапазонов линейных данных, как вы заявили, 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 фрейм данных:

enter image description here

Вы также можете построить участки для проверки.

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')

enter image description here

0 голосов
/ 26 ноября 2018

Чтобы просто продолжить и согласовать свою линейную регрессию, вам нужно будет сделать следующее:

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 на разных диапазонах (но это просто отбросит термины, которые не выровнены должным образом, поэтому, если какой-то элемент нарушает интервал, вы все равно обнаружите его как интервал).Все зависит от того, что именно вас интересует.

...