Obsah
1. Knihovna Polars: výkonnější alternativa ke knihovně Pandas
2. Je Polars skutečně plnohodnotnou náhradou ke knihovně Pandas?
4. Instalace dalších podpůrných balíčků
5. Základy práce s datovými řadami (serie)
6. Odlišné datové typy prvků datových řad
7. Speciální způsoby uložení pravdivostních hodnot, řetězců a chybějících hodnot
8. Explicitní specifikace typů prvků
9. Výběr prvků s využitím indexu, indexů a řezů
10. Výběr prvků s využitím filtru (podmínky)
12. Načtení datových rámců ze souborů typu CSV
13. Práce s CSV soubory bez hlavičky
14. Načtení datových rámců ze souborů typu TSV
15. Získání metainformací o datovém rámci
16. Načtení dat obsahujících časová razítka
17. Načtení datových rámců z relační databáze
18. Složitější dotaz s klauzulí WHERE, popř. ORDER BY
19. Repositář s demonstračními příklady
1. Knihovna Polars: výkonnější alternativa ke knihovně Pandas
Knihovna Pandas, s níž jsme se na stránkách Roota seznámili v tomto seriálu, se v ekosystému programovacího jazyka Python poměrně často používá, a to například v oblasti zpracování dat a datových analýz. Mezi nectnosti této knihovny patří nižší výpočetní výkon i fakt, že zpracovávané datové rámce (data frames) musí být uloženy v operační paměti, což omezuje celkový objem zpracovávaných dat. Poměrně nedávno však vznikla alternativa ke knihovně Pandas, která se jmenuje Polars, jež tyto problémy do jisté míry řeší. Název knihovny Polars naznačuje, že její výpočetní jádro je naprogramováno v jazyce Rust, ovšem celé rozhraní je určeno přímo pro Python (jen na několika místech se setkáme s označením datového typu pocházejícího z Rustu). V právě začínajícím miniseriálu se seznámíme se základními vlastnostmi ale i některými omezeními Polarsu.
2. Je Polars skutečně plnohodnotnou náhradou ke knihovně Pandas?
V ideálním světě by mělo být možné přejít z knihovny Pandas na knihovnu Polars jedinou změnou ve zdrojových kódech:
import pandas
by se nahradilo za:
import polars
Ve skutečnosti se však obě knihovny od sebe v několika ohledech liší. Především v knihovně Polars je možné jako indexy prvků v datových řadách, popř. jako indexy záznamů v datových rámcích použít pouze celá kladná čísla, zatímco v knihovně Pandas mohou být jak indexy použity například řetězce či dokonce složitější struktury. To vede k nutnosti změny programové logiky, což se mnohdy nevyplatí.
Dále některé funkce a metody v Polars (prozatím) nenajdeme, popř. je omezena jejich funkcionalita. To se týká i tak základních operací, jako je načítání dat ze souborů či z databází apod. Polars tedy může být dobrou alternativou, ale nikoli plnohodnotnou náhradou za knihovnu Pandas (minimálně v současnosti)
3. Instalace knihovny Polars
Knihovna Polars se skládá z několika zdrojových kódů napsaných v Pythonu (rozhraní) a z jedné nativní knihovny, jejíž původní zdrojové kódy jsou napsány v Rustu. Pro instalaci Polars však není zapotřebí mít překladač Rustu nainstalován. Instalaci provedeme standardně – nástrojem pip, resp. pip3:
$ pip3 install --user polars Collecting polars Downloading polars-0.15.11-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (14.6 MB) |████████████████████████████████| 14.6 MB 508 kB/s Collecting typing_extensions>=4.0.0; python_version < "3.10" Using cached typing_extensions-4.4.0-py3-none-any.whl (26 kB) Installing collected packages: typing-extensions, polars Successfully installed polars-0.15.11 typing-extensions-4.4.0
Po instalaci si zkontrolujeme, zda je balíček dostupný:
$ pip3 freeze | grep polars polars==0.15.11
Popř:
$ python3 Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import polars
Pro zajímavost: interní struktura nainstalovaného balíčku vypadá následovně:
. ├── internals │ ├── dataframe │ │ └── __pycache__ │ ├── expr │ │ └── __pycache__ │ ├── lazyframe │ │ └── __pycache__ │ ├── __pycache__ │ ├── series │ │ └── __pycache__ │ └── sql │ └── __pycache__ ├── __pycache__ └── testing └── __pycache__
Nejdůležitější je přitom nativní knihovna polars.abi3.so o velikosti přibližně 40MB, v níž je realizována naprostá většina funkcionality Polars:
$ ls -l polars.abi3.so -rwxrwxr-x 1 ptisnovs ptisnovs 44313016 Jan 4 11:29 polars.abi3.so
4. Instalace dalších podpůrných balíčků
Pro některé operace, například pro načítání datových rámců přímo z databází, je nutné mít nainstalovány i další podpůrné balíčky. Jedním z těchto balíčků je balíček nazvaný connectorx, jenž se nainstaluje snadno:
$ pip3 install --user connectorx>=0.3.1 Collecting connectorx Downloading connectorx-0.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (46.6 MB) Installing collected packages: connectorx Successfully installed connectorx-0.3.1
Dále je mnohdy zapotřebí nainstalovat i balíček pyarrow (o jehož významu se zmíníme příště):
$ pip3 install --user pyarrow Collecting pyarrow Downloading pyarrow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (36.0 MB) |████████████████████████████████| 36.0 MB 3.4 MB/s Collecting numpy>=1.16.6 Downloading numpy-1.24.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB) |████████████████████████████████| 17.3 MB 11.2 MB/s Installing collected packages: numpy, pyarrow Successfully installed numpy-1.24.1 pyarrow-10.0.1
V případě, že nebudou tyto tři balíčky nainstalovány, nebude možné spustit dnešní dva poslední příklady, které načítají data z relační databáze. Uvidíte přitom zhruba následující chybové hlášení:
File "data_frame_09_load_sql.py", line 11, in <module> df = polars.read_sql(query, connection_string) File "/home/ptisnovs/.local/lib/python3.8/site-packages/polars/io.py", line 1099, in read_sql raise ImportError( ImportError: connectorx is not installed. Please run `pip install connectorx>=0.3.1`.
5. Základy práce s datovými řadami (serie)
Základním stavebním kamenem knihovny Polars je datový typ Series (datová řada), který zapouzdřuje jednodimenzionální pole (nikoli seznam) s přidanými metainformacemi. Datová řada představuje uspořádaný sloupec údajů, které mají shodný typ (například polars.Int8 nebo polars.Float64 atd.), přičemž každému prvku je přiřazen jednoznačný index. V konkurenční knihovně Pandas se v případě indexu nemusí jednat o celé číslo, protože indexem mohou být i řetězce atd. – což je velmi užitečné, ovšem ne příliš rychlé a paměťově efektivní řešení. Z tohoto důvodu se v současné verzi knihovny Polars používají jako indexy pouze celá čísla začínající nulou a tvořící ucelenou aritmetickou posloupnost.
Instance třídy Series mají několik užitečných atributů:
# | Atribut | Stručný popis |
---|---|---|
1 | name | jméno řady (pokud je specifikováno) |
2 | dtype | typ prvků uložených v datové řadě |
3 | flags | informace, zda jsou prvky seřazeny či nikoli |
4 | shape | n-tice obsahující informaci o tvaru datové řady (obsahuje jedinou hodnotu s počtem prvků) |
Dále instance této třídy podporují několik desítek metod; s některými z nich se podrobněji seznámíme v dalším textu.
Nejprve si ukážeme, jakým způsobem se datové řady vytváří. Nejjednodušší je situace ve chvíli, kdy jsou hodnoty, které se mají převést na datovou řadu, připraveny ve formě seznamů nebo n-tic (ve smyslu základních datových typů programovacího jazyka Python). Z takto připravených hodnot se datová řada připraví přímočaře, a to konstruktorem polars.Series. Začneme využitím seznamu, který v našem konkrétním případě obsahuje sekvenci celých čísel, tedy prvků shodného datového typu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", [1, 2, 3, 4]) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledná datová řada i základní informace o ní se zobrazí v tomto formátu:
shape: (4,) Series: 'sloupec' [i64] [ 1 2 3 4 ] Column type Int64
Ve druhém demonstračním příkladu předáváme konstruktoru n-tici, proto se používá dvojice kulatých závorek:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", (1, 2, 3, 4)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledek by měl být totožný s prvním demonstračním příkladem:
shape: (4,) Series: 'sloupec' [i64] [ 1 2 3 4 ] Column type Int64
Pro konstrukci datové řady je však v případě potřeby možné použít i standardní generátor range, popř. podobné funkce z knihovny Numpy:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", range(10)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Opět se podívejme na výslednou datovou řadu, která by nyní měla obsahovat deset prvků:
Series: 'sloupec' [i64] [ 0 1 2 3 4 5 6 7 8 9 ] Column type Int64
Nic nám ovšem nebrání ani ve využití „generátorů řad“ poskytovaných knihovnou Numpy:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars import numpy as np # vytvoření datové řady s = polars.Series("sloupec", np.arange(10, 11, 0.1)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledek:
shape: (10,) Series: 'sloupec' [f64] [ 10.0 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 ] Column type Float64
Využití generátoru linspace je stejně snadné:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars import numpy as np # vytvoření datové řady s = polars.Series("sloupec", np.linspace(10, 11, 10)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledek:
shape: (10,) Series: 'sloupec' [f64] [ 10.0 10.111111 10.222222 10.333333 10.444444 10.555556 10.666667 10.777778 10.888889 11.0 ] Column type Float64
6. Odlišné datové typy prvků datových řad
V dalším demonstračním příkladu se pokoušíme vytvořit datovou řadu na základě obsahu seznamu, který obsahuje jak celá čísla, tak i hodnoty s plovoucí řádovou čárkou. Knihovna Polars by měla správně odvodit, že prvky datové řady budou typu polars.Float64:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", [1.2, 2, 3, 4.5]) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Z vypsaných výsledků je patrné, že se skutečně odvodil datový typ polars.Float64 (v hranaté závorce se přitom používá jméno typu f64 z Rustu, což na jednu stranu není příliš konzistentní, ale ke zmatkům by nemělo v praxi dojít):
shape: (4,) Series: 'sloupec' [f64] [ 1.2 2.0 3.0 4.5 ] Column type Float64
V následující tabulce jsou vypsány všechny datové typy, s nimiž je možné v datových řadách i v datových rámcích pracovat. S těmito typy se postupně seznámíme v dalším textu:
Datový typ | Stručný popis |
---|---|
Int8 | osmibitové celé číslo se znaménkem |
Int16 | šestnáctibitové celé číslo se znaménkem |
Int32 | 32bitové celé číslo se znaménkem |
Int64 | 64bitové celé číslo se znaménkem |
UInt8 | osmibitové celé číslo bez znaménka |
UInt16 | šestnáctibitové celé číslo bez znaménka |
UInt32 | 32bitové celé číslo bez znaménka |
UInt64 | 64bitové celé číslo bez znaménka |
Float32 | 32bitové číslo s plovoucí řádovou čárkou (single, float) |
Float64 | 64bitové číslo s plovoucí řádovou čárkou (double) |
Boolean | pravdivostní hodnoty uložené v úsporném formátu |
Utf8 | úsporně uložené řetězce |
Binary | sekvence bajtů |
List | seznamy, opět uložené úsporným způsobem |
Struct | datové struktury |
Object | atributy objektů |
Date | datum |
Time | čas v rámci jednoho dne reprezentovaný v nanosekundách |
Datetime | časové razítko |
Duration | časový úsek reprezentovaný v mikrosekundách |
7. Speciální způsoby uložení pravdivostních hodnot, řetězců a chybějících hodnot
Některé typy hodnot jsou v datové řadě (a taktéž v datovém rámci) uloženy specifickým způsobem tak, aby se buď snížila doba přístupu nebo (častěji) došlo k ušetření obsazené kapacity operační paměti.
Týká se to řetězců v datové řadě. Ve skutečnosti mohou být všechny řetězce uloženy do jediného bloku (jsou totiž neměnné) a datová řada bude pouze obsahovat offsety (celá čísla) a délky jednotlivých segmentů. To například znamená, že tři řetězce „foo“, „bar“ a „baz“ mohou být uloženy jako jediný řetězec „foobarbaz“ a v datové řadě se budou namísto objektů typu str ukládat pouze hodnoty 0, 3, 6 (offsety) a 3, 3, 3 (délky – toto je ovšem zjednodušený pohled, ve skutečnosti je to poněkud složitější). Nicméně z pohledu uživatele se s datovou řadou obsahující řetězce pracuje zcela běžným způsobem:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", ("foo", "bar", "baz")) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
A takto bude vypadat výsledná datová řada z pohledu uživatele:
shape: (3,) Series: 'sloupec' [str] [ "foo" "bar" "baz" ] Column type Utf8
Specificky uložené jsou taktéž datové řady obsahující pravdivostní hodnoty True a False. Z těchto hodnot je interně vytvořen bitový vektor, tj. vždy osm za sebou jdoucích hodnot je uloženo v jednom bajtu. Oproti uložení reference na objekty (přístup Pythonu) se tedy jedná o dosti výrazné ušetření kapacity paměti a i přístup k takovým hodnotám je rychlejší:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", (True, False, True)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledek získaný po spuštění skriptu:
shape: (3,) Series: 'sloupec' [bool] [ true false true ] Column type Boolean
Některé hodnoty v datové řadě mohou chybět. To představuje poměrně zajímavý problém, protože například celá čísla, pravdivostní hodnoty či řetězce nemají mechanismus pro reprezentaci chybějící hodnoty. V knihovně Polars je tento problém vyřešen tak, že se u datových řad, v nichž se mohou vyskytovat chybějící hodnoty, používá bitový vektor, který tyto hodnoty maskuje. Datová řada z následujícího příkladu by tedy byla uložena jako sekvence hodnot 1, 2, 100 (například), 3, 4 a bitový vektor 00100 (v knihovně Pandas je to řešeno speciálními datovými typy):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", [1, 2, None, 3, 4]) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledek:
shape: (5,) Series: 'sloupec' [i64] [ 1 2 null 3 4 ] Column type Int64
8. Explicitní specifikace typů prvků
Při načítání, resp. konstrukci datové řady je možné explicitně specifikovat, jakého typu mají být prvky v řadě uložené. Knihovna Polars se v takovém případě pokusí o provedení datových konverzí tak, aby požadavek splnila. Příkladem může být konverze všech celočíselných hodnot na typ Int8, tedy na celá čísla uložená v jediném bajtu (se znaménkem):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", [1, 2, 3, 4], polars.Int8) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print()
Výsledný datový rámec obsahuje i informace o typu prvků, což je ostatně patrné i z následujícího výpisu:
Series: 'sloupec' [i8] [ 1 2 3 4 ] Column type Int8
9. Výběr prvků s využitím indexu, indexů a řezů
Velmi často je zapotřebí z datové řady získat prvky s určitým indexem, indexy (více prvků), nebo získat řez (slice) prvků. I tyto operace jsou knihovnou Polars poskytovány. Pro datovou řadu s s deseti prvky s indexy od 0 do 10 jsou k dispozici tyto „indexovací“ operátory:
Zápis | Význam |
---|---|
s[1] | výběr jediného prvku z datové řady |
s[2, 4, 6] | výběr více prvků, vrátí se nová datová řada |
s[2:6] | řez datovou řadu s indexy od-do (kromě), vrátí se nová datová řada |
s[1:8:2] | řez s uvedením kroku mezi prvky |
s[::2] | opět řez, bez uvedení mezí, ale s krokem |
s[11:0:-1] | řez s otočením pořadí prvků (bez prvního prvku původní řady) |
s[11::-1] | řez s otočením pořadí prvků (obsahuje všechny prvky původní řady) |
Podívejme se nyní na praktický příklad:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", range(1, 11)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print() # výběr prvků print(s[1]) print(s[2, 4, 6]) print(s[2:6]) print(s[1:8:2]) print(s[::2]) print(s[11:0:-1]) print(s[11::-1])
Podívejme se nyní na výsledky činnosti tohoto skriptu. Je patrné, že se z původní datové řady vytváří nové datové řady:
shape: (10,) Series: 'sloupec' [i64] [ 1 2 3 4 5 6 7 8 9 10 ] Column type Int64 2 shape: (3,) Series: 'sloupec' [i64] [ 3 5 7 ] shape: (4,) Series: 'sloupec' [i64] [ 3 4 5 6 ] shape: (4,) Series: 'sloupec' [i64] [ 2 4 6 8 ] shape: (5,) Series: 'sloupec' [i64] [ 1 3 5 7 9 ] shape: (9,) Series: 'sloupec' [i64] [ 10 9 8 7 6 5 4 3 2 ] shape: (10,) Series: 'sloupec' [i64] [ 10 9 8 7 6 5 4 3 2 1 ]
10. Výběr prvků s využitím filtru (podmínky)
Další variantou, jak vybírat prvky z datových řad, je (nepřímé) použití datových řad obsahujících pouze pravdivostní hodnoty. Pokud n-tý prvek v takto zkonstruované datové řadě obsahuje hodnotu True, je prvek ze zdrojové datové řady přidán do řady cílové. A tuto pomocnou datovou řadu (říkejme jí výběrový vektor) lze vytvořit například výrazem s > 5, který zkonstruuje výběrový vektor obsahující hodnoty True pouze pro ty prvky, které jsou větší než 5 a False pro všechny ostatní prvky. Následně tento výběrový vektor použijeme pro výběr prvků ze zdrojové datové řady (tj. s je použito jak v podmínce, tak i jako zdrojová datová řada):
Zápis | Význam |
---|---|
s[s > 5] | výsledkem je datová řada obsahující pouze prvky větší než 5 |
s[s < 5] | výsledkem je datová řada obsahující pouze prvky menší než 5 |
s[s != 5] | výsledkem je datová řada obsahující prvky odlišné od 5 |
s[s %2 == 1] | výběr lichých prvků (nedělitelných dvěma) |
s[s %2 != 1] | výběr sudých prvků (dělitelných dvěma) |
Opět se podívejme na demonstrační příklad, v němž jsou tyto zápisy použity:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # vytvoření datové řady s = polars.Series("sloupec", range(1, 11)) # zobrazíme datovou řadu print(s) print() # podrobnější informace o datové řadě print("Column type") print(s.dtype) print() # výběr prvků na základě filtru print(s[s > 5]) print(s[s < 5]) print(s[s != 5]) print(s[s %2 == 1]) print(s[s %2 != 1])
Výsledky získané po spuštění tohoto příkladu ukazují, že se (podle očekávání) vrátí nové datové řady:
shape: (10,) Series: 'sloupec' [i64] [ 1 2 3 4 5 6 7 8 9 10 ] Column type Int64 shape: (5,) Series: 'sloupec' [i64] [ 6 7 8 9 10 ] shape: (4,) Series: 'sloupec' [i64] [ 1 2 3 4 ] shape: (9,) Series: 'sloupec' [i64] [ 1 2 3 4 6 7 8 9 10 ] shape: (5,) Series: 'sloupec' [i64] [ 1 3 5 7 9 ] shape: (5,) Series: 'sloupec' [i64] [ 2 4 6 8 10 ]
11. Datové rámce
Složením několika datových řad o stejném počtu prvků vznikne velmi důležitá datová struktura nazvaná datový rámec (data frame). Datové rámce se v určitém ohledu podobají tabulkám používaným v relačních databázích: jednotlivé sloupce jsou pojmenované a současně může být každý sloupec jiného datového typu (všechny prvky ve sloupci toto kritérium musí splňovat, což ovšem vede k určitým problémům, o nichž si řekneme v navazujících kapitolách a zejména pak v navazujícím článku). Tato vlastnost odlišuje datové rámce od dvourozměrných polí masivně využívaných například v knihovně Numpy; na druhou stranu je ovšem možné s jednotlivými sloupci datového rámce pracovat jako s jednorozměrným polem kompatibilním právě s knihovnou Numpy a jejími datovými typy.
12. Načtení datových rámců ze souborů typu CSV
CSV neboli Comma-Separated Values [1] je jedním z nejčastěji používaných souborových formátů v této oblasti, a to přesto, že je export a import CSV v některých případech problematický (například některé české mutace Excelu namísto čárek používají středníky, problémy nastávají s buňkami obsahujícími znaky pro konec řádku atd.). Tyto soubory jsou mnohdy obrovské a i z tohoto důvodu se začínají v některých oblastech nahrazovat například za Parquet soubory atd. I přesto se ale s CSV setkáme, a to poměrně často. Příkladem může být export dat z Promethea, z některých systémů pro strukturované logy atd.
Jeden z nejjednodušších příkladů používajících knihovnu Polars bude načítat soubor CSV (Comma-Separated Values), jehož obsah lze najít na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/polars/hall_of_fame.csv. V tomto souboru je uložena tabulka se dvěma sloupci získaná z https://www.tiobe.com/tiobe-index/, přičemž soubor má i řádek s hlavičkou (ta ovšem někdy může chybět, jak ostatně uvidíme dále):
Year,Winner 2022,C++ 2021,Python 2020,Python 2019,C 2018,Python 2017,C 2016,Go 2015,Java 2014,JavaScript 2013,Transact-SQL 2012,Objective-C 2011,Objective-C 2010,Python 2009,Go 2008,C 2007,Python 2006,Ruby 2005,Java 2004,PHP 2003,C++
V demonstračním příkladu provedeme načtení souboru s využitím funkce nazvané polars.read_csv, které prozatím předáme jediný parametr obsahující název souboru. Vytvořený datový rámec zobrazíme funkcí print a navíc si necháme vypsat i datové typy přiřazené jednotlivým sloupcům:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame.csv") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledek získaný po spuštění tohoto demonstračního příkladu by měl vypadat následovně:
shape: (20, 2) ┌──────┬────────┐ │ Year ┆ Winner │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════╪════════╡ │ 2022 ┆ C++ │ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────┴────────┘ Column types [Int64, Utf8]
13. Práce s CSV soubory bez hlavičky
Pokusme se nyní o načtení CSV souboru, který na prvním řádku neobsahuje hlavičku, resp. přesněji řečeno názvy sloupců. I s takovými soubory se můžeme v praxi setkat:
2022,C++ 2021,Python 2020,Python 2019,C 2018,Python 2017,C 2016,Go 2015,Java 2014,JavaScript 2013,Transact-SQL 2012,Objective-C 2011,Objective-C 2010,Python 2009,Go 2008,C 2007,Python 2006,Ruby 2005,Java 2004,PHP 2003,C++
Načtení obsahu tohoto datového souboru provedeme skriptem, který se vlastně nijak zásadně (pochopitelně až na odlišné jméno vstupního souboru) neliší od skriptu popsaného v předchozí kapitole:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame_no_header.csv") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledek ovšem v tomto případě nebude korektní, protože se data uložená v prvním řádku použila pro získání jmen sloupců:
shape: (19, 2) ┌──────┬────────┐ │ 2022 ┆ C++ │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════╪════════╡ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ 2018 ┆ Python │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────┴────────┘ Column types [Int64, Utf8]
Úprava nebo přesněji řečeno oprava tohoto skriptu je ve skutečnosti snadná – postačuje funkci read_csv předat nepovinný parametr has_header nastavený na hodnotu False:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame_no_header.csv", has_header=False) # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledek je nyní korektní, i když jména sloupců musela být doplněna automaticky:
shape: (20, 2) ┌──────────┬──────────┐ │ column_1 ┆ column_2 │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════════╪══════════╡ │ 2022 ┆ C++ │ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────────┴──────────┘ Column types [Int64, Utf8]
Chybějící jména sloupců můžeme doplnit nepovinným parametrem new_columns (viz zvýrazněný kód):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame_no_header.csv", has_header=False, new_columns=["Rok", "Vítěz"]) # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledek bude nyní vypadat odlišně – samotná data v datovém rámci jsou sice stejná, ovšem názvy sloupců se budou lišit:
shape: (20, 2) ┌──────┬────────┐ │ Rok ┆ Vítěz │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════╪════════╡ │ 2022 ┆ C++ │ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────┴────────┘ Column types [Int64, Utf8]
14. Načtení datových rámců ze souborů typu TSV
V dalším kroku se pokusíme načíst soubor https://github.com/tisnik/most-popular-python-libs/blob/master/polars/hall_of_fame.tsv. TSV neboli Tab-Separated Values [2] [3] je velmi podobným formátem jako CSV, ovšem s tím rozdílem, že oddělovačem jednotlivých buněk je znak tabulátoru (tím současně odpadají mnohé problémy CSV zmíněné výše). Podobně jako v případě CSV i zde možnost ukládat na první řádek souboru hlavičku:
Year Winner 2022 C++ 2021 Python 2020 Python 2019 C 2018 Python 2017 C 2016 Go 2015 Java 2014 JavaScript 2013 Transact-SQL 2012 Objective-C 2011 Objective-C 2010 Python 2009 Go 2008 C 2007 Python 2006 Ruby 2005 Java 2004 PHP 2003 C++
Podobně jako v knihovně Pandas není ani v knihovně Polars při běžném použití importní funkce polars.read_csv tento formát správně rozpoznán, o čemž se můžeme velmi snadno přesvědčit spuštěním následujícího skriptu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame.tsv") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledkem činnosti tohoto skriptu bude datový rámec obsahující pouze jediný sloupec. Navíc znaky \t, které nejsou dále zpracovány, „rozhodí“ interní formátování datových rámců na terminálu:
shape: (20, 1) ┌────────────┐ │ Year Winner │ │ --- │ │ str │ ╞════════════╡ │ 2022 C++ │ │ 2021 Python │ │ 2020 Python │ │ 2019 C │ │ ... │ │ 2006 Ruby │ │ 2005 Java │ │ 2004 PHP │ │ 2003 C++ │ └────────────┘ Column types [Utf8]
Soubory založené na formátu TSV lze načíst tak, že nepovinným (pojmenovaným) parametrem sep specifikujeme oddělovač mezi záznamy (což je mimochodem zcela kompatibilní s knihovnou Pandas). V tomto případě se jedná o znak „\t“ (Python používá céčkovský způsob zápisu řídicích znaků):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame.tsv", sep="\t") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledek nyní bude plně odpovídat očekávání:
shape: (20, 2) ┌──────┬────────┐ │ Year ┆ Winner │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════╪════════╡ │ 2022 ┆ C++ │ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────┴────────┘ Column types [Int64, Utf8]
15. Získání metainformací o datovém rámci
Podobně jako v případě systému Pandas je možné i v Polars velmi snadno získat základní metainformace a taktéž statistické informace o vybraném datovém rámci. K tomuto účelu se používá metoda nazvaná describe, kterou zavoláme v následujícím demonstračním příkladu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("hall_of_fame.csv") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print() # více podrobnějších informací o datovém rámci print(df.describe()) print()
Po spuštění tohoto skriptu se nejprve zobrazí obsah datového rámce tak, jak to již dobře známe z předchozích kapitol:
shape: (20, 2) ┌──────┬────────┐ │ Year ┆ Winner │ │ --- ┆ --- │ │ i64 ┆ str │ ╞══════╪════════╡ │ 2022 ┆ C++ │ │ 2021 ┆ Python │ │ 2020 ┆ Python │ │ 2019 ┆ C │ │ ... ┆ ... │ │ 2006 ┆ Ruby │ │ 2005 ┆ Java │ │ 2004 ┆ PHP │ │ 2003 ┆ C++ │ └──────┴────────┘ Column types [Int64, Utf8]
Následně se vypíšou statistické informace získané analýzou dat v datovém rámci. U sloupců s numerickými hodnotami se spočítají základní statistické veličiny, u sloupců s řetězci se alespoň nalezne první a poslední prvek z (potenciálně) seřazeného sloupce:
shape: (7, 3) ┌────────────┬─────────┬──────────────┐ │ describe ┆ Year ┆ Winner │ │ --- ┆ --- ┆ --- │ │ str ┆ f64 ┆ str │ ╞════════════╪═════════╪══════════════╡ │ count ┆ 20.0 ┆ 20 │ │ null_count ┆ 0.0 ┆ 0 │ │ mean ┆ 2012.5 ┆ null │ │ std ┆ 5.91608 ┆ null │ │ min ┆ 2003.0 ┆ C │ │ max ┆ 2022.0 ┆ Transact-SQL │ │ median ┆ 2012.5 ┆ null │ └────────────┴─────────┴──────────────┘
16. Načtení dat obsahujících časová razítka
Pokusme se nyní načíst tabulku, která ve svém druhém sloupci obsahuje časová razítka, tedy jak plné datum, tak i čas (v tomto konkrétním případě s přesností na sekundy). Jedná se konkrétně o tento soubor:
n,Timestamp 1,2020-01-15 03:59:47 2,2020-01-15 08:19:25 3,2020-01-15 11:42:07 4,2020-01-15 14:58:48 5,2020-01-15 18:21:56 6,2020-01-15 21:10:01 7,2020-01-15 23:13:58 8,2020-01-16 01:51:52 9,2020-01-16 05:55:55 10,2020-01-16 10:11:54 11,2020-01-16 14:02:32 12,2020-01-16 17:35:25 13,2020-01-16 19:35:43 14,2020-01-16 22:29:24
Prozatím při načtení nebudeme žádným způsobem specifikovat typy sloupců, takže vlastně zopakujeme zdrojový kód prvního příkladu pro načtení datového rámce z CSV:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("timestamps.csv") # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Z obsahu datového rámce vypsaného tímto skriptem je patrné, že se hodnoty ve druhém sloupci načetly jako řetězce:
shape: (14, 2) ┌─────┬─────────────────────┐ │ n ┆ Timestamp │ │ --- ┆ --- │ │ i64 ┆ str │ ╞═════╪═════════════════════╡ │ 1 ┆ 2020-01-15 03:59:47 │ │ 2 ┆ 2020-01-15 08:19:25 │ │ 3 ┆ 2020-01-15 11:42:07 │ │ 4 ┆ 2020-01-15 14:58:48 │ │ ... ┆ ... │ │ 11 ┆ 2020-01-16 14:02:32 │ │ 12 ┆ 2020-01-16 17:35:25 │ │ 13 ┆ 2020-01-16 19:35:43 │ │ 14 ┆ 2020-01-16 22:29:24 │ └─────┴─────────────────────┘ Column types [Int64, Utf8]
Výše uvedené chování nám samozřejmě nebude v mnoha případech vyhovovat, protože budeme chtít s časovými údaji provádět různé operace. Jedno z možných řešení tohoto problému spočívá v tom, že při načítání tabulky funkcí pandas.read_csv použijeme parametr parse_dates, kterému předáme buď hodnotu True nebo explicitně názvy sloupců, u nichž se má datum zpracovat (mimochodem – naprosto stejného chování dosáhneme se stejným parametrem u knihovny Pandas):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # přečtení zdrojových dat df = polars.read_csv("timestamps.csv", parse_dates=True) # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Obsah a především metainformace o datovém rámci prozrazují, že skutečně došlo ke konverzi časových údajů na hodnoty typu datetime uložené s přesností na mikrosekundy:
shape: (14, 2) ┌─────┬─────────────────────┐ │ n ┆ Timestamp │ │ --- ┆ --- │ │ i64 ┆ datetime[μs] │ ╞═════╪═════════════════════╡ │ 1 ┆ 2020-01-15 03:59:47 │ │ 2 ┆ 2020-01-15 08:19:25 │ │ 3 ┆ 2020-01-15 11:42:07 │ │ 4 ┆ 2020-01-15 14:58:48 │ │ ... ┆ ... │ │ 11 ┆ 2020-01-16 14:02:32 │ │ 12 ┆ 2020-01-16 17:35:25 │ │ 13 ┆ 2020-01-16 19:35:43 │ │ 14 ┆ 2020-01-16 22:29:24 │ └─────┴─────────────────────┘ Column types [Int64, Datetime(tu='us', tz=None)]
17. Načtení datových rámců z relační databáze
V praxi se poměrně často setkáme s požadavkem na načtení dat uložených v nějaké formě databáze. Může se jednat například o relační databázi, z níž jsou data přečtena na základě uživatelem zapsaného dotazu (query). Dobrým příkladem může být načtení dat uložených v databázi PostgreSQL. Pro jednoduchost budeme načítat data z jediné tabulky, tj. dotaz nebude obsahovat klauzuli JOIN (i tu je však možné bez problémů v případě potřeby použít). Data budeme načítat z tabulky rule_hit, jejíž schéma je následující:
Table "public.rule_hit" Column | Type | Modifiers ---------------+-------------------+----------- org_id | integer | not null cluster_id | character varying | not null rule_fqdn | character varying | not null error_key | character varying | not null template_data | character varying | not null
Povšimněte si, že pro načtení dat z databáze potřebujeme specifikovat takzvaný connection string, v němž je uvedena jak použitá databáze, tak i její umístění (jméno počítače+port) i přihlašovací údaje (uživatel postgres s heslem postgres). Každá databáze ovšem akceptuje odlišně strukturovaný connection string. A dále samozřejmě potřebujeme specifikovat vlastní dotaz. Oba údaje jsou předány do funkce read_sql:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # connection string a vlastní dotaz connection_string = "postgresql://postgres:postgres@localhost:5432/testdb" query = "select * from rule_hit" # přečtení zdrojových dat df = polars.read_sql(query, connection_string) # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Výsledkem činnosti tohoto skriptu je plnohodnotný datový rámec, což je ostatně patrné i při pohledu na výsledek:
shape: (45, 5) ┌──────────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────┐ │ org_id ┆ cluster_id ┆ rule_fqdn ┆ error_key ┆ template_data │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i32 ┆ str ┆ str ┆ str ┆ str │ ╞══════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══════════════╡ │ 11789773 ┆ 6d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ NODES_MINIMUM_REQUIRE ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.nod... ┆ MENTS_NOT_M... ┆ "nodes": [... │ │ 11789773 ┆ 6d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ BUGZILLA_BUG_1766907 ┆ { │ │ ┆ af-548dfc97... ┆ l.bug_rules... ┆ ┆ "type": "r... │ │ 11789773 ┆ 6d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ NODE_KUBELET_VERSION ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.nod... ┆ ┆ "nodes_wit... │ │ 11789773 ┆ 6d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ SAMPLES_FAILED_IMAGE_ ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.sam... ┆ IMPORT_ERR ┆ "info": { │ │ ┆ ┆ ┆ ┆ ... │ │ ... ┆ ... ┆ ... ┆ ... ┆ ... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ BUGZILLA_BUG_1766907 ┆ { │ │ ┆ af-548dfc97... ┆ l.bug_rules... ┆ ┆ "type": "r... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ NODE_KUBELET_VERSION ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.nod... ┆ ┆ "nodes_wit... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ SAMPLES_FAILED_IMAGE_ ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.sam... ┆ IMPORT_ERR ┆ "info": { │ │ ┆ ┆ ┆ ┆ ... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91 ┆ ccx_rules_ocp.externa ┆ AUTH_OPERATOR_PROXY_E ┆ { │ │ ┆ af-548dfc97... ┆ l.rules.clu... ┆ RROR ┆ "op": { │ │ ┆ ┆ ┆ ┆ ... │ └──────────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────┘ Column types [Int32, Utf8, Utf8, Utf8, Utf8]
18. Složitější dotaz s klauzulí WHERE popř. ORDER BY
Dotazy, které se posílají do vybrané (relační) databáze, mohou být samozřejmě složitější, než tomu bylo v předchozím demonstračním příkladu. Můžeme například použít dotaz s podmínkou uvedenou za WHERE (tedy filtraci), popř. specifikovat řazení vrácených záznamů klauzulí ORDER BY. Opět se podívejme na jednoduchý příklad, který stále využívá stejnou tabulku:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import polars # connection string a vlastní dotaz connection_string = "postgresql://postgres:postgres@localhost:5432/testdb" query = """ SELECT org_id, cluster_id, rule_fqdn FROM rule_hit ORDER by org_id, cluster_id """ # přečtení zdrojových dat df = polars.read_sql(query, connection_string) # zobrazíme datový rámec print(df) print() # podrobnější informace o datovém rámci print("Column types") print(df.dtypes) print()
Nyní bude výsledný datový rámec vypadat odlišně, protože bude obsahovat pouze tři vybrané sloupce. Navíc si povšimněte, že záznamy uložené v datovém rámci jsou skutečně seřazeny, nejdříve podle čísla organizace a posléze podle jména ve druhém, resp. hodnoty ve třetím sloupci:
shape: (45, 3) ┌──────────┬─────────────────────────────────────┬─────────────────────────────────────┐ │ org_id ┆ cluster_id ┆ rule_fqdn │ │ --- ┆ --- ┆ --- │ │ i32 ┆ str ┆ str │ ╞══════════╪═════════════════════════════════════╪═════════════════════════════════════╡ │ 1 ┆ 9d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.ocs.check_ocs_vers... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.ocs.check_pods_scc... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.ocs.ceph_check_mon... │ │ 1 ┆ 9d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.ocs.pvc_phase_chec... │ │ ... ┆ ... ┆ ... │ │ 11789775 ┆ 8d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.external.rules.ima... │ │ 11789775 ┆ 8d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.external.rules.ima... │ │ 11789775 ┆ 8d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.external.bug_rules... │ │ 11789775 ┆ 8d5892d3-1f74-4ccf-91af-548dfc97... ┆ ccx_rules_ocp.external.bug_rules... │ └──────────┴─────────────────────────────────────┴─────────────────────────────────────┘ Column types [Int32, Utf8, Utf8]
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 (nikoli ovšem pro starší verze Pythonu 2!) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Projekt Polars na GitHubu
https://github.com/pola-rs/polars - Dokumentace k projektu Polars (popis API)
https://pola-rs.github.io/polars/py-polars/html/reference/index.html - Polars: The Next Big Python Data Science Library… written in RUST?
https://www.youtube.com/watch?v=VHqn7ufiilE - Polars API: funkce pro načtení datového rámce z CSV
https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_csv.html - Polars API: funkce pro načtení datového rámce z relační databáze
https://pola-rs.github.io/polars/py-polars/html/reference/api/polars.read_sql.html - Python’s Pandas vs Polars: Who Wins this Fight in Library
https://analyticsindiamag.com/pythons-pandas-vs-polars-who-wins-this-fight-in-library/ - Polars vs Pandas: what is more convenient?
https://medium.com/@ilia.ozhmegov/polars-vs-pandas-what-is-more-convenient-331956742a69 - A Gentle Introduction to Pandas Data Analysis (on Kaggle)
https://www.youtube.com/watch?v=_Eb0utIRdkw&list=PL7RwtdVQXQ8oYpuIIDWR0SaaSCe8ZeZ7t&index=4 - Speed Up Your Pandas Dataframes
https://www.youtube.com/watch?v=u4_c2LDi4b8&list=PL7RwtdVQXQ8oYpuIIDWR0SaaSCe8ZeZ7t&index=5 - pandas.read_csv
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html - How to define format when using pandas to_datetime?
https://stackoverflow.com/questions/36848514/how-to-define-format-when-using-pandas-to-datetime - Pandas : skip rows while reading csv file to a Dataframe using read_csv() in Python
https://thispointer.com/pandas-skip-rows-while-reading-csv-file-to-a-dataframe-using-read_csv-in-python/ - Skip rows during csv import pandas
https://stackoverflow.com/questions/20637439/skip-rows-during-csv-import-pandas - Denni kurz
https://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt - UUID objects according to RFC 4122 (knihovna pro Python)
https://docs.python.org/3.5/library/uuid.html#uuid.uuid4 - Object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Object_identifier - Digital object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Digital_object_identifier - voluptuous na (na PyPi)
https://pypi.python.org/pypi/voluptuous - Repositář knihovny voluptuous na GitHubu
https://github.com/alecthomas/voluptuous - pytest-voluptuous 1.0.2 (na PyPi)
https://pypi.org/project/pytest-voluptuous/ - pytest-voluptuous (na GitHubu)
https://github.com/F-Secure/pytest-voluptuous - schemagic 0.9.1 (na PyPi)
https://pypi.python.org/pypi/schemagic/0.9.1 - Schemagic / Schemagic.web (na GitHubu)
https://github.com/Mechrophile/schemagic - schema 0.6.7 (na PyPi)
https://pypi.python.org/pypi/schema - schema (na GitHubu)
https://github.com/keleshev/schema