Nauč se Python > Kurzy > Datový kurz PyLadies > Datové typy a základy vizualizace v pandas > Pandas - datové typy a základní operace

Pandas - datové typy a základní operace

V minulé lekci jsme si představili knihovnu pandas a její základní třídy: Series, DataFrame a Index. Brali jsme je ovšem jako statické objekty, které jsme si pouze prohlíželi.

V této lekci začneme upravovat existující tabulky. Ukážeme si:

  • jak přidat či ubrat sloupce a řádky
  • jak změnit hodnotu konkrétní buňky
  • jaké datové typy se hodí pro který účel
  • aritmetické a logické operace, které lze se sloupci provádět
  • filtrování a řazení řádků

A jelikož o výsledky práce určitě nechceš přijít, nakonec se bude hodit i ukládání výsledků do externích souborů.

In [1]:
# Obligátní import
import pandas as pd

Manipulace s DataFrames

Pro rozehřátí budeme pracovat s malou tabulkou obsahující několik základních informací o planetách, které snadno najdeš např. na wikipedii.

In [2]:
planety = pd.DataFrame({
    "jmeno": ["Merkur", "Venuše", "Země", "Mars", "Jupiter", "Saturn", "Uran", "Neptun"],
    "symbol": ["☿", "♀", "⊕", "♂", "♃", "♄", "♅", "♆"],
    "obezna_poloosa": [0.39, 0.72, 1.00, 1.52, 5.20, 9.54, 19.22, 30.06],
    "obezna_doba": [0.24, 0.62, 1, 1.88, 11.86, 29.46, 84.01, 164.8],
})
planety = planety.set_index("jmeno")    # Se jmenným indexem se ti bude snáze pracovat
planety
Out[2]:
symbol obezna_poloosa obezna_doba
jmeno
Merkur 0.39 0.24
Venuše 0.72 0.62
Země 1.00 1.00
Mars 1.52 1.88
Jupiter 5.20 11.86
Saturn 9.54 29.46
Uran 19.22 84.01
Neptun 30.06 164.80

Přidání nového sloupce

Když chceme přidat nový sloupec (Series), přiřadíme ho do DataFrame jako hodnotu do slovníku - tedy v hranatých závorkách s názvem sloupce. Dobrá zpráva je, že stejně jako v konstruktoru si pandas poradí jak se Series, tak s obyčejným seznamem.

V našem konkrétním případě si najdeme a přidáme počet známých měsíců (velkých i malých).

In [3]:
mesice = [0, 0, 1, 2, 79, 82, 27, 14]      # Alternativně mesice = pd.Series([...])
planety["mesice"] = mesice
planety
Out[3]:
symbol obezna_poloosa obezna_doba mesice
jmeno
Merkur 0.39 0.24 0
Venuše 0.72 0.62 0
Země 1.00 1.00 1
Mars 1.52 1.88 2
Jupiter 5.20 11.86 79
Saturn 9.54 29.46 82
Uran 19.22 84.01 27
Neptun 30.06 164.80 14

💡 V tomto případě jsme přímo upravili existující DataFrame. Většina metod / operací v pandas (už znáš např. set_index) ve výchozím nastavení vždy vrací nový objekt s aplikovanou úpravou a ten původní objekt nechá v nezměněném stavu. Je to dobrým zvykem, který budeme dodržovat. Přiřazování sloupců je jednou z akceptovaných výjimek tohoto jinak uznávaného pravidla, zejména když se tabulka upravuje jen v úzkém rozsahu řádků kódu (případně kdyby kopírování bylo příliš náročné na paměť).

DataFrame však nabízí ještě metodu assign, která nemění tabulku, ale vytváří její kopii s přidanými (nebo nahrazenými) sloupci. Pokud se chceš vyhnout nepříjemnému sledování, kterou tabulku jsi změnil/a či nikoliv, assign ti můžeme jen doporučit.

Mimochodem, kopii tabulky můžeš kdykoliv vytvořit metodou copy - to se hodí třeba při psaní funkcí, kde se vstupní tabulka z různých důvodů upravuje.

In [4]:
# Nový dočasný DataFrame
planety.assign(
    je_stavebnice=[True, False, False, False, False, False, False, False],
    ma_vztah_k_vestonicim=[False, True, False, False, False, False, False, False],
)
# Objekt `planety` zůstal nezměněn.
Out[4]:
symbol obezna_poloosa obezna_doba mesice je_stavebnice ma_vztah_k_vestonicim
jmeno
Merkur 0.39 0.24 0 True False
Venuše 0.72 0.62 0 False True
Země 1.00 1.00 1 False False
Mars 1.52 1.88 2 False False
Jupiter 5.20 11.86 79 False False
Saturn 9.54 29.46 82 False False
Uran 19.22 84.01 27 False False
Neptun 30.06 164.80 14 False False
In [5]:
planety2 = planety.copy()
planety2["je_nezdrava_tycinka"] = [False, False, False, True, False, False, False, False]
planety2
# Ani teď se původní `planety` nezmění
Out[5]:
symbol obezna_poloosa obezna_doba mesice je_nezdrava_tycinka
jmeno
Merkur 0.39 0.24 0 False
Venuše 0.72 0.62 0 False
Země 1.00 1.00 1 False
Mars 1.52 1.88 2 True
Jupiter 5.20 11.86 79 False
Saturn 9.54 29.46 82 False
Uran 19.22 84.01 27 False
Neptun 30.06 164.80 14 False

Úkol: Zkus (jedním či druhým způsobem) přidat sloupec s rokem objevu ("objeveno"). Údaje najdeš např. na https://cs.wikipedia.org/wiki/Slune%C4%8Dn%C3%AD_soustava.

Pro hodnoty nového sloupce lze použít i jednu skalární hodnotu (v praxi se ale s touto potřebou nepotkáme tak často) - stejná hodnota se pak použije ve všech řádcích:

In [6]:
planety["je_planeta"] = True
planety
Out[6]:
symbol obezna_poloosa obezna_doba mesice je_planeta
jmeno
Merkur 0.39 0.24 0 True
Venuše 0.72 0.62 0 True
Země 1.00 1.00 1 True
Mars 1.52 1.88 2 True
Jupiter 5.20 11.86 79 True
Saturn 9.54 29.46 82 True
Uran 19.22 84.01 27 True
Neptun 30.06 164.80 14 True

Přidání nového řádku

Když se strojem času vrátíme do dětství (nebo rané dospělosti) autorů těchto materiálů, tedy před rok 2006, kdy se v Praze konal astronomický kongres, který definoval pojem "planeta" (ale ne před rok 1930!), přibude nám nová planeta: Pluto.

Do naší tabulky ho coby nový řádek vložíme pomocí indexeru loc, který jsme již dříve používali pro "koukání" do tabulky:

In [7]:
planety.loc["Pluto"] = ["♇", 39.48, 247.94, 5, True]   # Seznam hodnot v řádku
planety
Out[7]:
symbol obezna_poloosa obezna_doba mesice je_planeta
jmeno
Merkur 0.39 0.24 0 True
Venuše 0.72 0.62 0 True
Země 1.00 1.00 1 True
Mars 1.52 1.88 2 True
Jupiter 5.20 11.86 79 True
Saturn 9.54 29.46 82 True
Uran 19.22 84.01 27 True
Neptun 30.06 164.80 14 True
Pluto 39.48 247.94 5 True

Úkol: Zkus přidat Slunce nebo nějakou zcela smyšlenou planetu.

Změna hodnoty buňky

"Indexery" .loc a .iloc se dvěma argumenty v hranatých závorkách odkazují přímo na konkrétní buňku, a přiřazením do nich (opět, podobně jako ve slovníku) se hodnota na příslušné místo zapíše. Jen je třeba zachovat pořadí (řádek, sloupec).

Vrátíme se opět do současnosti a Pluto zbavíme jeho statutu:

In [8]:
planety.loc["Pluto", "je_planeta"] = False
planety
Out[8]:
symbol obezna_poloosa obezna_doba mesice je_planeta
jmeno
Merkur 0.39 0.24 0 True
Venuše 0.72 0.62 0 True
Země 1.00 1.00 1 True
Mars 1.52 1.88 2 True
Jupiter 5.20 11.86 79 True
Saturn 9.54 29.46 82 True
Uran 19.22 84.01 27 True
Neptun 30.06 164.80 14 True
Pluto 39.48 247.94 5 False

⚠ Pozor: Podobně jako u slovníku, ale možná poněkud neintuitivně, je možné zapsat hodnotu do řádku i sloupce, které neexistují!

In [9]:
planety_bad = planety.copy()     # Pro jistotu si uděláme kopii

planety_bad.loc["Zeme", "planeta"] = True
planety_bad
Out[9]:
symbol obezna_poloosa obezna_doba mesice je_planeta planeta
jmeno
Merkur 0.39 0.24 0.0 True NaN
Venuše 0.72 0.62 0.0 True NaN
Země 1.00 1.00 1.0 True NaN
Mars 1.52 1.88 2.0 True NaN
Jupiter 5.20 11.86 79.0 True NaN
Saturn 9.54 29.46 82.0 True NaN
Uran 19.22 84.01 27.0 True NaN
Neptun 30.06 164.80 14.0 True NaN
Pluto 39.48 247.94 5.0 False NaN
Zeme NaN NaN NaN NaN NaN True

💡 Jistě se ptáš, co znamená NaN v tabulce. Hodnota NaN (Not a Number) označuje chybějící, neplatnou nebo neznámou hodnotu. V našem příkladu jsme ji nezadali, tedy se není co divit. O problematice chybějících hodnot a jejich napravování si budeme povídat někdy příště, prozatím se jimi nenech znervóznit.

Přiřazovat je možné i do rozsahů v indexech - jen je potřeba hlídat, abychom přiřazovali buď skalární hodnotu (tedy jedna hodnota pro celou oblast, bezrozměrné ne-pole), nebo vícerozměrný objekt (Series, DataFrame, seznam, ...) stejného tvaru (počtu řádků a sloupců) jako oblast, do které přiřazujeme:

In [10]:
planety.loc["Merkur":"Mars", "je_obr"] = False
planety.loc["Jupiter":"Neptun", "je_obr"] = [True, True, True, True]
planety
Out[10]:
symbol obezna_poloosa obezna_doba mesice je_planeta je_obr
jmeno
Merkur 0.39 0.24 0 True False
Venuše 0.72 0.62 0 True False
Země 1.00 1.00 1 True False
Mars 1.52 1.88 2 True False
Jupiter 5.20 11.86 79 True True
Saturn 9.54 29.46 82 True True
Uran 19.22 84.01 27 True True
Neptun 30.06 164.80 14 True True
Pluto 39.48 247.94 5 False NaN

Úkol: Shodou okolností (nebo jde o astronomickou nevyhnutelnost?) mají všichni planetární obři alespoň nějaký prstenec. Dokážeš jednoduše vytvořit sloupec "ma_prstenec"?

Odstranění řádku

Pro odebrání sloupce či řádku z DataFrame slouží metoda drop. Její první argument očekává označení (index) jednoho nebo více řádků či sloupců, které chceš odebrat. Argument axis označuje, ve které dimenzi se operace má aplikovat. Můžeš použít buď číslo 0 či 1 (odpovídá pořadí od nuly, ve kterém se uvádějí klíče při odkazování na buňky), anebo pojmenování dané dimenze:

Osa (axis):

  • 0 nebo "index" → řádky
  • 1 nebo "columns" → sloupce

Tento argument používají i četné další metody a funkce, proto se ujisti, že mu rozumíš

Když už jsme se vrátili do budoucnosti (resp. současnosti), vypořádejme se nemilosrdně s Plutem (pro metodu drop je výchozí hodnotou argumentu axis 0, nemusíme ho tedy psát):

In [11]:
planety = planety.drop("Pluto")   # Přidej axis="rows", chceš-li být explicitní
planety
Out[11]:
symbol obezna_poloosa obezna_doba mesice je_planeta je_obr
jmeno
Merkur 0.39 0.24 0 True False
Venuše 0.72 0.62 0 True False
Země 1.00 1.00 1 True False
Mars 1.52 1.88 2 True False
Jupiter 5.20 11.86 79 True True
Saturn 9.54 29.46 82 True True
Uran 19.22 84.01 27 True True
Neptun 30.06 164.80 14 True True

Úkol: Zkus z planety vytvořit tabulku, která nebude obsahovat ani Uran, ani Neptun (jedním příkazem).

Odstranění sloupce

U sloupce funguje metoda drop velmi podobně, jen tentokrát argument axis uvést musíme.

Odstraňme zbytečný sloupec s informační hodnotou na úrovni "stěrače stírají, klakson troubí"...

In [12]:
planety = planety.drop("je_planeta", axis="columns")   
planety
Out[12]:
symbol obezna_poloosa obezna_doba mesice je_obr
jmeno
Merkur 0.39 0.24 0 False
Venuše 0.72 0.62 0 False
Země 1.00 1.00 1 False
Mars 1.52 1.88 2 False
Jupiter 5.20 11.86 79 True
Saturn 9.54 29.46 82 True
Uran 19.22 84.01 27 True
Neptun 30.06 164.80 14 True

Metoda drop, v souladu s výše zmíněnou konvencí, vrací nový DataFrame (a proto výsledek operace musíme přiřadit do planety). Pokud chceš operovat rovnou na tabulce, můžeš použít příkaz del (funguje stejně jako u slovníku) nebo poprosit pandí bohy (a autory těchto materiálů) o odpuštění a přidat argument inplace=True (tento argument lze, bohužel, použít i u mnoha dalších operací):

In [13]:
# Jen na vlastní nebezpečí

# Alternativa 1)
# del planety["je_planeta"]

# Alternativa 2)
# planety.drop("je_planeta", axis=1, inplace=True)

Datové typy

Příprava dat

Nyní opustíme planety a podíváme se na některé zajímavé charakteristiky zemí kolem světa (ježto definice toho, co je to země, je poněkud vágní, bereme v potaz členy OSN), zachycené k jednomu konkrétnímu roku uplynulé dekády (protože ne vždy jsou všechny údaje k dispozici, bereme poslední rok, kde je známo dost ukazatelů). Data pocházejí povětšinou z projektu Gapminder, doplnili jsme je jen o několik dalších informací z wikipedie.

Následující kód (nemusíš mu rozumět) stáhne potřebný soubor a uloží ho v místním adresáři. Alternativně ho můžeš stáhnout manuálně z https://raw.githubusercontent.com/janpipek/data-pro-pyladies/master/data/countries.csv.

In [14]:
# Nutné importy
import os
import requests

# Seznam souborů (viz níže)
zdroj = "https://raw.githubusercontent.com/janpipek/data-pro-pyladies/master/data/countries.csv"
jmeno = zdroj.rsplit("/")[-1]

if not os.path.exists(jmeno):
    print(f"Soubor {jmeno} ještě není stažen, jdeme na to...")
    response = requests.get(zdroj)
    with open(jmeno, "wb") as out:
        out.write(response.content)
    print(f"Soubor {jmeno} úspěšně stažen.")
else:
    print(f"Soubor {jmeno} už byl stažen, použijeme místní kopii.")
Soubor countries.csv už byl stažen, použijeme místní kopii.

A otevřeme ho pomocí již známé funkce read_csv (Poznámka: pandas umí otevřít soubor i přímo z internetu, ale raději použijeme místní kopii, aby ses mohl/a k práci vrátit i off-line).

In [15]:
# Místo `set_index` vybereme index rovnou při načítání
countries = pd.read_csv("countries.csv", index_col="name")

countries = countries.sort_index()
countries
Out[15]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Afghanistan AFG south_asia asia low_income False False NaN 2018 652860.0 34500000.0 0.03 20.62 21.07 NaN 2090.0 66.3 58.69 65.812 63.101 1946-11-19
Albania ALB europe_central_asia europe upper_middle_income False False NaN 2018 28750.0 3238000.0 7.29 26.45 25.66 5.978 3193.0 12.5 78.01 80.737 76.693 1955-12-14
Algeria DZA middle_east_north_africa africa upper_middle_income False False NaN 2018 2381740.0 36980000.0 0.69 24.60 26.37 NaN 3296.0 21.9 77.86 77.784 75.279 1962-10-08
Andorra AND europe_central_asia europe high_income False False NaN 2017 470.0 88910.0 10.17 27.63 26.43 NaN NaN 2.1 82.55 NaN NaN 1993-07-28
Angola AGO sub_saharan_africa africa upper_middle_income False False NaN 2018 1246700.0 20710000.0 5.57 22.25 23.48 NaN 2473.0 96.0 65.19 64.939 59.213 1976-12-01
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Venezuela VEN america americas upper_middle_income False False NaN 2018 912050.0 30340000.0 7.60 27.45 28.13 7.332 2631.0 12.9 75.91 79.079 70.950 1945-11-15
Vietnam VNM east_asia_pacific asia lower_middle_income False False NaN 2018 330967.0 90660000.0 3.91 20.92 21.07 NaN 2745.0 17.3 74.88 81.203 72.003 1977-09-20
Yemen YEM middle_east_north_africa asia lower_middle_income False False NaN 2018 527970.0 26360000.0 0.20 24.44 26.11 NaN 2223.0 33.8 67.14 66.871 63.875 1947-09-30
Zambia ZMB sub_saharan_africa africa lower_middle_income False False NaN 2018 752610.0 14310000.0 3.56 20.68 23.05 11.260 1930.0 43.3 59.45 65.362 59.845 1964-12-01
Zimbabwe ZWE sub_saharan_africa africa low_income False False NaN 2018 390760.0 13330000.0 4.96 22.03 24.65 20.850 2110.0 46.6 60.18 63.944 60.120 1980-08-25

193 rows × 20 columns

Namátkou si vybereme nějakou zemi a podíváme se, jaké údaje o ní v tabulce máme.

In [16]:
countries.loc["Czechia"]
Out[16]:
iso                                             CZE
world_6region                   europe_central_asia
world_4region                                europe
income_groups                           high_income
is_eu                                          True
is_oecd                                        True
eu_accession                             2004-05-01
year                                           2018
area                                          78870
population                                1.059e+07
alcohol_adults                                16.47
bmi_men                                       27.91
bmi_women                                     26.51
car_deaths_per_100000_people                   5.72
calories_per_day                               3256
infant_mortality                                2.8
life_expectancy                               79.37
life_expectancy_female                       81.858
life_expectancy_male                         76.148
un_accession                             1993-01-19
Name: Czechia, dtype: object

Už na první pohled je každé pole jiného typu. Ale jakého? Na to nám odpoví atribut dtypes naší tabulky (u Series použiješ dtype, resp. raději dtype.name, pokud chceš stejně pěknou řetězcovou reprezentaci).

In [17]:
countries.dtypes
Out[17]:
iso                              object
world_6region                    object
world_4region                    object
income_groups                    object
is_eu                              bool
is_oecd                            bool
eu_accession                     object
year                              int64
area                            float64
population                      float64
alcohol_adults                  float64
bmi_men                         float64
bmi_women                       float64
car_deaths_per_100000_people    float64
calories_per_day                float64
infant_mortality                float64
life_expectancy                 float64
life_expectancy_female          float64
life_expectancy_male            float64
un_accession                     object
dtype: object

Typy v pandas vycházejí z toho, jak je definuje knihovna numpy (obecně užitečná pro práci s numerickými poli a poskytující vektorové operace s rychlostí řádově vyšší než v Pythonu jako takovém). Ta potřebuje především vědět, jak alokovat pole pro prvky daného typu na to, aby mohly být seřazeny efektivně jeden za druhým, a tedy i kolik bajtů paměti každý zabírá. Kopíruje přitom "nativní" datové typy, které už můžeš znát z jiných jazyků (např. C)). Umístění v paměti je něco, co v Pythonu obvykle neřešíme, ale rychlé počítání se bez toho neobejde. My nepůjdeme do detailů, ale požadavek na rychlost se nám tu a tam vynoří a my budeme klást důraz na to, aby se operace prováděly na úrovni numpy a nikoliv v Pythonu.

Poněkud tajuplný systém typů v numpy (popsaný v dokumentaci) je naštěstí v pandas (mírně) zjednodušen a nabízí jen několik užitečných základních (rodin) typů, které si teď představíme.

Celá čísla (integers)

V Pythonu je pro celá čísla vyhrazen přesně jeden typ: int, který možňuje pracovat s libovolně velkými celými čísly (0, -58 nebo třeba 123456789012345678901234567890). V pandas se můžeš setkat s int8, int16, int32, int64, uint8, uint16, uint32 a uint64 - všechny mají stejné základní vlastnosti a každý z nich má jen určitý rozsah čísel, která do něj lze uložit. Liší se velikostí paměti, kterou jedno číslo zabere (číslovka v názvu vyjadřuje počet bitů), a tím, zda jsou podporována i záporná čísla (předpona u znamená unsigned (bez znaménka), tedy že počítáme pouze s nulou a kladnými čísly).

Rozsahy:

  • int8: -128 až 127
  • uint8: 0 až 255
  • int16: -32 768 až 32 767
  • uint16: 0 až 65 535
  • int32: -2 147 483 648 až 2 147 483 647 (tedy +/- ~2 miliardy)
  • uint32: 0 až 4 294 967 295 (tedy až ~4 miliardy)
  • int64: -9 223 372 036 854 775 808 až 9 223 372 036 854 775 807 (tedy +/- ~9 trilionů)
  • uint64: 0 až 18 446 744 073 709 551 615 (tedy až ~18 trilionů)

💡 Aby toho nebylo málo, ke každému int? / uint? typu existuje ještě jeho alternativa, která umožňuje ve sloupci použít chybějící hodnoty, t.j. NaN. Místo malého i, případně u v názvu se použije písmeno velké. Tato vlastnost (tzv. "nullable integer types") je relativně užitečná, ale je dosud poněkud experimentální. My ji nebudeme v kurzu využívat.

Detailní vysvětlení toho, jak jsou celá čísla v paměti počítače reprezentována, najdeš třeba ve wikipedii.

V pandas je výchozí celočíselný typ int64, a pokud neřekneš jinak, automaticky se pro celá čísla použije (ve většině případů to bude vhodná volba):

In [18]:
countries["year"]
Out[18]:
name
Afghanistan    2018
Albania        2018
Algeria        2018
Andorra        2017
Angola         2018
               ... 
Venezuela      2018
Vietnam        2018
Yemen          2018
Zambia         2018
Zimbabwe       2018
Name: year, Length: 193, dtype: int64
In [19]:
pd.Series([0, 123, 12345])

# pd.Series([0, 123, 12345], dtype="int64")   # totéž
Out[19]:
0        0
1      123
2    12345
dtype: int64

Pomocí argumentu dtype můžeš ovšem přesně specifikovat, který typ celých čísel chceš:

In [20]:
pd.Series([0, 123, 12345], dtype="int16")
Out[20]:
0        0
1      123
2    12345
dtype: int16

⚠ Pozor: Když vybíráš konkrétní celočíselný typ, musíš si dát pozor na rozsahy, protože pandas tě nebude varovat, pokud se nějaká z tvých hodnot do rozsahu "nevleze" a vesele zahodí tu část binární reprezentace, která je navíc a dostaneš mnohem menší číslo, než jsi čekal/a:

In [21]:
pd.Series([0, 123, 12345], dtype="int8")
Out[21]:
0      0
1    123
2     57
dtype: int8

Toto naštěstí neplatí pro typ s nejširším rozsahem (int64). Zkusme do něj vložit veliké číslo (třeba 123456789012345678901234567890) a uvidíme, co se stane:

In [22]:
# Toto vyhodí výjimku:
# pd.Series([0, 123, 123456789012345678901234567890], dtype="int64")

# Toto projde, ale už to není int64:
pd.Series([0, 123, 123456789012345678901234567890])
Out[22]:
0                                 0
1                               123
2    123456789012345678901234567890
dtype: object
  • Když ho budeme explicitně požadovat, vyhodí se výjimka.
  • Když pandas necháme dělat jeho práci, použije se obecný typ object a přijdeme o jistou část výhod: sloupec nám zabere násobně více paměti a aritmetické operace s ním jsou o řád až dva pomalejší. Pokud to není naší prioritou, není to zase takový problém.

Obecně proto doporučujeme držet se int64, resp. nechat pandas, aby jej za nás automaticky použil. Teprve v případě, že si to budou žádat přísné paměťové nároky, se ti vyplatí hledat ten "nejvíce růžový" typ.

Úkol: Zkus vytvořit Series s datovým typem uint8, obsahující (alespoň) jedno malé záporné číslo. Co se stane?

Čísla s plovoucí desetinnou čárkou (floats)

Podobně jako u celočíselných hodnot, i jednomu typu v Python (float) odpovídá několik typů v pandas: float16, float32, float64. Součástí názvu je opět počet bitů, které jedno číslo potřebuje ke svému uložení. Naštěstí v tomto případě float64 přesně odpovídá svým chováním float z Pythonu, zbylé dva typy nejsou tak přesné a mají menší rozsah - kromě optimalizace paměťových nároků u specifického druhu dat je nejspíš nepoužiješ.

Více teoretického čtení o reprezentaci čísel s desetinnou čárkou najdeš na wiki.

In [23]:
countries["bmi_men"]
Out[23]:
name
Afghanistan    20.62
Albania        26.45
Algeria        24.60
Andorra        27.63
Angola         22.25
               ...  
Venezuela      27.45
Vietnam        20.92
Yemen          24.44
Zambia         20.68
Zimbabwe       22.03
Name: bmi_men, Length: 193, dtype: float64
In [24]:
# Docela přesné pí
pd.Series([3.14159265])
Out[24]:
0    3.141593
dtype: float64
In [25]:
# Ne už tak přesné pí
pd.Series([3.14159265], dtype="float16")
Out[25]:
0    3.140625
dtype: float16

Úkol: Vytvoř pole typu float64 jen ze samých celých čísel. Co se stane?

Logické hodnoty (booleans)

Toto je asi nejméně překvapivý datový typ. Chová se v zásadě stejně jako typ bool v Pythonu. Nabírá hodnot True a False (které lze též pokládat za 1 a 0 v některých operacích). Má ještě jednu skvělou vlastnost - objekty Series i DataFrame jde filtrovat právě pomocí sloupce logického typu (o tom viz níže).

In [26]:
countries["is_oecd"].iloc[:20]
Out[26]:
name
Afghanistan            False
Albania                False
Algeria                False
Andorra                False
Angola                 False
Antigua and Barbuda    False
Argentina              False
Armenia                False
Australia               True
Austria                 True
Azerbaijan             False
Bahamas                False
Bahrain                False
Bangladesh             False
Barbados               False
Belarus                False
Belgium                 True
Belize                 False
Benin                  False
Bhutan                 False
Name: is_oecd, dtype: bool
In [27]:
# Vytvoření nového sloupce
pd.Series([True, False, False])
Out[27]:
0     True
1    False
2    False
dtype: bool

Jde to ovšem i takto:

In [28]:
pd.Series([1, 0, 0], dtype="bool")
Out[28]:
0     True
1    False
2    False
dtype: bool

Úkol: Co se stane, když vytvoříš Series typu bool z řetězců "True" a "False" (nezapomeň na uvozovky)?

Řetězce a obecné objekty (strings, objects)

Aktuální verze knihovny pandas (1.2) má k řetězcům poněkud schizofrenní postoj, respektive je v procesu přechodu od ne úplně šťastného přístupu (obecný datový typ object) k o něco lepšímu (speciální typ string) - v dokumentaci se doporučuje používat přístup druhý, přestože to je zároveň označeno za experimentální. Rozdíl je v současnosti víceméně estetický (a my z pohodlnosti obvykle nebudeme sloupce na string převádět).

In [29]:
countries["iso"]
Out[29]:
name
Afghanistan    AFG
Albania        ALB
Algeria        DZA
Andorra        AND
Angola         AGO
              ... 
Venezuela      VEN
Vietnam        VNM
Yemen          YEM
Zambia         ZMB
Zimbabwe       ZWE
Name: iso, Length: 193, dtype: object

Toto tě pravděpodobně překvapí - ve výchozím stavu řetězce spadají společně s dalšími neurčenými nebo nerozpoznanými hodnotami do kategorie object, která umožňuje v daném sloupci mít cokoliv, co znáš z Pythonu, a chová se tak do značné míry jako obyčejný seznam s výhodami (žádné podivné konverze, sledování rozsahů, ...) i nevýhodami (je to pomalejší, než by mohlo; nikdo ti nezaručí, že ve sloupci budou jen řetězce).

Budeš-li chtít být explicitní či získat navíc trochu typové kontroly, můžeš datový typ string uvést v konstuktoru, případně konvertovat sloupec pomocí metody astype:

In [30]:
# countries["iso"].astype("string")

# Domácí mazlíčci
mazlicci = pd.Series(
    ["pes", "kočka", "křeček", "tarantule", "hroznýš"],
    dtype="string"
)
mazlicci
Out[30]:
0          pes
1        kočka
2       křeček
3    tarantule
4      hroznýš
dtype: string
In [31]:
# mazlicci[0] = 42  # Chyba

Datový typ objekt je jedinou možností v případě, že máme v Series heterogenní data:

In [32]:
pd.Series([1, "dvě", 3.0])   # Řetězec a další "smetí"
Out[32]:
0      1
1    dvě
2      3
dtype: object

Pozor, třeba i takový seznam může být hodnotou v sloupci typu object:

In [33]:
# Objednávky
pd.Series(
    [["řízek", "brambory", "cola"], ["smažák", "hranolky"], ["sodovka"]],
    index=["Eva", "Evelína", "Evženie"])
Out[33]:
Eva        [řízek, brambory, cola]
Evelína         [smažák, hranolky]
Evženie                  [sodovka]
dtype: object

Úkol: Co za druh objektu (a jaký dtype) dostaneme, když se pokusíme získat jeden řádek z tabulky planety?

Úkol: Co se stane, když sloupec planety["obezna_doba"] převedeš na object, resp. string?

Datum / čas (datetime)

Časovými daty se blíže zabývá jedna z následujících lekcí, nicméně nějaká v tabulce zemí už máme, a tak alespoň pro úplnost uvedeme, co v tomto směru pandas nabízí:

  • Časové či datumové údaje (datetime) jakožto body na časové ose.

  • Časové údaje s označením časové zóny (datetimes with time zone).

  • Časové úseky (timedeltas) jakožto určení délky nějakého úseku (počítáno v nanosekundách)

  • Období (periods) udávají nějak určená časová období (třeba "únor 2020")

💡 Pro převod z nejrůznějších formátů na datum / čas slouží funkce to_datetime, kterou použijeme pro následující ukázku:

In [34]:
pd.to_datetime(countries["un_accession"])
Out[34]:
name
Afghanistan   1946-11-19
Albania       1955-12-14
Algeria       1962-10-08
Andorra       1993-07-28
Angola        1976-12-01
                 ...    
Venezuela     1945-11-15
Vietnam       1977-09-20
Yemen         1947-09-30
Zambia        1964-12-01
Zimbabwe      1980-08-25
Name: un_accession, Length: 193, dtype: datetime64[ns]

Kategorické (category)

Pokud chceme být efektivní při práci se sloupci, kde se často opakují hodnoty (zejména řetězcové), můžeme je zakódovat do kategorií. Tím mnohdy ušetříme zabrané místo a urychlíme některé operace. Při takové konverzi pandas najde všechny unikátní hodnoty v daném sloupci, uloží si je do zvláštního seznamu a do sloupce uloží jenom indexy z tohoto seznamu. Vše se chová transparentně a při používání tak většinou ani nepoznáte, jestli máte sloupec typu object nebo category.

💡 Pro převod mezi různými datovými typy slouží metoda astype, která jako svůj argument akceptuje jméno dtype, na který chceme převést:

In [35]:
countries["income_groups"].astype("category")
Out[35]:
name
Afghanistan             low_income
Albania        upper_middle_income
Algeria        upper_middle_income
Andorra                high_income
Angola         upper_middle_income
                      ...         
Venezuela      upper_middle_income
Vietnam        lower_middle_income
Yemen          lower_middle_income
Zambia         lower_middle_income
Zimbabwe                low_income
Name: income_groups, Length: 193, dtype: category
Categories (4, object): ['high_income', 'low_income', 'lower_middle_income', 'upper_middle_income']

Úkol: Napadne tě, které sloupce z tabulky countries bychom měli překonvertovat na nějaký jiný typ?

Matematika

Počítání se Series v pandas je navrženo tak, aby co nejméně překvapilo. Jednotlivé sloupce se tak můžou stát součástí aritmetických výrazů společně se skalárními hodnotami, s jinými sloupci, numpy poli příslušného tvaru, a dokonce i seznamy.

In [36]:
# Očekávaná doba života ve dnech
countries["life_expectancy"] * 365
Out[36]:
name
Afghanistan    21421.85
Albania        28473.65
Algeria        28418.90
Andorra        30130.75
Angola         23794.35
                 ...   
Venezuela      27707.15
Vietnam        27331.20
Yemen          24506.10
Zambia         21699.25
Zimbabwe       21965.70
Name: life_expectancy, Length: 193, dtype: float64
In [37]:
# Hustota obyvatelstva
countries["population"] / countries["area"]
Out[37]:
name
Afghanistan     52.844408
Albania        112.626087
Algeria         15.526464
Andorra        189.170213
Angola          16.611855
                  ...    
Venezuela       33.265720
Vietnam        273.924591
Yemen           49.927079
Zambia          19.013832
Zimbabwe        34.113011
Length: 193, dtype: float64
In [38]:
# Jak nám podražily obědy
pd.Series([109, 99], index=["řízek", "smažák"]) + [20.9, 10.9]   # sčítání se seznamem
Out[38]:
řízek     129.9
smažák    109.9
dtype: float64

Úkol: Spočti celkový počet mrtvých v automobilových haváriích v jednotlivých zemích (použij sloupce "population" a "car_deaths_per_100000_people" a jednoduchou aritmetiku). Sedí výsledek pro ČR?

In [39]:
# Jak dlouho jsou v OSN?
from datetime import datetime
datetime.now() - pd.to_datetime(countries["un_accession"])
Out[39]:
name
Afghanistan   27150 days 13:13:55.741167
Albania       23838 days 13:13:55.741167
Algeria       21348 days 13:13:55.741167
Andorra       10097 days 13:13:55.741167
Angola        16180 days 13:13:55.741167
                         ...            
Venezuela     27519 days 13:13:55.741167
Vietnam       15887 days 13:13:55.741167
Yemen         26835 days 13:13:55.741167
Zambia        20563 days 13:13:55.741167
Zimbabwe      14817 days 13:13:55.741167
Name: un_accession, Length: 193, dtype: timedelta64[ns]

💡 Čísla s plovoucí desetinnou čárkou mohou obsahovat i speciální hodnoty "not a number" a plus nebo mínus nekonečno. Vzniknou např. při nevhodném dělení nulou:

In [40]:
pd.Series([0, -1, 1]) / pd.Series([0, 0, 0])
Out[40]:
0    NaN
1   -inf
2    inf
dtype: float64

Varování: Nabádáme tě k opatrnosti při práci s omezenými celočíselnými typy. Podobně jako při jejich nevhodné konverzi, i tady může výsledek takzvaně přetéct a ukazovat pochybné výsledky. O důvod víc, proč se držet int64.

In [41]:
pd.Series([7, 14, 149], dtype="int8") * 2
Out[41]:
0    14
1    28
2    42
dtype: int8

Porovnávání

Pro Series lze použít nejen operátory početní, ale také logické. Výsledkem pak není jedna logická hodnota, ale sloupec logických hodnot.

In [42]:
# 15 litrů čistého alkoholu na osobu na rok budeme považovat za hranici nadměrného pití
# (nekonzultováno s adiktology!)

# Kde se hodně pije?
countries["alcohol_adults"] > 15
Out[42]:
name
Afghanistan    False
Albania        False
Algeria        False
Andorra        False
Angola         False
               ...  
Venezuela      False
Vietnam        False
Yemen          False
Zambia         False
Zimbabwe       False
Name: alcohol_adults, Length: 193, dtype: bool
In [43]:
# Skoro nikde. A jak jsme na tom u nás?
countries.loc["Czechia", "alcohol_adults"] > 15
Out[43]:
True
In [44]:
# Jsou muži v jednotlivých zemích tlustší než ženy?
countries["bmi_men"] > countries["bmi_women"]
Out[44]:
name
Afghanistan    False
Albania         True
Algeria        False
Andorra         True
Angola         False
               ...  
Venezuela      False
Vietnam        False
Yemen          False
Zambia         False
Zimbabwe       False
Length: 193, dtype: bool

Úkol: Zjistěte, jestli se v jednotlivých zemích dožívají více muži nebo ženy.

In [45]:
# Leží země v Africe?
countries["world_4region"] == "africa"
Out[45]:
name
Afghanistan    False
Albania        False
Algeria         True
Andorra        False
Angola          True
               ...  
Venezuela      False
Vietnam        False
Yemen          False
Zambia          True
Zimbabwe        True
Name: world_4region, Length: 193, dtype: bool

Podobně jako v Pythonu lze podmínky kombinovat pomocí operátorů. Vzhledem k jistým syntaktickým požadavkům Pythonu je ale potřeba použít místo vám známých logických operátorů jejich alternativy: & (místo and), | (místo or) a ~ (místo not). Protože mají jiné priority než jejich klasičtí bratříčci, bude lepší, když při kombinaci s jinými operátory vždycky použiješ závorky.

In [46]:
# Kde se ženy i muži dožívají přes 75 let?
(countries["life_expectancy_male"] > 75) & (countries["life_expectancy_female"] > 75)
Out[46]:
name
Afghanistan    False
Albania         True
Algeria         True
Andorra        False
Angola         False
               ...  
Venezuela      False
Vietnam        False
Yemen          False
Zambia         False
Zimbabwe       False
Length: 193, dtype: bool

Filtrování

Pokud chceš z tabulky vybrat řádky, které splňují nějaké kritérium, musíš (není to vždy těžké :-)) toto kritérium převést do podoby sloupce logických hodnot. Potom tento sloupec (sloupec samotný, nikoliv jeho název!) vložíš do hranatých závorek jako index DataFrame.

Když budeš například chtít informace jen o členech EU, můžeš k tomu přímo použít sloupec "is_eu", který logické hodnoty obsahuje:

In [47]:
countries[countries["is_eu"]]
Out[47]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Austria AUT europe_central_asia europe high_income True True 1995-01-01 2018 83879.0 8441000.0 12.40 26.47 25.09 3.541 3768.0 2.9 81.84 84.249 79.585 1955-12-14
Belgium BEL europe_central_asia europe high_income True True 1952-07-23 2018 30530.0 10820000.0 10.41 26.76 25.14 5.427 3733.0 3.3 81.23 83.751 79.131 1945-12-27
Bulgaria BGR europe_central_asia europe upper_middle_income True False 2007-01-01 2018 111000.0 7349000.0 11.40 26.54 25.52 9.662 2829.0 9.3 75.32 78.485 71.618 1955-12-14
Croatia HRV europe_central_asia europe high_income True False 2013-01-01 2018 56590.0 4379000.0 15.00 26.60 25.18 6.434 3059.0 3.6 77.66 81.167 74.701 1992-05-22
Cyprus CYP europe_central_asia europe high_income True False 2004-05-01 2018 9250.0 1141000.0 8.84 27.42 25.93 6.419 2649.0 2.5 80.79 82.918 78.734 1960-09-20
Czechia CZE europe_central_asia europe high_income True True 2004-05-01 2018 78870.0 10590000.0 16.47 27.91 26.51 5.720 3256.0 2.8 79.37 81.858 76.148 1993-01-19
Denmark DNK europe_central_asia europe high_income True True 1973-01-01 2018 42922.0 5611000.0 12.02 26.13 25.11 3.481 3367.0 2.9 81.10 82.878 79.130 1945-10-24
Estonia EST europe_central_asia europe high_income True True 2004-05-01 2018 45230.0 1339000.0 17.24 26.26 25.19 5.896 3253.0 2.3 77.66 82.111 73.201 1991-09-17
Finland FIN europe_central_asia europe high_income True True 1995-01-01 2018 338420.0 5419000.0 13.10 26.73 25.58 3.615 3368.0 1.9 82.06 84.423 78.934 1955-12-14
France FRA europe_central_asia europe high_income True True 1952-07-23 2018 549087.0 63780000.0 12.48 25.85 24.83 2.491 3482.0 3.5 82.62 85.747 79.991 1945-10-24
Germany DEU europe_central_asia europe high_income True True 1952-07-23 2018 357380.0 81800000.0 12.14 27.17 25.74 3.280 3499.0 3.1 81.25 83.632 79.060 1973-09-18
Greece GRC europe_central_asia europe high_income True True 1981-01-01 2018 131960.0 11450000.0 11.01 26.34 24.92 9.175 3400.0 3.6 81.34 84.071 79.129 1945-10-25
Hungary HUN europe_central_asia europe upper_middle_income True True 2004-05-01 2018 93030.0 9934000.0 16.12 27.12 25.98 5.234 3037.0 5.3 75.90 79.557 72.610 1955-12-14
Ireland IRL europe_central_asia europe high_income True True 1973-01-01 2018 70280.0 4631000.0 14.92 27.65 26.62 3.768 3600.0 3.0 81.49 83.737 79.885 1955-12-14
Italy ITA europe_central_asia europe high_income True True 1952-07-23 2018 301340.0 61090000.0 9.72 26.48 24.79 3.778 3579.0 2.9 82.62 85.435 81.146 1955-12-14
Latvia LVA europe_central_asia europe high_income True True 2004-05-01 2018 64490.0 2226000.0 13.45 26.46 25.62 8.275 3174.0 6.9 75.13 79.498 69.882 1991-09-17
Lithuania LTU europe_central_asia europe high_income True True 2004-05-01 2018 65286.0 3278000.0 16.30 26.86 26.01 8.090 3417.0 3.3 75.31 80.060 69.554 1991-09-17
Luxembourg LUX europe_central_asia europe high_income True True 1952-07-23 2018 2590.0 530000.0 12.84 27.43 26.09 5.971 3539.0 1.5 82.39 84.227 79.981 1945-10-24
Malta MLT europe_central_asia europe high_income True False 2004-05-01 2018 320.0 420600.0 4.10 27.68 27.05 2.228 3378.0 5.1 81.75 82.724 79.570 1964-12-01
Netherlands NLD europe_central_asia europe high_income True True 1952-07-23 2018 41540.0 16760000.0 9.75 26.02 25.47 2.237 3228.0 3.2 81.92 83.841 80.440 1945-12-10
Poland POL europe_central_asia europe high_income True True 2004-05-01 2018 312680.0 38330000.0 14.43 26.67 25.92 7.675 3451.0 4.5 78.19 81.732 74.043 1945-10-24
Portugal PRT europe_central_asia europe high_income True True 1986-01-01 2018 92225.0 10700000.0 13.89 26.68 26.18 5.078 3477.0 3.0 81.30 84.372 78.685 1955-12-14
Romania ROU europe_central_asia europe upper_middle_income True False 2007-01-01 2018 238390.0 21340000.0 16.15 25.41 25.22 8.808 3358.0 9.7 75.53 79.158 72.265 1955-12-14
Slovakia SVK europe_central_asia europe high_income True True 2004-05-01 2018 49035.0 5489000.0 13.31 26.93 26.32 6.746 2944.0 5.8 77.16 80.511 73.589 1993-01-19
Slovenia SVN europe_central_asia europe high_income True True 2004-05-01 2018 20270.0 2045000.0 14.94 27.44 26.58 5.315 3168.0 2.1 81.12 84.017 78.499 1992-05-22
Spain ESP europe_central_asia europe high_income True True 1986-01-01 2018 505940.0 47040000.0 11.83 27.50 26.31 5.146 3174.0 3.5 83.23 86.119 80.694 1955-12-14
Sweden SWE europe_central_asia europe high_income True True 1995-01-01 2018 447420.0 9546000.0 9.50 26.38 25.15 2.737 3179.0 2.4 82.37 84.443 81.126 1946-11-19
United Kingdom GBR europe_central_asia europe high_income True True 1973-01-01 2018 243610.0 63180000.0 13.24 27.39 26.94 3.377 3424.0 3.5 81.19 83.558 80.127 1945-10-24

Nemusíš použít existující sloupec v tabulce, ale i jakoukoliv vypočítanou hodnotu stejného tvaru:

In [48]:
# Prťavé země
countries[countries["population"] < 100_000]   # Podtržítko pomáhá oddělit tisíce vizuálně
Out[48]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Andorra AND europe_central_asia europe high_income False False NaN 2017 470.0 88910.0 10.17 27.63 26.43 NaN NaN 2.10 82.55 NaN NaN 1993-07-28
Antigua and Barbuda ATG america americas high_income False False NaN 2018 440.0 91400.0 8.17 25.77 27.51 NaN 2417.0 5.80 77.60 79.028 74.154 1981-11-11
Dominica DMA america americas upper_middle_income False False NaN 2017 750.0 67700.0 8.68 24.57 28.78 NaN 2931.0 19.60 73.01 NaN NaN 1978-12-18
Liechtenstein LIE europe_central_asia europe high_income False False NaN 2017 160.0 36870.0 NaN NaN NaN NaN NaN 1.76 NaN NaN NaN 1990-09-18
Marshall Islands MHL east_asia_pacific asia upper_middle_income False False NaN 2017 180.0 56690.0 NaN 29.37 31.39 1.800 NaN 29.60 65.00 NaN NaN 1991-09-17
Monaco MCO europe_central_asia europe high_income False False NaN 2017 2.0 35460.0 NaN NaN NaN NaN NaN 2.80 NaN NaN NaN 1993-05-28
Nauru NRU east_asia_pacific asia NaN False False NaN 2015 20.0 10440.0 4.81 33.90 35.02 NaN NaN 29.10 NaN NaN NaN 1999-09-14
Palau PLW east_asia_pacific asia upper_middle_income False False NaN 2017 460.0 20920.0 9.86 30.38 31.85 10.730 NaN 14.20 NaN NaN NaN 1994-12-15
Saint Kitts and Nevis KNA america americas high_income False False NaN 2017 260.0 54340.0 10.62 28.23 30.51 NaN 2492.0 8.40 NaN NaN NaN 1983-09-23
San Marino SMR europe_central_asia europe high_income False False NaN 2017 60.0 32160.0 NaN NaN NaN 5.946 NaN 2.60 NaN NaN NaN 1992-03-02
Seychelles SYC sub_saharan_africa africa upper_middle_income False False NaN 2018 460.0 87420.0 12.11 25.56 27.97 11.700 NaN 11.70 74.23 78.730 69.693 1976-09-21
Tuvalu TUV east_asia_pacific asia upper_middle_income False False NaN 2017 30.0 9888.0 2.14 NaN NaN NaN NaN 22.80 NaN NaN NaN 2000-09-05

...a samozřejmě kombinace:

In [49]:
# Chudší země EU
countries[countries["is_eu"] & (countries["income_groups"] != "high_income")]
Out[49]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Bulgaria BGR europe_central_asia europe upper_middle_income True False 2007-01-01 2018 111000.0 7349000.0 11.40 26.54 25.52 9.662 2829.0 9.3 75.32 78.485 71.618 1955-12-14
Hungary HUN europe_central_asia europe upper_middle_income True True 2004-05-01 2018 93030.0 9934000.0 16.12 27.12 25.98 5.234 3037.0 5.3 75.90 79.557 72.610 1955-12-14
Romania ROU europe_central_asia europe upper_middle_income True False 2007-01-01 2018 238390.0 21340000.0 16.15 25.41 25.22 8.808 3358.0 9.7 75.53 79.158 72.265 1955-12-14
In [50]:
# Které země OECD mají očekávanou dobu dožití méně 78 let?
countries[countries["is_oecd"] & (countries["life_expectancy"] < 78)]
Out[50]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Estonia EST europe_central_asia europe high_income True True 2004-05-01 2018 45230.0 1339000.0 17.24 26.26 25.19 5.896 3253.0 2.3 77.66 82.111 73.201 1991-09-17
Hungary HUN europe_central_asia europe upper_middle_income True True 2004-05-01 2018 93030.0 9934000.0 16.12 27.12 25.98 5.234 3037.0 5.3 75.90 79.557 72.610 1955-12-14
Latvia LVA europe_central_asia europe high_income True True 2004-05-01 2018 64490.0 2226000.0 13.45 26.46 25.62 8.275 3174.0 6.9 75.13 79.498 69.882 1991-09-17
Lithuania LTU europe_central_asia europe high_income True True 2004-05-01 2018 65286.0 3278000.0 16.30 26.86 26.01 8.090 3417.0 3.3 75.31 80.060 69.554 1991-09-17
Mexico MEX america americas upper_middle_income False True NaN 2018 1964380.0 117500000.0 8.55 27.42 28.74 9.468 3072.0 11.3 76.78 79.880 75.120 1945-11-07
Slovakia SVK europe_central_asia europe high_income True True 2004-05-01 2018 49035.0 5489000.0 13.31 26.93 26.32 6.746 2944.0 5.8 77.16 80.511 73.589 1993-01-19

Protože tento způsob filtrování je poněkud nešikovný, existuje ještě metoda query, která umožňuje vybírat řádky na základě řetězce, který popisuje nějakou (ne)rovnost z názvů sloupců a číselných hodnot (což poměrně často jde, někdy ovšem nemusí).

In [51]:
# Opravdu veliké země (počet obyvatel nad 100 milionů)
countries.query("population > 100_000_000")
Out[51]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Bangladesh BGD south_asia asia low_income False False NaN 2018 147630.0 1.544000e+08 0.17 20.40 20.55 4.401 2450.0 30.7 73.41 74.937 71.484 1974-09-17
Brazil BRA america americas upper_middle_income False False NaN 2018 8515770.0 2.001000e+08 10.08 25.79 25.99 1.872 3263.0 14.6 75.70 79.527 72.340 1945-10-24
China CHN east_asia_pacific asia upper_middle_income False False NaN 2018 9562911.0 1.359000e+09 5.56 22.92 22.91 3.590 3108.0 9.2 76.92 78.163 75.096 1945-10-24
India IND south_asia asia lower_middle_income False False NaN 2018 3287259.0 1.275000e+09 2.69 20.96 21.31 3.034 2459.0 37.9 69.10 70.678 67.538 1945-10-30
Indonesia IDN east_asia_pacific asia lower_middle_income False False NaN 2018 1910931.0 2.472000e+08 0.56 21.86 22.99 1.232 2777.0 22.8 72.03 71.742 67.426 1950-09-28
Japan JPN east_asia_pacific asia high_income False True NaN 2018 377962.0 1.263000e+08 7.79 23.50 21.87 1.381 2726.0 2.0 84.17 87.244 80.803 1956-12-18
Mexico MEX america americas upper_middle_income False True NaN 2018 1964380.0 1.175000e+08 8.55 27.42 28.74 9.468 3072.0 11.3 76.78 79.880 75.120 1945-11-07
Nigeria NGA sub_saharan_africa africa lower_middle_income False False NaN 2018 923770.0 1.709000e+08 12.72 23.03 23.67 NaN 2700.0 69.4 66.14 55.158 53.512 1960-10-07
Pakistan PAK south_asia asia lower_middle_income False False NaN 2018 796100.0 1.832000e+08 0.05 22.30 23.45 NaN 2440.0 65.8 67.96 67.869 65.750 1947-09-30
Russia RUS europe_central_asia europe high_income False False NaN 2018 17098250.0 1.426000e+08 16.23 26.01 27.21 14.380 3361.0 8.2 71.07 76.882 65.771 1945-10-24
United States USA america americas high_income False True NaN 2018 9831510.0 3.185000e+08 9.70 28.46 28.34 9.523 3682.0 5.6 79.14 81.942 77.429 1945-10-24
In [52]:
# V kterých zemích EU se hodně jí?
countries.query("is_eu & (calories_per_day > 3500)")
Out[52]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Austria AUT europe_central_asia europe high_income True True 1995-01-01 2018 83879.0 8441000.0 12.40 26.47 25.09 3.541 3768.0 2.9 81.84 84.249 79.585 1955-12-14
Belgium BEL europe_central_asia europe high_income True True 1952-07-23 2018 30530.0 10820000.0 10.41 26.76 25.14 5.427 3733.0 3.3 81.23 83.751 79.131 1945-12-27
Ireland IRL europe_central_asia europe high_income True True 1973-01-01 2018 70280.0 4631000.0 14.92 27.65 26.62 3.768 3600.0 3.0 81.49 83.737 79.885 1955-12-14
Italy ITA europe_central_asia europe high_income True True 1952-07-23 2018 301340.0 61090000.0 9.72 26.48 24.79 3.778 3579.0 2.9 82.62 85.435 81.146 1955-12-14
Luxembourg LUX europe_central_asia europe high_income True True 1952-07-23 2018 2590.0 530000.0 12.84 27.43 26.09 5.971 3539.0 1.5 82.39 84.227 79.981 1945-10-24

Úkol: Která jediná země Afriky patří do skupiny s vysokými příjmy?

Úkol: Ve kterých zemích se pije opravdu hodně (použij výše uvedené nebo jakékoliv jiné kritérium)

Řazení

V úvodní lekci pandas jsme si již ukázali, jak pomocí metody sort_index seřadit řádky podle indexu. Jelikož countries už jsou srovnané, vyzkoušíme si to ještě jednou na planetách:

In [53]:
planety.sort_index()
Out[53]:
symbol obezna_poloosa obezna_doba mesice je_obr
jmeno
Jupiter 5.20 11.86 79 True
Mars 1.52 1.88 2 False
Merkur 0.39 0.24 0 False
Neptun 30.06 164.80 14 True
Saturn 9.54 29.46 82 True
Uran 19.22 84.01 27 True
Venuše 0.72 0.62 0 False
Země 1.00 1.00 1 False

Pro řazení hodnot v Series se použije metoda sort_values:

In [54]:
# 10 zemí s nejmenším počtem obyvatel
countries["population"].sort_values().head(10)
Out[54]:
name
Tuvalu                    9888.0
Nauru                    10440.0
Palau                    20920.0
San Marino               32160.0
Monaco                   35460.0
Liechtenstein            36870.0
Saint Kitts and Nevis    54340.0
Marshall Islands         56690.0
Dominica                 67700.0
Seychelles               87420.0
Name: population, dtype: float64

Nepovinný argument ascending říká, kterým směrem máme řadit. Výchozí hodnota je True, změnou na False tedy budeme řadit od největšího k nejmenšímu:

In [55]:
# Největších 10 zemí podle rozlohy
countries["area"].sort_values(ascending=False).head(10)
Out[55]:
name
Russia           17098250.0
Canada            9984670.0
United States     9831510.0
China             9562911.0
Brazil            8515770.0
Australia         7741220.0
India             3287259.0
Argentina         2780400.0
Kazakhstan        2724902.0
Algeria           2381740.0
Name: area, dtype: float64

V případě tabulky je třeba jako první argument uvést jméno sloupce (nebo sloupců), podle kterých chceme řadit:

In [56]:
# 10 zemí s největší spotřebou alkoholu na jednoho obyvatele
countries.sort_values("alcohol_adults", ascending=False).head(10)
Out[56]:
iso world_6region world_4region income_groups is_eu is_oecd eu_accession year area population alcohol_adults bmi_men bmi_women car_deaths_per_100000_people calories_per_day infant_mortality life_expectancy life_expectancy_female life_expectancy_male un_accession
name
Moldova MDA europe_central_asia europe lower_middle_income False False NaN 2018 33850.0 3496000.0 23.01 24.24 27.06 5.529 2714.0 13.6 72.41 76.090 67.544 1992-03-02
South Korea KOR east_asia_pacific asia high_income False True NaN 2018 100280.0 48770000.0 19.15 23.99 23.33 4.319 3334.0 2.9 81.35 85.467 79.456 1991-09-17
Belarus BLR europe_central_asia europe upper_middle_income False False NaN 2018 207600.0 9498000.0 18.85 26.16 26.64 8.454 3250.0 3.4 73.76 78.583 67.693 1945-10-24
North Korea PRK east_asia_pacific asia low_income False False NaN 2018 120540.0 24650000.0 18.28 22.02 21.25 NaN 2094.0 19.7 71.13 75.512 68.450 1991-09-17
Ukraine UKR europe_central_asia europe lower_middle_income False False NaN 2018 603550.0 44700000.0 17.47 25.42 26.23 8.771 3138.0 7.7 72.29 77.067 67.246 1945-10-24
Estonia EST europe_central_asia europe high_income True True 2004-05-01 2018 45230.0 1339000.0 17.24 26.26 25.19 5.896 3253.0 2.3 77.66 82.111 73.201 1991-09-17
Czechia CZE europe_central_asia europe high_income True True 2004-05-01 2018 78870.0 10590000.0 16.47 27.91 26.51 5.720 3256.0 2.8 79.37 81.858 76.148 1993-01-19
Uganda UGA sub_saharan_africa africa low_income False False NaN 2018 241550.0 36760000.0 16.40 22.36 22.48 13.690 2130.0 37.7 62.86 62.667 58.252 1962-10-25
Lithuania LTU europe_central_asia europe high_income True True 2004-05-01 2018 65286.0 3278000.0 16.30 26.86 26.01 8.090 3417.0 3.3 75.31 80.060 69.554 1991-09-17
Russia RUS europe_central_asia europe high_income False False NaN 2018 17098250.0 142600000.0 16.23 26.01 27.21 14.380 3361.0 8.2 71.07 76.882 65.771 1945-10-24

💡 V následující buňce je celý kód uzavřen do závorky. Umožnili jsme si tím roztáhnout jeden výraz na více řádků, abychom jeho části mohli náležitě okomentovat.

In [57]:
(
    # Uvažuj jenom EU
    countries[countries["is_eu"]]
    
    # Seřaď nejdřív podle data vstupu do EU, pak podle vstupu do OSN
    .sort_values(["eu_accession", "un_accession"])

    # Zobraz si jen ty dva sloupce
    [["eu_accession", "un_accession"]]
)
Out[57]:
eu_accession un_accession
name
France 1952-07-23 1945-10-24
Luxembourg 1952-07-23 1945-10-24
Netherlands 1952-07-23 1945-12-10
Belgium 1952-07-23 1945-12-27
Italy 1952-07-23 1955-12-14
Germany 1952-07-23 1973-09-18
Denmark 1973-01-01 1945-10-24
United Kingdom 1973-01-01 1945-10-24
Ireland 1973-01-01 1955-12-14
Greece 1981-01-01 1945-10-25
Portugal 1986-01-01 1955-12-14
Spain 1986-01-01 1955-12-14
Sweden 1995-01-01 1946-11-19
Austria 1995-01-01 1955-12-14
Finland 1995-01-01 1955-12-14
Poland 2004-05-01 1945-10-24
Hungary 2004-05-01 1955-12-14
Cyprus 2004-05-01 1960-09-20
Malta 2004-05-01 1964-12-01
Estonia 2004-05-01 1991-09-17
Latvia 2004-05-01 1991-09-17
Lithuania 2004-05-01 1991-09-17
Slovenia 2004-05-01 1992-05-22
Czechia 2004-05-01 1993-01-19
Slovakia 2004-05-01 1993-01-19
Bulgaria 2007-01-01 1955-12-14
Romania 2007-01-01 1955-12-14
Croatia 2013-01-01 1992-05-22

Úkol: Seřaď země světa podle hustoty obyvatel.

Úkol: Které země mají problémy s nadváhou (průměrné BMI mužů a žen je přes 25)?

Úkol: V kterých 20 zemích umře absolutně nejvíc lidí při automobilových haváriích?

Ulož výsledky!

A tím už pomalu končíme. Jenže jsme udělali (skoro) netriviální množství práce a ta bude do příště ztracená. Naštěstí zapsat DataFrame do externího souboru v některém z typických formátů není vůbec komplikované. K sadě funkcí pd.read_XXX existují jejich protějšky DataFrame.to_XXX. Liší se různými parametry, ale základní použití je velmi jednoduché:

In [58]:
planety.to_csv("planety.csv")
In [59]:
planety.to_excel("planety.xlsx")

Excel ani CSV nejsou formáty pro ukládání velikých dat zcela vhodné (jako alternativy se nabízí třeba feather nebo parquet), pro naše účely (malé soubory, čitelný textový formát) ale budou CSV postačovat.

Jednou z možností je i vytvoření HTML tabulky (které lze dodat i různé formátování, což ovšem nechme raději na jindy nebo na doma, viz dokumentace "Styling"):

In [60]:
planety.to_html("planety.html")

Úkol: Podívej se, co ve výstupních souborech najdeš.

Úkol: Podívej se na seznam možných výstupních formátů a zkus si planety nebo země zapsat do nějakého z nich: https://pandas.pydata.org/pandas-docs/stable/reference/frame.html#serialization-io-conversion

A to už je opravdu všechno. 👋


Toto je stránka lekce z kurzu, který probíhá nebo proběhl naživo s instruktorem.