Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

Często osoby wkraczające w dziedzinę Data Science mają mniej niż realistyczne oczekiwania co do tego, co je czeka. Wiele osób myśli, że teraz napiszą fajne sieci neuronowe, stworzą asystenta głosowego z Iron Mana lub pobiją wszystkich na rynkach finansowych.
Ale pracuj Dane Naukowiec kieruje się danymi, a jednym z najważniejszych i najbardziej czasochłonnych aspektów jest ich przetwarzanie przed wprowadzeniem ich do sieci neuronowej lub analizą w określony sposób.

W tym artykule nasz zespół opisze, jak szybko i łatwo przetwarzać dane, korzystając ze szczegółowych instrukcji i kodu. Staraliśmy się, aby kod był dość elastyczny i mógł być używany dla różnych zestawów danych.

Wielu profesjonalistów może nie znajdzie w tym artykule nic nadzwyczajnego, ale początkujący będą mogli nauczyć się czegoś nowego, a każdy, kto od dawna marzył o zrobieniu osobnego notatnika do szybkiego i uporządkowanego przetwarzania danych, może skopiować kod i sformatować go dla siebie, lub pobierz gotowy notatnik z Githuba.

Otrzymaliśmy zbiór danych. Co zrobic nastepnie?

Czyli standard: trzeba zrozumieć z czym mamy do czynienia, jaki jest ogólny obraz. Aby to zrobić, używamy pand do prostego definiowania różnych typów danych.

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

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

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

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

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

Spójrzmy na wartości kolumn:

  1. Czy liczba wierszy w każdej kolumnie odpowiada całkowitej liczbie wierszy?
  2. Jaka jest istota danych w każdej kolumnie?
  3. Na którą kolumnę chcemy kierować, aby dokonać dla niej prognoz?

Odpowiedzi na te pytania pozwolą Ci przeanalizować zbiór danych i z grubsza nakreślić plan kolejnych działań.

Ponadto, aby głębiej przyjrzeć się wartościom w każdej kolumnie, możemy skorzystać z funkcji pandy opisz(). Wadą tej funkcji jest jednak to, że nie dostarcza informacji o kolumnach zawierających wartości łańcuchowe. Zajmiemy się nimi później.

df.describe()

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

Magiczna wizualizacja

Spójrzmy, gdzie nie mamy żadnych wartości:

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

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

To było krótkie spojrzenie z góry, teraz przejdziemy do ciekawszych rzeczy

Spróbujmy znaleźć i jeśli to możliwe usunąć kolumny, które mają tylko jedną wartość we wszystkich wierszach (nie będą one miały żadnego wpływu na wynik):

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

Teraz chronimy siebie i powodzenie naszego projektu przed zduplikowanymi liniami (liniami zawierającymi te same informacje w tej samej kolejności, co jedna z istniejących linii):

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

Zbiór danych dzielimy na dwa: jeden z wartościami jakościowymi, drugi z wartościami ilościowymi

W tym miejscu musimy dokonać małego wyjaśnienia: jeśli linie z brakującymi danymi w danych jakościowych i ilościowych nie są ze sobą bardzo skorelowane, to będziemy musieli zdecydować, co poświęcimy - wszystkie linie z brakującymi danymi, tylko część z nich, lub niektóre kolumny. Jeśli linie są skorelowane, mamy pełne prawo podzielić zbiór danych na dwie części. W przeciwnym razie najpierw będziesz musiał zająć się liniami, które nie korelują brakujących danych pod względem jakościowym i ilościowym, a dopiero potem podzielić zbiór danych na dwie części.

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

Robimy to, aby ułatwić nam przetwarzanie tych dwóch różnych typów danych – później zrozumiemy, o ile ułatwia nam to życie.

Pracujemy z danymi ilościowymi

Pierwszą rzeczą, którą powinniśmy zrobić, to ustalić, czy w danych ilościowych występują „kolumny szpiegowskie”. Nazywamy te kolumny tak, ponieważ przedstawiają się jako dane ilościowe, ale działają jako dane jakościowe.

Jak je definiujemy? Oczywiście wszystko zależy od charakteru analizowanych danych, ale generalnie takie kolumny mogą zawierać niewiele unikalnych danych (w granicach 3-10 unikalnych wartości).

print(df_numerical.nunique())

Po zidentyfikowaniu kolumn szpiegowskich przeniesiemy je z danych ilościowych do danych jakościowych:

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']) #добавляем третью колонку-шпион в качественные данные

Wreszcie całkowicie oddzieliliśmy dane ilościowe od jakościowych i teraz możemy z nimi właściwie pracować. Pierwszą rzeczą jest zrozumienie, gdzie mamy puste wartości (NaN, a w niektórych przypadkach 0 zostanie przyjęte jako wartości puste).

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

W tym miejscu ważne jest, aby zrozumieć, w których kolumnach zera mogą wskazywać brakujące wartości: czy wynika to ze sposobu gromadzenia danych? A może ma to związek z wartościami danych? Na te pytania należy odpowiedzieć indywidualnie dla każdego przypadku.

Jeśli więc nadal uznamy, że może brakować nam danych tam, gdzie są zera, powinniśmy zastąpić te zera NaN, aby ułatwić późniejszą pracę z tymi utraconymi danymi:

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

Zobaczmy teraz, gdzie brakuje nam danych:

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

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

Tutaj te wartości w kolumnach, których brakuje, należy zaznaczyć na żółto. I teraz zaczyna się zabawa – jak sobie z tymi wartościami poradzić? Czy powinienem usunąć wiersze z tymi wartościami czy kolumnami? Lub wypełnić te puste wartości innymi?

Oto przybliżony diagram, który może pomóc Ci zdecydować, co w zasadzie można zrobić z pustymi wartościami:

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

0. Usuń niepotrzebne kolumny

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

1. Czy liczba pustych wartości w tej kolumnie jest większa niż 50%?

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

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

2. Usuń linie z pustymi wartościami

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

3.1. Wstawianie losowej wartości

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

3.2. Wstawianie wartości stałej

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. Wstaw średnią lub najczęstszą wartość

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. Wstaw wartość obliczoną przez inny model

Czasami wartości można obliczyć za pomocą modeli regresji wykorzystując modele z biblioteki sklearn lub innych podobnych bibliotek. O tym, jak można tego dokonać, nasz zespół poświęci w najbliższym czasie osobny artykuł.

Zatem na razie narracja o danych ilościowych zostanie przerwana, ponieważ istnieje wiele innych niuansów dotyczących lepszego przygotowania i wstępnego przetwarzania danych do różnych zadań, a w tym artykule uwzględniono podstawowe kwestie dotyczące danych ilościowych, a teraz czas wrócić do danych jakościowych, które oddzieliliśmy kilka kroków od ilościowych. Możesz zmieniać ten notatnik według własnego uznania, dostosowując go do różnych zadań, dzięki czemu wstępne przetwarzanie danych przebiega bardzo szybko!

Dane jakościowe

Zasadniczo w przypadku danych jakościowych stosuje się metodę One-hot-encoding w celu sformatowania ich z ciągu znaków (lub obiektu) na liczbę. Zanim przejdziemy do tego punktu, skorzystajmy z powyższego diagramu i kodu, aby poradzić sobie z pustymi wartościami.

df_categorical.nunique()

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

Notatnik-ściągawka do szybkiego wstępnego przetwarzania danych

0. Usuń niepotrzebne kolumny

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

1. Czy liczba pustych wartości w tej kolumnie jest większa niż 50%?

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

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

2. Usuń linie z pustymi wartościami

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

3.1. Wstawianie losowej wartości

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

3.2. Wstawianie wartości stałej

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)

W końcu poradziliśmy sobie z wartościami null w danych jakościowych. Teraz czas na wykonanie one-hot-kodowania na wartościach znajdujących się w Twojej bazie danych. Ta metoda jest bardzo często stosowana, aby zapewnić algorytmowi możliwość uczenia się na podstawie danych wysokiej jakości.

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))

Zatem w końcu zakończyliśmy przetwarzanie odrębnych danych jakościowych i ilościowych – czas je ponownie połączyć

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

Po połączeniu zbiorów danych w jeden możemy w końcu zastosować transformację danych za pomocą MinMaxScaler z biblioteki sklearn. Dzięki temu nasze wartości będą wynosić od 0 do 1, co będzie pomocne podczas uczenia modelu w przyszłości.

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

Te dane są teraz gotowe na wszystko - sieci neuronowe, standardowe algorytmy ML itp.!

W tym artykule nie uwzględniliśmy pracy z danymi szeregów czasowych, ponieważ w przypadku takich danych należy zastosować nieco inne techniki przetwarzania, w zależności od zadania. W przyszłości nasz zespół poświęci temu tematowi osobny artykuł i mamy nadzieję, że będzie on mógł wnieść w Twoje życie coś ciekawego, nowego i przydatnego, tak jak ten.

Źródło: www.habr.com

Dodaj komentarz