Python – DataFrame

In Programozás, Tanulás
Görgess lejjebb

Nincs is izgalmasabb kezdés, mint a Data Frame a Python esetén. De van .. Ellenben a 27. napon a saját kihívásomban jelenleg itt tartok, és úgy éreztem érdemes lenne összefoglalni a témát és az általam tanultakat.

A programozás nekem olyan, amit tán valahol már le is írtam, de egy örökké visszatérő mágnes, ami nem bír elengedni és én nem bírom elengedni. Ám van bennem egy gát, ami lehet a kitartásom, a hitetlenségem, amely képes oda vezetni, hogy “erre születni kell”, “ehhez már késő”, és ehhez hasonló gondolatok keringenek bennem, de mégis kelt egy olyan érzetet is, hogy meg kell mutassam magam számára azt, hogy kitartással egy igyekezettel megtanulható ez, és lehet esélyem a flow állapot átélésére programozás közben.. Mert mikor apró sikerélmények érnek, mikor ismerem az adott témát, akkor szinte maguktól mennek az ujjaim, és ez az érzés.. felbecsülhetetlen…

Szóval vissza is oda, hogy ennek a tanulási folyamatnak az elejére csatlakozom most be azzal, hogy elkezdem írni azt, hogyan haladok heti szinten, témánként.

Mikor elkezdtem írni ezt a kis jegyzetet, akkor a 8. hete tartom a láncot, amely számomra azt jelenti, hogy a hét 7 napjából 5 napon minimum 1 órát foglalkozom az ismeretek bővítésével, gyakorlással. Ebben az időszakban sikerült 28 napon keresztül ezt megszakítás nélkül csinálnom, ám ezt nem tudtam tartani, viszont gond az nincsen :), jó lesz ez, nem szabad feladni.

Jelen írásom a Data Science tudás elmélyítését követi le, ahol a 8. héten a DataFrame-ek világával ismerkedem egy ideje, így ezt fogom most magam számára összefoglalni, tovább mélyíteni a tudásomat (vagy csak jegyzetet készíteni, mikor elakadok és vissza kell térnem egy biztos ponthoz). Az utam egyik állomása abban az esetben, ha azt végig tudom tartani, akkor valahova időben 2020. közepére tehető.

Szóval most nincs is más hátra, mint előre!

Pandas, DataFrame, (Series)

Minden ott kezdődik, hogy első lépésként ha szeretnénk a DataFrame-et használni, akkor bizony importálni szükséges a a pandas-t.
A pandas egy open source könyvtár, amely elsősorban adatok elemzésére használatos függvények és metódusok gyűjteménye.

Na de álljunk csak meg még egy pillanatra. Mi az a DataFrame egyáltalán?

A DataFrame egy két dimenziós adatstruktúra, ahol a sorok és oszlopok nevesítve vannak, nagyságrendileg mint egy excel tábla. Itt kell megjegyeznem, hogy amennyiben a DataFrame egy oszlopáról beszélünk csak, akkor az egy Series lesz típusát tekintve, azaz minden m x 1-es tábla, így a DataFrame minden oszlopa egyben egy Series is. De térjünk is vissza az adatainkhoz, illetve a Pandas importálásához, amely az alábbi módon történik:

import pandas as pd

Az as pd lényegében arra utal, hogy a jövőben milyen módon fogjuk hívni, hogy ne kelljen mindig kiírni a pandas szót.

Létrehozni DataFrame-et a következő megoldásokkal lehet, főként a teljesség igénye nélkül alap esetben a következőkkel:

#Egy un. Dictionary segítségével hozzuk létre
data_1 = {'nev':['Levente', 'Maria'], 'eletkor': [20, 40]}
df_1 = pd.DataFrame(data_1)

#Fontos, hogy dupla [[]] jelet kell alkalmazni
data_2 = [['Levente', 20], ['Maria', 40]]
df_2 = pd.DataFrame(data_2, columns=['nev', 'eletkor'])

#Beolvashatjuk egy csv fájl tartalmát is közvetlenül
data_3 = pd.read_csv('nevek_korok.csv')

#Lehet egy lista is eleme, ahol nem adunk az oszlopnak nevet
tanulok = ['Laci', 'Pista', 'Levente', 'Agi', 'Mari']
data_4 = pd.DataFrame(tanulok) 

#Eredménye
         0
0  Laci   
1  Pista  
2  Levente
3  Agi    
4  Mari   

Tételezzük fel, hogy szeretnénk beilleszteni egy új oszlopot is utólag, ez sem jelenthet akadályt

#Amennyiben egy értéket akarunk adni minden sornak
data_1["osztaly"] = 1

#Eltérő értékek esetén pontosan annyi értéket kell megadnunk, amennyi sorunk van

Természetesen nem szükséges nekünk kézzel felvinni az adatokat, nem lenne túl hatékony, így például csv fájloból is be tudjuk olvasni azokat. Ennek a módja a következő:

new_df = pd.read_csv("fajl_neve.csv")

Ha már van egy DataFrame-ünk, akkor itt az ideje dolgozni az adatokon, amelyeket beolvastunk, innentől jön csak az igazi izgalom 🙂

Először is érdemes átvizsgálni, hogy az importált táblázat illetve adatok mit is tartalmaznak. Ehhez a a .head() illetve .info() az első lépcsőfok számunkra. A .head() esetén az első 5 sort listázza ki számunkra, a .info() egy összefoglalót ad a táblázat adatairól, pl:

year 220 non-null int64 (220 darab érték van az év oszlopban, amelynek típusa integer)

Jupyter Notebook használata esetén a több sor és oszlop alkalmazása esetén azokat nem írja ki alap esetben, így pl a soroknál … látható. Amennyiben szeretnénk minden sort és oszlopt látni, akkor a következő értékeket kell megadnunk:

from IPython.display import display

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

A DataFrame gyors áttekintését szolgálja még a .describe() is.

type(ufo['City'])
type(ufo.City)

is.

Oszlopok kiválasztása

Előfordulhat, hogy vannak olyan értékek, amelyeket külön is szeretnénk látni, míg a többi érték abban a pillanatban nem érdekes számunkra, amelyre két lehetőségünk is van. Tételezzük fel, hogy az age oszlop adataira vagyunk csak kíváncsiak (mint arról fent volt már szó, ennek eredménye egy Series lesz):

new_df["age"]
vagy
new_df.age

#Utóbbi nem ajánlott, mert a . után metódusok jöhetnek
#Így confused lesz vagy lehet a Python

type(new_df['age'])
type(new_df.age)

Amennyiben az oszlop neve több mint egy szóból áll, akkor már csak az első megoldással tudunk kiválasztani oszlopot, mint például a new_df[“Szuletesi hely”]

Több oszlop kiválasztásánál már kicsit vigyáznunk kell, mivel ott dupla [ ] közé kell tennünk a kívánt oszlopokat a következők szerint (eredménye már DataFrame lesz):

new_df[["age", "name"]]

FONTOS: Ha csak egy oszlopot választunk ki, akkor annak típusa nem DataFrame, hanem Series lesz.

Sorok kiválasztása

Sorok esetén egy kicsit másként néz ki a megoldás, de egy kis gyakorlással ez is könnyedén megy.

marcius = new_df.iloc[2]

FONTOS: Az eredmény egy sor esetén is Series lesz, illetve a kapcsos zárójelben az érték 0-tól indul, akár az index.

Több sor kiválasztása úgy történik, mintha egy listából választanánk ki az értékeket:

aprilis_majus_junius = new_df.iloc[3:]

Sorok kiválasztása szűrés alapján

Először is jó tisztába lenni azzal, hogy kb mennyi oszlopa és sora van az adathalmazunknak, erre a df.shape a megoldás, eredménye egy Tuple lesz.

# Csak a sorok száma
df.shape[0]

# Csak az oszlopok száma
df.shape[1]

A következőkben egy bizonyos feltétel alapján szeretnénk szűrni egy adott oszlop értékei közt. A példa esetén az lesz a feladatok, hogy 500, véletlen ügyfél adat közt

A kiválasztott oszlopban tudjuk szűrni logikai feltételek alapján is az értékeket, a már-már megszokott módon.

new_df[new_df.age == 40]
new_df[new_df.name == "alma"]

Szintén van lehetőség és illetve vagy kapcsolatok felépítésére is a feltételek esetén ( | – vagy ,

new_df[(new_df.age == 40) | (new_df.age == 20)]

Szintén izgalmas kérdés az, hogy vajon ha keresünk egy értéket, akkor az benne van-e az adott sorban, amire a .isin() függvény ad nekünk megoldást.

new_df[new_df.name.isin(["Peter", "Alma", "Pityuka"])]

Szintén érdekes kérdés az, ha keresünk egy értéket a DataFrame-ben. Például van egy táblánk, amelyben munkavállalókkal kapcsolatos adatok vannak, egy-egy rekord pedig tartalmazza a fizetéseket is. Ebben az esetben ha szeretnénk egy személyre szűrni, akkor azt a következővel tudjuk megtenni:

df[df['Name'] == 'Peter']['Fizetes']

Ha arra vagyunk kíváncsiak, kinek van a legmagasabb fizetése, akkor

df[df['Fizetes'] == df['Fizetes'].max()]

Amennyiben arra lennénk kíváncsiak, hogy évente milyen átlagfizetések voltak, akkor

df.groupby('Ev').mean()['Fizetes']

Szintén egy érdemes kérdés lehet az, és egyben trükkös is, ha azt szeretnénk tudni, mennyi dolgozónk volt az adott évben, aki egyedül reprezentálta az adott munkatípust.

sum(df[df['Ev'] == 2018]['Munkakor'].value_counts() == 1)

Amint az utóbbi szűrésünk megvan, akkor egy érdekes kérdéssel kerülünk szembe, hiszen az adott sorok indexét visszük tovább jelen esetben, amely az új DataFrame nem akkor segítség nekünk.
Ennek kiküszöbölésére nyújt nekünk segítséget a .reset_index(). Egyelőre nézzük mi az eredménye “nyersen”, ha használjuk azt.

df = pd.DataFrame([
  ['January', 100, 100, 23, 100],
  ['February', 51, 45, 145, 45],
  ['March', 81, 96, 65, 96],
  ['April', 80, 80, 54, 180],
  ['May', 51, 54, 54, 154],
  ['June', 112, 109, 79, 129]],
  columns=['month', 'clinic_east',
           'clinic_north', 'clinic_south',
           'clinic_west']
)

df2 = df.iloc[[1, 3, 5]]
df3 = df2.reset_index()


Ahogyan az látható is, a régi indexek egy új oszlopba mozogtak át, amely az index nevet kapta meg. Azonban ha alkalmazzuk a drop=True értéket, akkor ezt az oszlopot már nem fogja vinni magával, azaz
df3 = df2.reset_index(drop=True)

Még ennél is elegánsabb megoldás, ha mindehhez nem hozunk létre új DataFrame-et hanem azt rögtön a meglévőbe frissítjük be. Ezt oldja meg számunkra az inplace=True megoldás, azaz egyben:

df2.reset_index(inplace=True, drop=True)

Itt jegyezném meg, hogy amennyiben szeretnénk egy oszlopot mint indexet beállítani, akkor majdnem hasonlóan a rest_index()-hez működik:

df2.set_index('month', inplace=True)

Értékek sorba rendezése

A sorba rendezés a .sort_values() segítségével történik, használata meglehetősen egyszerű. Egy oszlop esetén Series lesz az eredmény típusa, két oszlop esetén DataFrame.

# Sorban rendezés
df2.clinic_east.sort_values()

# Csökkenő sorrendben
df2.clinic_east.sort_values(ascending=False)

df2.sort_values('clinic_east', ascending=False)

# Több oszlop esetén, habár jelen esetben ennek nem sok értelme van
columns = ['clinic_east', 'clinis_south']
df2.sort_values(columns)


Egyéb műveletek sorokon, oszlopokon

Általában mikor új oszlopot veszünk fel, az valamilyen kapcsolatban van egy meglévő oszloppal.
Vegyük például a következő példát. Ahogyan látható az első oszlopban a neveknél van mindenféle megoldás, teljes nagy betű, kicsi, vegyes. Ahhoz, hogy ezt “javítani” tudjuk egy nagyon hatékony .apply() függvény van segítségünkre.

A .apply() függvény arra jó, hogy minden paraméterben megadott függvényt hozzárendeljen az adott értékhez az oszlopban. Jelen esetben egy új oszlopot hozunk létre eredményként:

df["Lowercase Name"] = df.Name.apply(lower)

Oszlopok és sorok törlése

Oszlopok törlésénél az axis=0 illetve axis=1 játszik még szerepet, amely sor és oszlopra vonatkozó paraméter, és a .drop() esetén kell megadnunk azt. Ha a lentiek esetán nem írjuk az inplace=True paramétert, akkor nem fogja véglegesen törölni a Python!

# axis = 1 esetén oszlopra vonatkoztatott
# ha oszlop törlésénél nem rakjuk ki az axis=1-et
# akkor hibát ad, mivel nem lesz olyan sor

df.drop('Name', axis=1, inplace=True)

# axis = 0 esetén sorra vonatkoztatott
torlendo_sorok = [0, 5]
df.drop(torlendo_sorok, axis=0, inplace=True)

Lambda függvény

Itt az a pont, ahol kicsit ki kell tekintetnünk egy nagyon hasznos függvényre, amelyet lambda-nak hívnak. Ezzel a megoldással nem szükséges külön függvényt létrehozni egy adott művelethez, hanem egyszerű függvények esetén ezzel tudunk operálni.

my_lambda = lambda x: (x * 2) + 3

Az x a bemenő értékünk, a kettőspont után pedig az eredménye következik

mylambda = lambda x: (x * 2) + 3
print(mylambda(5))
# Eredménye: 13

Hasonló példa lehet az, hogy a függvénynek paraméterben megadott érték (szöveg) első és utolsó betűjét kapjuk meg:

first_last = lambda x: x[0] + x[-1]
print(first_last("Ez egy szöveg"]
# Eredménye: Eg

Akár a függvények esetében, szerencsére a lambda esetén is van lehetőségünk feltételek alapján végrehajtani a függvényt.

lambda x: [OUTCOME IF TRUE] \
    if [CONDITIONAL] \
    else [OUTCOME IF FALSE]

A Pandas-ban gyakran használják a lambda függvényt ahhoz, hogy komplex megoldásokat hajtsanak végre oszlopokon.
Vegyük példának azt az esetet, hogy van egy listánk a hírlevelünkre feliratkozott személyekről, és szeretnénk csoportosítani azt szolgáltató szerint.

df["szolgaltato"] = df.email.apply(lambda x: x.split("@")[-1]

# A split függvénynek eredménye egy lista, amelynek 0. eleme a nevek, míg az első eleme maguk a szolgáltatók

Abban az esetben, ha nem oszlopra szeretnénk végrehajtani a függvényt, hanem bizonyos sorokra, ergó a kalkulált értékünkhöz több oszlop eredménye is fontos, akkor azt egy paraméter segítségével tehetjük meg (axis=1).
Vegyük ehhez a következő táblázatunkat:

df["Gross"] = df.apply(lambda adott_sor: adott_sor["Price"] * 1.27 \
          if adott_sor["Is taxed"] == True \
          else adott_sor["Price"], axis = 1)

A másik megoldás természetesen az, hogy külön a lambda függvényre hivatkozva készítjük el a fentieket, és azt adjuk meg paraméterként az apply függvénynek. Ebben az esetben a fenti így néz ki:

adozas = lambda row: row["Price"] * 1.27 \
          if row["Is taxed"] == True \
          else row["Price"]

df.Gross = df.apply(adozas, axis = 1)

Oszlopok átnevezése

Könnyen előfordulhat, hogy az általunk kapott és használt adatok esetén a mezők nevei számunkra nem a legtökéletesebbek, de szerencsére könnyedén meg tudjuk változtatni azokat. Erre a célra szolgál a .columns.

df.columns = ['ID', 'Nev', 'Eletkor']

Egy másik megoldás az átnevezés, amely preferáltam azért, mert itt a hibázási (felcserélési) lehetőség alacsonyabb, illetve egy új DataFrame-et hoz létre, míg az eredetit meghagyja, mindezt az inplace = True segítségével.

df.rename(columns={
   'name' : 'First name',
   'age' : 'Age'},
   inplace = True)

Másik megoldás is létezik az átnevezésre, amely lényegében kicseréli a karaktereket az oszlopokban:

df.columns = df.columns.str.replace(' ', '_')

Talán ide tartozik még szintén az a téma, hogyan tudunk kicsit belenézni abba, hogy egy DataFrame milyen értékeket tartalmaz?

Erre a következő kis metódusok vannak:

  • .head() – Alap esetben az első 5 sor az adott DF-ből

Kettő darab megoldást bocsátanék elemzésre, amelyek eredménye azonos

print(cart[cart.cart_time.isnull()]) / valójában csak erre az oszlopra vagyok kíváncsi

null_ertekek = cart.columns[cart.isnull().any()]
print(null_ertekek)
cart[null_ertekek].isnull().sum()

Submit a comment

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöljük.

hét + 10 =