Notatnik-podpowiadacz do szybkiego przetwarzania danych

Często osoby wchodzące w obszar Data Science mają nieco nierealistyczne wyobrażenia o tym, co je czeka. Wiele osób myśli, że teraz będą tworzyć niesamowite sieci neuronowe, stworzą asystenta głosowego rodem z Iron Mana lub pokonają wszystkich na rynkach finansowych.
Jednak praca Dane Scientisty związana jest z danymi, a jednym z najważniejszych i czasochłonnych momentów jest przetwarzanie danych przed ich podaniem do sieci neuronowej lub analizowaniem w określony sposób.

W tym artykule nasz zespół opisze, jak łatwo i szybko przetworzyć dane z krok po kroku instrukcją i kodem. Staraliśmy się zrobić kod na tyle elastycznym, aby można go było zastosować do różnych zbiorów danych.

Wielu profesjonalistów może nie znaleźć nic nadzwyczajnego w tym artykule, ale początkujący mogą wyciągnąć coś nowego, a także każdy, kto od dawna marzył o stworzeniu własnego notatnika do szybkiego i uproszczonego przetwarzania danych, może skopiować kod i dostosować go do swoich potrzeb, lub pobrać gotowy notatnik z Github.

Otrzymaliśmy zbiór danych. Co dalej?

Zatem standard: musimy zrozumieć, z czym mamy do czynienia, ogólny obraz. W tym celu używamy pandas, aby po prostu określić różne typy danych.

import pandas as pd #importujemy pandas
import numpy as np  #importujemy numpy
df = pd.read_csv("AB_NYC_2019.csv") #czytamy zbiór danych i zapisujemy go do zmiennej df

df.head(3) #sprawdzamy pierwsze 3 wiersze, aby zrozumieć, jak wyglądają wartości

Notatnik-podpowiadacz do szybkiego przetwarzania danych

df.info() #Demonstrujemy informacje o kolumnach

Notatnik-podpowiadacz do szybkiego przetwarzania danych

Patrzymy na wartości w kolumnach:

  1. Czy liczba wierszy w każdej kolumnie odpowiada ogólnej liczbie wierszy?
  2. Jaka jest istota danych w każdej kolumnie?
  3. Jaką kolumnę chcemy uczynić targetem, aby robić prognozy dla niej?

Odpowiedzi na te pytania pozwolą przeanalizować zbiór danych i w przybliżeniu nakreślić plan najbliższych działań.

Ponadto, aby uzyskać głębsze spojrzenie na wartości w każdej kolumnie, możemy skorzystać z funkcji pandas describe(). Niestety, wadą tej funkcji jest to, że nie dostarcza informacji o kolumnach z wartościami tekstowymi. Z nimi zajmiemy się później.

df.describe()

Notatnik-podpowiadacz do szybkiego przetwarzania danych

Magiczna wizualizacja

Spójrzmy na to, gdzie mamy całkowity brak wartości:

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

Notatnik-podpowiadacz do szybkiego przetwarzania danych

To był krótki rzut oka z góry, teraz przejdziemy do bardziej interesujących rzeczy

Spróbujmy znaleźć i ewentualnie usunąć kolumny, które zawierają tylko jedną wartość w każdym wierszu (nie będą miały wpływu na wynik):

df = df[[c for c
        in list(df)
        if len(df[c].unique()) > 1]] #Zapisujemy zbiór danych, pozostawiając tylko te kolumny, w których jest więcej niż jedna unikalna wartość

Teraz chronimy siebie i sukces naszego projektu przed duplikatami wierszy (wierszy, które zawierają te same informacje w tej samej kolejności co już istniejący wiersz):

df.drop_duplicates(inplace=True) #Robimy to, jeśli uważamy to za konieczne.
                                 #W niektórych projektach usuwanie takich danych od początku może być niewskazane.

Dzielimy zbiór danych na dwa: jeden z wartościami jakościowymi, a drugi — z wartościami ilościowymi

Tutaj musimy dokonać małego uściślenia: jeśli wiersze z brakującymi danymi w danych jakościowych i ilościowych nie mają ze sobą silnego związku, będziemy musieli podjąć decyzję, czy poświęcamy wszystkie wiersze z brakującymi danymi, tylko ich część czy określone kolumny. Jeśli jednak wiersze mają związek, mamy pełne prawo podzielić zbiór danych na dwa. W przeciwnym razie najpierw musimy rozwiązać problem wierszy, w których brakujące dane w danych jakościowych i ilościowych są niezgodne, a dopiero potem dzielić zbiór danych na dwa.

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

Robimy to, aby łatwiej przetwarzać te dwa różne typy danych — później przekonamy się, jak bardzo to ułatwia nam życie.

Pracujemy z danymi ilościowymi

Pierwsze, co powinniśmy zrobić, to określić, czy w danych ilościowych nie ma „kolumn szpiegów”. Nazywamy te kolumny tak, ponieważ udają dane ilościowe, a w rzeczywistości działają jak dane jakościowe.

Jak je zidentyfikować? Oczywiście wszystko zależy od charakteru danych, które analizujesz, ale generalnie takie kolumny mogą zawierać niewiele unikalnych danych (około 3-10 unikalnych wartości).

print(df_numerical.nunique())

Po tym, jak zidentyfikujemy kolumny-szpiedzy, przeniesiemy je z danych ilościowych do jakościowych:

spy_columns = df_numerical[['kolumna1', 'kolumna2', 'kolumna3']]#wyodrębniamy kolumny szpiegowskie i zapisujemy w osobnym dataframe
df_numerical.drop(labels=['kolumna1', 'kolumna2', 'kolumna3'], axis=1, inplace = True)#usuwamy te kolumny z danych ilościowych
df_categorical.insert(1, 'kolumna1', spy_columns['kolumna1']) #dodajemy pierwszą kolumnę szpiegowską do danych jakościowych
df_categorical.insert(1, 'kolumna2', spy_columns['kolumna2']) #dodajemy drugą kolumnę szpiegowską do danych jakościowych
df_categorical.insert(1, 'kolumna3', spy_columns['kolumna3']) #dodajemy trzecią kolumnę szpiegowską do danych jakościowych

W końcu całkowicie oddzieliliśmy dane ilościowe od jakościowych i teraz możemy z nimi odpowiednio pracować. Po pierwsze, należy zrozumieć, gdzie mamy puste wartości (NaN, a w niektórych przypadkach 0 będą traktowane jako puste wartości).

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

Na tym etapie ważne jest zrozumienie, w których kolumnach zera mogą oznaczać brakujące wartości: czy ma to związek z tym, jak zbierano dane? A może jest związane z wartościami danych? Na te pytania należy odpowiedzieć w każdym konkretnym przypadku.

Zatem, jeśli zdecydujemy, że dane mogą być brakujące tam, gdzie są zera, powinniśmy zastąpić zera NaN, aby później łatwiej było pracować z tymi utraconymi danymi:

df_numerical[["kolumna 1", "kolumna 2"]] = df_numerical[["kolumna 1", "kolumna 2"]].replace(0, nan)

Teraz zobaczmy, gdzie brakuje nam danych:

sns.heatmap(df_numerical.isnull(),yticklabels=False,cbar=False,cmap='viridis') # Można również skorzystać z df_numerical.info()

Notatnik-podpowiadacz do szybkiego przetwarzania danych

Tutaj powinny być oznaczone żółtym kolorem te wartości w kolumnach, które są brakujące. A najciekawsze zaczyna się teraz — jak postępować z tymi wartościami? Usunąć wiersze z tymi wartościami czy kolumny? A może uzupełnić te puste wartości jakimiś innymi?

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

Notatnik-podpowiadacz do szybkiego przetwarzania danych

0. Usuwamy niepotrzebne kolumny

df_numerical.drop(labels=["kolumna1","kolumna2"], axis=1, inplace=True)

1. Czy liczba pustych wartości w tej kolumnie przekracza 50%?

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

df_numerical.drop(labels=["kolumna1","kolumna2"], axis=1, inplace=True)#Usuwamy, jeśli jakaś kolumna ma więcej niż 50 pustych wartości

2. Usuwamy wiersze z pustymi wartościami

df_numerical.dropna(inplace=True)#Usuwamy wiersze z pustymi wartościami, jeśli pozostanie wystarczająco dużo danych do nauki

3.1. Wstawiamy losową wartość

import random #importujemy random
df_numerical["kolumna"].fillna(lambda x: random.choice(df[df[column] != np.nan]["kolumna"]), inplace=True) #wstawiamy losowe wartości w puste komórki tabeli

3.2. Wstawiamy stałą wartość

from sklearn.impute import SimpleImputer #importujemy SimpleImputer, który pomoże wstawiać wartości
imputer = SimpleImputer(strategy='constant', fill_value="") #wstawiamy określoną wartość za pomocą SimpleImputer
df_numerical[["nowa_kolumna1",'nowa_kolumna2','nowa_kolumna3']] = imputer.fit_transform(df_numerical[['kolumna1', 'kolumna2', 'kolumna3']]) #Stosujemy to dla naszej tabeli
df_numerical.drop(labels = ["kolumna1","kolumna2","kolumna3"], axis = 1, inplace = True) #Usuwamy kolumny ze starymi wartościami

3.3. Wstawiamy średnią lub najczęściej występującą wartość

from sklearn.impute import SimpleImputer #importujemy SimpleImputer, który pomoże wstawiać wartości
imputer = SimpleImputer(strategy='mean', missing_values = np.nan) #zamiast mean możemy również użyć most_frequent
df_numerical[["nowa_kolumna1",'nowa_kolumna2','nowa_kolumna3']] = imputer.fit_transform(df_numerical[['kolumna1', 'kolumna2', 'kolumna3']]) #Stosujemy to dla naszej tabeli
df_numerical.drop(labels = ["kolumna1","kolumna2","kolumna3"], axis = 1, inplace = True) #Usuwamy kolumny ze starymi wartościami

3.4. Wstawiamy wartość obliczoną przez inny model

Czasami wartości można obliczyć za pomocą modeli regresyjnych, używając modeli z biblioteki sklearn lub innych podobnych bibliotek. Nasz zespół poświęci osobny artykuł na to, jak można to zrobić w najbliższej przyszłości.

A zatem, opowieść o danych ilościowych na chwilę się zatrzyma, ponieważ istnieje wiele innych niuansów dotyczących tego, jak najlepiej przeprowadzać przygotowanie danych i preprocessing dla różnych zadań, a podstawowe rzeczy dotyczące danych ilościowych zostały uwzględnione w tym artykule, i teraz nadszedł czas, aby wrócić do danych jakościowych, które oddzieliliśmy kilka kroków temu od ilościowych. Możesz modyfikować ten notatnik w dowolny sposób, dostosowując go do różnych zadań, aby preprocessing danych przebiegał bardzo szybko!

Dane jakościowe

Głównie do danych jakościowych stosuje się metodę One-hot-encoding, aby sformatować je z string (lub obiekt) na liczbę. Zanim przejdziemy do tego punktu, skorzystajmy ze schematu i kodu powyżej, aby zrozumieć wartości puste.

df_categorical.nunique()

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

Notatnik-podpowiadacz do szybkiego przetwarzania danych

0. Usuwamy niepotrzebne kolumny

df_categorical.drop(labels=["kolumna1", "kolumna2"], axis=1, inplace=True)

1. Czy liczba pustych wartości w tej kolumnie przekracza 50%?

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

df_categorical.drop(labels=["kolumna1", "kolumna2"], axis=1, inplace=True) #Usuwamy, jeśli jakaś kolumna
                                                                           #ma więcej niż 50% pustych wartości

2. Usuwamy wiersze z pustymi wartościami

df_categorical.dropna(inplace=True) #Usuwamy wiersze z pustymi wartościami,
                                   #jeśli później pozostanie wystarczająco danych do nauki

3.1. Wstawiamy losową wartość

import random
df_categorical["kolumna"].fillna(lambda x: random.choice(df[df[column] != np.nan]["kolumna"]), inplace=True)

3.2. Wstawiamy stałą wartość

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='constant', fill_value="")
df_categorical[["nowa_kolumna1", 'nowa_kolumna2', 'nowa_kolumna3']] = imputer.fit_transform(df_categorical[['kolumna1', 'kolumna2', 'kolumna3']])
df_categorical.drop(labels=["kolumna1", "kolumna2", "kolumna3"], axis=1, inplace=True)

Tak więc, w końcu uporaliśmy się z pustymi wartościami w danych jakościowych. Teraz czas na wykonanie one-hot-encoding dla wartości, które znajdują się w Twojej bazie danych. Ta metoda jest często stosowana, aby Twój algorytm mógł uczyć się z uwzględnieniem danych jakościowych.

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 = ["kolumna1", "kolumna2", "kolumna3"]
for feature in features_to_encode:
    df_categorical = encode_and_bind(df_categorical, feature))

A więc, w końcu skończyliśmy przetwarzać oddzielnie dane jakościowe i ilościowe — czas na ich połączenie z powrotem

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

Po połączeniu zestawów danych w jeden, na koniec możemy użyć transformacji danych przy użyciu MinMaxScaler z biblioteki sklearn. Dzięki temu nasze wartości będą w granicach od 0 do 1, co pomoże podczas szkolenia 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 do wszystkiego — do sieci neuronowych, standardowych algorytmów ML itp!

W tym artykule nie uwzględniliśmy pracy z danymi związanymi z szeregami czasowymi, ponieważ do takich danych należy stosować nieco inne techniki przetwarzania, w zależności od Twojego zadania. W przyszłości nasz zespół poświęci temu tematowi osobny artykuł i mamy nadzieję, że przyniesie on coś interesującego, nowego i użytecznego do Twojego życia, tak jak ten.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster