April 24, 2021

Мусорная реформа данных

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

В качестве примера воспользуемся игрушечным набором (рассмотрен при изучении описательных характеристик таблицы), содержащим информацию о поломках машин, а именно - тип машины, флаг наличия дефекта, дату поломки, дату поставки (начала эксплуатации), разницы между этими датами (в месяцах):

В качестве мусорных нас прежде всего интересуют незаполненные и нулевые значения, пустые строки. Для получения незаполненных значений можно воспользоваться методом isnull, а пустых и нулевых - операциями сравнения. Кроме того, нас могут интересовать индексы "мусорных" строк, которые можно получить, обратившись к свойству index.

На этой базе напишем следующую функцию:

def df_col_rubbish_info(df_col):
    info={}
    info['null_idx'] = df_col[df_col.isnull()].index
    info['null_num'] = df_col[df_col.isnull()].shape[0]
    info['null_pct'] = df_col[df_col.isnull()].shape[0]/df_col.shape[0]
    if df_col.dtype!='object':
        info['zero_idx'] = df_col[df_col == 0].index
        info['zero_num'] = df_col[df_col == 0].shape[0]
        info['zero_pct'] = df_col[df_col == 0].shape[0]/df_col.shape[0]
    else:
        info['empty_idx'] = df_col[df_col == ''].index
        info['empty_num'] = df_col[df_col == ''].shape[0]
        info['empty_pct'] = df_col[df_col == ''].shape[0]/df_col.shape[0]

    return info

Рассмотрим ее применение на примере столбца дата_поломки:

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

def df_rubbish_info(df):
    return get_df_info(df, df_col_rubbish_info)

def get_df_info(df, df_col_f):
    info_d = {}
    for col in df.columns:
        info_d[col] = df_col_f(df[col])
    return info_d

Код для генерации набора представлен ниже:

import pandas as pd
import numpy as np
# генерируем список типов машин
np.random.seed(0)
car_types = ['type1', 'type2', 'type3', 'type4']
car_nums = np.random.randint(25, 100, size=len(car_types))
cars_l = [[car_type]*car_num for car_type, car_num \
          in zip(car_types, car_nums)]    
cars_l = [item for sub_l in cars_l for item in sub_l]
# генерируем в случайном порядке флаги наличия поломок для машин разных типов
defects_num = [np.random.randint(car_num+1) for car_num in car_nums]
defects_ar = np.array([])
for i, car_num in enumerate(car_nums):
    defects_car = np.zeros(car_num)
    idx = np.random.choice(np.arange(car_num),size=defects_num[i], 
                           replace=False)
    defects_car[idx]=1 
    defects_ar = np.concatenate((defects_ar,defects_car))
# формируем датафрейм
data = pd.DataFrame({'типы_машин':cars_l, 'наличие_дефекта':defects_ar})
# задаем даты поломок произвольно из заданного периода
defects_dates = pd.date_range(start='2019-01-01',end='2020-12-31',freq='D')   
idx = np.random.choice(np.arange(len(defects_dates)), size=int(sum(defects_ar)),
                       replace=True)
defects_dates_col=pd.Series(data=defects_dates[idx],
                             index=data[data['наличие_дефекта']==1].index)
data['дата_поломки'] = defects_dates_col
# задаем в случайном порядке даты ввода в эксплуатацию машин
start_dates = pd.date_range(start='2018-06-01', end='2019-01-01', freq='D')
idx = np.random.choice(np.arange(len(start_dates)), size=data.shape[0],
                       replace=True)
data['дата_поставки'] = pd.Series(data=start_dates[idx])
# считаем разницу в месяцах между датами поломок и поставок машин
data['разница'] = data['дата_поломки'] - data['дата_поставки'] 
data['разница'] = data['разница'].map(lambda s: np.ceil(s.days/30))
# перемешиваем данные
data = data.sample(frac=1)