2 способа формирования исторических данных
Как быстро сконструировать исторические признаки для модели и не допустить ошибку. Рассмотрим два способа.
На самом деле об одном из них я уже рассказывал ранее. Он заключается в объединении таблицы с ее же копией, сдвинутой на заданное количество периодов. Данная логика реализована в функции get_shift_data. Также написан ее более продвинутый аналог get_shift_data_tilln, возвращающий все признаки раньше заданного.
Другим способом является использование метода shift библиотеки Pandas. Рассмотрим игрушечный набор о заработках людей по месяцам (код для генерации представлен в конце статьи):
Рассмотренным ранее способом для получения матрицы с текущим и за три прошлых месяцев доходами можно было получить с помощью следующего кода:
n_m_b = 3 df_hist = general.hist_feat.get_shift_data_tilln(data[['dates', 'names', 'income']],'dates','income', n_m_b)
Отобразим результаты для человека с инициалами "V A":
va_ch = data[data['names']=='V A'].sort_values(by='dates') va1 = df_hist[df_hist['names']=='V A'].sort_values(by='dates')
DataFrame va_ch содержит первоначальные данные, из которых часть в результирующую таблицу не попала, так как не содержит сведения обо всех прошлых периодах:
Для устранения потери данных в df_hist следует дополнить исходную таблицу data пропущенных периодов, как указывалось в статье.
Для получения аналогичных результатов с применением shift следует отсортировать данные, так как функция не проверяет упорядоченность по времени данных, а просто сдвигает заданный столбец (на место отсутствующих полей либо ставит nan, либо заданное значение). При этом есть возможность сдвигать по группам, так как нас интересуют доходы разных людей:
data.sort_values(by='dates', inplace=True)
for i in range(1,n_m_b+1):
data[f'income_{str(i)}'] = data.groupby('names')['income'].shift(i)
va2 = data[data['names']=='V A'].sort_values(by='dates')
va2.dropna(inplace=True)Данные разнятся по сравнению с прошлыми результатами, так как в первом случае проверялась непрерывность значений, а во втором - нет (например, для записи с датой 2011-04 в качестве прошлых месяцев фигурируют месяцы из 2010 года).
Поэтому дополнительно потребуется заполнить пропущенные значения времени (подробнее в статье):
pairs = general.hist_feat.full_period_data(data['dates'], data['names'].unique(), freq = 'M', fmt_str=None)\
.rename(columns={0:'dates',1:'names'})
data = pd.merge(pairs, data, on=['names', 'dates'], how='left')Если теперь опять вызвать код из позапрошлого блока, получим:
Теперь две таблицы дают одинаковый результат. Ниже представлен код для генерации использованного в статье набора данных:
import pandas as pd
import numpy as np
import string
# генерируем игрушечные данные
np.random.seed(0)
letters = np.array(list(string.ascii_uppercase))
data_size = 100
people_num = data_size//50
start_date = '2010-01'
dates = pd.period_range(start_date, freq='M', periods=data_size)
# людям соответствуют по две случайные буквы алфавита (имя, фамилия)
names = np.random.choice(letters, size=(people_num,2))
# склеим буквы имен и фамилий
names = pd.DataFrame(names).apply(lambda x:(x[0]+' '+x[1]), axis=1)
# случайно выберем (с возвращением) людей для дополнения наших данных
# до заданного размера
names = names.sample(n=data_size, replace=True, axis=0)
# доходы людей по дням от 1 до 7 условных единиц
income = np.random.randint(1,8, size=data_size)
data = pd.DataFrame({'dates':dates,'names':names,'income':income})
data = data.sample(frac=1)