March 28, 2021

Предобработка исторических данных

Покажем, как формировать исторические признаки для моделей машинного обучения. Описанные в статье шаблоны нацелены на многократное использование в реальных задачах.

Предположим, что мы располагаем информацией о доходах людей по дням, имеющей следующий вид (код для генерации представлен в конце статьи):

Нашей задачей является подготовка данных для предсказания доходов в последующем месяце на основании исторических сведений предыдущих месяцев. При этом предсказание на новый период производится 15 числа текущего.

Одним из этапов на пути к построению модели является формирование истории помесячных доходов за предыдущие периоды в качестве параметров.

Для этого потребуется научиться:

  • преобразовывать строковые данные к типу даты и времени;
  • сместить даты на 14 дней назад;
  • сгруппировать данные помесячно.

Сначала загрузим данные и преобразуем числовой столбец дохода (income):

# считываем с параметром dtype='str'
# чтобы избежать ошибок преобразований, о которых писал ранее
df = pd.read_excel('time/data.xlsx', dtype='str')
df['income'] = df['income'].astype('int')

Для преобразования столбца dates к типу даты и времени воспользуемся функцией Pandas to_datetime:

df['dates'] = pd.to_datetime(df['dates']) #format='%d-%m-%Y'

В качестве второго параметра можно задать формат даты и времени, если по умолчанию он распознается с ошибками.

Для смещения даты можно использовать объект Pandas Timedelta:

df['date_sh'] = df['dates'].map(lambda x: x - pd.Timedelta(days=14))
df['date_sh'] = df['date_sh'].map(lambda x: x.to_period('M'))

После смещения преобразовываем дни к типу Period с месячным интервалом, по которому будет удобно группировать (обратите внимание на столбец 'date_sh').

Затем группируем строки по столбцам 'date_sh' и 'names' с подсчетом суммы доходов в столбце 'income'.

df_gr = df.groupby(['date_sh', 'names'], as_index=False)['income'].sum()

Можно выборочно проверить, насколько правильно прошла группировка:

df[df['names']=='D D']
df_gr[df_gr['names']=='D D']

Также может понадобиться заполнить пропуски месяцев для отдельных людей нулями. Для этого сначала получим датафрейм, содержащий декартово произведение всех людей и рассматриваемых месяцев:

names_pers = pd.MultiIndex.from_product([df_gr['names'].unique(),df_gr['date_sh'].unique()]).to_frame().reset_index(drop=True)
names_pers.columns = ['names', 'pers']
names_pers['pers'] = names_pers['pers'].map(lambda x: pd.Period(x, freq='M'))

Теперь объединим таблицы и заполним недостающие значения дохода нулями:

df_nullplus = pd.merge(df_gr, names_pers, left_on=['names','date_sh'], right_on=['names','pers'], how='outer').drop('date_sh', axis=1)
df_nullplus = df_nullplus.rename(columns={'pers':'date_sh'})
df_nullplus['income'].fillna(0,inplace=True)

Как можно заметить, добавились отсутствующие в первоначальной таблице строки с нулевыми значениями.

Таким образом, мы сформировали базовую структуру исторических признаков, с которой удобно формировать признаки (например, выделить доход в предыдущем месяце или два месяца назад).

Код для генерации рассмотренных данных:

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//10
start_d = '2021-01-01'
dates = pd.date_range(start_d, freq='D', 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)
pd.DataFrame({'dates':dates,'names':names,'income':income}).to_excel('time/data.xlsx', index=False)