A modern adatelemzés világában az adatok számos formában érkezhetnek, de kevés formátum olyan elterjedt és rugalmas, mint a JSON (JavaScript Object Notation). A webes API-k, konfigurációs fájlok és NoSQL adatbázisok gyakran használják ezt a könnyen olvasható, hierarchikus felépítésű formátumot. Bár a JSON rendkívül sokoldalú, a Pythonban történő hatékony manipulációja és elemzése kihívást jelenthet, különösen akkor, ha az adatok összetettek, nagy méretűek, vagy beágyazott struktúrákat tartalmaznak. Itt lép be a képbe a Pandas, a Python nyílt forráskódú adatmanipulációs és elemző könyvtára, amely forradalmasítja a JSON adatok kezelését.
Ebben a cikkben részletesen bemutatjuk, hogyan használhatjuk ki a Pandas erejét a JSON adatok hatékony betöltésére, strukturálására, tisztítására és elemzésére. Megvizsgáljuk a leggyakoribb problémákat és a legjobb gyakorlatokat, hogy a beágyazott és félig strukturált JSON adatokból is kihozhasd a maximumot.
Miért éppen a JSON és miért a Pandas?
A JSON népszerűsége több okból is fakad. Emberi szemmel könnyen olvasható, gépek számára egyszerűen értelmezhető, és a kulcs-érték párokon alapuló struktúrája kiválóan alkalmas rugalmas, hierarchikus adatok ábrázolására. Gondoljunk csak egy termékre, amelyhez több címke, értékelés és technikai specifikáció is tartozhat – mindez könnyedén modellezhető JSON-ban.
Azonban a JSON rugalmassága egyben a legnagyobb kihívása is lehet. A beágyazott objektumok és listák nehézkessé tehetik az adatok hagyományos, táblázatos formában történő feldolgozását, ami elengedhetetlen az elemzéshez és vizualizációhoz. Itt jön képbe a Pandas. A Pandas központi adatstruktúrája, a DataFrame
, egy két dimenziós, méretezhető és hatékony táblázatos adatstruktúra, amely rendkívül jól teljesít strukturált adatokkal. A kihívás az, hogyan alakítsuk át a hierarchikus JSON-t egy lapos, elemzésre kész DataFrame
-mé.
Alapvető JSON betöltés Pandasszal
A Pandas alapvető funkciói kiválóan alkalmasak az egyszerűbb JSON adatok betöltésére. A pd.read_json()
függvény a sarokköve ennek a folyamatnak. Ez a függvény számos forrásból képes JSON adatokat beolvasni:
- Fájl útvonala (helyi vagy URL)
- JSON string
- JSON fájl objektum
Nézzünk egy egyszerű példát:
import pandas as pd
import json
# Példa egyszerű JSON adatokra
simple_json_data = '''
[
{"id": 1, "nev": "Anna", "kor": 30, "varos": "Budapest"},
{"id": 2, "nev": "Bence", "kor": 24, "varos": "Debrecen"},
{"id": 3, "nev": "Cecília", "kor": 35, "varos": "Szeged"}
]
'''
# JSON stringből DataFrame létrehozása
df_simple = pd.read_json(simple_json_data)
print(df_simple)
Ez a kód egy teljesen lapos JSON struktúrából egyenesen egy DataFrame
-et hoz létre, ahol a JSON kulcsai oszlopfejlécekké válnak. A pd.read_json()
függvény számos paraméterrel rendelkezik, amelyek segítségével szabályozhatjuk a beolvasás módját, például az orient
paraméterrel, ami meghatározza a JSON string formátumát (pl. ‘records’, ‘columns’, ‘index’, ‘split’, ‘values’). A ‘records’ (list of dicts) a leggyakoribb, és alapértelmezett, ha a JSON egy listával kezdődik.
Beágyazott JSON struktúrák kezelése: A json_normalize ereje
Az igazi kihívás akkor kezdődik, amikor a JSON adatok beágyazott objektumokat vagy objektumlistákat tartalmaznak. Gondoljunk egy vásárlói profilra, amely nem csak alapvető információkat tartalmaz, hanem egy listát a korábbi rendelésekről, vagy egy beágyazott címet. A pd.read_json()
alapból nem tudja kezelni ezeket a komplex struktúrákat.
Itt jön a képbe a json_normalize()
függvény, amely a pandas.json_normalize
modul része. Ez a funkció kifejezetten arra készült, hogy laposítsa (flatten) a félig strukturált JSON adatokat egy táblázatos DataFrame
formátumba. A kulcsa az, hogy képes kivonni a beágyazott adatokat, és új oszlopokká alakítani őket.
Vegyünk egy komplexebb JSON példát:
import pandas as pd
from pandas import json_normalize
import json
complex_json_data = '''
[
{
"id": 1,
"nev": "Alice",
"email": "[email protected]",
"rendelesek": [
{"termek_id": "A101", "mennyiseg": 2, "ar": 10.5},
{"termek_id": "B202", "mennyiseg": 1, "ar": 25.0}
],
"elerhetoseg": {
"telefon": "123-4567",
"cim": {
"utca": "Fő utca 10",
"varos": "Budapest",
"iranyitoszam": "1001"
}
}
},
{
"id": 2,
"nev": "Bob",
"email": "[email protected]",
"rendelesek": [
{"termek_id": "C303", "mennyiseg": 5, "ar": 5.0}
],
"elerhetoseg": {
"telefon": "987-6543",
"cim": {
"utca": "Király utca 5",
"varos": "Debrecen",
"iranyitoszam": "4002"
}
}
}
]
'''
# JSON stringből Python lista/szótár struktúra
data = json.loads(complex_json_data)
# Az alapvető adatok normalizálása
df_alap = json_normalize(data)
print("Alap DataFrame:")
print(df_alap.head())
# Láthatjuk, hogy a 'rendelesek' és az 'elerhetoseg' oszlopok maradtak beágyazott listák/szótárak.
# Most bontsuk ki a beágyazott 'elerhetoseg.cim' adatokat
df_elerhetoseg_cim = json_normalize(
data,
meta=['id', 'nev', 'email', ['elerhetoseg', 'telefon']], # Ezeket az oszlopokat megtartjuk
record_path=['elerhetoseg', 'cim'], # Ezt a beágyazott utat bontjuk ki
sep='_' # Osztó karakter a kibontott oszlopnevek között
)
print("nElerhetoseg.cim kibontva:")
print(df_elerhetoseg_cim.head())
# Most bontsuk ki a 'rendelesek' listát!
# Fontos: A record_path egy lista, ha egy listát kell kibontani, ami dict-eket tartalmaz.
df_rendelesek = json_normalize(
data,
record_path='rendelesek', # A rendelesek listát bontjuk ki
meta=['id', 'nev', 'email'], # Megtartanánk az ügyfél adatait
sep='_'
)
print("nRendelések kibontva:")
print(df_rendelesek.head())
# Ha az összes adatot egyetlen DataFrame-ben szeretnénk látni,
# a 'rendelesek' listát külön DataFrame-be érdemes normalizálni,
# majd az eredeti DataFrame-hez hozzácsatolni az ügyfél azonosítója (id) alapján.
# Vagy több meta paraméterrel normalizálunk, de az a rendelesek esetén duplikálja az adatot.
# A json_normalize alapértelmezetten laposítja a szótárakat, de a listákat nem.
# A fenti példákban láthatjuk, hogy a json_normalize hogyan képes laposítani.
# A 'meta' paraméter segít megőrizni azokat az oszlopokat a szülő objektumból,
# amelyeket meg szeretnénk tartani a kibontott gyermek adatok mellett.
# A 'record_path' paraméterrel adhatjuk meg a beágyazott lista vagy szótár útvonalát,
# amit "laposítani" szeretnénk. Ha a record_path egy lista, akkor beágyazott szótárakra vonatkozik.
# Ha egy string, akkor egy listára.
A `json_normalize()` kulcsparaméterei:
data
: A bemeneti JSON adat (általában egy lista szótárakból, vagy egyetlen szótár).record_path
: Ez a paraméter határozza meg, melyik beágyazott listát szeretnénk kibontani. Ha a JSON egy listát tartalmaz, ami további szótárakat rejt (pl. `[{ „user”: „A”, „orders”: [{…}, {…}] }]`), akkor a `record_path=’orders’` kibontja a rendeléseket külön sorokba. Ha egy beágyazott szótár kulcsát szeretnénk laposítani (pl. `{„user”: „A”, „address”: {„street”: „X”}}`), akkor a `json_normalize` alapból megteszi ezt, de ha csak egy bizonyos kulcs alatt lévő listát akarunk, akkor adjuk meg a teljes útvonalat listaként, pl. `record_path=[‘user_data’, ‘address’, ‘street’]`.meta
: Egy lista azon kulcsokról, amelyeket a szülő szintről szeretnénk megtartani a normalizáltDataFrame
-ben. Például, ha a rendeléseket bontjuk ki, akkor a vásárló ID-jét is megtarthatjuk, hogy utólag tudjuk azonosítani, melyik rendelés melyik vásárlóhoz tartozik. Ez lehet egy egyszerű kulcs (pl. `’id’`) vagy egy beágyazott kulcs útvonala (pl. `[‘elerhetoseg’, ‘telefon’]`).errors
: Meghatározza, hogyan kezelje a hiányzó kulcsokat. Az alapértelmezett `’raise’` hibát dob, de beállítható `’ignore’`-re is, ekkor a hiányzó kulcsok helyén NaN (Not a Number) értékek lesznek.sep
: A szétválasztó karakter, ami a kibontott oszlopnevek között jelenik meg. Alapértelmezetten `.`, de a fenti példában `_` karaktert használtam, ami gyakran olvashatóbb. Például `elerhetoseg.cim.utca` helyett `elerhetoseg_cim_utca`.
A json_normalize()
különösen hasznos, amikor a JSON adatok nem illeszkednek szigorú sémához, vagy dinamikusan változhatnak. A beágyazott listákból új sorokat generál, a beágyazott szótárak kulcsait pedig új oszlopokká alakítja, prefixekkel ellátva az egyediség érdekében. Ez a funkció az egyik legerősebb eszköz a Pandas arzenáljában a JSON adatok strukturálásához.
Adattisztítás és átalakítás a Pandas segítségével
Miután a JSON adatok DataFrame
-be kerültek, a Pandas teljes eszköztára a rendelkezésünkre áll az adattisztításhoz és átalakításhoz. Néhány gyakori művelet:
1. Oszlopok átnevezése és kiválasztása
A json_normalize()
generált oszlopnevei néha túl hosszúak vagy nem egyértelműek lehetnek. Az oszlopok átnevezése egyszerű a df.rename()
metódussal, a felesleges oszlopok eldobása pedig a df.drop()
segítségével:
# Oszlopok átnevezése
df_rendelesek = df_rendelesek.rename(columns={'id': 'ugyfel_id', 'nev': 'ugyfel_nev'})
print("nÁtnevezett oszlopok:")
print(df_rendelesek.head())
# Kiválasztott oszlopok megtartása
df_reszlet = df_rendelesek[['ugyfel_id', 'termek_id', 'ar']]
print("nKiválasztott oszlopok:")
print(df_reszlet.head())
2. Hiányzó értékek kezelése
A hiányzó adatok (NaN – Not a Number) gyakoriak a JSON adatok normalizálásakor, különösen, ha az eredeti JSON struktúra nem egységes. A Pandas kínál eszközöket a hiányzó értékek kezelésére:
df.isnull().sum()
: Megmutatja, hány hiányzó érték van oszloponként.df.dropna()
: Eltávolítja azokat a sorokat vagy oszlopokat, amelyek hiányzó értékeket tartalmaznak.df.fillna()
: Kiegészíti a hiányzó értékeket egy megadott értékkel (pl. 0, ‘ismeretlen’, vagy az oszlop átlaga/mediánja).
3. Adattípus konverzió
A JSON adatok betöltésekor előfordulhat, hogy a Pandas nem a megfelelő adattípust (dtype) rendeli hozzá az oszlopokhoz (pl. számok stringként, dátumok stringként). A df.astype()
vagy pd.to_numeric()
, pd.to_datetime()
funkciók segítenek ezen:
# Például, ha 'ar' oszlop stringként töltődött be
# df_rendelesek['ar'] = pd.to_numeric(df_rendelesek['ar'], errors='coerce')
# print("nÁr oszlop típusának ellenőrzése:", df_rendelesek['ar'].dtype)
(A fenti példában az ‘ar’ eleve számként kerül beolvasásra, de valós forgatókönyvekben ez nem mindig van így.)
4. Szűrés és lekérdezés
A DataFrame
-en belüli szűrés és lekérdezés kulcsfontosságú az elemzéshez:
# Szűrés termék_id alapján
df_a_termekek = df_rendelesek[df_rendelesek['termek_id'] == 'A101']
print("nA101 termék rendelései:")
print(df_a_termekek)
# Több feltétel
df_nagym_rendelesek = df_rendelesek[(df_rendelesek['mennyiseg'] > 1) & (df_rendelesek['ar'] > 15)]
print("nNagyobb mennyiségű és drágább rendelések:")
print(df_nagym_rendelesek)
5. Adatok aggregálása
A Pandas groupby()
metódusa rendkívül hatékony az adatok csoportosítására és aggregálására (összegzés, átlag, minimum, maximum stb.).
# Rendelések összesített értéke ügyfelenként
ugyfel_rendeles_ossz = df_rendelesek.groupby('ugyfel_id')['ar'].sum()
print("nÜgyfelenkénti rendelési összérték:")
print(ugyfel_rendeles_ossz)
# Átlagos mennyiség termékenként
termek_atlag_mennyiseg = df_rendelesek.groupby('termek_id')['mennyiseg'].mean()
print("nTermékenkénti átlagos mennyiség:")
print(termek_atlag_mennyiseg)
Fejlett technikák és legjobb gyakorlatok
Nagy JSON fájlok kezelése
Ha gigabájtos JSON fájlokkal dolgozunk, a teljes fájl memóriába olvasása problémás lehet. A Pandas pd.read_json()
nem támogatja a chunk-onkénti olvasást közvetlenül, mint a CSV fájloknál. Ebben az esetben a legjobb megközelítés a natív Python json
könyvtár használata a fájl soronkénti vagy blokkonkénti beolvasására, majd a json_normalize()
alkalmazása minden egyes JSON objektumra/listára, és végül az eredmények konkatenálása pd.concat()
segítségével. Egy másik alternatíva lehet a Dask
könyvtár használata, amely skálázhatóbb DataFrame
-eket biztosít nagy adathalmazokhoz.
Performance optimalizálás
Amikor komplex JSON-t normalizálunk, különösen nagy adathalmazok esetén, a teljesítmény kulcsfontosságú. Néhány tipp:
- Használjunk
json_normalize()
-t, amikor csak lehetséges, mert C-ben implementált, és sokkal gyorsabb, mint a kézi Python ciklusok. - Minimalizáljuk a felesleges oszlopok normalizálását. Csak azokat a kulcsokat tartsuk meg a `meta` paraméterben, amelyekre valóban szükségünk van.
- Kerüljük a soronkénti iterációt a
DataFrame
-en (pl.df.iterrows()
). Helyette használjunk Pandas vektorizált műveleteket (pl.df.apply()
, vagy közvetlenül oszlopokon végzett műveletek).
Visszaalakítás JSON-ná
Miután manipuláltuk és megtisztítottuk az adatokat, előfordulhat, hogy vissza szeretnénk alakítani őket JSON formátumba, például egy API végpontra való elküldéshez. A DataFrame.to_json()
metódus kiválóan alkalmas erre, és számos paraméterrel szabályozható a kimeneti JSON struktúra (pl. orient='records'
a leggyakoribb, ami egy listát hoz létre szótárakból, ami az API-k számára ideális).
# Az ügyfelenkénti rendelési összérték visszaalakítása JSON-ná
json_output = ugyfel_rendeles_ossz.to_json(orient='records', indent=4)
print("nJSON kimenet (rendelési összérték):")
print(json_output)
Megjegyzés: A to_json()
alapértelmezetten lapos struktúrájú JSON-t hoz létre. A komplex, beágyazott JSON struktúrák visszaállítása egy lapos DataFrame
-ből sokkal bonyolultabb, és gyakran manuális aggregációt és szótár/lista építést igényel.
Konklúzió
A JSON adatok manipulálása a Pythonban, különösen a Pandas könyvtár segítségével, egy rendkívül hatékony és rugalmas megközelítés. A pd.read_json()
és a json_normalize()
függvények a bemeneti JSON összetettségétől függetlenül lehetővé teszik a hierarchikus és félig strukturált adatok táblázatos formába alakítását. Ezáltal megnyílik az út a robosztus adatfeldolgozás, -tisztítás, -elemzés és -vizualizáció előtt, kihasználva a DataFrame
által kínált összes előnyt.
Azáltal, hogy elsajátítjuk ezeket az eszközöket és technikákat, jelentősen növelhetjük adatmanipulációs képességeinket, és sokkal gyorsabban juthatunk el az adatokból származó értékes betekintésekhez. Ne feledjük, a gyakorlat teszi a mestert! Kísérletezzünk különböző JSON struktúrákkal, és fedezzük fel a Pandas további erejét a Python adatelemzésében.
Leave a Reply