Obsah¶
- Prednáška 5
- Základné nastavenia
- Dátová množina - Titanic
- Úprava hodnôt
- Nahradenie chýbajúcich hodnôt (prvý prístup)
- Nahradenie chýbajúcich hodnôt (druhý, sofistikovanejší prístup)
- Odvodenie nových atribútov
- Diskretizácia (číselné atribúty na ordinálne)
- Krížové a kontingenčné tabuľky
- Príklady
- Príklad - Nahradenie chýbajúcich hodnôt pre atribút
embarked
- Príklad - Diskretizácia veku do
age_ordinal
- Príklad - Kombinácie pohlavia a titulov
- Príklad - Kontingenčná tabuľka prežitia podľa veku, triedy a pohlavia
- Príklad - Vplyv výšky cestovného a miesta nalodenia na prežitie
- Príklad (bonus) - Označenie paluby (
deck
)
- Príklad - Nahradenie chýbajúcich hodnôt pre atribút
- Záver
Prednáška 5: Spracovanie dát na príklade Titanic¶
V tejto prednáške sa budeme venovať základnému predspracovaniu dát, upravovaniu hodnôt v stĺpcoch, nahrádzaniu chýbajúcich hodnôt a vytváraniu nových atribútov. Budeme pracovať s dobre známou dátovou množinou o pasažieroch na Titanicu.
Tiež si ukážeme, ako môžeme niektoré číselné atribúty previesť na kategorické intervaly, a ako skúmať vzájomné vzťahy medzi rôznymi atribútmi pomocou kontingenčných alebo krížových tabuliek.
Základné nastavenia¶
Importujeme potrebné knižnice. Nasledujúci odstavec by mal byť spustený ako prvý predtým, ako sa budú používať importované objekty pd
, np
, plt
a sns
.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# nastavíme zobrazovanie grafov priamo v odstavcoch zápisníka
%matplotlib inline
# inicializujeme knižnicu seaborn
sns.set()
Dátová množina - Titanic¶
RMS Titanic bola britská osobná loď, ktorá stroskotala 15. apríla 1912 na svojej prvej komerčnej plavbe. V čase svojej služby bola najväčšou plávajúcou loďou na svete. Pri nehode zahynulo viac než 1500 osôb z celkového odhadovaného počtu 2224 pasažierov a členov posádky.
Dátová množina obsahuje informácie o pasažieroch Titanicu. Každý pasažier je popísaný nasledovnými atribútmi:
pclass
- trieda, v ktorej pasažier cestovalsurvived
- udáva, či pasažier nehodu parníku prežil (1), alebo nie (0)name
- meno cestujúcehosex
- pohlavieage
- veksibsp
- počet súrodencov pasažiera, resp. druhov/družiekparch
- počet rodičov/detí medzi cestujúcimiticket
- číslo lístkafare
- výška cestovnéhocabin
- kajuta, v ktorej bol cestujúci ubytovanýembarked
- prístav, kde cestujúci nastúpil (S
- Southampton,C
- Cherbourg,Q
- Queenstown)
data = pd.read_csv("data/titanic.csv") # načítame si dáta zo súboru ../data/titanic.csv
print(data.shape) # zobrazíme rozmer dátovej tabuľky v tvare (počet riadkov, počet stĺpcov)
print(data.columns) # zobrazíme názvy stĺpcov
data.head() # zobrazíme prvých 5 riadkov tabuľky
Úprava hodnôt¶
Keďže označenie lístka nevieme priamo interpretovať a nevieme z neho zistiť užitočné informácie o pasažieroch, stĺpec ticket
odstránime z dátovej tabuľky.
data.drop(columns="ticket", inplace=True) # odstránime stĺpec ticket priamo z dátového rámca data
V sĺpci embarked
si nahradíme skratky S
, Q
, a C
za Southampton
, Queenstown
a Cherbourg
.
# hodnoty kategorických atribútov môžete premapovať pomocou metódy map objektu typu Series
data["embarked"] = data["embarked"].map({"S": "Southampton", "Q": 'Queenstown', "C": "Cherbourg"})
data["embarked"].value_counts() # zobrazíme si rôzne hodnoty po nahradení a ich početnosti
Nahradenie chýbajúcich hodnôt (prvý prístup)¶
Jedným zo základných krokov predspracovania dát je spracovanie prázdnych hodnôt. Pri výskyte prázdnych hodnôt je potrebné rozlišovať, či ide o chýbajúce hodnoty - tzn. daný záznam by mal mať uvedenú hodnotu, ale z nejakého dôvodu nie je vyplnená, alebo či pre daný záznam daný atribút nemá význam uvádzať. Pri chýbajúcich hodnotách sa môžeme pokúsiť hodnoty doplniť tak, aby sme čo najmenej narušili vzťahy v dátach, resp. ak je chýbajúcich hodnôt príliš veľa, môžeme z dátovej množiny odstrániť jednotlivé stĺpce, alebo záznamy s chýbajúcimi hodnotami.
# metóda isna vráti tabuľku iba s Boolovskými hodnotami pre každý stĺpec (True - chýbajúca hodnota, False - neprázdna hodnota)
# sum potom spočíta počet hodnôt True (tzn. počet chýbajúcich hodnôt pre každý stĺpec)
data.isna().sum() # spočítame si počet chýbajúcich hodnôt
Nahradíme chýbajúce hodnoty pre stĺpec fare
.
data["fare"].hist() # vykreslíme si histogram hodnôt
# vypočítame si strednú hodnotu a medián
fare_mean = data["fare"].mean()
fare_median = data["fare"].median()
print("fare mean: {0:.4f}, median: {1:.4f}".format(fare_mean, fare_median))
# keďže hodnoty fare sú značne vychýlené, chýbajúce hodnoty nahradíme mediánom, ktorý lepšie charakterizuje
# najčastejšie sa vyskytujúce hodnoty
data["fare"].fillna(fare_median, inplace=True)
data["fare"].isna().sum() # skontrolujeme počet chýbajúcich hodnôt po nahradení
Odvodenie nových atribútov¶
Odvodením nových atribútov z existujúcich hodnôt môžeme z dátovej množiny často získať užitočné informácie, ktoré v nej nie sú priamo vyjadrené.
# vytvoríme si nový atribút family, ktorý bude udávať celkový počet príbuzných (súčet sibsp + parch)
data["family"] = data.eval("sibsp + parch")
p = data["family"].hist()
# vytvoríme si nový binárny atribút has_family, ktorý bude udávať, či pasažier cestoval s rodinou
data["has_family"] = data.eval("family > 0")
data["has_family"].sum() # spočítame, koľko pasažierov cestovalo s rodinou
Mená pasažierov sú vo formáte priezvisko, titul. meno
. Vyextrahujeme si z mena hodnoty titulov.
# definujeme si funkciu, ktorá z celého reťazca mena vyextrahuje iba časť titulu
def extract_title(name):
if pd.isna(name): # pomocou pd.isna otestujeme, či je name prázdna hodnota
return np.nan # ak je name prázdna hodnota, vrátime prázdnu hodnotu aj pre titul
# (prázdne hodnoty sú v pandas reprezentované číselnou konštantou np.nan - Not A Number)
start = name.find(",") + 1
end = name.find(".")
return name[start:end].strip() # z mena vrátime podreťazec od , do . (bez prázdnych znakov na začiatku a konci)
# pomocou metódy apply aplikujeme našu funkciu extract_title na všetky hodnoty stĺpca name a vrátené hodnoty uložíme v stĺpci title
data["title"] = data["name"].apply(extract_title)
# stĺpec name už nebudeme potrebovať, tak ho odstránime z dátovej tabuľky
data.drop(columns="name", inplace=True)
# zobrazíme si tituly a koľko krát sa vyskytli
data["title"].value_counts()
# pomocou metódy apply si premapujeme tituly na skrátený zoznam, ktorý priradíme do stĺpca title_short
def map_title(title):
# všetky hodnostné, alebo šľachtické tituly namapujeme na hodnotu 'rare title'
if title in {"Master", "Dr", "Rev", "Col", "Major", "Don", "Jonkheer", "Sir", "Dona", "Lady", "Capt", "the Countess"}:
return "rare title"
elif title in {"Mlle", "Ms"}: # tituly z francúzštiny
return "Miss"
elif title in {"Mme"}:
return "Mrs"
return title
data["title_short"] = data["title"].apply(map_title)
data["title_short"].value_counts()
Nahradenie chýbajúcich hodnôt (druhý, sofistikovanejší prístup)¶
Chýbajúce hodnoty jedného atribútu môžeme lepšie odhadnúť na základe iných atribútov. Napr. zobrazíme si rozdielny vek podľa pohlavia a titulu.
# v kontingenčnej tabuľke si zoskupíme dáta podľa pohlavia a titulu pasažierov a vypočítame základné štatistiky o veku
# parameter margins=True pridáva do kontingenčnej tabuľky celkové štatistiky pre každý riadok a stĺpec
pd.pivot_table(data, index=["sex", "title_short"], values="age", aggfunc=["median", "mean", "min", "max", "count"], margins=True)
# zaujímavý je nízky vek mužov so šľachtickým titulom, zobrazíme si histogram hodnôt v tejto skupine pasažierov
l = data.query("sex == 'male' and title_short == 'rare title'")["age"].hist()
Metódu apply
môžeme aplikovať aj na transformovanie/extrahovanie dát odvodených z hodnôt na danom riadku. V nasledujúcom kóde nahradíme chýbajúce hodnoty veku mediánom podľa hodnôt pohlavia a titulu.
# najprv si vypočítame medián pre jednotlivé skupiny pomocou kontingenčnej tabuľky
ptable = pd.pivot_table(data, index=["sex", "title_short"], values="age", aggfunc="median")
ptable
Riadky a stĺpce kontingenčnej tabuľky môžu byť označené na viacerých úrovniach, takže musíme pri prístupe k hodnotám tabuľky ako index zadať n-ticu hodnôt pre každú úroveň.
V našej tabuľke uloženej v premennej ptable
majú stĺpce len jednu úroveň (age
), ale riadky sú označené dvoma úrovňami (sex
a title_short
).
# ak chceme napr. vypísať konkrétnu hodnotu na riadku pre kombináciu (female, Miss), musíme zadať index ako n-ticu
# (podobne by sme museli zadať n-ticu hodnôt aj pre stĺpce, ak by sme mali hierarchické označenia stĺpcov)
ptable["age"][("female", "Miss")]
# do premennej age1 si uložíme iba neprázdne hodnoty age (pôvodné dáta sa nezmenia)
age1 = data["age"].dropna() # metóda dropna vráti iba záznamy s neprázdnymi hodnotami
# definujeme si funkciu, s ktorou nahradíme chýbajúce hodnoty veku podľa pohlavia a titulu
# row bude objekt reprezentujúci jeden riadok v tabuľke
def replace_missing_age(row):
age = row["age"] # zistíme si vek, pohlavie a titul pasažiera
sex = row["sex"]
title = row["title_short"]
# ak je vek chýbajúca hodnota, nahradíme ho mediánom v danej skupine určenej podľa pohlavia a titulu
if pd.isna(age):
return ptable["age"][(sex, title)]
else:
return age # inak vrátime známu hodnotu
# aplikujeme funkciu replace_missing_age na každý riadok tabuľky (axis=1, prednastavená hodnota axis=0 by aplikovala funkciu po stĺpcoch)
# výsledok sú hodnoty age s nahradenými chýbajúcimi hodnotami, ktoré si uložíme do premennej age2
age2 = data.apply(replace_missing_age, axis=1)
# pre porovnanie si naraz vykreslíme histogram bez nahradenia a po nahradení chýbajúcich hodnôt
p = plt.hist([age1, age2])
data["age"] = age2 # nahradíme stĺpec age s vyplnenými chýbajúcimi hodnotami v pôvodných dátach
Diskretizácia (číselné atribúty na ordinálne)¶
Číselné atribúty je možné jednoducho previesť na ordinálne (usporiadané kategorické) atribúty rozdelením na intervaly. Hodnoty môžu byť rozdelené na rovnako široké intervaly, alebo podľa dát na rôzne intervaly tak, aby bola početnosť hodnôt v každom intervale približne rovnaká.
data["fare"].describe() # zobrazíme si základné štatistiky pre atribút fare
data["fare_ordinal"] = pd.cut(data["fare"], 3) # pomocou metódy cut rozdelíme hodnoty do 3 rovnako veľkých intervalov
data["fare_ordinal"].value_counts() # zobrazíme si označenia intervalov a ich početnosti
# väčšina hodnôt patrí približne do intervalu od 0-170, pre lepšie pochopenie distribúcie hodnôt si zobrazíme histogram
p = data["fare"].hist(bins=20) # parameter bins udáva počet intervalov pre výpočet histogramu
# namiesto rozdelenia na rovnako veľké intervaly môžeme dáta rozdeliť pomocou metódy qcut na intervaly
# s približne rovnakým počtom hodnôt
data["fare_ordinal"] = pd.qcut(data["fare"], 3)
data["fare_ordinal"].value_counts() # zobrazíme si označenia intervalov a ich početnosti
# pri metóde cut môžeme priamo zadať hraničné hodnoty intervalov, napr. rozdelíme fare na intervaly [0-25], (25-100]
# a (100, 520]
# štandardne prvý interval nezahŕňa najmenšiu hodnotu, takže ak chceme zahrnúť aj 0 hodnoty, nastavíme include_lowest na True
data["fare_ordinal"] = pd.cut(data["fare"], bins=[0, 25, 100, 520], include_lowest=True)
data["fare_ordinal"].value_counts()
# označenia intervalov môžeme priamo pomenovať zadaním parametra labels
data["fare_ordinal"] = pd.cut(
data["fare"], bins=[0, 25, 100, 520], include_lowest=True,
labels=["normal", "more expensive", "most expensive"]
)
data["fare_ordinal"].value_counts()
Krížové a kontingenčné tabuľky¶
Závislosti medzi kategorickými atribútmi môžete skúmať pomocou krížových tabuliek, ktoré udávajú početnosť výskytu všetkých kombinácií hodnôt medzi zvolenými kategorickými atribútmi podobne, ako kontingenčné tabuľky sumarizujú závislosť medzi kategorickými a číselnými hodnotami. Krížové tabuľky sa generujú pomocou metódy crosstab
.
# vypočítame tabuľku početností pre všetky kombinácie hodnôt medzi atribútmi pclass a sex
# všimnite si, že do metódy crosstab musíte, na rozdiel od pivot_table, zadať ako index a columns
# priamo dátové atribúty (objekty typu Series)
pd.crosstab(index=data["pclass"], columns=data["sex"])
# podobne ako pri kontingenčnej tabuľke, ako riadky alebo stĺpce môžeme zadať viacero atribútov
# napr. v nasledujúcej tabuľke vypočítame pre každú kombináciu triedy a výšky cestovného,
# koľko žien a koľko mužov si kúpilo daný lístok
pd.crosstab(index=[data["pclass"], data["fare_ordinal"]], columns=data["sex"])
Príklady¶
V nasledujúcich sekciách uvidíte vybrané príklady, ktoré demonštrujú rôzne postupy spracovania dátových hodnôt.
Príklad - Nahradenie chýbajúcich hodnôt pre atribút embarked
¶
Chýbajúce hodnoty pre atribút embarked
môžeme jednoducho nahradiť najfrekventovanejšou hodnotou. Niekedy sa takýto prístup volá aj modus (pre kategórie).
# Najfrekventovanejšiu hodnotu zisťujeme napr. cez value_counts().idxmax()
most_frequent_embarked = data["embarked"].value_counts().idxmax()
print(f"Najfrekventovanejšia hodnota pre 'embarked': {most_frequent_embarked}")
# Nahradíme chýbajúce hodnoty touto najfrekventovanejšou hodnotou
data["embarked"].fillna(most_frequent_embarked, inplace=True)
# skontrolujeme počet chýbajúcich hodnôt po nahradení
print("Počet chýbajúcich hodnôt v 'embarked':", data["embarked"].isna().sum())
Príklad - Diskretizácia veku do age_ordinal
¶
Tu si odvodíme nový atribút age_ordinal
diskretizáciou hodnôt age
na intervaly 0-13, 13-19, 19-65, 65-maximálny vek s označeniami child
, young
, adult
, old
.
data["age_ordinal"] = pd.cut(
data["age"],
bins=[0, 13, 19, 65, data["age"].max()],
include_lowest=True,
labels=["child", "young", "adult", "old"]
)
data["age_ordinal"].value_counts()
Príklad - Kombinácie pohlavia a titulov¶
Pomocou krížovej tabuľky zistíme, koľko mužov a žien má aký titul.
pd.crosstab(index=data["title_short"], columns=data["sex"])
Príklad - Kontingenčná tabuľka prežitia podľa veku (age_ordinal
), triedy a pohlavia¶
Vytvoríme kontingenčnú tabuľku, v ktorej prehľadne zobrazíme počet (alebo strednú hodnotu) prežitia pre skupiny rozdelené podľa veku (age_ordinal
), triedy a pohlavia. Ak použijeme ako agregačnú funkciu strednú hodnotu survived
, bude výstup hovoriť o priemernej (resp. percentuálnej) miere prežitia pre danú kombináciu atribútov.
pd.pivot_table(
data,
index=["age_ordinal", "pclass"],
columns="sex",
values="survived",
aggfunc="mean",
margins=True
)
Príklad - Vplyv výšky cestovného a miesta nalodenia na prežitie¶
Zistíme, či má na prežitie vplyv výška cestovného, alebo miesto nalodenia. Môžeme sa pozrieť napríklad na krížovú tabuľku (alebo pivot) medzi fare_ordinal
, embarked
a priemerom survived
.
pd.pivot_table(
data,
index="fare_ordinal",
columns="embarked",
values="survived",
aggfunc="mean",
margins=True
)
Príklad (bonus) - Označenie paluby (deck
)¶
Označenia kajuty začínajú písmenom, ktoré označuje palubu na ktorej sa kajuta nachádzala (napr. kajuta C22
sa nachádzala na palube C
, atď.). Pomocou metódy apply
odvoďte nový atribút deck
s označením paluby.
Pozrime sa tiež, na ktorej palube by ste mali väčšiu šancu na záchranu.
def extract_deck(cabin):
"""
Funkcia, ktorá z označenia kajuty vyextrahuje prvé písmeno (palubu).
Ak cabin nie je k dispozícii (nan), vráti nan.
"""
if pd.isna(cabin):
return np.nan
return cabin[0] # prvý znak reťazca
data["deck"] = data["cabin"].apply(extract_deck)
data[["cabin", "deck"]].head(10)
# Teraz sa môžeme pozrieť na priemerné prežitie (survived) podľa paluby.
pd.pivot_table(data, index="deck", values="survived", aggfunc="mean")
Záver¶
V tejto časti sme si ukázali základné kroky predspracovania dátovej množiny o pasažieroch Titanicu. Odstránili sme niektoré nepotrebné atribúty, doplnili a nahradili chýbajúce hodnoty rôznymi spôsobmi, odvodili sme nové atribúty a preskúmali sme vzťahy medzi atribútmi prostredníctvom krížových a kontingenčných tabuliek. Taktiež sme sa venovali základnej diskretizácii číselných atribútov.
V ďalšom spracovaní by sme mohli napríklad pokračovať vytváraním nových, zložitejších atribútov, alebo začať s modelovaním (klasifikáciou), aby sme zistili, ktoré faktory mali na prežitie pasažierov najvýznamnejší vplyv.