Декартово произведение столбцов с Python
Данный трюк окажет неоценимую помощь в восстановлении "разрывов" в данных. В качестве примера можно привести некую временную статистику, регулярность которой из-за пропусков или ошибок может нарушаться.
Например, вы наблюдаете за ежемесячными значениями некого счетчика и хотите на основании исторических данных фиксированного количества предыдущих периодов предсказывать ожидаемое значение. При этом, если счетчик посылает только ненулевые значения, в данных могут появляться пропуски (например, за январь действий совершено не было, соответственно, их количество равно 0), которые нужно заполнять для построения непрерывной матрицы признаков.
Вопрос дополнения данных рассмотрим на примере игрушечного набора о помесячном доходе людей (код для генерации приведен в конце статьи):
В данной таблице, людей идентифицируют две буквы в столбце names, дата в строчном виде представлена в поле dates, ее значение изменяется в пределах следующих значений:
data['dates'].min(), data['dates'].max()
Мы хотим исключить месяцы, в которые доход людей из таблицы не представлен (как это легко сделать рассказано в статье), а таковых много (для каждого должно быть 100 месяцев):
Необходимо составить матрицу, в которой имеются записи для каждой пары (месяц, человек). Это можно сделать разными способами. Например, создать мультииндексный объект библиотеки Pandas из попарных произведений (метод from_product) и преобразовать его в DataFrame (метод to_frame):
# преобразуем строчное представление месяцев в периоды pers_col = pd.to_datetime(data['dates'], format='%Y%m').map(lambda x: x.to_period('M')) # создадим последовательность периодов в месяц от наименьшего до наибольшего pers_all = pd.period_range(start=pers_col.min(), end=pers_col.max(), freq='M') # из всех пар уникальных имен и периодов сформируем попарные произведения res = pd.MultiIndex.from_product([pers_all, data['names'].unique()]).to_frame().reset_index(drop=True)
Альтернативным путем является использование функции product из модуля itertools:
from itertools import product prod = np.array(list(product(pers_all, data['names'].unique()))) res = pd.DataFrame(prod, columns=['pers', 'names'])
Подведем итог, написав функцию, которая на основании списков значений, включая временной период, составляет полный датафрейм с их попарными произведениями:
def full_period_data(pers_col, *data_cols, freq = 'M', fmt_str=None): # pers_col of strings if fmt_str: pers_col = pd.to_datetime(pers_col, format=fmt_str).map(lambda x: x.to_period(freq)) pers_all = pd.period_range(start=pers_col.min(), end=pers_col.max(), freq=freq) return pd.MultiIndex.from_product([pers_all, *data_cols]).to_frame().reset_index(drop=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_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['dates'] = data['dates'].map(lambda x: x.strftime('%Y%m'))