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:
In [1]:
import pandas as pd
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.
In [2]:
pd.read_csv("data/kisnevsor.csv")
Out[2]:
Mi viszont azt szeretnénk, ha a legelső oszlopot a sorok neveiként olvasnánk be, mint az exceles esetben.
In [3]:
pd.read_csv("data/kisnevsor.csv",index_col=0)
Out[3]:
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.
In [4]:
pd.read_csv("data/kisnevsor.csv",header=None,index_col=0)
Out[4]:
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.
In [ ]:
nevsor=pd.read_csv("data/kisnevsor.csv")
ujnevsor=nevsor.set_index("Unnamed: 0")
Ezt megtehettük volna úgy is, hogy a nevsor
nevű DataFrame-et rögtön felülírjuk helyben (inplace
):
In [ ]:
nevsor.set_index("Unnamed: 0",inplace=True)
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.
In [5]:
df=pd.read_csv("data/kisnevsor.csv",index_col=0)
print(df["Eszter"])
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.
In [6]:
df[["Eszter","Nem","Kor"]]
Out[6]:
Ha egy oszlopnak szeretnénk elérni az egyik elemét:
In [7]:
print(df["Eszter"]["Zita"])
Vigyázat, mindig először az oszlop nevét írtuk! Ha egy sort szeretnénk visszakapni, az ix
objektumot kell használunk.
In [8]:
df.ix["Zita"]
Out[8]:
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!
In [9]:
df.iloc[:,0] # az első (0.) oszlop
Out[9]:
In [10]:
df.iloc[0,0] # az első sor első eleme
Out[10]:
Sőt, a DataFrame belsejét átalakíthatjuk numpy array-jé, és alkalmazhatjuk rá a korábban tanult módszereket :-)
In [11]:
df.as_matrix()
Out[11]:
Írassuk ki a táblázatunk oszlopainak a nevét!
In [12]:
df.columns
Out[12]:
Írassuk ki a táblázatunk sorainak a nevét!
In [13]:
df.index
Out[13]:
Szükség lehet rá, hogy a fenti listákat tényleg Python-féle list-ként kapjuk vissza.
In [14]:
df.columns.tolist()
Out[14]:
In [15]:
list(df.columns)
Out[15]:
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:
In [16]:
df.sum()
Out[16]:
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.
In [17]:
df[["Eszter","Orsi"]].sum(axis=1)
Out[17]:
Számoltassuk meg, hány elem van az oszlopokban, illetve a sorokban!
In [18]:
df.count()
Out[18]:
In [19]:
df.count(axis=1)
Out[19]:
Ezt persze az array-hez hasonlóan is megtehettük volna:
In [20]:
df.shape
Out[20]:
További ötletek beépített függvényekre: mean, median, min, max, std
.
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:
In [21]:
df["Nem"]=="lány"
Out[21]:
Láttuk, hogy minden sorhoz kaptunk egy igaz/hamis értéket. Most a fenti kifejezést beírjuk a []
-be:
In [22]:
df[df["Nem"]=="lány"]
Out[22]:
De más feltételt is megadhatunk, például hogy kinek adott Eszter 3-asnál jobb jegyet.
In [23]:
df[df["Eszter"]>3]
Out[23]:
Két feltételt összefűzhetünk egymáshoz, ilyenkor a &
é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:
In [24]:
df[(df["Eszter"]>3) & (df["Kor"]>20)]
Out[24]:
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.
In [25]:
df.sort_values(by="Kor",ascending=False)
Out[25]:
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.
In [26]:
df.sort_values(by="Kor",ascending=False,inplace=True)
Persze, ezt elérhettük volna szokásos értékadással is.
In [27]:
df=df.sort_values(by="Kor",ascending=False)
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):
In [28]:
df.sort_index(inplace=True)
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.
In [29]:
df.loc["Dávid"]=[5,5,"fiú",20]
df
Out[29]:
Ha új oszlopot, akkor hasonlóan járunk el, de nem szükséges a loc
, mert az a sorokat indexeli.
In [30]:
df["Emelt"]=[0,0,1,1,0]
df
Out[30]:
Ha sort szeretnénk törölni, a drop
függvénnyel tehetjük meg.
In [31]:
df.drop("Bálint",inplace=True)
df
Out[31]:
Elég ritkán, de szeretnénk a táblázatunkból oszlopokat törölni:
In [32]:
del df["Kor"] #ritkán
In [33]:
df["Kor"]=[22,19,20,20]
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ú.
In [34]:
df.groupby("Emelt").max()
Out[34]:
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.
In [35]:
df.groupby(["Emelt","Nem"]).max()
Out[35]:
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.
In [36]:
erettsegi_adat=pd.read_csv("data/erettsegi.csv.gz",encoding="utf8",sep=";",index_col=0)
Listáztassuk ki, milyen oszlopnevek vannak a fájlban!
In [37]:
print("\n".join(erettsegi_adat.columns.tolist()))
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.
In [38]:
erettsegi_adat.head().transpose()
Out[38]:
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.
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.
In [39]:
erettsegi_adat[erettsegi_adat["szint"]=="E"].groupby("év")["érdemjegy"].mean()
Out[39]:
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.
In [40]:
erettsegi_adat[
(erettsegi_adat["szint"]=="K") &\
(erettsegi_adat["év"]==2015)].\
groupby("vizsgázó neme")["össz pontszám"].mean()
Out[40]:
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.
In [41]:
erettsegi_adat.groupby(["vizsgázó képzési típusa", "vizsgázó részvétele"])["év"].count()
Out[41]:
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.
In [42]:
%pylab inline
Az ábra paramétereit (title, ylabel stb.) a matplotlib
ben megszokott módon állíthatjuk be.
Elsőként növeljük meg alapértelmezetten a tengelyfeliratokat:
In [43]:
rcParams["font.size"]=15
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.
In [44]:
erettsegi_adat[erettsegi_adat["szint"]=="E"].groupby("év")["érdemjegy"].mean().plot(kind="bar", figsize=(12, 9))
ylabel("Emelt szint átlag")
ylim(0,5)
Out[44]:
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ó?
In [45]:
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")
Out[45]:
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.
In [46]:
pd.concat([df,df])
Out[46]:
Létrehozunk egy másik DataFrame-et, és egy Énekkar
nevű oszlopot teszünk bele az előző DataFrame indexeivel.
In [47]:
import numpy as np
df2=pd.DataFrame(np.array([[0,1,1,1]]).transpose(),columns=["Énekkar"],index=df.index)
df2
Out[47]:
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).
In [48]:
pd.concat([df,df2],axis=1)
Out[48]:
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
.)
In [49]:
pd.merge(df,df2,left_index=True,right_index=True)
Out[49]:
Ha egy stringeket tartalmazó oszlopban végig kell néznünk, hogy megvan-e valamilyen karaktersorozat:
In [50]:
df["Nem"].str.contains("án")
Out[50]:
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:
In [51]:
def hozzaad(x):
return x+1
Ezek után mindenkit öregítünk egy évvel.
In [52]:
df["Kor"].apply(hozzaad)
Out[52]:
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.
In [53]:
def atlag(sor):
return (sor["Eszter"]+sor["Orsi"])/2
In [54]:
df.apply(atlag,axis=1)
Out[54]:
Ú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.
In [55]:
df.loc["Károly"]=[4,5,"fiú",1,20]
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.
In [56]:
p=df.groupby(["Nem","Emelt"]).count()
p.reset_index(inplace=True)
p
Out[56]:
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:
In [57]:
p.pivot_table(values="Eszter",columns="Emelt",index="Nem")
Out[57]: