ML: Obróbka danych: Dane kategoryczne

W poprzednim wpisie

była mowa o kwestii brakujących danych w zbiorach. Kolejnym problemem z jakim jesteśmy zmuszeni sobie radzić jest obróbka danych kategorycznych. Spójrzmy na poniższy wycinek, tym razem pochodzący ze zbioru BreastCancer – jeżeli chcecie go ściągnąć znajduje się tutaj

Dane kategoryzujące

Załadujmy wspomniany zbiór i zobaczmy co w nim siedzi (od razu zrobimy też coś z brakującymi wartościami):

Na początek zwróćmy uwagę na linię 9 powyższego kodu, która jest odpowiedzialna za wyświetlenie kilku podstawowych informacji na temat wczytanych danych. Możemy zobaczyć, że mamy tu 286 próbek, 10 kolumn, w których przechowywane są dane dwóch typów – int64 – kolumna DegMalig i w pozostałych kolumnach object.

Żadna kolumna nie przechowuje wartości pustych (NaN lub null). Czy to oznacza, że w zbiorze nie brakuje, żadnych wartości? Niekoniecznie. Jeżeli byśmy wykonali samą linijkę 14 dla każdej z kolumn, zauważylibyśmy, że w niektórych znajduje się znak zapytania. Uzbrojony w tą wiedzę napisałem resztę tego kodu. Po wykonaniu pętli for, możemy się dowiedzieć, że w kolumnach NodeCaps i BreastQuad znajduje się kolejno 8 i 1 pustych wartości. Wszystkich wierszy mamy 286, więc brakujące wartości stanowią niewielki ich procent (~3.14%). Możemy je więc bezpiecznie usunąć – robimy to w liniach 31 i 32.

Rzut oka na kolumny

Wykonując linię 34 zobaczymy coś takiego:

W linku, który wrzuciłem wcześniej znajduje się opis zbioru, ale mimo wszystko napiszę kilka słów o jego kolumnach, ponieważ zrozumienie przechowywanych wartości ma kluczowe znaczenie dla właściwej obróbki danych:

  • Wartość kolumny Class informuje o remisji guza – zasadniczo jest to wartość – według ludzi tworzących ten zbiór – zależna od pozostałych, którą chcielibyśmy przewidywać. Jest to zmienna kategoryzująca, mogąca przyjmować jedną z dwóch wartości.
  • Wartość kolumny Age mówi o przedziale wiekowym pacjentki. Unikalnych przedziałów jest 6 z czego najczęściej pojawia się 50-59 lat.
  • Kolumna Menopause przechowuje jedną z trzech kategorii, mówiących o przebiegu menopauzy pacjentki.
  • TumorSize zawiera przedział wielkości guza w minimetrach. Przedziałów w zbiorze jest 11.
  • Kolumna InvNodes zawiera informację o ilości zajętych przez nowotwór węzłów chłonnych, w postaci jednego z siedmiu przedziałów.
  • Kolumna NodeCaps daję informację, czy zarażony węzeł chłonny pozwala nowotworowi na przedostanie się po za swój obręb i kontakt z innymi tkankami.
  • DegMalig mówi o stopniu rozwoju nowotworu (od 1 do 3)
  • Breast wskazuje w której piersi rozwija się nowotwór.
  • Kolumna BreastQuad, wskazuje lokalizację nowotworu w piersi. Przyjmuje jedną z pięciu wartości.
  • Irradiat przechowuje informację o tym czy zastosowana była radioterapia.

Na chwilę zapomnijmy o naszym datasecie, i zastanówmy się nad teorią stojącą za danymi kategoryzującymi.


Dane kategoryczne

są to takie dane, z których każda może przyjąć jedną ze skończonej liczby kategorii. Cena produktu jest wartością ciągłą i numeryczną – różne produkty mogą kosztować różne kwoty, na których to kwotach można wykonywać operacje arytmetyczne. Ceny produktów po dodaniu są dla nas wartościową informacją o tym jak bardzo ucierpi nasz budżet po zakupach. Co innego rodzaj produktów – to już jest wartość kategoryczna. Możemy do koszyka włożyć kapustę, szampon oraz baterię do latarki, ale działania arytmetyczne mają sens dopiero gdy mówimy o ich cenach.

Korzystając z algorytmów uczenia maszynowego powinniśmy przekształcić wartości kategoryzujące na postać liczbową. Oczywiście nie w celu umożliwienia wykonywania na nich działań arytmetycznych. Jeżeli chodzi o ich interpretację to nic się nie zmienia. Po prostu większość algorytmów nie radzi sobie z danymi w innej postaci – np. ciągami znaków, czego zresztą można się spodziewać – w końcu algorytmy są oparte na obliczeniach matematycznych.  Istnieją również algorytmy (albo też całe biblioteki) do których można podawać dane w postaci innej niż numeryczna, nie mniej najwięcej pewności co do jakości dostarczonych danych zawsze daje samodzielne ich przygotowanie.

Dane kategoryczne możemy przydzielić do jednej z dwóch kategorii.

Dane kategoryczne porządkowe:

są to takie dane, których wartości mimo że są kategoriami jesteśmy w stanie porównać/sortować. Np. jeżeli mamy zbiór z danymi technicznymi samochodów, a w nim kolumnę z informacją o ilości cylindrów w układzie v (jest to z pewnością zmienna kategoryzująca) z wartościami takimi jak v8, v3, v16 i v4, to posortowanie ich do postaci v3, v4, v8, v16 jest logiczne i niesie za sobą informację: v3 < v4 (bo trzy cylindry to mniej niż cztery) , v4 < v8 itd.

Jeżeli chodzi o sposób postępowania z nimi w procesie obróbki danych, to jesteśmy zmuszeni dokonać mapowania z ciągu znaków na postać całkowitoliczbową, zachowując informację o różnicy między wartościami. Wracając do przykładu z silnikami nasze mapowanie mogłoby wyglądać tak: v3 – 1, v4 -2, v8 – 6, v16 – 14. Dzięki takiemu mapowaniu algorytm wie nie tylko to, że v3 jest mniejsze od v8, ale także zna skalę tej różnicy. Zobaczmy jak zrobić to w Pythonie, korzystając z biblioteki Pandas:

Możemy też użyć klasy LabelEncoder z biblioteki Scikit-Learn, co jest przydatne zwłaszcza wówczas, gdy mamy do czynienia z wieloma kategoriami. Przykład użycia tej klasy pokażę gdy będziemy przetwarzali dane w zbiorze dotyczącym raka piersi.

 Dane kategoryczne nominalne:

są to takie kategorie, których nie jesteśmy w stanie obiektywnie ze sobą porównywać. Jeżeli kategorię stanowi narodowość to nie możemy ułożyć takiego równania: Francuz < Anglik; Anglik < Rosjanin; Rosjanin < Polak.

W jaki sposób się postępuje z takimi danymi? Niestety nie możemy postąpić analogicznie jak w powyższym przypadku, z tego względu że algorytm traktowałby te wartości dokładnie tak jak dane porządkowe – mógłby próbować je porównywać.  Jednym z najpopularniejszych sposobów radzenia sobie z tym problemem jest zamiana danej kolumny na N kolumn, gdzie N, to liczba kategorii nominalnych. Każda taka kolumna mogłaby przechowywać wartości 0 lub 1, co oznaczałoby przynależność próbki do danej kategorii. Ta metoda nosi nazwę kodowania jeden do wielu. Spróbujmy ją zastosować dla kolumny ‘Country of origin’ naszego mini zbioru z samochodami:

W pierwszej linii wybieramy kolumnę, do której chcemy zastosować kodowanie jeden do wielu. Następnie usuwamy daną kolumnę z podstawowego dataseta. Kolejny krok to zastosowanie metody get_dummies z biblioteki Pandas – to właśnie ta metoda jest odpowiedzialna z a realizację kodowania. Teraz zawartość zmiennej country_of_origin_ds prezentuje się w taki sposób:

Kodowanie jeden do wielu

Indeksy odpowiadają indeksom w oryginalnym datasecie, więc możemy dołączyć do niego country_of_origin_ds , co też robimy w 6 linii. Czy to wszystko? I tak i nie.  Jeżeli chodzi o samą obróbkę danych nominalnych to  udało nam się poprawnie zakodować dane do postaci zrozumiałej dla algorytmów i w tym kontekście jest ok. Jeżeli jednak zaczęlibyśmy mówić o wykorzystaniu tych danych w procesie uczenia maszynowego, to w zależności od rodzaju algorytmu może on mieć problem powodowany przez tzw. Dummy Variable Trap,. Jest to temat na osobny artykuł i w tym tekście – jako o obróbce danych – zostawię to w tej postaci.

Bogatsi o nową wiedzę…


…wróćmy do naszego zbioru danych z danymi dotyczącymi raka piersi:

Jak widać wszystkie kolumny zawierają dane oznaczające jakąś kategorię. Nawet jedyna kolumna zawierająca wartości typu numerycznego – DegMalig – przechowuje informację o stopniu zaawansowana guza. Zastanówmy się, które z kolumn przechowują dane nominalne, a które dane porządkowe.

Według mnie:

  • Dane nominalne są przechowywane w kolumnach: Class, Menopause, NodeCaps, Breast, BreastQuad i Irradiat.
  • Dane porządkowe są przechowywane w kolumnach: Age, TumorSize, InvNodes, DegMalig. Przy czym ta ostatnia jest w postaci numerycznej.

Najpierw załatwmy kwestię danych porządkowych. Musimy dla każdej z kolumn wprowadzić odpowiednie mapowanie – użyjemy wspomnianej wcześniej klasy LabelEncoder, dzięki której nie musimy sami wymyślać mapowania. (traktujmy poniższy jako ciąg dalszy kodu, który operował na tym zbiorze a który napisałem wcześniej):

Teraz przejdźmy do danych nominalnych – zmienimy wszystkie, ale w rzeczywistości, jeżeli cecha którą chcemy przewidywać jest daną nominalną, nie musimy wobec niej stosować kodowania jeden do wielu. Wystarczy mapowanie:

 


Mam nadzieję, że powyższy tekst pomoże w radzeniu sobie z problemem obróbki danych kategorycznych. W kolejnym wpisie dotyczącym tematu przetwarzania danych, chciałbym poruszyć kwestię danych numerycznych. W niedalekiej przyszłości napiszę też kilka słów o problemie, który tylko zaznaczyłem – Dummy Variable Trap. Tymczasem dziękuje za uwagę 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *