Przekształcenie Boxa-Coxa | Box-Cox Transformation

Przekształcenie Boxa Coxa ma na celu przekształcić dane w taki sposób, aby ich rozkład był jak najbardziej zbliżony do rozkładu normalnego, czyli aby histogram wyglądał jak dzwon.

Ta technika ma swoje miejsce w inżynierii cech ponieważ nie wszystkie gatunki modeli predykcyjnych są odporne na skrzywione dane, dlatego warto użyć tego zabiegu podczas eksperymentowania. Zapewne nie zapewni spektakularnej poprawy choć na etapie „fine tuning” może spełnić swoje zadanie poprawiając naszą metrykę ewaluacyjną.

Zasada działania

Samo przekształcenie ma następującą formułę:

wyrażone kodem używając standardowej biblioteki Python

import math

def box_cox(x, lmbda):
  if lmbda == 0:
    return [math.log(v) for v in x]

  return [(math.pow(v, lmbda) - 1) / lmbda for v in x]

lub używając pakietu numpy

import numpy as np

def box_cox(x: list, lmbda: float):
  if lmbda == 0:
    return np.log(x)

  return (np.power(x, lmbda) - 1) / lmbda

Dane mam, ale jak dobrać lambdę?

Sprawa nie jest skomplikowana, potrzebujemy testu na „normalność”, porównać jego wyniki dla kilku lambda w zakresie (zwyczajowo) <-5, 5> następnie wybrać tę której wynik testu jest najlepszy.

Gotowe rozwiązanie dostarcza nam pakiet scipy

Kiedy nie podamy drugiego argumentu (lambdy) dla funkcji boxcox, zostanie ona dobrana oraz zwrócona.

Jedyny problem który napotykamy korzystając z tej implementacji, to wymóg aby dane wejściowe były większe od zera. Wystarczy jednak przesunąć wartości o minimum zbioru danych kiedy wymaga tego sytuacja.

def shift_to_positive(x):
  min_value = np.min(x)
  if min_value > 0:
    return x, 0
  
  shift_value = np.abs(min_value) + 1
  
  return x + shift_value, shift_value

Przykład dla populacji w poszczególnych państwach w 2007 roku.

Pełny kod znajdziesz w tym notatniku, tutaj skomentuję tylko wyniki.

Po lewej stronie widzimy rozkład dla naszych danych wejściowych. Co prawda wprawne oko zauważy że nałożenie logarytmu (środkowa kolumna) świetnie przybliży nasze dane do rozkładu normalnego jednak, najlepszy efekt osiągniemy wykorzystując tytułowe przekształcenie (prawa kolumna)

Zastosowanie w inżynierii cech

W takiej sytuacji na pewno chcemy zachować rozdział pomiędzy zbiorem treningowych i testowym, zatem lambdę chcemy dopasować tylko na zbiorze treningowym.

W pakiecie scikit-learn powszechnie stosowanym interfejsem do tego zabiegu jest Transformer który posiada metody fit oraz transform. Pozwala zachować w kodzie oraz użyć go składową FeatureUnion oraz Pipeline

Poniższy blok kodu przedstawia jego implementację:

import numpy as np
from sklearn.base import (
    TransformerMixin, 
    BaseEstimator
)
class BoxCoxTransformer(BaseEstimator, TransformerMixin):
    fitted_lambda: float

    def fit(self, x: np.array) -> 'BoxCoxTransformer':
      
        _, self.fitted_lambda = stats.boxcox(x)
        return self

    def transform(self, x: np.array) -> np.array:
        # Note that for x of length = 1 stats.boxcox will raise error
        return stats.boxcox(x, self.fitted_lambda)

Zastosowanie dla naszego zbioru danych populacji w 2007 znajdziesz w tej sekcji notatnika a poniżej przedstawiam Ci rozkłady dla zbioru treningowego i testowego.

Jak widzimy, kształty się zgadzają, różnią się jedynie ilością wystąpień → cel osiągnięty.


Pełny notatnik notatnik znajdziesz na colab lub pobierz go sam:

Udostępnij ten wpis


Dobrnąłeś do końca. Jeśli ten artykuł był dla Ciebie wartościowy i chcesz otrzymywać informacje o kolejnych, to zapraszam Cię do zapisania się do listy mailingowej. Gwarantuję zero spamu.

Radek.

Inne artykuły

Partycjonowanie bazy danych okładka

Partycjonowanie bazy danych

Partycjonowanie pozwala podzielić tabelę na mniejsze części, gdzie każda z nich może się znajdować na innym serwerze. Zobacz, jak to działa oraz dlaczego jest to …