Нататнік-шпаргалка для хуткага Data preprocessing

Часта людзі, якія заходзяць у вобласць Data Science, маюць не зусім рэалістычныя ўяўленні аб тым, што іх чакае. Многія думаюць, што цяпер яны будуць крута пісаць нейрасеткі, ствараць галасавога памочніка з Жалезнага Чалавека або абыгрываць усіх на фінансавых рынках.
Але праца Дата Scientist завязана на дадзеных, і адзін з найважнейшых і час затратных момантаў – гэта апрацоўка дадзеных перад тым, як іх падаваць у нейросетку або аналізаваць пэўным спосабам.

У гэтым артыкуле наша каманда апіша тое, як можна лёгка і хутка апрацаваць дадзеныя з пакрокавай інструкцыяй і кодам. Мы стараліся зрабіць так, каб код быў даволі гнуткім і яго можна было прымяняць для розных датасетаў.

Многія прафесіяналы магчыма і не знойдуць нічога экстраардынарнага ў гэтым артыкуле, але пачаткоўцы змогуць запазычыць нешта новае, а таксама кожны, хто даўно марыў зрабіць сабе асобны notebook для хуткай і структураванай апрацоўкі дадзеных можа скапіяваць код і адфарматаваць яго пад сябе, ці спампаваць гатовы notebook з Github.

Атрымалі dataset. Што рабіць далей?

Такім чынам, стандарт: трэба зразумець, з чым маем справу, агульную карціну. Для гэтага выкарыстоўваем pandas, каб проста вызначыць розныя тыпы дадзеных.

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

Дадаць каментар