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.
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.
- část: Lekce 1–3 (tento článek)
- 2. část: Lekce 4–6
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]})
| Yes | No | |
|---|---|---|
| 0 | 50 | 131 |
| 1 | 21 | 2 |
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.']})
| Bob | Sue | |
|---|---|---|
| 0 | I liked it. | Pretty good. |
| 1 | It 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'])
| Bob | Sue | |
|---|---|---|
| Product A | I liked it. | Pretty good. |
| Product B | It 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
ilocumí přijmout i booleovské pole (array). Na rozdíl odlocvš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í.
| Metoda | Series.map() | DataFrame.apply() |
|---|---|---|
| Objekt | Series | DataFrame |
| Jednotka aplikace | aplikuje 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()aDataFrame.map().
Series.apply():
by_row='compat'(výchozí): chová se stejně jakoSeries.map()by_row=False: předá do funkce celou Series najednou (podobné chování jakoDataFrame.apply()saxis='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.

