Hlavní navigace

Knihovna Pandas: zobrazení obsahu datových rámců, vykreslení grafů a validace dat

3. 12. 2020
Doba čtení: 37 minut

Sdílet

Ve druhém článku o knihovně Pandas se budeme zabývat třemi tématy: zobrazením obsahu i struktury datových rámců, vykreslováním grafů na základě dat získaných z rámců a taktéž validací údajů uložených v datových rámcích.

Obsah

1. Knihovna Pandas: zobrazení datových rámců, vykreslení grafů a validace dat

2. Nové datové typy podporované knihovnou Pandas

3. Zobrazení obsahu datového rámce

4. Zobrazení podrobnějších informací o datovém rámci

5. Základní statistické informace o datech uložených v rámci

6. Jazyk Python a knihovna Matplotlib

7. Zobrazení jednoduchého grafu

8. Kooperace mezi Pandas a Matplotlibem

9. Přímé vykreslení grafu bez použití knihovny Matplotlib

10. Přidání klouzavého průměru do grafu

11. Vylepšený výpočet klouzavého průměru

12. Od liniových grafů ke grafům sloupcovým

13. Výběr části datového rámce při vykreslování grafu

14. Zobecnění předchozího příkladu – zpracování numerických dat ve všech sloupcích

15. Validace dat v Pythonu

16. Validace s využitím knihovny Voluptuous

17. Validace s využitím knihovny Opulent Pandas

18. Repositář s demonstračními příklady a datovými soubory

19. Články s informacemi o různých způsobech validace datových struktur

20. Odkazy na Internetu

1. Knihovna Pandas: zobrazení datových rámců, vykreslení grafů a validace dat

Ve druhém článku o knihovně Pandas se budeme zabývat třemi tématy. Nejprve si ukážeme způsob zobrazení datových rámců, a to jak jejich obsahu, tak i struktury, velikosti, základních statistických informací atd. Dále si popíšeme, jak lze zobrazit jednoduché grafy (prozatím grafy liniové a taktéž grafy sloupcové) s využitím možností nabízených jak knihovnou Pandas, tak i knihovnou Matplotlib. A konečně v závěrečné části článku si ukážeme některé možnosti validace dat uložených v datových rámcích.

Připomeňme si, že knihovna Pandas nabízí uživatelům-programátorům tuto funkcionalitu:

  1. Načtení dat z různých datových zdrojů do datových rámců (CSV, TSV, databáze, tabulkové procesory, …).
  2. Programová konstrukce datových rámců.
  3. Prohlížení obsahu datových rámců.
  4. Iterace nad daty, řazení a další podobné operace (bude ukázáno příště).
  5. Spojování, seskupování a změna tvaru dat (taktéž bude ukázáno příště).
  6. Práce s takzvanými sériemi (většinou získanými z datových rámců).
  7. Vykreslování grafů z údajů získaných z datových rámců (základy si ukážeme dnes, další příklady v navazujících článcích).

2. Nové datové typy podporované knihovnou Pandas

Společně s knihovnou Pandas je dodávána i deklarace nových datových typů, které jsou vypsány v tabulce pod tímto odstavcem. Důležité jsou především první dva typy, tedy Series odvozený od jednodimenzionálního pole knihovny Numpy a DataFrame popsaný v předchozím článku. Nesmíme ovšem zapomenout ani na datové typy určené pro reprezentaci časových údajů, popř. o rozšíření typů z knihovny Numpy o možnost reprezentace neexistující hodnoty: NULL, resp. N/A:

# Datový typ Stručný popis
1 Series odvozeno od 1D pole knihovny Numpy, rozšířeno o popis os
2 DataFrame reprezentace dat uložených do tabulky s popisem os (sloupců, řádků)
     
3 DatetimeTZDtype datum s přidanou informací o časové zóně
4 PeriodDtype reprezentace časové periody (offsetu)
5 IntervalDtype reprezentace numerického intervalu (odvozeno od dalších typů, například int64 atd.)
6 Int8Dtype typ int8 rozšířený pro podporu hodnoty pandas.NA
7 Int16Dtype typ int16 rozšířený pro podporu hodnoty pandas.NA
8 Int32Dtype typ int32 rozšířený pro podporu hodnoty pandas.NA
9 Int64Dtype typ int64 rozšířený pro podporu hodnoty pandas.NA
10 UInt8Dtype typ uint8 rozšířený pro podporu hodnoty pandas.NA
11 UInt16Dtype typ uint16 rozšířený pro podporu hodnoty pandas.NA
12 UInt32Dtype typ uint32 rozšířený pro podporu hodnoty pandas.NA
13 UInt64Dtype typ uint64 rozšířený pro podporu hodnoty pandas.NA
14 CategoricalDtype kategorie (odvozeno od jazyka R, bude popsáno příště)
15 SparseDtype použito pro ukládání řídkých polí (bude popsáno příště)
16 StringDtype rozšíření řetězců; prozatím ve fázi experimentálního rozšíření
17 BooleanDtype rozšíření pravdivostního typu; prozatím ve fázi experimentálního rozšíření

3. Zobrazení obsahu datového rámce

V praktické části dnešního článku si nejdříve ukážeme způsob získání a popř. i výpisu základních informací o datových rámcích. Samotné zobrazení obsahu datového rámce je triviální – postačuje referenci na datový rámec předat funkci print, která automaticky obsah datového rámce převede na řetězec, který následně vypíše:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df)

Výsledek:

                  země        měna  množství  kód    kurz
0            Austrálie       dolar         1  AUD  16.231
1             Brazílie        real         1  BRL   4.160
2            Bulharsko         lev         1  BGN  13.467
3                 Čína  žen-min-pi         1  CNY   3.381
4               Dánsko      koruna         1  DKK   3.536
5                  EMU        euro         1  EUR  26.340
6             Filipíny        peso       100  PHP  46.038
7             Hongkong       dolar         1  HKD   2.864
8           Chorvatsko        kuna         1  HRK   3.481
9                Indie       rupie       100  INR  29.950
10           Indonesie       rupie      1000  IDR   1.567
11              Island      koruna       100  ISK  16.330
12              Izrael  nový šekel         1  ILS   6.649
13            Japonsko         jen       100  JPY  21.383
14        Jižní Afrika        rand         1  ZAR   1.445
15              Kanada       dolar         1  CAD  17.011
16  Korejská republika         won       100  KRW   1.990
17            Maďarsko      forint       100  HUF   7.328
18            Malajsie     ringgit         1  MYR   5.425
19              Mexiko        peso         1  MXN   1.104
20                 MMF         ZPČ         1  XDR  31.598
21              Norsko      koruna         1  NOK   2.471
22         Nový Zéland       dolar         1  NZD  15.416
23              Polsko       zlotý         1  PLN   5.900
24            Rumunsko         leu         1  RON   5.405
25               Rusko        rubl       100  RUB  29.180
26            Singapur       dolar         1  SGD  16.530
27             Švédsko      koruna         1  SEK   2.577
28           Švýcarsko       frank         1  CHF  24.363
29             Thajsko        baht       100  THB  73.313
30             Turecko        lira         1  TRY   2.911
31                 USA       dolar         1  USD  22.201
32      Velká Británie       libra         1  GBP  29.464

Zobrazit lze i část datového rámce. Prvních n řádků (ve výchozím nastavení pět řádků) se vypíše metodou head:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.head())

Výsledek:

        země        měna  množství  kód    kurz
0  Austrálie       dolar         1  AUD  16.231
1   Brazílie        real         1  BRL   4.160
2  Bulharsko         lev         1  BGN  13.467
3       Čína  žen-min-pi         1  CNY   3.381
4     Dánsko      koruna         1  DKK   3.536

4. Zobrazení podrobnějších informací o datovém rámci

O datových rámcích je možné získat i další informace. Důležitá je mnohdy informace o typech sloupců, což je problematika, které jsme se již částečně dotkli minule:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.dtypes)

Výsledek ukazuje, jakého datového typu jsou položky v jednotlivých sloupcích:

země         object
měna         object
množství      int64
kód          object
kurz        float64
dtype: object

V dalším textu využijeme uspořádaný seznam jmen všech sloupců:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.columns)

Výsledek, se kterým je možné nakládat jako s běžnou sekvencí:

Index(['země', 'měna', 'množství', 'kód', 'kurz'], dtype='object')

Podrobnější informace o datovém rámci, obsazení paměti atd. zajistí funkce info:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.info())

Výsledek:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 33 entries, 0 to 32
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   země      33 non-null     object
 1   měna      33 non-null     object
 2   množství  33 non-null     int64
 3   kód       33 non-null     object
 4   kurz      33 non-null     float64
dtypes: float64(1), int64(1), object(3)
memory usage: 1.4+ KB

U této funkce (info) lze řídit podrobnost informací parametrem verbose:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.info(verbose=False))

Výsledek je nyní méně podrobný:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 33 entries, 0 to 32
Columns: 5 entries, země to kurz
dtypes: float64(1), int64(1), object(3)
memory usage: 1.4+ KB
None

Další informace se týkají os (axes), tedy osy vertikální i horizontální (v rámci tabulky), dále počtu dimenzí (prakticky vždy dvě), tvaru (počet řádků×počet sloupců) a velikosti (výsledek počet řádků×počet sloupců):

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print("Axes: ", df.axes)
print("Ndim: ", df.ndim)
print("Size: ", df.size)
print("Shape: ", df.shape)

Výsledek:

Axes:  [RangeIndex(start=0, stop=33, step=1), Index(['země', 'měna', 'množství', 'kód', 'kurz'], dtype='object')]
Ndim:  2
Size:  165
Shape:  (33, 5)

5. Základní statistické informace o datech uložených v rámci

Metodou describe lze získat základní (a mnohdy velmi užitečné) statistické informace o záznamech uložených v datovém rámci. Podívejme se nyní na způsob použití této metody:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading data file with custom format. separator + skip rows specification."""
 
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.describe())

S výsledkem:

          množství       kurz
count    33.000000  33.000000
mean     55.272727  14.879061
std     174.929141  15.649135
min       1.000000   1.104000
25%       1.000000   3.381000
50%       1.000000   7.328000
75%     100.000000  22.201000
max    1000.000000  73.313000
Poznámka: povšimněte si, že návratový typ je opět datovým rámcem, o čemž se můžeme velmi snadno přesvědčit:
import pandas
 
df = pandas.read_csv("denni_kurz.txt", sep="|", skiprows=1)
 
df["kurz"] = pandas.to_numeric(df["kurz"].str.replace(',','.'), errors='coerce')
 
print(df.describe().info())

Tentokrát získáme tento výstup:

<class 'pandas.core.frame.DataFrame'>
Index: 8 entries, count to max
Data columns (total 2 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   množství  8 non-null      float64
 1   kurz      8 non-null      float64
dtypes: float64(2)
memory usage: 192.0+ bytes
None

6. Jazyk Python a knihovna Matplotlib

S knihovnou Matplotlib jsme se již na tomto serveru několikrát setkali (viz též odkazy na další informační zdroje uvedené na konci článku), takže již víme, že Matplotlib je knihovna určená pro programovací jazyk Python, která slouží k tvorbě a částečně i k interaktivním úpravám různých typů grafů, například klasických grafů funkcí jedné proměnné, ovšem i mnoha grafů více či méně složitějších (grafy více funkcí, trojrozměrné grafy, polární grafy, zobrazení kontur atd.). Možnosti knihovny Matplotlib jsou skutečně značně široké a přitom je její použití poměrně jednoduché a snadno pochopitelné, pokud samozřejmě vynecháme některé pokročilejší operace. Jednou ze zajímavých možností představuje použití této knihovny v interaktivním prostředí IPython, popř. IPython Notebook, zejména v kombinaci s další populární Pythonovskou knihovnou Numpy. A v kontextu tohoto článku je ještě důležitější to, že Matplotlib dokáže velmi dobře kooperovat i s knihovnou Pandas.

Obrázek 1: Jeden typ grafu podporovaný knihovnou Matplotlib – Funkce typu z=f(x,y) zobrazená formou vrstevnic.

7. Zobrazení jednoduchého grafu

V dalším demonstračním příkladu si ukážeme, jakým způsobem je možné vykreslit jednoduchý liniový graf, a to zcela bez použití knihovny Pandas. Použijeme pouze základní knihovnu csv, dále knihovnu Numpy pro výpočet regresní přímky a konečně knihovnu Matplotlib pro vykreslení grafu z dat načtených ze souboru typu CSV:

#!/usr/bin/env python3
 
import sys
import csv
import numpy as np
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  kafka_lags.py input_file.csv")
    print("Example:")
    print("  kafka_lags.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
# Try to open the CSV file specified.
with open(input_csv) as csv_input:
    # And open this file as CSV
    csv_reader = csv.reader(csv_input)
 
    # Skip header
    next(csv_reader, None)
    rows = 0
 
    # Read all rows from the provided CSV file
    data = [(row[0], int(row[1])) for row in csv_reader]
    print(data)
 
# Linear regression
time = [item[0] for item in data]
messages = [item[1] for item in data]
 
# Linear regression
x = np.arange(0, len(messages))
coef = np.polyfit(x, messages, 1)
poly1d_fn = np.poly1d(coef)
 
# Create new histogram graph
plt.plot(messages, "b", poly1d_fn(np.arange(0, len(messages))), 'y--')
 
# Title of a graph
plt.title("Messages in Kafka")
 
# Add a label to x-axis
plt.xlabel("Time")
 
# Add a label to y-axis
plt.ylabel("Messages")
 
plt.legend(loc="upper right")
 
# Set the plot layout
plt.tight_layout()
 
# And save the plot into raster format and vector format as well
plt.savefig("kafka_lags.png")
plt.savefig("kafka_lags.svg")
 
# Try to show the plot on screen
plt.show()

Po spuštění tohoto příkladu by se měl vykreslit tento graf:

Obrázek 2: Graf zobrazený předchozím demonstračním příkladem.

8. Kooperace mezi Pandas a Matplotlibem

Předchozí příklad byl ve skutečnosti zbytečně složitý a současně ne vždy plně funkční. Data jsme totiž načítali s využitím standardní knihovny csv bez jejich kontroly. A v tomto konkrétním případě byla vstupní data získána exportem z Grafany, která (z nějakého důvodu) přidává na konec souboru prázdný řádek, který není zpracován korektně. Příklad tedy změníme, a to tak, že CSV načteme přímo do datového rámce, což je bezpečnější a současně i z pohledu programátora kratší řešení:

#!/usr/bin/env python3
 
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_kafka_lags_pandas.py input_file.csv")
    print("Example:")
    print("  plot_kafka_lags_pandas.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
df = pd.read_csv(input_csv)
 
print(df.info())
print(df.describe())
 
# Linear regression
time = df["Time"]
messages = df["topic : uploads"]
 
# Linear regression
x = np.arange(0, len(messages))
coef = np.polyfit(x, messages, 1)
poly1d_fn = np.poly1d(coef)
 
# Create new histogram graph
plt.plot(messages, "b", poly1d_fn(np.arange(0, len(messages))), 'y--')
 
# Title of a graph
plt.title("Messages in Kafka")
 
# Add a label to x-axis
plt.xlabel("Time")
 
# Add a label to y-axis
plt.ylabel("Messages")
 
plt.legend(loc="upper right")
 
# Set the plot layout
plt.tight_layout()
 
# And save the plot into raster format and vector format as well
plt.savefig("kafka_lags_pandas.png")
plt.savefig("kafka_lags_pandas.svg")
 
# Try to show the plot on screen
plt.show()

Výsledek by v tomto případě měl vypadat takto:

Obrázek 3: Graf zobrazený předchozím demonstračním příkladem.

9. Přímé vykreslení grafu bez použití knihovny Matplotlib

Ve skutečnosti je možné jít ve zjednodušování a zkracování programového kódu ještě dále, protože se o vytvoření grafu může postarat přímo knihovna Pandas. Objekt představující datový rámec podporuje i metodu plot pro přímé vykreslení grafu:

df = pd.read_csv(input_csv)
 
# Create new histogram graph
df.plot(x="Time", y="topic : uploads")

Samotné vykreslení grafu (či jeho tisk) je provedeno přes Matplotlib:

plt.show()

Úplný zdrojový kód takto upraveného příkladu vypadá následovně (můžeme vidět, že vykreslení je skutečně otázka několika programových řádků):

#!/usr/bin/env python3
 
import sys
import pandas as pd
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_kafka_lags_pandas_2.py input_file.csv")
    print("Example:")
    print("  plot_kafka_lags_pandas_2.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
df = pd.read_csv(input_csv)
 
print(df.info())
print(df.describe())
 
# Create new histogram graph
df.plot(x="Time", y="topic : uploads")
 
# Try to show the plot on screen
plt.show()

Výsledek prozatím vypadá jednodušeji, což je ovšem cena za to, že má celý skript délku jen několika řádků:

Obrázek 4: Graf zobrazený předchozím demonstračním příkladem. Povšimněte si, že formátování údajů na osách v žádném případě není dokonalé, takže se mnohdy „ručním“ úpravám na úrovni Matplotlibu nevyhneme.

10. Přidání klouzavého průměru do grafu

Zkusme nyní do grafu přidat i druhý průběh s klouzavým průměrem (tedy s„vyhlazenou“ křivkou). Výpočet klouzavého průměru je relativně přímočarý, pouze musíme zajistit, že se do datového rámce přidá další sloupec s výsledky. Povšimněte si, jak se využívají nám již známé metody df.shape, popř. df.iloc:

for i in range(0, df.shape[0]-2):
    df.loc[df.index[i+2], 'SMA_3'] = np.round(((df.iloc[i, 1]+ df.iloc[i+1, 1] +df.iloc[i+2, 1])/3),1)

Nevýhodou tohoto přístupu je fakt, že pokud bude nutné změnit „okno“ pro výpočet klouzavého průměru, bude se muset změnit i samotný zdrojový kód. Lepší řešení si ukážeme v navazující kapitole.

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

#!/usr/bin/env python3
 
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_kafka_lags_pandas_sma_3.py input_file.csv")
    print("Example:")
    print("  plot_kafka_lags_pandas_sma_3.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
df = pd.read_csv(input_csv)
 
for i in range(0, df.shape[0]-2):
    df.loc[df.index[i+2], 'SMA_3'] = np.round(((df.iloc[i,1]+ df.iloc[i+1,1] +df.iloc[i+2,1])/3),1)
 
print(df)
print(df.info())
print(df.describe())
 
# Create new histogram graph
df.plot(x="Time", y=["topic : uploads", "SMA_3"])
 
# Try to show the plot on screen
plt.show()

S výsledkem:

Obrázek 5: Graf zobrazený předchozím demonstračním příkladem.

11. Vylepšený výpočet klouzavého průměru

V předchozí kapitole ukázaný výpočet klouzavého průměru byl sice funkční, ovšem nepříliš přehledný. Existuje však i možnost použít při výpočtu přímo možností nabízených samotnou knihovnou Pandas, konkrétně metody rolling a mean. Takto jednoduše lze do datového rámce přidat další sloupec, který bude obsahovat klouzavý průměr hodnot ze sloupce druhého (druhý sloupec má index=1):

df['SMA_3'] = df.iloc[:,1].rolling(window=3).mean()

Výsledný datový rámec bude vypadat následovně:

                     Time  topic : uploads      SMA_3
0     2020-12-01 06:14:00               13        NaN
1     2020-12-01 06:14:20               13        NaN
2     2020-12-01 06:14:40                9  11.666667
3     2020-12-01 06:15:00               18  13.333333
4     2020-12-01 06:15:20               18  15.000000
...                   ...              ...        ...
1076  2020-12-01 12:12:40               33  29.666667
1077  2020-12-01 12:13:00               31  30.666667
1078  2020-12-01 12:13:20               31  31.666667
1079  2020-12-01 12:13:40               17  26.333333
1080  2020-12-01 12:14:00                6  18.000000
Poznámka: první dvě chybějící hodnoty ve sloupci „SMA3“ při vykreslování grafu nevadí.

Úplný zdrojový kód tohoto příkladu:

#!/usr/bin/env python3
 
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_kafka_lags_pandas_sma_3.py input_file.csv")
    print("Example:")
    print("  plot_kafka_lags_pandas_sma_3.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
df = pd.read_csv(input_csv)
 
df['SMA_3'] = df.iloc[:,1].rolling(window=3).mean()
 
print(df)
print(df.info())
print(df.describe())
 
# Create new histogram graph
df.plot(x="Time", y=["topic : uploads", "SMA_3"])
 
# Try to show the plot on screen
plt.show()

Obrázek 6: Graf zobrazený předchozím demonstračním příkladem.

Změna příkladu tak, aby se počítal klouzavý průměr z dvaceti hodnot, je otázkou jediné úpravy (zvýrazněné tučně):

#!/usr/bin/env python3
 
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_kafka_lags_pandas_sma_20.py input_file.csv")
    print("Example:")
    print("  plot_kafka_lags_pandas_sma_20.py overall.csv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_csv = sys.argv[1]
 
df = pd.read_csv(input_csv)
 
df['SMA_20'] = df.iloc[:,1].rolling(window=20).mean()
 
print(df)
print(df.info())
print(df.describe())
 
# Create new histogram graph
df.plot(x="Time", y=["topic : uploads", "SMA_20"])
 
# Try to show the plot on screen
plt.show()
 
# Create new histogram graph
df.plot(x="Time", y="SMA_20")
 
# Try to show the plot on screen
plt.show()

Obrázek 7: Graf zobrazený předchozím demonstračním příkladem.

Obrázek 8: Křivka zobrazující pouze průběh klouzavého průměru.

12. Od liniových grafů ke grafům sloupcovým

Vykreslit je možné i sloupcové grafy. V následujícím demonstračním příkladu budou zpracovávána tato data (výsledky benchmarků):

Width       Height      ANSI C      Cython #1   Cython #2   Cython #3   Numba #1/interpret  Numba #2    Numba #3    Numba #4
2048        0           0,00        0,03        0,02        0,03        0,03                0,76        5,92        5,89
2048        100         0,06        1,03        0,22        0,08        1,84                2,18        6,22        6,20
2048        200         0,11        2,10        0,42        0,14        3,59                3,56        6,60        6,58
2048        300         0,17        3,17        0,61        0,21        5,56                4,92        6,94        6,93
2048        400         0,23        4,04        0,81        0,26        7,16                6,38        7,30        7,33
2048        500         0,29        5,05        0,99        0,31        9,63                7,90        7,64        7,67
2048        600         0,34        6,16        1,19        0,37        11,62               9,27        8,01        8,04
2048        700         0,40        7,04        1,38        0,43        12,56               10,52       8,38        8,37
2048        800         0,46        8,17        1,56        0,48        14,20               11,99       8,70        8,83
2048        900         0,52        9,51        1,81        0,56        16,23               13,58       9,09        9,19
2048        1000        0,58        10,63       2,18        0,60        17,91               14,89       9,41        9,41
2048        1100        0,64        11,11       2,24        0,66        19,58               16,68       9,90        9,77
2048        1200        0,70        12,48       2,36        0,72        23,49               18,01       10,19       10,15
2048        1300        0,75        13,09       2,67        0,78        23,08               19,33       10,47       10,57
2048        1400        0,81        14,26       2,75        0,83        25,22               20,30       11,11       10,91
2048        1500        0,87        16,19       3,01        0,89        26,88               22,10       11,24       11,47
2048        1600        0,92        16,83       3,13        0,96        28,87               23,56       11,64       11,55
2048        1700        0,98        17,41       3,33        1,01        30,80               24,73       11,98       11,92
2048        1800        1,04        18,25       3,52        1,07        33,12               26,90       12,51       12,71
2048        1900        1,10        20,10       3,71        1,13        33,82               28,46       12,67       12,84
2048        2000        1,16        20,80       3,97        1,18        37,45               29,40       13,44       13,01
2048        2100        1,21        22,08       4,13        1,24        37,80               30,15       13,52       13,39
2048        2200        1,27        23,65       4,49        1,30        39,46               32,09       13,75       13,81
2048        2300        1,33        23,51       4,48        1,36        42,15               33,15       14,07       14,21
2048        2400        1,39        25,66       4,70        1,42        44,28               35,19       14,31       14,38
2048        2500        1,45        25,77       5,07        1,50        46,78               36,94       14,85       14,84
2048        2600        1,51        26,98       5,28        1,53        46,53               38,30       15,12       15,39
2048        2700        1,58        27,82       5,52        1,60        48,47               40,44       15,56       15,40
2048        2800        1,63        28,56       5,48        1,66        50,65               40,87       15,70       15,96
2048        2900        1,68        29,92       5,74        1,73        54,63               44,21       16,49       16,23
2048        3000        1,85        30,59       6,04        1,77        53,87               42,86       16,78       16,46
2048        3100        1,80        31,48       6,11        1,87        55,37               45,98       16,88       17,18
2048        3200        1,85        33,32       6,37        1,89        58,27               45,04       17,12       17,49
2048        3300        1,92        37,60       6,55        1,94        59,18               49,68       17,68       17,79
2048        3400        1,97        36,76       6,72        2,35        61,80               53,67       17,99       17,84
2048        3500        2,03        35,78       6,89        2,06        66,23               52,20       18,99       18,69

Povšimněte si, jaký znak je použit pro reprezentaci plovoucí desetinné čárky/tečky. Data je nutné načíst a posléze zkonvertovat hodnoty v příslušných sloupcích, například takto:

data_columns = ["ANSI C", "Cython #1", "Cython #2", "Cython #3", "Numba #1/interpret", "Numba #2", "Numba #3", "Numba #4"]
 
for data_column in data_columns:
    df[data_column] = pd.to_numeric(df[data_column].str.replace(',', '.'), errors='coerce')

Vykreslení běžného liniového grafu:

df.plot(x="Height", y=data_columns)

Obrázek 9: Liniový graf.

Vykreslení sloupcového grafu je mírně odlišné a vypadá takto:

df.plot.bar(x="Height", y=data_columns)

Obrázek 10: Sloupcový graf.

Opět si pro úplnost ukažme celý zdrojový kód skriptu, který zajistí vykreslení grafů:

#!/usr/bin/env python3
 
import sys
import pandas as pd
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_benchmark_results_line_chart.py ")
    print("Example:")
    print("  plot_benchmark_results_line_chart.py data.tsv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_file = sys.argv[1]
 
df = pd.read_csv(input_file, sep="\t")
 
data_columns = ["ANSI C", "Cython #1", "Cython #2", "Cython #3", "Numba #1/interpret", "Numba #2", "Numba #3", "Numba #4"]
 
for data_column in data_columns:
    df[data_column] = pd.to_numeric(df[data_column].str.replace(',', '.'), errors='coerce')
 
print(df)
print()
 
print(df.info())
print()
 
print(df.describe())
print()
 
 
# Create new histogram graph
df.plot(x="Height", y=data_columns)
 
# Try to show the plot on screen
plt.show()
#!/usr/bin/env python3
 
import sys
import pandas as pd
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_benchmark_results_bar_chart.py ")
    print("Example:")
    print("  plot_benchmark_results_bar_chart.py data.tsv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_file = sys.argv[1]
 
df = pd.read_csv(input_file, sep="\t")
 
data_columns = ["ANSI C", "Cython #1", "Cython #2", "Cython #3", "Numba #1/interpret", "Numba #2", "Numba #3", "Numba #4"]
 
for data_column in data_columns:
    df[data_column] = pd.to_numeric(df[data_column].str.replace(',', '.'), errors='coerce')
 
print(df)
print()
 
print(df.info())
print()
 
print(df.describe())
print()
 
 
# Create new histogram graph
df.plot.bar(x="Height", y=data_columns)
 
# Try to show the plot on screen
plt.show()

13. Výběr části datového rámce při vykreslování grafu

Předchozí sloupcový graf nebyl příliš přehledný, protože obsahoval mnoho údajů s rozdílným měřítkem. Samozřejmě nám nic nebrání v tom, abychom z datového rámce vybrali pouze určité řádky. Třída, jejímiž jsou datové rámce instancemi, přetěžuje operátor pro indexaci, takže je možné například napsat:

df = df[5:10]

Tímto přiřazením dojde ke zúžení datového rámce na pět řádků a tudíž i k vykreslení odlišného grafu:

Obrázek 11: Zmenšení datového rámce na pět řádků.

#!/usr/bin/env python3
 
import sys
import pandas as pd
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_benchmark_results_bar_chart_2.py ")
    print("Example:")
    print("  plot_benchmark_results_bar_chart_2.py data.tsv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_file = sys.argv[1]
 
df = pd.read_csv(input_file, sep="\t")
 
df = df[5:10]
data_columns = ["ANSI C", "Cython #1", "Cython #2", "Cython #3", "Numba #1/interpret", "Numba #2", "Numba #3", "Numba #4"]
 
for data_column in data_columns:
    df[data_column] = pd.to_numeric(df[data_column].str.replace(',', '.'), errors='coerce')
 
print(df)
print()
 
print(df.info())
print()
 
print(df.describe())
print()
 
 
# Create new histogram graph
df.plot.bar(x="Height", y=data_columns)
 
# Try to show the plot on screen
plt.show()

14. Zobecnění předchozího příkladu – zpracování numerických dat ve všech sloupcích

Seznam sloupců:

data_columns = ["ANSI C", "Cython #1", "Cython #2", "Cython #3", "Numba #1/interpret", "Numba #2", "Numba #3", "Numba #4"]

lze nahradit za:

data_columns = df.columns[1:]

což vede k obecnějšímu příkladu, v němž se jména sloupců v explicitně zapsané podobě nevyskytují:

#!/usr/bin/env python3
 
import sys
import pandas as pd
import matplotlib.pyplot as plt
 
# Check if command line argument is specified (it is mandatory).
if len(sys.argv) < 2:
    print("Usage:")
    print("  plot_benchmark_results.py ")
    print("Example:")
    print("  plot_benchmark_results.py data.tsv")
    sys.exit(1)
 
# First command line argument should contain name of input CSV.
input_file = sys.argv[1]
 
df = pd.read_csv(input_file, sep="\t")
 
df = df[10:]
data_columns = df.columns[1:]
 
for data_column in data_columns:
    df[data_column] = pd.to_numeric(df[data_column].str.replace(',', '.'), errors='coerce')
 
print(df)
print()
 
print(df.info())
print()
 
print(df.describe())
print()
 
 
# Create new histogram graph
df.plot.bar(x=df.columns[0], y=data_columns)
 
# Try to show the plot on screen
plt.show()

15. Validace dat v Pythonu

V závěrečné části článku se ve stručnosti budeme zabývat problematikou validace dat v datových rámcích. Validaci dat je možné využít v mnoha oblastech. Představme si například dokumentovou databázi, složitý konfigurační soubor nebo asi nejlépe klasickou webovou službu, která přijme data ve formátu JSON, převede je knihovní funkcí do nativní datové struktury (typicky do slovníku seznamů či hierarchicky uspořádaných slovníků) a následně provede validaci této struktury, ovšem nikoli programově (testováním jednotlivých atributů), ale na základě deklarativního popisu této struktury. Například můžeme specifikovat, že v atributu nazvaném „price“ by mělo být uloženo nezáporné číslo menší než 100000, v atributu pojmenovaném „valid_from“ musí být uložen řetězec odpovídající skutečnému datu (to už nelze otestovat primitivním regulárním výrazem, ale složitějším predikátem) a v atributu „login“ bude buď nick uživatele nebo bude tento atribut obsahovat null/None (popř. alternativně nebude existovat vůbec).

V případě formátu JSON je samozřejmě možné validaci provádět už nad vstupními daty přes JSON Schema, dtto při použití jazyka XML pomocí XML Schema (a dalších podobných nástrojů), ovšem možnosti těchto nástrojů jsou omezené – stále se totiž jedná „pouze“ o DSL, v nichž se složitější kritéria zapisují velmi složitě a většinou i nečitelně.

Nás dnes ovšem bude zajímat především validace tabulkových dat reprezentovaných v knihovně Pandas datovými rámci.

16. Validace s využitím knihovny Voluptuous

Ukažme si nejprve validaci dat s využitím obecné validační knihovny Voluptuous. V příkladu je použito velmi jednoduché schéma předepisující typy dat ve sloupcích:

schema = Schema((int, int, float))

Datovým rámcem můžeme procházet s využitím iterátoru a postupně validovat jeho řádky převedené na pojmenované n-tice (což není nejrychlejší řešení – výhodnější je procházení daty po sloupcích a nikoli po řádcích):

for record in df.itertuples():
    validate_item(schema, record)

Celý příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
import pandas
from voluptuous import Schema
from voluptuous import Invalid
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_item(schema, data):
    try:
        print("\n\n")
        print(schema)
        print(data)
        schema(data)
        print("pass")
    except Exception as e:
        print(e)
 
 
def validate_data_frame(data_frame):
    print()
 
    print("Validation")
    print("---------------------------")
 
    schema = Schema((int, int, float))
 
    for record in df.itertuples():
        validate_item(schema, record)
 
 
df = pandas.read_csv("missing_integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Ve druhém příkladu je ukázán vlastní validátor představovaný funkcí, která pro nevalidní data vyhodí výjimku:

def pos(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))

Příklad použití ve schématu:

schema = Schema((pos, pos, float))

Celý příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
import pandas
from voluptuous import Schema
from voluptuous import Invalid
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_item(schema, data):
    try:
        print("\n\n")
        print(schema)
        print(data)
        schema(data)
        print("pass")
    except Exception as e:
        print(e)
 
 
def pos(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
 
 
def validate_data_frame(data_frame):
    print()
 
    print("Validation")
    print("---------------------------")
 
    schema = Schema((pos, pos, float))
 
    for record in df.itertuples():
        validate_item(schema, record)
 
 
df = pandas.read_csv("missing_integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Test, zda třetí sloupec neobsahuje hodnoty NaN:

def pos(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
 
 
def nnat(value):
    if type(value) is not float or math.isnan(value):
        raise Invalid("non-NaN value expected, but got {v} instead".format(v=value))
 
 
schema = Schema((pos, pos, nnat))

Celý příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from voluptuous import Schema
from voluptuous import Invalid
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_item(schema, data):
    try:
        print("\n\n")
        print(schema)
        print(data)
        schema(data)
        print("pass")
    except Exception as e:
        print(e)
 
 
def pos(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
 
 
def nnat(value):
    if type(value) is not float or math.isnan(value):
        raise Invalid("non-NaN value expected, but got {v} instead".format(v=value))
 
 
def validate_data_frame(data_frame):
    print()
 
    print("Validation")
    print("---------------------------")
 
    schema = Schema((pos, pos, nnat))
 
    for record in df.itertuples():
        validate_item(schema, record)
 
 
df = pandas.read_csv("missing_integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Schéma popisující záznam v datovém rámci ve formě mapy_

schema = Schema({
    "Index": int,
    "_1": posint,
    "_2": posint,
    "Change": Any(str, float),
    "Language": str,
    "Ratings": posfloat,
    "Changep": float,
    })

V tomto případě je ovšem průchod datovým rámcem odlišný a vypadá následovně:

for record in df.itertuples():
    schema(record._asdict())

Celý příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from voluptuous import Schema
from voluptuous import Invalid
from voluptuous import Any
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_item(schema, data):
    try:
        print("\n")
        # print(schema)
        print(data)
        schema(data._asdict())
        print("pass")
    except Exception as e:
        print(e)
 
 
def posint(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
 
 
def posfloat(value):
    if type(value) is not float or value <= 0:
        raise Invalid("positive float value expected, but got {v} instead".format(v=value))
 
 
def validate_data_frame(data_frame):
    print()
 
    print("Validation")
    print("---------------------------")
 
    schema = Schema({
        "Index": int,
        "_1": posint,
        "_2": posint,
        "Change": Any(str, float),
        "Language": str,
        "Ratings": posfloat,
        "Changep": float,
        })
 
    for record in df.itertuples():
        validate_item(schema, record)
 
 
df = pandas.read_csv("tiobe.tsv", sep="\t")
print_data_frame(df)
validate_data_frame(df)
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from voluptuous import Schema
from voluptuous import Invalid
from voluptuous import Any
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_item(schema, data):
    try:
        print("\n")
        # print(schema)
        print(data)
        schema(data._asdict())
        print("pass")
    except Exception as e:
        print(e)
 
 
def posint(value):
    if type(value) is not int or value <= 0:
        raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
 
 
def posfloat(value):
    if type(value) is not float or value <= 0:
        raise Invalid("positive float value expected, but got {v} instead".format(v=value))
 
 
def validate_data_frame(data_frame):
    print()
 
    print("Validation")
    print("---------------------------")
 
    schema = Schema({
        "Index": int,
        "Year2020": posint,
        "Year2019": posint,
        "Change": Any(str, float),
        "Language": str,
        "Ratings": posfloat,
        "Changep": float,
        })
 
    for record in df.itertuples():
        validate_item(schema, record)
 
 
colnames = ("Year2020", "Year2019", "Change", "Language", "Ratings", "Changep")
df = pandas.read_csv("tiobe.tsv", sep="\t", names=colnames, header=1)
print_data_frame(df)
validate_data_frame(df)

17. Validace s využitím knihovny Opulent Pandas

Přímo pro účely validace datových rámců je určena knihovna nazvaná Opulent Pandas. Opět si ukažme způsob jejího použití. Schéma dvousloupcového datového rámce může vypadat takto:

schema = Schema({
    Required('Block size'): [TypeValidator(int)],
    Required('Time to read'): [TypeValidator(int)],
    })

Validace obsahu celého rámce je v tomto případě přímočará:

schema.validate(data_frame)

Celý příklad:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from opulent_pandas import Schema, TypeValidator, Required
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
def validate_data_frame(data_frame):
    schema = Schema({
        Required('Block size'): [TypeValidator(int)],
        Required('Time to read'): [TypeValidator(int)],
        })
 
    schema.validate(data_frame)
 
 
df = pandas.read_csv("integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Vytvoření vlastního validátoru – ten je tvořen třídou s metodou validate:

class PosintValidator(BaseValidator):
    def validate(self, values):
        if not (values > 0).all():
            raise Error("positive integer value expected")

Schéma s vlastním validátorem a jeho použití pro validaci obsahu celého rámce:

schema = Schema({
    Required('Block size'): [PosintValidator()],
    Required('Time to read'): [PosintValidator()],
    })
 
schema.validate(data_frame)

Úplný kód příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from opulent_pandas import Schema, Required, BaseValidator, Error
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
class PosintValidator(BaseValidator):
    def validate(self, values):
        if not (values > 0).all():
            raise Error("positive integer value expected")
 
 
def validate_data_frame(data_frame):
 
    schema = Schema({
        Required('Block size'): [PosintValidator()],
        Required('Time to read'): [PosintValidator()],
        })

    schema.validate(data_frame)
 
 
df = pandas.read_csv("integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Vzhledem k tomu, že implementace Any nefunguje správně (alespoň ne v současné verzi knihovny), je nutné některé validátory napsat tak, že iterují přes celý sloupec:

class NotNaNValidator(BaseValidator):
    def validate(self, values):
        for value in values:
            if math.isnan(value):
                raise Error("regular float value expected, but got: {}".format(value))

A opět ukázka celého příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import math
from opulent_pandas import Schema, Required, BaseValidator, Error
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
class PosintValidator(BaseValidator):
    def validate(self, values):
        if not (values > 0).all():
            raise Error("positive integer value expected")
 
 
class NotNaNValidator(BaseValidator):
    def validate(self, values):
        for value in values:
            if math.isnan(value):
                raise Error("regular float value expected, but got: {}".format(value))
 
 
def validate_data_frame(data_frame):
 
    schema = Schema({
        Required('Block size'): [PosintValidator()],
        Required('Time to read'): [NotNaNValidator()],
        })
 
    schema.validate(data_frame)
 
 
df = pandas.read_csv("missing_integer_values.csv")
print_data_frame(df)
validate_data_frame(df)

Nepatrně složitější zápis vlastního validátoru:

class IntOrNAValidator(BaseValidator):
    def validate(self, values):
        for value in values:
            if (type(value) == np.int64):
                return
            if not (pandas.isna(value)):
                raise Error("Int value or NA expected")

Použitý v příkladu:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
"""Reading CSV file that contains column with integer values (some are missing)."""
 
 
import pandas
import numpy as np
import math
from opulent_pandas import Schema, Required, BaseValidator, TypeValidator, Error, Any
 
 
def print_data_frame(df):
    print("Data frame")
    print("---------------------------")
    print(df)
    print()
 
    print("Column types")
    print("---------------------------")
    print(df.dtypes)
 
 
class PosintValidator(BaseValidator):
    def validate(self, values):
        if not (values > 0).all():
            raise Error("positive integer value expected")
 
 
class IntOrNAValidator(BaseValidator):
    def validate(self, values):
        for value in values:
            if (type(value) == np.int64):
                return
            if not (pandas.isna(value)):
                raise Error("Int value or NA expected")
 
 
def validate_data_frame(data_frame):
 
    schema = Schema({
        Required('Block size'): [PosintValidator()],
        Required('Time to read'): [IntOrNAValidator()],
        })
 
    schema.validate(data_frame)
 
 
df = pandas.read_csv("missing_integer_values.csv", dtype={"Time to read": "Int64"})
print_data_frame(df)
validate_data_frame(df)

18. Repositář s demonstračními příklady a datovými soubory

Zdrojové kódy všech dnes popsaných demonstračních příkladů určených pro Python 3 a nejnovější stabilní verzi knihovny Pandas 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:

Root tip

# Demonstrační příklad Stručný popis příkladu Cesta
1 data_frame_info1.py výpis obsahu datového rámce https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info1.py
2 data_frame_info2.py výpis prvních pěti řádků z datového rámce https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info2.py
3 data_frame_info3.py výpis informace o typech sloupců v datovém rámci https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info3.py
4 data_frame_info4.py výpis uspořádaného seznamu jmen všech sloupců https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info4.py
5 data_frame_info5.py podrobnější informace o datovém rámci, obsazení paměti atd. https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info5.py
6 data_frame_info6.py podrobnější informace o datovém rámci, obsazení paměti atd. https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info6.py
7 data_frame_info7.py popis os, počtu dimenzí, tvaru a velikosti datového rámce https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info7.py
8 data_frame_info8.py základní statistické informace o datech uložených v rámci https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info8.py
9 data_frame_info9.py výsledkem metody info je nový datový rámec https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/da­ta_frame_info9.py
       
10 plot_kafka_lags.py zobrazení jednoduchého grafu bez použití knihovny Pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags.py
11 plot_kafka_lags_pandas.py zobrazení jednoduchého grafu s použitím knihovny Pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags_pandas.py
12 plot_kafka_lags_pandas2.py snazší způsob vykreslení grafu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags_pandas2.py
13 plot_kafka_lags_pandas_sma3.py přidání klouzavého průměru do grafu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags_pandas_sma3.py
14 plot_kafka_lags_pandas_sma3_.py vylepšení předchozího příkladu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags_pandas_sma3_.py
15 plot_kafka_lags_pandas_sma10.py klouzavý průměr přes deset hodnot https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_kaf­ka_lags_pandas_sma10.py
16 plot_benchmark_results_line_chart.py liniový graf https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_ben­chmark_results_line_chart­.py
17 plot_benchmark_results_bar_chart1.py sloupcový graf https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_ben­chmark_results_bar_chart.py
18 plot_benchmark_results_bar_chart2.py výběr části datového rámce při vykreslování grafu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_ben­chmark_results_bar_chart.py
19 plot_benchmark_results_bar_chart3.py zobecnění předchozího příkladu – zpracování numerických dat ve všech sloupcích https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/plot_ben­chmark_results_bar_chart.py
       
20 check_types1.py kontrola typů sloupců s využitím knihovny Voluptuous https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/chec­k_types1.py
21 check_types2.py kontrola typů sloupců s využitím knihovny Voluptuous https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/chec­k_types2.py
22 check_types3.py kontrola typů sloupců s využitím knihovny Voluptuous https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/chec­k_types3.py
23 check_types4.py kontrola typů sloupců s využitím knihovny Voluptuous https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/chec­k_types4.py
24 check_types5.py kontrola typů sloupců s využitím knihovny Voluptuous https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/chec­k_types5.py
25 opulent_pandas1.py kontrola typů sloupců s využitím knihovny opulent-pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/o­pulent_pandas1.py
26 opulent_pandas2.py kontrola typů sloupců s využitím knihovny opulent-pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/o­pulent_pandas2.py
27 opulent_pandas3.py kontrola typů sloupců s využitím knihovny opulent-pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/o­pulent_pandas3.py
28 opulent_pandas4.py kontrola typů sloupců s využitím knihovny opulent-pandas https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/o­pulent_pandas4.py

Demonstrační příklady načítají následující soubory s daty:

benchmarks1.tsvbenchmarks2.tsv
# Datový soubor Stručný popis souboru Cesta
1 integer_values.csv dvousloupcová tabulka s celými čísly https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/in­teger_values.csv
2 missing_integer_values.csv dvousloupcová tabulka s celými čísly, z nichž některé chybí https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/mis­sing_integer_values.csv
3 timestamps.csv tabulka s časovými údaji https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/timestamps.csv
4 custom_timestamps.csv tabulka s časovými údaji používajícími nestandardní formát https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/cus­tom_timestamps.csv
5 denni_kurz.txt semistrukturovaný soubor s nestandardními oddělovači https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/denni_kurz.txt
6 tiobe.tsv data získaná ze stránek Tiobe indexu ve formátu TSV https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/tiobe.tsv
7 tiobe.txt data získaná ze stránek Tiobe indexu v textovém formátu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/tiobe.txt
8 výsledky benchmarků několika implementací Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/ben­chmarks1.tsv
9 výsledky benchmarků několika implementací Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/pandas/ben­chmarks2.tsv

19. Články s informacemi o různých způsobech validace datových struktur

V této kapitole jsou uvedeny odkazy na články, v nichž jsme se zabývali různými způsoby validace datových struktur. Pravděpodobně nejlepší přístup nalezneme v knihovně clojure.spec určené pro jazyk Clojure, ovšem i pro Python existuje několik velmi užitečných knihoven, například již výše zmíněná a použitá knihovna Voluptuous:

  1. Validace dat s využitím knihovny spec v Clojure 1.9.0
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/
  2. Validace dat s využitím knihovny spec v Clojure 1.9.0 (dokončení)
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0-dokonceni/
  3. Validace datových struktur v Pythonu pomocí knihoven Schemagic a Schema
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-pomoci-knihoven-schemagic-a-schema/
  4. Validace datových struktur v Pythonu (2. část)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-2-cast/
  5. Validace datových struktur v Pythonu (dokončení)
    https://www.root.cz/clanky/validace-datovych-struktur-v-pythonu-dokonceni/

20. Odkazy na Internetu

  1. Plotting with matplotlib
    https://pandas.pydata.org/pandas-docs/version/0.13/visualization.html
  2. Plot With Pandas: Python Data Visualization for Beginners
    https://realpython.com/pandas-plot-python/
  3. Pandas Dataframe: Plot Examples with Matplotlib and Pyplot
    https://queirozf.com/entries/pandas-dataframe-plot-examples-with-matplotlib-pyplot
  4. Opulent-Pandas na PyPi
    https://pypi.org/project/opulent-pandas/
  5. pandas_validator na PyPi
    https://pypi.org/project/pan­das_validator/
  6. pandas-validator (dokumentace)
    https://pandas-validator.readthedocs.io/en/latest/
  7. 7 Best Python Libraries for Validating Data
    https://www.yeahhub.com/7-best-python-libraries-validating-data/
  8. Universally unique identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Universally_unique_iden­tifier
  9. Nullable integer data type
    https://pandas.pydata.org/pandas-docs/stable/user_guide/integer_na.html
  10. pandas.read_csv
    https://pandas.pydata.org/pandas-docs/stable/reference/api/pan­das.read_csv.html
  11. How to define format when using pandas to_datetime?
    https://stackoverflow.com/qu­estions/36848514/how-to-define-format-when-using-pandas-to-datetime
  12. 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/
  13. Skip rows during csv import pandas
    https://stackoverflow.com/qu­estions/20637439/skip-rows-during-csv-import-pandas
  14. Denni kurz
    https://www.cnb.cz/cs/finan­cni_trhy/devizovy_trh/kur­zy_devizoveho_trhu/denni_kur­z.txt
  15. UUID objects according to RFC 4122 (knihovna pro Python)
    https://docs.python.org/3­.5/library/uuid.html#uuid­.uuid4
  16. Object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Object_identifier
  17. Digital object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Digital_object_identifi­er
  18. voluptuous na (na PyPi)
    https://pypi.python.org/py­pi/voluptuous
  19. Repositář knihovny voluptuous na GitHubu
    https://github.com/alectho­mas/voluptuous
  20. pytest-voluptuous 1.0.2 (na PyPi)
    https://pypi.org/project/pytest-voluptuous/
  21. pytest-voluptuous (na GitHubu)
    https://github.com/F-Secure/pytest-voluptuous
  22. schemagic 0.9.1 (na PyPi)
    https://pypi.python.org/py­pi/schemagic/0.9.1
  23. Schemagic / Schemagic.web (na GitHubu)
    https://github.com/Mechrop­hile/schemagic
  24. schema 0.6.7 (na PyPi)
    https://pypi.python.org/pypi/schema
  25. schema (na GitHubu)
    https://github.com/keleshev/schema
  26. XML Schema validator and data conversion library for Python
    https://github.com/brunato/xmlschema
  27. xmlschema 0.9.7
    https://pypi.python.org/py­pi/xmlschema/0.9.7
  28. jsonschema 2.6.0
    https://pypi.python.org/py­pi/jsonschema
  29. warlock 1.3.0
    https://pypi.python.org/pypi/warlock
  30. Python Virtual Environments – A Primer
    https://realpython.com/python-virtual-environments-a-primer/
  31. pip 1.1 documentation: Requirements files
    https://pip.readthedocs.i­o/en/1.1/requirements.html
  32. unittest.mock — mock object library
    https://docs.python.org/3­.5/library/unittest.mock.html
  33. mock 2.0.0
    https://pypi.python.org/pypi/mock
  34. An Introduction to Mocking in Python
    https://www.toptal.com/python/an-introduction-to-mocking-in-python
  35. Unit testing (Wikipedia)
    https://en.wikipedia.org/wi­ki/Unit_testing
  36. Unit testing
    https://cs.wikipedia.org/wi­ki/Unit_testing
  37. Test-driven development (Wikipedia)
    https://en.wikipedia.org/wiki/Test-driven_development
  38. Pip (dokumentace)
    https://pip.pypa.io/en/stable/
  39. 5 Differences between clojure.spec and Schema
    https://lispcast.com/clojure.spec-vs-schema/
  40. Schema: Clojure(Script) library for declarative data description and validation
    https://github.com/plumatic/schema
  41. clojure.spec – Rationale and Overview
    https://clojure.org/about/spec

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.