import pandas as pd

"""
Explanation: A Pandas könyvtár
Mérési vagy szimulációs adatainkat gyakran célszerűbb a puszta számokat tartalmazó list vagy numpy.array adatszerkezet helyett a pandas könyvtár DataFrame osztályában tárolunk, ahol a számok mellett feliratozni is tudjuk a sorokat, illetve oszlopokat, illetve a különböző oszlopokban különböző adattípusok lehetnek.
A DataFrame-re gondolhatunk úgy, mint egy szokásos Excel-táblázatra, az osztályhoz tartozó függvények egy része ugyanis nagyon hasonlít a táblázatkezelőkből ismertekére. Használata első ránézésre talán bonyolultnak tűnhet, de mindenképpen megéri a befektetett energiát.
A szokásos import:
End of explanation
"""


pd.read_csv("data/kisnevsor.csv")

"""
Explanation: Beolvasás fájlból
Legtöbbször vesszővel vagy tabulátorral elválasztott értékeket (amit a sep kulcsszóval állíthatunk be, ha szükséges) olvasunk be egyszerű szövegfájlokból. Itt be kell állítanunk, hogy a táblázatunk tartalmaz-e fejlécet (header), illetve hogy a soroknak van-e nevük (index).
Az alapértelmezett beállítás megpróbálja kitalálni, hogy van-e fejléc, és a sorokat magától megszámozza.
End of explanation
"""


pd.read_csv("data/kisnevsor.csv",index_col=0)

"""
Explanation: Mi viszont azt szeretnénk, ha a legelső oszlopot a sorok neveiként olvasnánk be, mint az exceles esetben.
End of explanation
"""


pd.read_csv("data/kisnevsor.csv",header=None,index_col=0)

"""
Explanation: Most a fejlécbeolvasót kikapcsoljuk. Így az első sor ismeretlen (NaN) sorfelirattal bekerül az értékek elé, az oszlopok pedig 0-tól számozódnak.
End of explanation
"""


nevsor=pd.read_csv("data/kisnevsor.csv")
ujnevsor=nevsor.set_index("Unnamed: 0")

"""
Explanation: Ha elfelejtettük beállítani az oszlopok neveit eredetileg, utólag is megtehetjük azt a .set_index(oszlopnev) függvény segítségével. A set_index() függvény visszatérési értéke alapesetben az új oszloppal indexelt DataFrame.
End of explanation
"""


nevsor.set_index("Unnamed: 0",inplace=True)

"""
Explanation: Ezt megtehettük volna úgy is, hogy a nevsor nevű DataFrame-et rögtön felülírjuk helyben (inplace):
End of explanation
"""


df=pd.read_csv("data/kisnevsor.csv",index_col=0)
print(df["Eszter"])

"""
Explanation: Adatok elérése
A pandas a DataFrame-ben tárolt értékeket elsősorban a fejléccel és a sorok neveivel teszi elérhetővé.
Ha egy oszlop nevét stringként szögletes zárójelekben írjuk a DataFrame neve mögé, visszakapjuk az oszlopot.
End of explanation
"""


df[["Eszter","Nem","Kor"]]

"""
Explanation: Ha több oszlopot is vissza szeretnénk kapni, akkor azokat egy stringeket tartalmazó listában írjuk a DataFrame mögötti szögletes zárójelbe.
End of explanation
"""


print(df["Eszter"]["Zita"])

"""
Explanation: Ha egy oszlopnak szeretnénk elérni az egyik elemét:
End of explanation
"""


df.ix["Zita"]

"""
Explanation: Vigyázat, mindig először az oszlop nevét írtuk! Ha egy sort szeretnénk visszakapni, az ix objektumot kell használunk.
End of explanation
"""


df.iloc[:,0] # az első (0.) oszlop

df.iloc[0,0]  # az első sor első eleme

"""
Explanation: Aki szeretné ugyanúgy számokkal indexelni a DataFrame-et, mint egy array-t, annak erre az iloc biztosít lehetőséget. Nézzük meg az előző eléréseket iloc-kal!
End of explanation
"""


df.as_matrix()

"""
Explanation: Sőt, a DataFrame belsejét átalakíthatjuk numpy array-jé, és alkalmazhatjuk rá a korábban tanult módszereket :-)
End of explanation
"""


df.columns

"""
Explanation: Az oszlopnevek és a sornevek elérése
Írassuk ki a táblázatunk oszlopainak a nevét!
End of explanation
"""


df.index

"""
Explanation: Írassuk ki a táblázatunk sorainak a nevét!
End of explanation
"""


df.columns.tolist()

list(df.columns)

"""
Explanation: Szükség lehet rá, hogy a fenti listákat tényleg Python-féle list-ként kapjuk vissza.
End of explanation
"""


df.sum()

"""
Explanation: Egyszerű sor- és oszlopműveletek
A DataFrame-re is könnyű néhány beépített függvény segítségével különböző aggregált értékeket számolni.
Például álljon itt oszloponként a számok összege:
End of explanation
"""


df[["Eszter","Orsi"]].sum(axis=1)

"""
Explanation: Mit tegyünk, ha ezt soronként szeretnénk visszakapni? Változtassuk meg az összegzés "tengelyét" (axis)! Az előző eset ugyanis az alapértelmezett axis=0 volt, ami oszloponként végzi a műveletet. Csak a jegyeket tartalmazó oszlopokat összegezzük.
End of explanation
"""


df.count()

df.count(axis=1)

"""
Explanation: Számoltassuk meg, hány elem van az oszlopokban, illetve a sorokban!
End of explanation
"""


df.shape

"""
Explanation: Ezt persze az array-hez hasonlóan is megtehettük volna:
End of explanation
"""


df["Nem"]=="lány"

"""
Explanation: További ötletek beépített függvényekre: mean, median, min, max, std.
Boolean indexing
Nagyon gyakran előfordul, hogy a táblázatunkból csak bizonyos feltételeknek megfelelő sorokat szeretnénk látni. Ha a táblázat sorainak számával megegyező hosszú igaz/hamis sorozatot adunk meg a DataFrame mögötti szögletes zárójelben, akkor csak az igaz elemeket fogjuk visszakapni visszatérési értékként.
Először nézzük meg, mi történik, ha megkérdezzük, hogy egy oszlop egyenlő-e egy értékkel:
End of explanation
"""


df[df["Nem"]=="lány"]

"""
Explanation: Láttuk, hogy minden sorhoz kaptunk egy igaz/hamis értéket. Most a fenti kifejezést beírjuk a []-be:
End of explanation
"""


df[df["Eszter"]>3]

"""
Explanation: De más feltételt is megadhatunk, például hogy kinek adott Eszter 3-asnál jobb jegyet.
End of explanation
"""


df[(df["Eszter"]>3) & (df["Kor"]>20)]

"""
Explanation: Két feltételt összefűzhetünk egymáshoz, ilyenkor a &amp; és a | operátorokat használjuk and és or helyett, mert azok nem tudnak két sorozatot elemenként összehasonlítani. A feltételeket zárójelbe kell tenni, különben hibát kapunk.
Ezek alapján az, akinek Eszter hármasnál jobbat adott, és idősebb 20 évesnél:
End of explanation
"""


df.sort_values(by="Kor",ascending=False)

"""
Explanation: Sorba rendezés
Szükségünk lehet arra, hogy a táblázatunkat sorba rendezzük valamelyik oszlop szerint. Ilyenkor a sort_values(by="oszlop_neve") függvényt használjuk, melynek megadhatjuk, hogy növekvő (ascending=True), vagy csökkenő (ascending=False) sorrendben szeretnénk-e a rendezést.
A függvény visszatérési értéke a rendezett táblázat.
End of explanation
"""


df.sort_values(by="Kor",ascending=False,inplace=True)

"""
Explanation: Ha azt szeretnénk, hogy az eredeti DataFrame-ben rendezve tárolódjanak el a sorok, be kell kapcsolnunk az inplace=True paramétert, ami felülírja a DataFrame-et a rendezés után.
End of explanation
"""


df=df.sort_values(by="Kor",ascending=False)

"""
Explanation: Persze, ezt elérhettük volna szokásos értékadással is.
End of explanation
"""


df.sort_index(inplace=True)

"""
Explanation: Ha a DataFrame indexe szerint szeretnénk sorba rendezni, akkor a sort_index() függvény segít (itt is választhatjuk, hogy helyben szeretnénk-e a rendezést az inplace=True segítségével):
End of explanation
"""


df.loc["Dávid"]=[5,5,"fiú",20]
df

"""
Explanation: Új sor/oszlop hozzáadása, törlés
Ha új sort szeretnénk hozzáadni a táblázathoz, akkor a .loc["Új_sor_indexe"] változónak egy, az oszlopok számával megegyező hosszúságú listát kell odaadnunk.
End of explanation
"""


df["Emelt"]=[0,0,1,1,0]
df

"""
Explanation: Ha új oszlopot, akkor hasonlóan járunk el, de nem szükséges a loc, mert az a sorokat indexeli.
End of explanation
"""


df.drop("Bálint",inplace=True)
df

"""
Explanation: Ha sort szeretnénk törölni, a drop függvénnyel tehetjük meg.
End of explanation
"""


del df["Kor"] #ritkán

df["Kor"]=[22,19,20,20]

"""
Explanation: Elég ritkán, de szeretnénk a táblázatunkból oszlopokat törölni:
End of explanation
"""


df.groupby("Emelt").max()

"""
Explanation: Csoportosítás
Egy oszlop értékei szerint csoportosíthatjuk a DataFrame-et, és utána a csoportokon végezhetünk műveleteket.
Például az emelt szintű érettségit tevők (1), illtve nem tevők (0) maximum jegyeit láthatjuk a következő sorban.
Figyeljük meg, hogy a Nem oszlop maximális értéke mindkét csoportban a "lány", hiszen az hátrébb áll az abc-ben, mint a fiú.
End of explanation
"""


df.groupby(["Emelt","Nem"]).max()

"""
Explanation: Egyszerre két oszlop szerint is csoportosíthatunk, ilyenkor listát kell a groupby-nak átadnunk. Itt már nem csak az Emelt oszlop, hanem a Nem oszlop is a táblázat indexének a része, ezt hívjuk többszintű indexelésnek. A továbbiakban az órán erre nem lesz szükség, csak a példa kedvéért áll itt.
End of explanation
"""


erettsegi_adat=pd.read_csv("data/erettsegi.csv.gz",encoding="utf8",sep=";",index_col=0)

"""
Explanation: Érettségi adatok feldolgozása
Az alábbiakban az elmúlt pár év érettségi statisztikai adatait fogjuk megvizsgálni. Ez a példa sok szempontból jól illusztrál olyan problémákat, amelyek valós adatbázis-elemzések kapcsán felmerülhetnek.
Ilyen például a hiányzó adatok kezelése, vagy a nem egészen kompatibilis adatbázisok egységes kezelése.
Az érettségi adatokat a fenti honlap az előzőekben megismert elválasztóval tagolt tagolt (comma separated value, röviden csv) formátumban teszi elérhetővé, itt az elválasztójel a pontosvessző.
Mivel ékezetes karakterek is vannak a fájlban, át kell állítanunk a karakterkódolást is a beolvasásnál. Az értékeket a sorokban a ";" karakter választja el, a sorok nevei a 0. oszlopban vannak.
End of explanation
"""


print("\n".join(erettsegi_adat.columns.tolist()))

"""
Explanation: Listáztassuk ki, milyen oszlopnevek vannak a fájlban!
End of explanation
"""


erettsegi_adat.head().transpose()

"""
Explanation: Látható, hogy az év, szint megadják, hogy melyik évben, melyik szintű érettségiről van szó. Azt is megállapíthatjuk, hogy ősszel vagy tavasszal (időszak) írta-e a diák az érettségit, az iskolájáról és a képzési típusról is rögzítve van a statisztika. Emellett részletes írásbeli és szóbeli, illetve összpontszám, összesített százalék is szerepel az adatok között.
Érdemes az első néhány sort kiíratni példaként, hogy lássuk, mivel is van dolgunk. Most transzponálva írjuk ki, hogy elférjen a képernyőre.
End of explanation
"""


erettsegi_adat[erettsegi_adat["szint"]=="E"].groupby("év")["érdemjegy"].mean()

"""
Explanation: Itt aztán már tényleg nagy hasznát vesszük a fentebb tanult csoportosítási, aggregálási műveleteknek, a következőkben felteszünk néhány példakérdést, és megválaszoljuk azt.

Melyik évben mennyi volt az emelt szintű érettségik jegyeinek átlaga?

Ehhez először kiválasztjuk az emelt szintű érettségit tartalmazó sorokat, majd azokat év szerint csoportosítjuk. Kiválasztjuk az "érdemjegy" oszlopot, amit a végén átlagolunk. A csoportosítás miatt az átlag évenként kerül kiszámítása.
End of explanation
"""


erettsegi_adat[
    (erettsegi_adat["szint"]=="K") &\
    (erettsegi_adat["év"]==2015)].\
groupby("vizsgázó neme")["össz pontszám"].mean()

"""
Explanation: Vajon a fiúk vagy a lányok írtak jobb pontszámú középszintű érettségit 2015-ben?

A "\" jel csak azért kell, hogy ne írjunk túl hosszú sorokat a Pythonnak, mert az nehéz lenne elolvasni. Ha ilyen jelet teszel a sor végére, akkor az értelmező úgy olvassa, mintha a következő sor a "\" jel helyére lenne fűzve.
Először logikai indexeléssel kiválasztjuk a 2015-ös középszintű érettségiket tartalmazó sorokat. Több feltételt a sorokra egyszerre az and operátor helyett az & operátorral adhatunk meg, és a feltételeket zárójeleznünk kell, hogy jól olvassa az értelmező. 
Ezek után csoportosítunk a vizsgázó neme szerint, majd vesszük az összpontszámok átlagát.
End of explanation
"""


erettsegi_adat.groupby(["vizsgázó képzési típusa", "vizsgázó részvétele"])["év"].count()

"""
Explanation: Számoljuk le, melyik iskolatípusban hány érettségiző jelent meg, illetve nem jelent meg!

Most egyszerre két oszlop szerint is csoportosítottunk, a csoportosítás alapját képező oszlopok nevét listaként kell megadni a groupby-nak. Utána egy tetszőleges oszlopot (pl. év) kiválasztva megszámláltathatjuk csoportonként a sorokat a count-tal.
End of explanation
"""


%pylab inline

"""
Explanation: Ábrázolás
A pandas nagy erőssége, hogy a DataFrame-ekből nagyon rövid szintaxissal lehet egészen elfogadható ábrákat készíteni. Ehhez a pandas a matplotlib könyvtárat használja, melyet emiatt be is kell importálnunk.
End of explanation
"""


rcParams["font.size"]=15

"""
Explanation: Az ábra paramétereit (title, ylabel stb.) a matplotlibben megszokott módon állíthatjuk be.
Elsőként növeljük meg alapértelmezetten a tengelyfeliratokat:
End of explanation
"""


erettsegi_adat[erettsegi_adat["szint"]=="E"].groupby("év")["érdemjegy"].mean().plot(kind="bar", figsize=(12, 9))
ylabel("Emelt szint átlag")
ylim(0,5)

"""
Explanation: Nézzük meg ábrán is az emelt szintű érettségik évenkénti átlagát! Ehhez csak a fenti parancs végére hozzá kell fűznünk a "plot" szócskát. Az oszlopdiagram rajzolásához megadhatjuk a plot függvénynek a kind kulcsszóval, hogy kind="bar". Az x tengely feliratai a DataFrame indexei lesznek, de az y tengelynek már mi kell, hogy nevet adjunk.
End of explanation
"""


subplot(1,2,1)
erettsegi_adat[
    (erettsegi_adat["vizsgázó részvétele"]=="megjelent") &\
    (erettsegi_adat["szint"]=="K")]\
    .groupby(["vizsgázó képzési típusa"])\
    .size()\
    .plot(kind="pie",autopct='%.1f',figsize=(20,10))
title("Középszint")

subplot(1,2,2)
erettsegi_adat[
    (erettsegi_adat["vizsgázó részvétele"]=="megjelent") &\
    (erettsegi_adat["szint"]=="E")]\
    .groupby(["vizsgázó képzési típusa"])\
    .size()\
    .plot(kind="pie",autopct='%.1f',figsize=(20,10))
title("Emelt szint")

"""
Explanation: Megnézhetjük kördiagramon, hogy melyik iskolatípusból hányan érettségiztek közép- és emelt szinten 2011 és 2015 között. Ehhez két alábrát készítünk a múltkor tanultakhoz hasonlóan.
Vajon mit csinált az autopct kulcsszó?
End of explanation
"""


pd.concat([df,df])

"""
Explanation: ☠ Haladóknak
Összefűzés, join
Két DataFrame-et összefűzhetünk egymás alá, ha a pd.concat() függvénynek egy ugyanannyi oszlopból álló DataFrame-eket tartalmazó listát adunk oda.
Példánkban kétszer egymás alá írjuk ugyanazt a DataFrame-et.
End of explanation
"""


import numpy as np
df2=pd.DataFrame(np.array([[0,1,1,1]]).transpose(),columns=["Énekkar"],index=df.index)
df2

"""
Explanation: Létrehozunk egy másik DataFrame-et, és egy Énekkar nevű oszlopot teszünk bele az előző DataFrame indexeivel.
End of explanation
"""


pd.concat([df,df2],axis=1)

"""
Explanation: Hogyan tudnánk ezt az oszlopot hozzáilleszteni az előző táblázathoz? Megtehetjük concat segítségével, de át kell állítanunk, hogy melyik irányban fűzzük össze a két táblázatot (axis=1 jelenti, hogy az oszlopok mellé szeretnénk írni).
End of explanation
"""


pd.merge(df,df2,left_index=True,right_index=True)

"""
Explanation: Megtehetnénk azt is, hogy elkészítjük a két táblázat Descartes-szorzatát, azaz az egyikből minden sort összepárosítunk a másik minden sorával, majd kiválogatjuk ebből a sorhalmazból csak azokat a sorokat, amelyekben az indexek megegyeznek.
(Aki ismeri az SQL-nyelv join parancsát, ez az ún. inner join.)
End of explanation
"""


df["Nem"].str.contains("án")

"""
Explanation: Contains
Ha egy stringeket tartalmazó oszlopban végig kell néznünk, hogy megvan-e valamilyen karaktersorozat:
End of explanation
"""


def hozzaad(x):
    return x+1

"""
Explanation: Apply
Ha egy tetszőleges függvényt szeretnénk egy oszlop minden egyes elemére alkalmazni, megtehetjük az apply segítségével. Az apply belsejébe írjuk a függvényt, amit alkalmazni szeretnénk.
Elsőként például készítünk egy függvényt, ami egy számhoz hozzáad egyet:
End of explanation
"""


df["Kor"].apply(hozzaad)

"""
Explanation: Ezek után mindenkit öregítünk egy évvel.
End of explanation
"""


def atlag(sor):
    return (sor["Eszter"]+sor["Orsi"])/2

df.apply(atlag,axis=1)

"""
Explanation: Az apply-t is lehet soronként is végeztetni az axis=1 kulcsszó segítségével. Például írjuk meg kézzel azt a függvényt, ami a két kapott jegy átlagát kiszámolja.
End of explanation
"""


df.loc["Károly"]=[4,5,"fiú",1,20]

"""
Explanation: Pivot
Új táblázatot is készíthetünk összesített eredmények alapján az eredetiből. Hogy legyen valami látható eredményünk, adjuk még hozzá Károlyt a táblázatunkhoz.
End of explanation
"""


p=df.groupby(["Nem","Emelt"]).count()
p.reset_index(inplace=True)
p

"""
Explanation: Most megnézzünk nemenként és emelt szintű érettségi szerint, hogy melyik kategóriában hány ember van. Mivel a csoportosítás készít nekünk egy többszintű indexet, ezt kiiktatjuk a reset_index(inplace=True) parancs segítségével.
End of explanation
"""


p.pivot_table(values="Eszter",columns="Emelt",index="Nem")

"""
Explanation: Készítsünk egy táblázatot, melyben a sorok a nemek, az oszlopok, hogy tett-e valaki emelt szintű érettségit, és az értékek a kategóriák leszámlálásai:
End of explanation
"""


