May 3, 2021

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)