Nauč se Python > Kurzy > Datový kurz PyLadies > Představení, Jupyter notebook, základy pandas > Základní manipulace s tabulkovými daty

Pandas a základní manipulace s tabulkovými daty

Před jakoukoli složitější analýzou je třeba naučit se základy práce se zpracovávanými daty - v našem případě půjde o tabulky.

Import knihoven

Při běžném programování se snažíme aliasům vyhýbat, protože snižují čitelnost kódu pro další programátory. U datové analytiky je to jinak, protože použití jednoho aliasu, který je navíc velmi běžný, nám ušetří spoustu psaní. Mimoto výsledkem našich analýz budou spíše tabulky a grafy než kód.

In [1]:
import pandas as pd

Načtení tabulky s daty

Pro čtení dat má Pandas celou řadu funkcí read_*, díky kterým si poradí s celou řadou různých formátů. Nejběžnějším je ale pořád formát CSV, ve kterém jsou jednotlivé hodnoty oddělené čárkami.

Pokud budeš tímto notebookem experimentovat, stáhni si nejdříve soubor s daty z tohoto odkazu. Data pro pokusy jsou vytvořena z komplexního Pokedexu na Githubu.

In [2]:
data = pd.read_csv("static/pokemon.csv")

Po načtení dat dává smysl se na ně hned podívat, abychom zjistili, že je vše v pořádku. Může se stát, že v CSV souboru bude chybět první řádek s názvy sloupců, hodnoty budou oddělené jiným znakem než čárkou a spousta dalších eventualit. Náš CSV soubor je však precizně připraven a tak, když si jej zobrazíme v notebooku, uvidíme kompletní tabulku, kde je vše jak má být.

In [3]:
data
Out[3]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed
0 1 bulbasaur 0.7 6.9 green quadruped False Grass Poison 45 49 49 45
1 2 ivysaur 1.0 13.0 green quadruped False Grass Poison 60 62 63 60
2 3 venusaur 2.0 100.0 green quadruped False Grass Poison 80 82 83 80
3 4 charmander 0.6 8.5 red upright False Fire NaN 39 52 43 65
4 5 charmeleon 1.1 19.0 red upright False Fire NaN 58 64 58 80
... ... ... ... ... ... ... ... ... ... ... ... ... ...
802 803 poipole 0.6 1.8 purple upright False Poison NaN 67 73 67 73
803 804 naganadel 3.6 150.0 purple wings False Poison Dragon 73 73 73 121
804 805 stakataka 5.5 820.0 gray quadruped False Rock Steel 61 131 211 13
805 806 blacephalon 1.8 13.0 white humanoid False Fire Ghost 53 127 53 107
806 807 zeraora 1.5 44.5 yellow humanoid False Electric NaN 88 112 75 143

807 rows × 13 columns

Základní zobrazení v notebooku nám ukáže prvních pět a posledních pět řádků v tabulce společně s informací o celkovém počtu řádků a sloupců. Naše tabulka je úmyslně dostatečně malá, aby se s ní pohodlně pracovalo, což ale většinou není ten případ. Proto se podíváme na to, jak si z tabulky vybrat jen to, co nás zajímá.

Sloupce a řádky

Sloupce i řádky mají tzv. index, s jehož pomocí se dá k jednotlivým částem tabulky přistoupit. V našem případě má každý řádek automaticky doplněný unikátní číselný index (ten bezejmenný sloupec nalevo) a jako index pro sloupce nám poslouží prvni řádek CSV souboru s názvy sloupců.

Později se naučíme indexy měnit a vkládat do nich i více než jednu hodnotu.

Sloupce

Sloupce lze vybrat stejně jako se vybírají hodnoty ze slovníku - do hranatých závorek stačí napsat název sloupce.

In [4]:
data['name']
Out[4]:
0        bulbasaur
1          ivysaur
2         venusaur
3       charmander
4       charmeleon
          ...     
802        poipole
803      naganadel
804      stakataka
805    blacephalon
806        zeraora
Name: name, Length: 807, dtype: object

Pokud potřebujeme více sloupců najednou, vložíme do hranatých závorek za proměnnou s tabulkou jejich seznam.

In [5]:
data[['name', 'color']]
Out[5]:
name color
0 bulbasaur green
1 ivysaur green
2 venusaur green
3 charmander red
4 charmeleon red
... ... ...
802 poipole purple
803 naganadel purple
804 stakataka gray
805 blacephalon white
806 zeraora yellow

807 rows × 2 columns

Pokud název sloupce neobsahuje mezeru a nekoliduje s názvem existující metody, dá se k němu přistoupit i pomocí tečkové notace, což je velmi užitečné především při psaní složitějších podmínek.

In [6]:
data.name
Out[6]:
0        bulbasaur
1          ivysaur
2         venusaur
3       charmander
4       charmeleon
          ...     
802        poipole
803      naganadel
804      stakataka
805    blacephalon
806        zeraora
Name: name, Length: 807, dtype: object

Pro sloupec shape je tento přístup nepoužitelný, protože koliduje s atributem, ve kterém je uložen počet řádků a slupců tabulky.

In [7]:
data.shape
Out[7]:
(807, 13)

Řádky

Pro řádky se používá tzv. indexer loc, který nám dokáže obstarat jeden či více řádků.

Můžeme si například říci o jeden řádek jeho indexem:

In [8]:
data.loc[1]
Out[8]:
id                 2
name         ivysaur
height             1
weight            13
color          green
shape      quadruped
is baby        False
type 1         Grass
type 2        Poison
hp                60
attack            62
defense           63
speed             60
Name: 1, dtype: object

O více řádků seznamem jejich indexů:

In [9]:
data.loc[[1, 2, 3]]
Out[9]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed
1 2 ivysaur 1.0 13.0 green quadruped False Grass Poison 60 62 63 60
2 3 venusaur 2.0 100.0 green quadruped False Grass Poison 80 82 83 80
3 4 charmander 0.6 8.5 red upright False Fire NaN 39 52 43 65

Nebo třeba stejně jako i seznamů o několik řádků pomocí rozsahu indexů:

In [10]:
data.loc[100:105]
Out[10]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed
100 101 electrode 1.2 66.6 red ball False Electric NaN 60 50 70 150
101 102 exeggcute 0.4 2.5 pink heads False Grass Psychic 60 40 80 40
102 103 exeggutor 2.0 120.0 yellow legs False Grass Psychic 95 95 85 55
103 104 cubone 0.4 6.5 brown upright False Ground NaN 50 50 95 35
104 105 marowak 1.0 45.0 brown upright False Ground NaN 60 80 110 45
105 106 hitmonlee 1.5 49.8 brown humanoid False Fighting NaN 50 120 53 87

Konkrétní hodnoty

Indexer loc je vhodný i pro získání konkrétní hodnoty, jen se při indexaci použijí oba indexy (pro řádek i sloupec) najednou ve formě ntice.

In [11]:
data.loc[101, 'name']
Out[11]:
'exeggcute'

Filtrace dat

Velmi často se hodí získat část tabulky, ve kteté hodnoty splňují určitá kritéria. Toho lze docílit tak, že napíšeme do hranatých závorek podmínku a dostaneme jen ty řádky, které danou podmínku splňují.

Například nás budou zajímat jen červení pokémoni.

In [12]:
data[data['color'] == "red"]
Out[12]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed
3 4 charmander 0.6 8.5 red upright False Fire NaN 39 52 43 65
4 5 charmeleon 1.1 19.0 red upright False Fire NaN 58 64 58 80
5 6 charizard 1.7 90.5 red upright False Fire Flying 78 84 78 100
44 45 vileplume 1.2 18.6 red humanoid False Grass Poison 75 80 85 50
45 46 paras 0.3 5.4 red armor False Bug Grass 35 70 55 25
... ... ... ... ... ... ... ... ... ... ... ... ... ...
726 727 incineroar 1.8 83.0 red upright False Fire Dark 95 115 90 60
740 741 oricorio 0.6 3.4 red wings False Fire Flying 75 70 70 93
775 776 turtonator 2.0 212.0 red upright False Fire Dragon 60 78 135 36
786 787 tapu-bulu 1.9 45.5 red arms False Grass Fairy 70 130 115 75
793 794 buzzwole 2.4 333.6 red tentacles False Bug Fighting 107 139 139 79

82 rows × 13 columns

Podmínky se dají i kombinovat, ale musí se dodržet jistá pravidla. První z nich je, že každá podmínka musí být samostatně uzavřena v kulatých závorkách. Dále pak logické spojky and a or jsou zde nahrazeny znaky & a |.

Co třeba červení pokémoni, jejichž síla útoku je vyšší nebo rovna 130?

In [13]:
data[(data['attack'] >= 130) & (data['color'] == "red")]
Out[13]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed
98 99 kingler 1.3 60.0 red armor False Water NaN 55 130 115 75
135 136 flareon 0.9 25.0 red quadruped False Fire NaN 65 130 60 65
211 212 scizor 1.8 118.0 red bug-wings False Bug Steel 70 130 100 65
249 250 ho-oh 3.8 199.0 red wings False Fire Flying 106 130 90 90
382 383 groudon 3.5 950.0 red upright False Ground NaN 100 150 140 90
385 386 deoxys 1.7 60.8 red humanoid False Psychic NaN 50 150 50 150
554 555 darmanitan 1.3 92.9 red quadruped False Fire NaN 105 140 55 95
716 717 yveltal 5.8 203.0 red wings False Dark Flying 126 131 95 99
786 787 tapu-bulu 1.9 45.5 red arms False Grass Fairy 70 130 115 75
793 794 buzzwole 2.4 333.6 red tentacles False Bug Fighting 107 139 139 79

Operace se sloupci

Podmínka zapsaná v hranatých závorkách ve skutečnosti není jen tak obyčejnou podmínkou. Jedná se totiž o hromadnou operaci napříč celým sloupcem, která pro každý řádek vrátí True nebo False, což v případě použití k filtraci rozhodne o jeho zobrazení.

In [14]:
data.speed >= 65
Out[14]:
0      False
1      False
2       True
3       True
4       True
       ...  
802     True
803     True
804    False
805     True
806     True
Name: speed, Length: 807, dtype: bool

I další známé operátory lze použít stejným způsobem a aplikovat je na sloupec a jednu hodnotu či na více sloupců najednou.

In [15]:
data["speed"] / 10
Out[15]:
0       4.5
1       6.0
2       8.0
3       6.5
4       8.0
       ... 
802     7.3
803    12.1
804     1.3
805    10.7
806    14.3
Name: speed, Length: 807, dtype: float64
In [16]:
data.hp * data.defense
Out[16]:
0       2205
1       3780
2       6640
3       1677
4       3364
       ...  
802     4489
803     5329
804    12871
805     2809
806     6600
Length: 807, dtype: int64

Výsledek takové operace lze velmi snadno přidat zpět k původní tabulce jako nový sloupec.

In [17]:
data["fast"] = data.speed >= 65
In [18]:
data
Out[18]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed fast
0 1 bulbasaur 0.7 6.9 green quadruped False Grass Poison 45 49 49 45 False
1 2 ivysaur 1.0 13.0 green quadruped False Grass Poison 60 62 63 60 False
2 3 venusaur 2.0 100.0 green quadruped False Grass Poison 80 82 83 80 True
3 4 charmander 0.6 8.5 red upright False Fire NaN 39 52 43 65 True
4 5 charmeleon 1.1 19.0 red upright False Fire NaN 58 64 58 80 True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
802 803 poipole 0.6 1.8 purple upright False Poison NaN 67 73 67 73 True
803 804 naganadel 3.6 150.0 purple wings False Poison Dragon 73 73 73 121 True
804 805 stakataka 5.5 820.0 gray quadruped False Rock Steel 61 131 211 13 False
805 806 blacephalon 1.8 13.0 white humanoid False Fire Ghost 53 127 53 107 True
806 807 zeraora 1.5 44.5 yellow humanoid False Electric NaN 88 112 75 143 True

807 rows × 14 columns

Řazení

K řazení podle hodnot slouží metoda sort_values, podle indexu pak řadí metoda sort_index. Prvních několik řádků získáme metodou head a naopak poslední nám poskytne metoda tail.

Pět nejrychlejších pokémonů tedy získáme snadno sestupným seřazením podle jejich rychlosti a omezením na prvních pět řádků.

In [19]:
data.sort_values(by="speed", ascending=False).head()
Out[19]:
id name height weight color shape is baby type 1 type 2 hp attack defense speed fast
290 291 ninjask 0.8 12.0 yellow bug-wings False Bug Flying 61 90 45 160 True
794 795 pheromosa 1.8 25.0 white humanoid False Bug Fighting 71 137 37 151 True
385 386 deoxys 1.7 60.8 red humanoid False Psychic NaN 50 150 50 150 True
100 101 electrode 1.2 66.6 red ball False Electric NaN 60 50 70 150 True
616 617 accelgor 0.8 25.3 red arms False Bug NaN 80 70 40 145 True

Změna indexu

Před změnou indexu je dobrý nápad se přesvědčit, zda sloupec s budoucím indexem obsahuje jen unikátní hodnoty.

In [20]:
data.shape
Out[20]:
(807, 14)
In [21]:
data.name.nunique()
Out[21]:
807

Protože počet unikátních hodnot ve sloupci se jménem pokémona je stejný jako celkový počet řádků, můžeme jméno použít jako index. To lze udělat buď nastavením atributu index nebo pomocí metody set_index, která toho umí i daleko více.

In [22]:
data.set_index("name")
Out[22]:
id height weight color shape is baby type 1 type 2 hp attack defense speed fast
name
bulbasaur 1 0.7 6.9 green quadruped False Grass Poison 45 49 49 45 False
ivysaur 2 1.0 13.0 green quadruped False Grass Poison 60 62 63 60 False
venusaur 3 2.0 100.0 green quadruped False Grass Poison 80 82 83 80 True
charmander 4 0.6 8.5 red upright False Fire NaN 39 52 43 65 True
charmeleon 5 1.1 19.0 red upright False Fire NaN 58 64 58 80 True
... ... ... ... ... ... ... ... ... ... ... ... ... ...
poipole 803 0.6 1.8 purple upright False Poison NaN 67 73 67 73 True
naganadel 804 3.6 150.0 purple wings False Poison Dragon 73 73 73 121 True
stakataka 805 5.5 820.0 gray quadruped False Rock Steel 61 131 211 13 False
blacephalon 806 1.8 13.0 white humanoid False Fire Ghost 53 127 53 107 True
zeraora 807 1.5 44.5 yellow humanoid False Electric NaN 88 112 75 143 True

807 rows × 13 columns

Celá řada metod vrátí po úpravě tabulky její kopii s provedenou změnou. Protože bychom v tomto případě raději upravili tabulku přímo, uložíme výsledek nastavení indexu zpět do proměnné data.

In [23]:
data = data.set_index("name")

Nyní si můžeme vyzkoušet řazení s upraveným indexem a také získávání konkrétních řádků s jeho pomocí.

In [24]:
data.sort_index()
Out[24]:
id height weight color shape is baby type 1 type 2 hp attack defense speed fast
name
abomasnow 460 2.2 135.5 white upright False Grass Ice 90 92 75 60 False
abra 63 0.9 19.5 brown upright False Psychic NaN 25 20 15 90 True
absol 359 1.2 47.0 white quadruped False Dark NaN 65 130 60 75 True
accelgor 617 0.8 25.3 red arms False Bug NaN 80 70 40 145 True
aegislash 681 1.7 53.0 brown blob False Steel Ghost 60 50 150 60 False
... ... ... ... ... ... ... ... ... ... ... ... ... ...
zoroark 571 1.6 81.1 gray upright False Dark NaN 60 105 60 105 True
zorua 570 0.7 12.5 gray quadruped False Dark NaN 40 65 40 65 True
zubat 41 0.8 7.5 purple wings False Poison Flying 40 45 35 55 False
zweilous 634 1.4 50.0 blue quadruped False Dark Dragon 72 85 70 58 False
zygarde 718 5.0 305.0 green squiggle False Dragon Ground 108 100 121 95 True

807 rows × 13 columns

In [25]:
data.loc["pikachu"]
Out[25]:
id                25
height           0.4
weight             6
color         yellow
shape      quadruped
is baby        False
type 1      Electric
type 2           NaN
hp                35
attack            55
defense           40
speed             90
fast            True
Name: pikachu, dtype: object

Pokračování

Tohle nejsou ani zdaleka všechny možnosti manipulace s daty, které Pandas umožňuje, ale pro začátek to bude stačit. Další si ukážeme během explorační datové analýzy v následujících kapitolách.

Pokud si chceš výsledky svých experimentů uložit do souboru, stačí použít metodu to_csv().

In [26]:
data.to_csv("vysledky_pokusu.csv")

Čas na hraní

Před další lekcí je důležité se s Pandasem trošku sžít a základní operace s daty dostat pod kůži. V dalších lekcích si budeme ukazovat pokročilejší metody pro práci s daty, které budou na těchto základech stavět. Po každé lekci bude také více než vhodné si nově získané znalosti zkusit a k tomu bude potřeba pro začátek sehnat nějaká data.

Lepších či horších zdrojů dat je na internetu nepřeberné množství, takže si každý jistě vybere. Na začátku doporučujeme si vybrat data, kterým rozumíš, protože se pak při analýze budeš moci soustředit na řešení pokročilejších úloh a nebudeš muset bazírovat nad významem krkolomně pojmenovaných sloupců.

Pro začátek se můžeš podívat třeba sem:

Zkus si nějakou datovou sadu stáhnout a prozkoumat. Když se ti zalíbí, tak si ji nech, budeš si s ní moci hrát čím dál sofistikovaněji po absolvování dalších lekcích.


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