Skip to content

Commit c99198e

Browse files
committed
Agregace a spojování
* Sjednoceni na python skript * Presun cteni na doma zvlast Closes #26
1 parent accd2cf commit c99198e

File tree

3 files changed

+111
-73
lines changed

3 files changed

+111
-73
lines changed

python-pro-data-1/agregace-a-spojovani/agregace-a-spojovani.md

Lines changed: 67 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Abychom měli nějaký praktický příklad k procvičování, použijeme fiktiv
88

99
Funkce `read_csv()` knihovny `pandas` umí stáhnout CSV soubor rovnou z internetu.
1010

11-
```pycon
11+
```py
1212
import pandas
1313

1414
u202 = pandas.read_csv("https://kodim.cz/cms/assets/kurzy/python-data-1/python-pro-data-1/agregace-a-spojovani/u202.csv")
@@ -47,13 +47,13 @@ V `pandas`, ale i obecně v datové analýze, je možné se s chybějícími dat
4747

4848
Důležité je mít na paměti, že vyřazením některých řádků může dojít ke zkreslení výsledků analýzy!
4949

50-
#### Odstranění neúplných řádků
50+
### Odstranění neúplných řádků
5151

5252
Předpokládejme, že jsme si ověřili, že data chybí skutečně pouze u studentů, kteří z daného předmětu nematurovali. Protože nás budou zajímat především statistiky jednotlivých předmětů, můžeme prázdné řádky vynechat, protože označují zkoušky, které ve skutečnosti neproběhly.
5353

5454
Pokud jsme tak ještě neučinili, načteme si naši první tabulku jako DataFrame.
5555

56-
```pycon
56+
```py
5757
import pandas
5858
u202 = pandas.read_csv('u202.csv')
5959
```
@@ -62,9 +62,11 @@ Pokud `pandas` narazí na prázdnou buňku, vloží místo ní do tabulky speci
6262

6363
Série obsahují metodu `isnull()`, která vrátí pravdivostní sérii s hodnotou `True` všude tam, kde v původní sérii chybí hodnota. Metoda `notnull()` pracuje přesně opačně. Vrátí pravdivostní sérii s hodnotami `True` všude tam, kde v původní sérii hodnota nechybí.
6464

65-
```pycon
65+
```py
6666
print(u202['znamka'].isnull())
67+
```
6768

69+
```shell
6870
0 True
6971
1 False
7072
2 False
@@ -85,9 +87,11 @@ Name: znamka, dtype: bool
8587

8688
Tyto metody můžeme využít například k tomu, abychom získali všechna data, kde chybí hodnota ve sloupečku `znamka`.
8789

88-
```pycon
90+
```py
8991
print(u202[u202['znamka'].isnull()])
92+
```
9093

94+
```shell
9195
cisloStudenta predmet znamka den
9296
0 1 Chemie NaN pá
9397
9 9 Dějepis NaN pá
@@ -105,27 +109,27 @@ Nyní bychom chtěli všechny tři naše tabulky spojit do jedné. Nejprve si uk
105109

106110
Začneme s tím, že každou tabulku uložíme do `DataFrame` s tím, že vyhodíme studenty, kteří na maturitu nedorazili.
107111

108-
```pycon
112+
```py
109113
u202 = pandas.read_csv('u202.csv').dropna()
110114
u203 = pandas.read_csv('u203.csv').dropna()
111115
u302 = pandas.read_csv('u302.csv').dropna()
112116
```
113117

114118
Pokud chceme tyto tři DataFrame spojit do jednoho, můžeme použít funkci `concat`.
115119

116-
```pycon
120+
```py
117121
maturita = pandas.concat([u202, u203, u302])
118122
```
119123

120124
Pozor ale na to, že v takto vzniklém DataFrame se nám **rozbije index**, protože se prostě spojí za sebe indexy jednotlivých tabulek. Pokud chceme, aby `pandas` při spojování index přepočítal, musíme nastavit hodnotu parametru `ignore_index` na `True`.
121125

122-
```pycon
126+
```py
123127
maturita = pandas.concat([u202, u203, u302], ignore_index=True)
124128
```
125129

126130
To už je lepší. Stále nám však zůstává jeden problém. Po spojení tabulek do jedné už nevíme, kdo maturoval v jaké místnosti. Tuto informaci si proto doplníme do původních tří tabulek jako nový sloupeček. Až poté tabulky spojíme do jedné.
127131

128-
```pycon
132+
```py
129133
u202['mistnost'] = 'u202'
130134
u203['mistnost'] = 'u203'
131135
u302['mistnost'] = 'u302'
@@ -134,7 +138,7 @@ maturita = pandas.concat([u202, u203, u302], ignore_index=True)
134138

135139
Takto už nám vznikla pěkná vyčištěná tabulka. Uložme si ji do CSV, ať ji nemusíme vyrábět pořád znova. Nebudeme ukládat index, protože ten si vždycky necháme vyrobit automaticky.
136140

137-
```pycon
141+
```py
138142
maturita.to_csv('maturita.csv', index=False)
139143
```
140144

@@ -146,10 +150,12 @@ Výslednou tabulku si můžete stáhnout jako soubor [maturita.csv](assets/matur
146150

147151
Naše výsledky byly anonymní. Pokud bychom ale chtěli vytisknout maturitní vysvědčení, potřebujeme k číslům studenta zjistit jejich jména. Jména najdeme v samostatné tabulce [studenti.csv](assets/studenti.csv). Načtěme si jej jako `DataFrame`.
148152

149-
```pycon
153+
```py
150154
studenti = pandas.read_csv('https://kodim.cz/cms/assets/kurzy/python-data-1/python-pro-data-1/agregace-a-spojovani/studenti.csv')
151-
studenti.head()
155+
print(studenti.head())
156+
```
152157

158+
```shell
153159
cisloStudenta jméno
154160
0 1 Jana Zbořilová
155161
1 2 Lukáš Jurčík
@@ -167,10 +173,12 @@ Propojení tabulek se v `pandas` dělá pomocí funkce `merge` (dokumentaci k n
167173

168174
Ve výchozím nastavení funkce `merge()` ponechá pouze řádky, které mají záznamy v obou tabulkách. V SQL bychom tuto operaci označili jako `INNER JOIN`.
169175

170-
```pycon
176+
```py
171177
propojeny_df = pandas.merge(u202, studenti)
172178
print(propojeny_df.head())
179+
```
173180

181+
```shell
174182
cisloStudenta predmet znamka den jmeno
175183
0 1 Chemie NaN pá Jana Zbořilová
176184
1 2 Dějepis 3.0 pá Lukáš Jurčík
@@ -181,38 +189,51 @@ print(propojeny_df.head())
181189

182190
Pokud by například nějaký student nebyl uvedený v tabulce se studenty, jeho maturitní výsledek by zmizel. U nového `DataFrame` bychom tedy měli zkontrolovat, zda má `spojenyDF` stejný počet řádků jako `u202`.
183191

184-
```pycon
185-
u202.shape
192+
```py
193+
print(u202.shape)
194+
```
195+
196+
```shell
186197
(15, 4)
187-
propojeny_df.shape
198+
```
199+
200+
```py
201+
print(propojeny_df.shape)
202+
```
203+
204+
```shell
188205
(15, 5)
189206
```
190207

191208
Zde vidíme, že data jsou zřejmě v pořádku.
192209

193210
Dále připojíme tabulku [predsedajici.csv](assets/predsedajici.csv), kde máme vypsané předsedy maturitních komisí. Tu si opět načteme jako `DataFrame`.
194211

195-
```pycon
212+
```py
196213
preds = pandas.read_csv('https://kodim.cz/cms/assets/kurzy/python-data-1/python-pro-data-1/agregace-a-spojovani/predsedajici.csv')
197214
```
198215

199216
Zkusme tabulky spojit jako předtím.
200217

201-
```pycon
218+
```py
202219
novy_propojeny_df = pandas.merge(propojeny_df, preds)
203220
print(novy_propojeny_df.head())
221+
```
204222

223+
```shell
205224
Empty DataFrame
206225
Columns: [den, datum, jmeno, cisloStudenta, predmet, znamka]
207226
Index: []
208227
```
209228

210229
Tentokrát jsme příliš neuspěli, výsledný `DataFrame` je prázdný. Proč tomu tak je? Protože v obou `DataFrame` máme sloupec `jmeno`, v jednom případě však jde o jméno studenta a ve druhém o jméno předsedy komise. To ale `pandas` samozřejmě neví. Proto mu musíme říct, že chceme data spojit pouze podle sloupce `den`.
211230

212-
```pycon
231+
```py
213232
novy_propojeny_df = pandas.merge(propojeny_df, preds, on=['den'])
214233
print(novy_propojeny_df.head())
234+
```
215235

236+
```shell
216237
datum jmeno_x den cisloStudenta predmet znamka mistnost jmeno_y
217238
0 21.5.2019 Marie Zuzaňáková út 3 Matematika 2.0 u202 Pavel Horák
218239
1 21.5.2019 Marie Zuzaňáková út 3 Chemie 5.0 u202 Pavel Horák
@@ -223,26 +244,32 @@ print(novy_propojeny_df.head())
223244

224245
Zatím to vypadá dobře. Pokud se ovšem podíváme na `shape`, něco nám tady nehraje.
225246

226-
```pycon
247+
```py
227248
print(novy_propojeny_df.shape)
249+
```
228250

251+
```shell
229252
(10, 8)
230253
```
231254

232255
Najednou máme v tabulce pouze 12 řádků, některé tedy zmizely. To znamená, že funkce `merge()` nenašla pro všechna zkoušení odpovídajícího předsedu. Jak je to možné? Zkusme nyní říct funkci `merge()`, aby nám zachovala v prvním `DataFrame` ty řádky, pro které nenajde odpovídající záznam. Této operaci se v jazyce SQL říká LEFT OUTER JOIN. My ho provede tak, že funkci `merge()` jako parametr `how` zadáme hodnotu `left`.
233256

234-
```pycon
257+
```py
235258
novy_propojeny_df = pandas.merge(propojeny_df, preds, on=['den'], how="outer")
236259
print(novy_propojeny_df.shape)
260+
```
237261

262+
```shell
238263
(14, 8)
239264
```
240265

241266
Tentokrát jsme již o data nepřišli, ale kde se stala chyba? Zkusme si zobrazit ty řádky, které se nepodařilo propojit. Poznáme je tak, že mají prázdný sloupec `datum`.
242267

243-
```pycon
268+
```py
244269
print(novy_propojeny_df[novy_propojeny_df["datum"].isnull()])
270+
```
245271

272+
```shell
246273
cisloStudenta predmet znamka den mistnost jmeno_x datum jmeno_y
247274
5 5.0 Dějepis 1.0 po u202 Kateřina Novotná NaN NaN
248275
6 7.0 Dějepis 4.0 po u202 Vasil Lácha NaN NaN
@@ -253,32 +280,36 @@ Nyní jsme již na stopě problému. Z nějakého důvodu nám nefunguje propoje
253280

254281
Pokud nemáme možnost vstupní data opravit, můžeme použít funkci `strip()`, která z řetězce odstraní mezery (a další bílé znaky) na začátku a na konci. Tyto mezery jsou v drtivé většině případů způsobeny chybou a proto jejich odstraněním nic nezkazíme.
255282

256-
```pycon
283+
```py
257284
preds["den"] = preds["den"].str.strip()
258285
novy_propojeny_df = pandas.merge(propojeny_df, preds, on=['den'], how="outer")
259286
print(novy_propojeny_df.shape)
287+
```
260288

289+
```shell
261290
(13, 8)
262291
```
263292

264293
Poslední nepříjemností, na kterou se podíváme, je to, že sloupce `jmeno` se automaticky přejmenovaly, aby neměly v tabulce stejný název. Zde můžeme použít metodu `rename`, abychom sloupečky přejmenovali na něco smysluplného.
265294

266-
```pycon
295+
```py
267296
novy_propojeny_df = novy_propojeny_df.rename(columns={'jmeno_x': 'jmeno', 'jmeno_y': 'predseda'})
268297
```
269298
### Agregace
270299

271300
Z databází známe kromě UNION a JOIN také operaci GROUP BY. V `pandas` ji provedeme tak, že pomocí metody `groupby` vyrobíme z `DataFrame` speciální objekt `DataFrameGroupBy`. Dejme tomu, že chceme grupovat podle sloupečku `mistnost`.
272301

273-
```pycon
302+
```py
274303
maturita.groupby('mistnost')
275304
```
276305

277306
Na tomto speciálním objektu pak můžeme používat různé agregační funkce. Nejjednodušší je funkce `count`
278307

279-
```pycon
280-
maturita.groupby('mistnost').count()
308+
```py
309+
print(maturita.groupby('mistnost').count())
310+
```
281311

312+
```shell
282313
jméno předmět známka den datum předs
283314
místnost
284315
u202 13 13 13 13 13 13
@@ -302,17 +333,19 @@ Další užitečné agregační funkce jsou například:
302333

303334
Nemusíme samozřejmě grupovat přes všechny sloupečky. Vybereme si pouze ty, které nás zajímají. Zkusme například spočítat průměrnou známku z jednotlivých předmětů.
304335

305-
```pycon
306-
maturita.groupby('predmet')['znamka'].mean()
336+
```py
337+
print(maturita.groupby('predmet')['znamka'].mean())
307338
```
308339

309340
Pomocí agregací můžeme vyřešit i náš problém s nákupy. Pokud máme stále načtený `Data Frame` `nakupy`, můžeme použít funkci `groupby` podle jména a následně spočítat sumu nákupů pomocí `.sum()`.
310341

311-
```pycon
342+
```py
312343
nakupy = pandas.read_csv('nakupy.csv')
313344
nakupy_celkem = nakupy.groupby("Jméno")["Částka v korunách"].sum()
314345
print(nakupy_celkem)
346+
```
315347

348+
```shell
316349
Jméno
317350
Libor 124
318351
Míša 160
@@ -323,74 +356,35 @@ Zuzka 80
323356
Name: Částka v korunách, dtype: int64
324357
```
325358

326-
#### Čtení na doma: Více různých agregací
327-
328-
Pokud chceme provést více různých agregací, použijeme metodu `agg`. Metodě `agg` vložíme jako parametr slovník, kde klíčem je název sloupce, pro který počítáme agregaci, a hodnotou je řetězec nebo seznam řetězců se jmény agregací, které chceme provést. Například u maturity chceme zjistit, jestli student prospěl, prospěl s vyznamenáním nebo neprospěl. K tomu potřebujeme funkci `max()` (pětka znamená, že student neuspěl a trojka znamená, že nemůže získat vyznamenání) a funkci `mean()` (abychom zjistili, zda je průměr známek menší než 1.5).
329-
330-
```pycon
331-
maturita.groupby("cisloStudenta").agg({"znamka": ["max", "mean"]})
332-
```
333-
334-
K určení výsledku studenta bychom ještě potřebovali nový sloupec, jehož hodnota bude určena na základě podmínky, což si ukážeme níže.
335-
336359
### Počítané sloupce
337360

338361
Občas je užitečné přidat nový sloupec, který obsahuje hodnotu vypočtenou na základě hodnot ostatních sloupců. Vraťme se například k naší tabulce s údaji o státech ve světě. Máme informaci o rozloze a počtu obyvatel, mohli bychom tedy přidal sloupec s hodnotou hustoty zalidnění (počet obyvatel na 1 km čtvereční), který získáme vydělením počtu obyvatel rozlohou země.
339362

340363
Pokud nemáme načtený soubor s daty, načteme si ho.
341364

342-
```pycon
365+
```py
343366
staty = pandas.read_json("staty.json")
344367
staty = staty.set_index("name")
345368
```
346369

347370
Přidání nového sloupce je poměrně jednoduché. Před znaménko `=` vložíme proměnnou s `DataFrame` a do hranatých závorek vložíme název nového sloupce. Na pravou stranu umístíme výpočet. Ve výpočtu pracujeme s jednotlivými sloupci, v našem konkrétním případě vydělíme sloupec `population` sloupcem `area`.
348371

349-
```pycon
372+
```py
350373
staty["population_density"] = staty["population"] / staty["area"]
351374
```
352375

353376
**Poznámka:** `pandas` nás neupozorní, pokud sloupec již existuje, musíme si tedy dát pozor, abychom nepřepsali nějaký existující sloupec.
354377

355-
#### Čtení na doma: Podmíněný sloupec
356-
357-
Občas chceme do výpočtu zapracovat i podmínku. Ve skutečnosti je podmínka to poslední, co nám chybělo k vyřešení našeho problému s finančním vypořádání spolubydlících pomocí `pandas`. Náš výpočet se skládá z pěti kroků.
358-
359-
1. Provedeme agregaci hodnot nákupů podle jmen. Tím zjistíme sumu, kolik každý utratil.
360-
1. Zjistíme si průměrnou útratu za osobu. K tomu použijeme funkci `mean()`.
361-
1. Přidáme sloupec s podmínkou. V podmínce porovnáváme, zda spolubydlící utratil více nebo méně, než je průměr. K tomu použijeme funkci `where`, která je součástí modulu `numpy`. Nejprve provedeme import modulu `numpy` a následně z modulu zavoláme funkci `where()`. Jako první parametr zadáme podmínku (porovnání hodnot), jako druhý hodnotu vloženou v případě splnění podmínky (text "má dáti") a jako poslední hodnotu vloženou v případě nesplnění podmínky (text "dostane"). Jako předposlední krok si určíme částku potřebnou k vypořádání - rozdíl mezi součtem pro danou osobu a průměrnou útratou. Poslední krok je pak jen výpisem hodnoty.
362-
363-
```pycon
364-
nakupy = pandas.read_csv('nakupy.csv')
365-
nakupy_celkem = nakupy.groupby("Jméno")[["Částka v korunách"]].sum()
366-
prumerna_hodnota = nakupy_celkem["Částka v korunách"].mean()
367-
import numpy
368-
nakupy_celkem["Operace"] = numpy.where(nakupy_celkem["Částka v korunách"] > prumerna_hodnota, "má dáti", "dostane")
369-
nakupy_celkem["Kolik"] = abs(nakupy_celkem["Částka v korunách"] - prumerna_hodnota)
370-
print(nakupy_celkem[["Operace", "Kolik"]])
371-
372-
Operace Kolik
373-
Jméno
374-
Libor dostane 118.166667
375-
Míša dostane 82.166667
376-
Ondra má dáti 257.833333
377-
Pavla dostane 192.166667
378-
Petr má dáti 296.833333
379-
Zuzka dostane 162.166667
380-
```
381-
382-
Srovnej si toto řešení s tím, které jsme si ukazovali na úvodním workshopu. Zdá se ti jednodušší?
383-
384378
### Řazení
385379

386380
Data řadíme poměrně často. U běžeckého závodu nás zajímají ti nejrychlejší běžci, u položek v e-shopu ty nejlépe hodnocené, u projektu zase chceme vidět úkoly s nejbližším deadline. Abychom tyto hodnoty získali, musíme data seřadit. Ve světě databází pro to používáme klíčová slova `ORDER BY`, v `pandas` nám poslouží metoda `sort_values`. Jako její první parametr zadáváme sloupec (nebo seznam sloupců), podle kterého (kterých) řadíme.
387381

388-
```pycon
382+
```py
389383
staty.sort_values(by="population")
390384
```
391385

392386
Metoda `sort_values` standardně řadí vzestupně. Chceme-li řadit sestupně, zadáme jí parametr `ascending` a nastavíme ho na `False`.
393387

394-
```pycon
388+
```py
395389
staty.sort_values(by="population", ascending=False)
396390
```

0 commit comments

Comments
 (0)