"Na tym wykładzie dowiemy się, w jaki sposób reprezentować różnego rodzaju dane tak, żeby można było używać ich do uczenia maszynowego."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"# Przydatne importy\n",
"\n",
"import ipywidgets as widgets\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Plik *mieszkania4.tsv* zawiera dane wydobyte z serwisu *gratka.pl* dotyczące cen mieszkańw Poznaniu."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" cena Powierzchnia w m2 Liczba pokoi Garaż Liczba pięter w budynku \\\n",
"0 290386 46 2 False 5.0 \n",
"1 450000 59 2 False 3.0 \n",
"2 375000 79 3 False 16.0 \n",
"3 400000 63 3 True 2.0 \n",
"4 389285 59 3 False 13.0 \n",
"\n",
" Piętro Typ zabudowy Materiał budynku Rok budowy \\\n",
"0 parter apartamentowiec cegła 2017.0 \n",
"1 2 kamienica cegła 1902.0 \n",
"2 5 blok płyta 1990.0 \n",
"3 2 blok cegła 2009.0 \n",
"4 12 blok NaN NaN \n",
"\n",
" opis \n",
"0 Polecam mieszkanie 2 pokojowe o metrażu 46,68 ... \n",
"1 Ekskluzywna oferta - tylko u nas! Projekt arch... \n",
"2 Polecam do kupna przestronne mieszkanie trzypo... \n",
"3 Dla rodziny albo pod wynajem. Świetna lokaliza... \n",
"4 NaN \n"
]
}
],
"source": [
"# Wczytanie danych (mieszkania) przy pomocy biblioteki pandas\n",
"\n",
"alldata = pandas.read_csv(\n",
" 'mieszkania4.tsv', header=0, sep='\\t',\n",
" usecols=['cena', 'Powierzchnia w m2', 'Liczba pokoi', 'Garaż', 'Liczba pięter w budynku', 'Piętro', 'Typ zabudowy', 'Materiał budynku', 'Rok budowy', 'opis'])\n",
"\n",
"print(alldata[:5])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jak widać powyżej, w pliku *mieszkania4.tsv* znajdują się dane różnych typów:\n",
"* dane numeryczne (po prostu liczby):\n",
" * cena\n",
" * powierzchnia w m<sup>2</sup>\n",
" * liczba pokoi\n",
"* dane częściowo numeryczne (liczby oraz wartości specjalne):\n",
" * liczba pięter w budynku\n",
" * piętro\n",
" * rok budowy\n",
"* dane boole'owskie (prawda/fałsz):\n",
" * garaż\n",
"* dane kategoryczne (wybór jednej z kilku kategorii):\n",
" * typ zabudowy\n",
"* dane tekstowe (dowolny tekst):\n",
" * opis"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Algorytmy uczenia maszynowego działają na danych liczbowych. Z tego powodu musimy znaleźć właściwy sposób reprezentowania pozostałych danych."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dane numeryczne"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Dane numeryczne to takie, które są liczbami. W większości przypadków możemy na nich operować bezpośrednio. Przykładem takich danych jest kolumna *Powierzchnia w m2* z powyższego przykładu:"
"Jak widać powyżej, tutaj oprócz liczb pojawiają się pewne tekstowe wartości specjalne, takie jak `parter`, `poddasze` czy `niski parter`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Takie wartości należy zamienić na liczby. Jak?\n",
"* Wydaje się, że `parter` czy `niski parter` można z powodzeniem potraktować jako piętro „zerowe” i zamienić na `0`.\n",
"* Z poddaszem sytuacja nie jest już tak oczywista. Czy mają Państwo jakieś propozycje?\n",
" * Może zamienić `poddasze` na wartośćNaN (zobacz poniżej)?\n",
" * Może wykorzystaćw tym celu wartość z sąsiedniej kolumny *Liczba pięter w budynku*?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można w tym celu wykorzystać funkcje [apply](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html?highlight=apply#pandas.DataFrame.apply) i [to_numeric](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_numeric.html) z biblioteki `pandas`."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Przed zamianą:\n",
"122 1\n",
"123 2\n",
"124 poddasze\n",
"125 5\n",
"126 parter\n",
"127 3\n",
"Name: Piętro, dtype: object\n"
]
}
],
"source": [
"print('Przed zamianą:')\n",
"print(alldata['Piętro'][122:128])"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Po zamianie:\n",
"122 1.0\n",
"123 2.0\n",
"124 NaN\n",
"125 5.0\n",
"126 0.0\n",
"127 3.0\n",
"Name: Piętro, dtype: float64\n"
]
}
],
"source": [
"# Zamiana wartości 'parter' i 'niski parter' w kolumnie 'Piętro' na 0.\n",
"alldata['Piętro'] = alldata['Piętro'].apply(lambda x: 0 if x in ['parter', 'niski parter'] else x)\n",
"\n",
"# Zamiana wszystkich wartości w kolumnie 'Piętro' na numeryczne.\n",
"# Parametr errors='coerce' powoduje, że napotkane nieliczbowe wartości będą zamieniane na NaN.\n",
"Wartość NaN (zob. też na [Wikipedii](https://pl.wikipedia.org/wiki/NaN)) – to wartość numeryczna oznaczająca „nie-liczbę”, „wartośćniezdefiniowaną”, np. niezdefiniowany wynik działania lub brak danych:"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"nan\n",
"nan\n",
"nan\n",
"nan\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"<ipython-input-20-d4d317707dde>:1: RuntimeWarning: invalid value encountered in sqrt\n",
" print(np.sqrt(-1)) # niezdefiniowany wynik działania (pierwiastek z liczby ujemnej)\n"
]
}
],
"source": [
"print(np.sqrt(-1)) # niezdefiniowany wynik działania (pierwiastek z liczby ujemnej)\n",
"\n",
"print(alldata['Piętro'][14]) # brak danych na temat piętra w rekordzie 14.\n",
"\n",
"# Jak uzyskać wartośćNaN?\n",
"print(float('NaN'))\n",
"print(np.nan)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Co można zrobić z wartością NaN?\n",
"* Czasami można wartośćNaN zamienićna `0`, np. być może w kolumnie „przychód” wartość NaN oznacza brak przychodu. Należy jednak być z tym ostrożnym. **W większości przypadków wstawienie 0 zamiast NaN będzie niepoprawne**, np. „rok 0” to nie to samo co „rok nieznany”. Nawet w kolumnie „cena” wartość NaN raczej oznacza, że cena jest nieznana, a to przecież nie to samo, co „cena równa 0 zł”.\n",
"* **Najbezpieczniej jest usunąć cały rekord (wiersz), który zawiera jakąkolwiek wartość NaN**. Należy przy tym pamiętać, że pozbywamy się w ten sposób (byćmoże wartościowych) danych. Jest to istotne zwłaszcza wtedy, gdy nasze dane zawierają dużo wartości niezdefiniowanych.\n",
"* WartośćNaN można też zamienićna średnią, medianę, modę itp. z pozostałych wartości w zbiorze danych. To dobra opcja, jeżeli usunięcie całych wierszy zawierających NaN pozbawiłoby nas zbyt wielu rekordów.\n",
"* Można użyć też bardziej zaawansowanych technik, np. [MICE](https://stats.stackexchange.com/questions/421545/multiple-imputation-by-chained-equations-mice-explained) czy KNN."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Przydatne artykuły na temat usuwania wartości niezdefiniowanych ze zbioru danych:\n",
"* [Working with missing data in machine learning](https://towardsdatascience.com/working-with-missing-data-in-machine-learning-9c0a430df4ce)\n",
"* [What’s the best way to handle NaN values?](https://towardsdatascience.com/whats-the-best-way-to-handle-nan-values-62d50f738fc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Biblioteka `pandas` dostarcza narzędzi do automatycznego usuwania wartości NaN: [dropna](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dropna.html)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Liczba rekordów przed usunięciem NaN: 4938\n",
"Liczba rekordów po usunięciu NaN: 888\n"
]
}
],
"source": [
"print('Liczba rekordów przed usunięciem NaN:', len(alldata))\n",
"Nie trzeba tego robić ręcznie. Można do tego celu użyć funkcji [get_dummies](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.get_dummies.html) z biblioteki `pandas`:"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" cena Powierzchnia w m2 Liczba pokoi Garaż Liczba pięter w budynku \\\n",
"Zwróćmy uwagę, że dzięki użyciu `get_dummies` nowe kolumny zostały utworzone i nazwane automatycznie, nie trzeba też już ręcznie konwertować wartości boole'owskich do numerycznych."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Funkcja `get_dummies` do określenia, na ile i jakich kolumn podzielić daną kolumnę kategoryczną, używa bieżącej zawartości tabeli. Dlatego należy jej użyć przed dokonaniem podziału na zbiory uczący i testowy.\n",
"\n",
"Więcej na ten temat można przeczytać w artykule [How to usepandas.get_dummies with the test set](http://fastml.com/how-to-use-pd-dot-get-dummies-with-the-test-set)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dane tekstowe"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Przetwarzanie danych tekstowych to szeroki temat, którym można zapełnić cały wykład. Dlatego tutaj przedstawię tylko najważniejsze metody."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Możemy na przykład tworzyć cechy sprawdzające występowanie poszczególnych wyrazów lub ciągów znaków w tekście:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" nowe_w_opisie opis\n",
"0 True Polecam mieszkanie 2 pokojowe o metrażu 46,68 ...\n",
"1 False Ekskluzywna oferta - tylko u nas! Projekt arch...\n",
"2 False Polecam do kupna przestronne mieszkanie trzypo...\n",
"3 False Dla rodziny albo pod wynajem. Świetna lokaliza...\n",
"13 False Witam,Mam na imię Jędrzej i w biurze Platan po...\n",
"... ... ...\n",
"4920 True Przestronne mieszkanie z pięknym widokiem!Dwup...\n",
"4925 True BEZ 2% PCC, BEZ PROWIZJI. Nowe mieszkanie 48,6...\n",
"4928 True Polecam do sprzedaży słoneczne mieszkanie dwup...\n",
"4934 False OKAZJA!! LUKSUSOWY APARTAMENT W SĄSIEDZTWIE PA...\n",
"4937 True Sprzedaż nowego mieszkania w FAŁKOWIE - Osiedl...\n",
"\n",
"[1333 rows x 2 columns]\n"
]
}
],
"source": [
"alldata['nowe_w_opisie'] = alldata['opis'].apply(lambda x: True if 'nowe' in x.lower() else False)\n",
"print(alldata[['nowe_w_opisie', 'opis']])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Można też zamienić tekst na wektory używając algorytmów TF–IDF, Word2Vec lub podobnych."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ciekawy artykuł na temat przygotowywania danych tekstowych do uczenia maszynowego można znaleźć na przykład tutaj: https://machinelearningmastery.com/prepare-text-data-machine-learning-scikit-learn/"