Příspěvek

Shrnutí kurzu Kaggle „Pandas“ (1) – Lekce 1–3

Shrnutí práce s knihovnou Pandas pro čištění a úpravu dat. Souhrn veřejného kurzu Kaggle „Pandas“, místy doplněný o poznámky. Tato část pokrývá lekce 1–3.

Shrnutí kurzu Kaggle „Pandas“ (1) – Lekce 1–3

Zde si poznamenávám, co jsem se naučil v kurzu Kaggle Pandas.
Protože je toho poměrně hodně, rozdělil jsem to na dvě části.

Certificate of Completion

Lekce 1. Vytváření, čtení a zápis

Načtení pandas

1
import pandas as pd

V pandas existují dva klíčové objekty: DataFrame a Series.

DataFrame

DataFrame lze chápat jako tabulku, případně jako matici. Je to matice složená z nezávislých prvků (entries); každý prvek má určitou hodnotu (value) a odpovídá jednomu řádku (row) neboli záznamu (record) a jednomu sloupci (column).

1
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
 YesNo
050131
1212

Prvky DataFrame nemusí být nutně číselné; následující příklad je DataFrame se stringovými hodnotami (recenze od uživatelů).

1
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
 BobSue
0I liked it.Pretty good.
1It was awful.Bland.

Při vytváření objektu DataFrame se používá konstruktor pd.DataFrame() a deklaruje se pomocí syntaxe pythonového slovníku (dictionary). Do klíčů (key) se dávají názvy sloupců a do hodnot (value) seznamy (list) položek, které se mají zapsat. To je standardní způsob, jak vytvořit nový DataFrame.

Při deklaraci DataFrame se názvy sloupců nastavují jako labely sloupců, zatímco labely řádků se bez explicitního určení automaticky nastaví na celá čísla 0, 1, 2, … V případě potřeby lze labely řádků nastavit ručně. V pandas se seznam labelů řádků nazývá Index a lze jej nastavit parametrem index konstruktoru.

1
2
3
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 
              'Sue': ['Pretty good.', 'Bland.']},
             index=['Product A', 'Product B'])
 BobSue
Product AI liked it.Pretty good.
Product BIt was awful.Bland.

Series

Series je posloupnost (sequence) datových hodnot, případně vektor.

1
pd.Series([1, 2, 3, 4, 5])

Series je v podstatě totéž co jeden sloupec DataFrame. Proto lze stejně jako u DataFrame nastavit index; jen místo „názvu sloupce“ má prostě „jméno“ (name).

1
pd.Series([30, 35, 40], index=['12015 Sales', '12016 Sales', '12017 Sales'], name='Product A')
1
2
3
4
12015 Sales    30
12016 Sales    35
12017 Sales    40
Name: Product A, dtype: int64

Series a DataFrame spolu úzce souvisejí. Pomáhá si DataFrame představit jednoduše jako „balík“ (kolekci) objektů Series.

Načtení datového souboru

V mnoha případech se data nepíší ručně, ale načítají se z existujícího zdroje. Data mohou být uložená v různých formátech; nejzákladnějším je CSV. Obsah CSV souboru obvykle vypadá takto:

Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11

CSV je tedy tabulka, kde jsou hodnoty oddělené čárkami (comma). Odtud název „Comma-Separated Values“, CSV.

Pro načtení dat ve formátu CSV do DataFrame se používá funkce pd.read_csv().

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv")

Pomocí atributu shape lze zkontrolovat rozměry DataFrame.

1
product_reviews.shape
1
(129971, 14)

Výstup v ukázce znamená, že daný DataFrame má 129971 záznamů a 14 sloupců.

Pomocí metody head() lze zobrazit prvních pět řádků DataFrame.

1
product_reviews.head()

Funkce pd.read_csv() má přes 30 parametrů. Například pokud CSV soubor už obsahuje vlastní index, lze nastavit parametr index_col, aby pandas místo automatického indexování použil daný sloupec jako index.

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv", index_col=0)

Zápis datového souboru

Pomocí metody to_csv() lze DataFrame exportovat do CSV souboru. Použití je následující:

1
product_reviews.to_csv("../output/product-reviews/example-data.csv")

Lekce 2. Indexování, výběr a přiřazování

Výběr konkrétních hodnot z DataFrame nebo Series je krok, kterým prochází téměř každé zpracování dat; proto je potřeba se nejdřív naučit rychle a efektivně vybírat potřebné datové body.

Přístup přes nativní Python

Nativní pythonové objekty poskytují výborné způsoby indexování dat a pandas je nabízí ve stejné podobě.

Atributy objektu

V Pythonu lze k hodnotě atributu objektu přistoupit přes název atributu. Například pokud má objekt example_obj atribut title, lze jej číst jako example_obj.title. Stejně lze přistupovat i ke sloupcům pandas DataFrame.

1
reviews.country
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Indexování jako slovník

U pythonového slovníku (dictionary) lze k hodnotám přistupovat pomocí indexovacího operátoru ([]). Stejným způsobem lze přistupovat i ke sloupcům pandas DataFrame.

1
reviews['country']
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Přístup přes atribut i přes slovníkové indexování jsou oba validní, ale slovníkové indexování má výhodu v tom, že zvládá i názvy sloupců obsahující rezervované znaky, například mezery (např. reviews['country providence'] je možné, ale reviews.country providence možné není).

I v takto vybraném pandas Series lze znovu použít indexovací operátor a načíst jednotlivou hodnotu.

1
reviews['country'][0]
1
'Italy'

Přístupory specifické pro pandas

Výše uvedené způsoby (indexovací operátor nebo přístup přes atribut) jsou skvělé tím, že dobře zapadají do ekosystému Pythonu. Kromě toho ale pandas poskytuje vlastní přístupory loc a iloc.

Výběr podle pozice (index-based selection)

Pomocí iloc lze provádět výběr podle pozice (index-based selection). Vyberete požadované položky pomocí celočíselných pozic v datech.

Například první řádek DataFrame lze vybrat takto:

1
reviews.iloc[0]
1
2
3
4
5
6
country                                                    Italy
description    Aromas include tropical fruit, broom, brimston...
                                     ...                        
variety                                              White Blend
winery                                                   Nicosia
Name: 0, Length: 13, dtype: object

Na rozdíl od nativního pythonového stylu, kde se nejdřív vybírá sloupec a pak řádek, iloc vybírá nejdřív řádek a potom sloupec. První sloupec DataFrame lze získat následovně:

1
reviews.iloc[:, 0]
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

V ukázce výše se pomocí : vybraly všechny řádky a z nich pak první sloupec. Pokud chceme z prvního sloupce vybrat druhý (1) a třetí (2) řádek, uděláme to takto:

1
reviews.iloc[1:3, 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

Případně lze předat seznam:

1
reviews.iloc[[1, 2], 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

Lze použít i záporné indexy a vybírat data od konce. Následující příklad vybírá posledních 5 řádků:

1
reviews.iloc[-5:]

Výběr podle štítků (label-based selection)

Další možností je použít loc pro výběr podle štítků (label-based selection). V tomto případě se nevybírá podle pozice v matici dat, ale podle hodnot indexu.

Například prvek odpovídající sloupci country v řádku s indexem 0 lze získat takto:

1
reviews.loc[0, 'country']
1
'Italy'

iloc ignoruje hodnoty indexu datasetu, chápe jej jako jednu velkou matici a přistupuje k prvkům podle pozice. Naproti tomu loc využívá indexové informace. Protože index často obsahuje smysluplné informace, bývá loc v mnoha případech intuitivnější než iloc.

Rozdíl ve vymezování rozsahů u iloc a loc

iloc používá stejné indexování jako standardní Python; proto 0:10 znamená polouzavřený interval od 0 do 10 bez 10, tedy 0,...,9.

Naopak loc chápe rozsah jako uzavřený interval, takže 0:10 znamená 0 až 10 včetně, tedy 0,...,10.

Důvodem je to, že loc může používat jako index nejen celá čísla, ale i libovolné standardní datové typy. Představme si DataFrame s indexy Apples, ..., Potatoes, ... a chceme vybrat plodiny v abecedním pořadí od Apples po Potatoes. V tomto případě je mnohem intuitivnější napsat „od Apples do Potatoes“ (df.loc['Apples':'Potatoes']), než nastavovat „od Apples do těsně před Potatoet“ (df.loc['Apples':'Potatoet']), protože po Potatoes by mohla následovat kombinace znaků typu Potatoet. Právě proto loc používá uzavřený interval.

Jinak je zbytek chování v zásadě stejný.

Osobně v datasetech se vzestupně seřazeným celočíselným indexem preferuji při výběru rozsahu přes : spíš iloc, abych se vyhnul záměně dané rozdílným chováním vymezování rozsahů. V ostatních případech mám raději intuitivnější loc.

Úprava indexu

Index lze podle potřeby i upravovat. Pomocí metody set_index() lze v datasetu nastavit určitý sloupec jako nový index, jako v následující ukázce:

1
reviews.set_index("title")

Podmíněný výběr

Doposud šlo o postupy, jak data vybírat a upravovat pomocí strukturálních vlastností DataFrame. Lze ale vybírat i data splňující složitější podmínky.

Například si představme DataFrame s informacemi o vínech a potřebujeme vybrat pouze italská vína s hodnocením alespoň 90 bodů.

1
reviews.country == 'Italy'

Tato podmínka vrací Series složený z booleovských hodnot True/False.

1
2
3
4
5
6
0          True
1         False
          ...  
129969    False
129970    False
Name: country, Length: 129971, dtype: bool

loc je primárně label-based, ale umí přijmout i booleovské pole nebo zarovnatelný booleovský Series. Proto lze vybrat jen italská vína takto:

1
reviews.loc[reviews.country == 'Italy']

Více podmínek lze kombinovat operátory & nebo |. Pro výběr vín, která jsou italská a zároveň mají hodnocení alespoň 90 bodů, použijeme:

1
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]

Vína, která jsou italská nebo mají hodnocení alespoň 90 bodů, lze vybrat takto:

1
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]

Pandas také nabízí několik vestavěných „podmíněných selektorů“, z nichž zvlášť užitečné jsou isin a isnull/notnull.

isin vrací booleovskou masku (True nebo False) podle toho, zda hodnota patří mezi položky v seznamu („is in“). Díky tomu lze data vyfiltrovat. Například vybrat vína z Itálie nebo Francie:

1
reviews.loc[reviews.country.isin(['Italy', 'France'])]

isna/notna se používá při výběru řádků s chybějící hodnotou (NaN) nebo bez ní. Například vybrat jen vína, u kterých nechybí cena:

1
reviews.loc[reviews.price.notna()]

Mimochodem, v původním kurzu Kaggle to uvedené nebylo, ale iloc umí přijmout i booleovské pole (array). Na rozdíl od loc však podporuje pouze pole, nikoli Series, takže podobné použití jako výše je obtížnější.

Přiřazování dat

Do DataFrame lze také nově přiřazovat data nebo existující hodnoty přepisovat.

1
2
reviews['critic'] = 'everyone'
reviews['critic']
1
2
3
4
5
6
0         everyone
1         everyone
            ...   
129969    everyone
129970    everyone
Name: critic, Length: 129971, dtype: object
1
2
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
1
2
3
4
5
6
0         129971
1         129970
           ...  
129969         2
129970         1
Name: index_backwards, Length: 129971, dtype: int64

Lekce 3. Souhrnné funkce a mapování

Rychlý přehled dat

Metoda describe() poskytuje „high-level“ přehled daného sloupce.

1
reviews.points.describe()
1
2
3
4
5
6
count    129971.000000
mean         88.447138
             ...      
75%          91.000000
max         100.000000
Name: points, Length: 8, dtype: float64

Výstup describe() závisí na datovém typu vstupu. Pro řetězcová data (ne číselná) vrací například toto:

1
reviews.taster_name.describe()
1
2
3
4
5
count         103727
unique            19
top       Roger Voss
freq           25514
Name: taster_name, dtype: object

Případně lze získat jen konkrétní statistiku.

1
reviews.points.mean()
1
88.44713820775404
1
reviews.taster_name.unique()
1
2
3
4
5
6
7
array(['Kerin O’Keefe', 'Roger Voss', 'Paul Gregutt',
       'Alexander Peartree', 'Michael Schachner', 'Anna Lee C. Iijima',
       'Virginie Boone', 'Matt Kettmann', nan, 'Sean P. Sullivan',
       'Jim Gordon', 'Joe Czerwinski', 'Anne Krebiehl\xa0MW',
       'Lauren Buzzeo', 'Mike DeSimone', 'Jeff Jenssen',
       'Susan Kostrzewa', 'Carrie Dykes', 'Fiona Adams',
       'Christina Pickard'], dtype=object)

Pokud chcete vědět, kolikrát se v DataFrame vyskytuje každá unikátní hodnota, použijte metodu value_counts().

1
reviews.taster_name.value_counts()
1
2
3
4
5
6
Roger Voss           25514
Michael Schachner    15134
                     ...  
Fiona Adams             27
Christina Pickard        6
Name: taster_name, Length: 19, dtype: int64

Zobrazení (Maps)

Zobrazení (map) je termín převzatý z matematiky a znamená funkci, která přiřazuje prvky jedné množiny prvkům jiné množiny. V data science je často potřeba převádět data do jiné reprezentace; k tomu se používají zobrazení, a proto jsou velmi důležitá.

Často se používají zejména dvě metody.

Metoda Series.map() přijme funkci, která převádí jednu hodnotu na jinou jedinou hodnotu. Tuto funkci pak hromadně aplikuje na všechny hodnoty v dané Series a vrátí novou Series. Například když chcete od bodového hodnocení vína odečíst průměr a získat odchylku, můžete to udělat takto:

1
2
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

Metoda DataFrame.apply() se používá, když chcete volat vlastní funkci pro každý řádek a aplikovat transformaci na celý DataFrame.

1
2
3
4
5
def remean_points(row):
    row.points = row.points - review_points_mean
    return row

reviews.apply(remean_points, axis='columns')

Pokud apply() zavoláte s parametrem axis='index', můžete funkci aplikovat ne po řádcích, ale po sloupcích.

Series.map() i DataFrame.apply() vracejí nový, transformovaný Series/DataFrame a původní data nijak nemění.

MetodaSeries.map()DataFrame.apply()
ObjektSeriesDataFrame
Jednotka aplikaceaplikuje se na jednotlivé hodnoty
(pokud Series chápeme jako sloupcový vektor, jde o aplikaci po řádcích)
ve výchozím stavu se aplikuje po řádcích
volitelně lze aplikovat po sloupcích

Mimochodem existují i Series.apply() a DataFrame.map().

  • Series.apply():
    • by_row='compat' (výchozí): chová se stejně jako Series.map()
    • by_row=False: předá do funkce celou Series najednou (podobné chování jako DataFrame.apply() s axis='index')
  • DataFrame.map(): aplikuje funkci na jednotlivé hodnoty v DataFrame (je podobné Series.map(), jen cílem je DataFrame místo Series)

Ve skutečnosti pandas podporuje řadu běžně používaných zobrazení přímo. Předchozí příklad lze napsat mnohem jednodušeji a pandas i tak pochopí záměr a bude fungovat správně:

1
2
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

Navíc pandas podporuje i operace mezi Series stejné délky. V příkladu s vínem lze například spojit informaci o zemi a regionu do jednoho řetězce:

1
reviews.country + " - " + reviews.region_1
1
2
3
4
5
6
0            Italy - Etna
1                     NaN
               ...       
129969    France - Alsace
129970    France - Alsace
Length: 129971, dtype: object

Tyto operace jsou rychlejší než map() nebo apply(), protože využívají interní techniky akcelerace v pandas. Pandas umí tímto způsobem pracovat se všemi standardními pythonovými operátory (>, <, == atd.). I tak je ale dobré znát map() a apply(), protože jsou flexibilnější a umožňují složitější transformace.

Tento příspěvek je licencován pod CC BY-NC 4.0 autorem.