Блокнот-шпаргалка для швидкого Data preprocessing

Часто люди, що заходять в область Data Science, мають не зовсім реалістичні уявлення про те, що на них чекає. Багато хто думає, що зараз вони будуть круто писати нейромережі, створювати голосового помічника із Залізної Людини або обігравати всіх на фінансових ринках.
Але робота дані Scientist пов'язана з даними, і з найважливіших і час витратних моментів — це обробка даних перед тим, як їх подавати в нейромережу чи аналізувати певним способом.

У цій статті наша команда опише те, як можна легко та швидко обробити дані з покроковою інструкцією та кодом. Ми намагалися зробити так, щоб код був досить гнучким і його можна було використовувати для різних датасетів.

Багато професіоналів можливо і не знайдуть нічого екстраординарного в цій статті, але початківці зможуть підкреслити щось нове, а також кожен, хто давно мріяв зробити собі окремий notebook для швидкої та структурованої обробки даних, може скопіювати код і відформатувати його під себе, або скачати готовий notebook з Github.

Отримали dataset. Що робити далі?

Отже, стандарт: треба зрозуміти, із чим маємо справу, загальну картину. Для цього використовуємо панда, щоб просто визначити різні типи даних.

import pandas as pd #импортируем pandas
import numpy as np  #импортируем numpy
df = pd.read_csv("AB_NYC_2019.csv") #читаем датасет и записываем в переменную df

df.head(3) #смотрим на первые 3 строчки, чтобы понять, как выглядят значения

Блокнот-шпаргалка для швидкого Data preprocessing

df.info() #Демонстрируем информацию о колонках

Блокнот-шпаргалка для швидкого Data preprocessing

Дивимося на значення колонок:

  1. Чи відповідає кількість рядків кожної колонки загальній кількості рядків?
  2. Яка суть даних у кожній колонці?
  3. Яку стовпчик ми хочемо зробити target, щоб робити передбачення для неї?

Відповіді на ці питання дозволять проаналізувати датасет і приблизно намалювати план найближчих дій.

Також для більш глибокого погляду на значення в кожній колонці можемо скористатися функцією pandas describe(). Щоправда, недолік цієї функції у цьому, що вона дає інформацію про колонки зі значеннями string. З ними ми розберемося пізніше.

df.describe()

Блокнот-шпаргалка для швидкого Data preprocessing

Чарівна візуалізація

Посоромимо на те, де в нас відсутні значення взагалі:

import seaborn as sns
sns.heatmap(df.isnull(),yticklabels=False,cbar=False,cmap='viridis')

Блокнот-шпаргалка для швидкого Data preprocessing

Це був невеликий погляд зверху, зараз ми приступимо до цікавіших речей

Спробуємо знайти і по можливості видалити колонки, в яких є лише одне значення у всіх рядках (вони на результат не впливатимуть):

df = df[[c for c
        in list(df)
        if len(df[c].unique()) > 1]] #Перезаписываем датасет, оставляя только те колонки, в которых больше одного уникального значения

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

df.drop_duplicates(inplace=True) #Делаем это, если считаем нужным.
                                 #В некоторых проектах удалять такие данные с самого начала не стоит.

Поділяємо датасет на два: один із якісними значеннями, а інший – з кількісними

Тут потрібно зробити невелике уточнення: якщо рядки з пропущеними даними у якісних і кількісних даних сильно не співвідносяться між собою, то потрібно буде ухвалити рішення, чим ми жертвуємо – усіма рядками з пропущеними даними, лише їхньою частиною чи певними колонками. Якщо ж рядки співвідносяться, ми маємо повне право розділити датасет на два. В іншому випадку потрібно буде спочатку розібратися з рядками, в яких не співвідносяться пропущені дані в якісних та кількісних, і лише потім розділяти датасет на два.

df_numerical = df.select_dtypes(include = [np.number])
df_categorical = df.select_dtypes(exclude = [np.number])

Ми робимо це, щоб нам було легше обробляти ці два різні типи даних — згодом ми зрозуміємо, наскільки це спрощує життя.

Працюємо з кількісними даними

Перше, що нам варто зробити – визначити, чи немає «колонок – шпигунів» у кількісних даних. Ми називаємо ці колонки так, тому що вони видають себе за кількісні дані, а самі працюють як якісні.

Як нам їх визначити? Звичайно, все залежить від природи даних, які Ви аналізуєте, але переважно такі колонки можуть мати мало унікальних даних (в районі 3-10 унікальних значень).

print(df_numerical.nunique())

Після того, як ми визначимося з колонками-шпигунами, ми перемістимо їх з кількісних даних у якісні:

spy_columns = df_numerical[['колонка1', 'колока2', 'колонка3']]#выделяем колонки-шпионы и записываем в отдельную dataframe
df_numerical.drop(labels=['колонка1', 'колока2', 'колонка3'], axis=1, inplace = True)#вырезаем эти колонки из количественных данных
df_categorical.insert(1, 'колонка1', spy_columns['колонка1']) #добавляем первую колонку-шпион в качественные данные
df_categorical.insert(1, 'колонка2', spy_columns['колонка2']) #добавляем вторую колонку-шпион в качественные данные
df_categorical.insert(1, 'колонка3', spy_columns['колонка3']) #добавляем третью колонку-шпион в качественные данные

Нарешті ми повністю відокремили кількісні дані від якісних і тепер можна з ними добре попрацювати. Перше слід зрозуміти, де у нас є порожні значення (NaN, а в деяких випадках і 0 будуть прийматися як порожні значення).

for i in df_numerical.columns:
    print(i, df[i][df[i]==0].count())

На цьому етапі важливо зрозуміти, в яких колонках нулі можуть означати відсутні значення: чи це пов'язано з тим, як збиралися дані? Чи це може бути пов'язане із значеннями даних? На ці питання потрібно відповідати у кожному окремому випадку.

Отже, якщо ми все ж таки вирішили, що дані у нас можуть бути відсутніми там, де є нулі, слід замінити нулі на NaN, щоб було легше потім працювати з цими втраченими даними:

df_numerical[["колонка 1", "колонка 2"]] = df_numerical[["колонка 1", "колонка 2"]].replace(0, nan)

Тепер подивимося, де у нас пропущені дані:

sns.heatmap(df_numerical.isnull(),yticklabels=False,cbar=False,cmap='viridis') # Можно также воспользоваться df_numerical.info()

Блокнот-шпаргалка для швидкого Data preprocessing

Тут мають бути відзначені жовтим кольором ті значення всередині стовпців, які відсутні. І найцікавіше починається тепер — як поводитися з цими значеннями? Видалити рядки з цими значеннями чи стовпці? Чи заповнити ці порожні значення якимись іншими?

Ось приблизна схема, яка може допомогти визначитися з тим, що можна в принципі зробити з порожніми значеннями:

Блокнот-шпаргалка для швидкого Data preprocessing

0. Видаляємо непотрібні колонки

df_numerical.drop(labels=["колонка1","колонка2"], axis=1, inplace=True)

1. Кількість порожніх значень у цій колонці більша за 50%?

print(df_numerical.isnull().sum() / df_numerical.shape[0] * 100)

df_numerical.drop(labels=["колонка1","колонка2"], axis=1, inplace=True)#Удаляем, если какая-то колонка имеет больше 50 пустых значений

2. Видаляємо рядки з порожніми значеннями

df_numerical.dropna(inplace=True)#Удаляем строчки с пустыми значениями, если потом останется достаточно данных для обучения

3.1. Вставляємо випадкове значення

import random #импортируем random
df_numerical["колонка"].fillna(lambda x: random.choice(df[df[column] != np.nan]["колонка"]), inplace=True) #вставляем рандомные значения в пустые клетки таблицы

3.2. Вставляємо константне значення

from sklearn.impute import SimpleImputer #импортируем SimpleImputer, который поможет вставить значения
imputer = SimpleImputer(strategy='constant', fill_value="<Ваше значение здесь>") #вставляем определенное значение с помощью SimpleImputer
df_numerical[["новая_колонка1",'новая_колонка2','новая_колонка3']] = imputer.fit_transform(df_numerical[['колонка1', 'колонка2', 'колонка3']]) #Применяем это для нашей таблицы
df_numerical.drop(labels = ["колонка1","колонка2","колонка3"], axis = 1, inplace = True) #Убираем колонки со старыми значениями

3.3. Вставляємо середнє чи макісмально часте значення

from sklearn.impute import SimpleImputer #импортируем SimpleImputer, который поможет вставить значения
imputer = SimpleImputer(strategy='mean', missing_values = np.nan) #вместо mean можно также использовать most_frequent
df_numerical[["новая_колонка1",'новая_колонка2','новая_колонка3']] = imputer.fit_transform(df_numerical[['колонка1', 'колонка2', 'колонка3']]) #Применяем это для нашей таблицы
df_numerical.drop(labels = ["колонка1","колонка2","колонка3"], axis = 1, inplace = True) #Убираем колонки со старыми значениями

3.4. Вставляємо значення, обчислене іншою моделлю

Часом значення можна обчислити за допомогою регресійних моделей, використовуючи моделі з бібліотеки sklearn або інших схожих бібліотек. Наша команда присвятить окрему статтю щодо того, як це можна зробити в найближчому майбутньому.

Отже, поки розповідь про кількісні дані перерветься, тому що є безліч інших нюансів про те, як краще робити data preparation і preprocessing для різних завдань, і базові речі для кількісних даних були враховані в цій статті, і зараз саме час повернутися до якісних даних, які ми відокремили кілька кроків тому від кількісних. Ви можете змінювати цей notebook так, як Вам завгодно, підлаштовуючи його під різні завдання, щоб data preprocessing проходив дуже швидко!

Якісні дані

В основному для якісних даних використовується метод One-hot-encoding, щоб відформатувати їх з string (або object) до числа. Перед тим як перейти до цього пункту, скористаємося схемою та кодом зверху, щоб розібратися з порожніми значеннями.

df_categorical.nunique()

sns.heatmap(df_categorical.isnull(),yticklabels=False,cbar=False,cmap='viridis')

Блокнот-шпаргалка для швидкого Data preprocessing

0. Видаляємо непотрібні колонки

df_categorical.drop(labels=["колонка1","колонка2"], axis=1, inplace=True)

1. Кількість порожніх значень у цій колонці більша за 50%?

print(df_categorical.isnull().sum() / df_numerical.shape[0] * 100)

df_categorical.drop(labels=["колонка1","колонка2"], axis=1, inplace=True) #Удаляем, если какая-то колонка 
                                                                          #имеет больше 50% пустых значений

2. Видаляємо рядки з порожніми значеннями

df_categorical.dropna(inplace=True)#Удаляем строчки с пустыми значениями, 
                                   #если потом останется достаточно данных для обучения

3.1. Вставляємо випадкове значення

import random
df_categorical["колонка"].fillna(lambda x: random.choice(df[df[column] != np.nan]["колонка"]), inplace=True)

3.2. Вставляємо константне значення

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='constant', fill_value="<Ваше значение здесь>")
df_categorical[["новая_колонка1",'новая_колонка2','новая_колонка3']] = imputer.fit_transform(df_categorical[['колонка1', 'колонка2', 'колонка3']])
df_categorical.drop(labels = ["колонка1","колонка2","колонка3"], axis = 1, inplace = True)

Отже, нарешті ми розібралися з порожніми значеннями якісних даних. Тепер час зробити one-hot-encoding для значень, які є у вашій базі даних. Цей метод дуже часто використовується для того, щоб ваш алгоритм міг навчатися з урахуванням якісних даних.

def encode_and_bind(original_dataframe, feature_to_encode):
    dummies = pd.get_dummies(original_dataframe[[feature_to_encode]])
    res = pd.concat([original_dataframe, dummies], axis=1)
    res = res.drop([feature_to_encode], axis=1)
    return(res)

features_to_encode = ["колонка1","колонка2","колонка3"]
for feature in features_to_encode:
    df_categorical = encode_and_bind(df_categorical, feature))

Отже, нарешті ми перестали обробляти окремо якісні та кількісні дані – час поєднувати їх назад.

new_df = pd.concat([df_numerical,df_categorical], axis=1)

Після того, як ми з'єднали разом датасети в один, під кінець ми можемо використовувати трансформацію даних за допомогою MinMaxScaler із бібліотеки sklearn. Це зробить наші значення від 0 до 1, що допоможе при навчанні моделі в майбутньому.

from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler()
new_df = min_max_scaler.fit_transform(new_df)

Ці дані тепер готові до всього — до нейронних мереж, стандартних ML алгоритмів тощо!

У цій статті ми не врахували роботу з даними, що належать до тимчасових рядів, тому що для таких даних слід використовувати трохи інші техніки їх обробки, залежно від Вашого завдання. У майбутньому наша команда присвятить цій темі окрему статтю, і ми сподіваємося, що вона зможе принести у Ваше життя щось цікаве, нове та корисне, як і це.

Джерело: habr.com

Додати коментар або відгук