Introducere
Pachetul Pandas reprezinta una din cele mai importante instrumente open-source aflata la dispozitia analistilor si cercetatorilor de date care utilizeaza limbajul Python. Pandas este o unealta puternica, rapida, flexibila si usor de utilizat pentru analiza si manipularea datelor.
O sarcina tipica in procesul de analiza si manipulare a datelor o reprezinta filtrarea acestora, adica extragerea unor inregistrari pe baza valorilor caracteristicilor. Una din cele mai cunoscute si eficiente modalitati de filtrare a datelor o reprezinta metoda pandas.DataFrame.query(). Metoda permite interogarea inregistrarilor de la nivelul unei structuri de date de tip DataFrame pe baza unei expresii furnizate, iar rezultatul ei il reprezinta o noua structura de tip DataFrame.
Pentru a putea rula secvente de cod care au in vedere filtrarea inregistrarilor, mai intai avem nevoie de o structura de tip DataFrame populata cu inregistrari. In acest sens, citim continutul setului de date CountryInfo de la Geonames in structura de date de tip DataFrame prin intermediul metodei pandas.read_csv(). Setul de date este disponibil online si contine informatii generale cu privire la nu mai putin de 252 de tari: coduri ISO, cod FIPS, denumire tara, capitala, suprafata, populatie, continent, limbi, valuta, geonameID.
import pandas as pd # lista personalizata de valori NaN; valoarea NA trebuie exclusa din lista implicita # deoarece in coloana continent valoarea NA se utilizeaza cu semnificatia de North America # mai mult in coloana continent nu apar valori nule; toate tarile sunt parte a unui continent na_values = ['', '#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan', '1.#IND', '1.#QNAN', 'N/A', 'NULL', 'NaN','n/a', 'nan', 'null'] # daca keep_default_na are valoare False si este precizata o lista de valori nule # acceptate prin na_values, doar aceste valori vor fi avute in vedere la parsare df = pd.read_csv("http://api.geonames.org/countryInfoCSV?username=demo", sep="\t", header=0, na_values=na_values, keep_default_na=False, usecols=['iso alpha2', 'iso alpha3', 'iso numeric', 'fips code', 'name', 'capital', 'areaInSqKm', 'population', 'continent', 'currency'])
df # Output iso alpha2 iso alpha3 iso numeric fips code name \ 0 AD AND 20 AN Andorra 1 AE ARE 784 AE United Arab Emirates 2 AF AFG 4 AF Afghanistan 3 AG ATG 28 AC Antigua and Barbuda 4 AI AIA 660 AV Anguilla .. ... ... ... ... ... 247 YE YEM 887 YM Yemen 248 YT MYT 175 MF Mayotte 249 ZA ZAF 710 SF South Africa 250 ZM ZMB 894 ZA Zambia 251 ZW ZWE 716 ZI Zimbabwe capital areaInSqKm population continent currency 0 Andorra la Vella 468.0 77006 EU EUR 1 Abu Dhabi 82880.0 9630959 AS AED 2 Kabul 647500.0 37172386 AS AFN 3 St John's 443.0 96286 NA XCD 4 The Valley 102.0 13254 NA XCD .. ... ... ... ... ... 247 Sanaa 527970.0 28498687 AS YER 248 Mamoudzou 374.0 279471 AF EUR 249 Pretoria 1219912.0 57779622 AF ZAR 250 Lusaka 752614.0 17351822 AF ZMW 251 Harare 390580.0 14439018 AF ZWL
Din acest moment putem folosi metoda pandas.DataFrame.query() pentry a filtra intregistrarile de la nivelul structurii de date de tip DataFrame, structura referita sub numele df.
df.columns Index(['iso alpha2', 'iso alpha3', 'iso numeric', 'fips code', 'name', 'capital', 'areaInSqKm', 'population', 'continent', 'currency'], dtype='object')
Sintaxa generala a metodei pandas.DataFrame.query() este disponibila mai jos. Se poate observa faptul ca la nivelul metodei sunt definiti 3 parametri:
• expr: expresia care urmeaza a fi evaluata pentru implementarea filtrarii; exista inclusiv posibilitatea precizarii unor variabile la nivelul acestei expresii, variabile ce sunt introduse prin intermediul caracterului @;
• inplace: stabileste daca structura de tip DataFrame urmeaza a fi actualizata sau nu; in cea de-a doua situatie, rezultatul este returnat intr-o noua structura de tip DataFrame;
• **kwargs: permite transmiterea si utilizarea parametrilor definiti pentru pandas.eval() ca si perechi de tipul cheie-valoare.
pandas.DataFrame.query(expr, inplace=False, **kwargs)
Implementarea expresiei de filtrare
Sa presupunem ca dorim sa obtinem lista tarilor a caror populatie a depasit 1 miliard de persoane. Pentru a interoga (filtra) datele de la nivelul setului de date CountryInfo, este necesara transmiterea unui sir de caractere care contine o expresie conditionala de genul ‘population > 1000000000’. Sunt doar doua inregistrari la nivelul setului de date care satisfac conditia precizata anterior, si aceste inregistrari sunt returnate.
df.query("population > 1000000000") # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 48 CN CHN 156 CH China Beijing 106 IN IND 356 IN India New Delhi areaInSqKm population continent currency 48 9596960.0 1392730000 AS CNY 106 3287590.0 1352617328 AS INR
In Pandas, la fel ca si in alte sisteme care utilizeaza structuri tabelare, de cele mai multe ori selectam inregistrari pe baza mai multor conditii, care utilizeaza coloane diferite. Acest aspect este tratat in expresia conditionala precizata in exemplul de mai jos, unde sunt utilizati operatori logici pentru a combina mai multe conditii. De exemplu, pentru a filtra inregistrarile care au in vedere state din Europa (continent) cu supratafa mai mica de 100 km2, este necesara urmatoarea implementare:
df.query("(continent == 'EU') and (areaInSqKm < 100)") # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 82 GG GGY 831 GK Guernsey St Peter Port 84 GI GIB 292 GI Gibraltar Gibraltar 139 MC MCO 492 MN Monaco Monaco 205 SM SMR 674 SM San Marino San Marino 237 VA VAT 336 VT Vatican City Vatican City areaInSqKm population continent currency 82 78.00 65228 EU GBP 84 6.50 33718 EU GIP 139 1.95 38682 EU EUR 205 61.20 33785 EU EUR 237 0.44 921 EU EUR
In mod similar exemplului precedent, putem extinde prima conditie de la nivelul expresiei de filtrare astfel incat sa fie incluse in rezultat state din Europa, Asia si Oceania. In acest caz, expresia poate include, pentru o implementare mai concisa, inclusiv operatorul in, care sa verifice valoarea de la nivelul coloanei continent intr-o lista de valori de tip sir de caractere: “(continent in [‘EU’, ‘AS’, ‘OC’]) and (areaInSqKm < 100)”.
df.query("(continent in ['EU', 'AS', 'OC']) and not (areaInSqKm > 100)") # Output iso alpha2 iso alpha3 iso numeric fips code \ 39 CC CCK 166 CK 82 GG GGY 831 GK 84 GI GIB 292 GI 107 IO IOT 86 IO 139 MC MCO 492 MN 164 NF NFK 574 NF 170 NR NRU 520 NR 182 PN PCN 612 PC 205 SM SMR 674 SM 221 TK TKL 772 TL 228 TV TUV 798 TV 233 UM UMI 581 NaN 237 VA VAT 336 VT name capital areaInSqKm population \ 39 Cocos (Keeling) Islands West Island 14.00 628 82 Guernsey St Peter Port 78.00 65228 84 Gibraltar Gibraltar 6.50 33718 107 British Indian Ocean Territory NaN 60.00 4000 139 Monaco Monaco 1.95 38682 164 Norfolk Island Kingston 34.60 1828 170 Nauru Yaren District 21.00 12704 182 Pitcairn Islands Adamstown 47.00 46 205 San Marino San Marino 61.20 33785 221 Tokelau NaN 10.00 1466 228 Tuvalu Funafuti 26.00 11508 233 U.S. Outlying Islands NaN 0.00 0 237 Vatican City Vatican City 0.44 921 continent currency 39 AS AUD 82 EU GBP 84 EU GIP 107 AS USD 139 EU EUR 164 OC AUD 170 OC AUD 182 OC NZD 205 EU EUR 221 OC NZD 228 OC AUD 233 OC USD 237 EU EUR
In expresia de filtrare putem utiliza orice operator logic (and, or, not) pentru a crea expresii mai complexe si pentru a extrage inregistrari intr-o maniera mult mai eficienta.
Dupa cum se poate observa din exemplele precedente, structura de date de tip DataFrame df contine o serie de coloane care prezinta denumiri ce includ spatii: iso alpha2, iso alpha3, iso numeric, fips code. Utilizarea directa a acestor coloane in expresia de filtrare nu face altceva decat sa determine aparitia unor erori.
df.query('not (continent == \'EU\') and (fips code.isnull())') # Output ... File "<unknown>", line 1 not (continent =='EU')and (fips code .isnull ()) ^ SyntaxError: Python keyword not valid identifier in numexpr query
In Pandas, exista insa posibilitatea referirii numelor coloanelor care contin caractere speciale, precum spatiul, prin intermediul caracterului tick (`). In expresia de filtrare de mai jos, mai pot fi observate doua alte aspecte ce prezinta interes: generarea caracterului apostrof (‘) intr-un sir de caractere delimitat tot prin caracterul apostrof (‘), respectiv utilizarea metodei isnull() pentru a identifica inregistrarile care prezinta valori nule in coloana fips code.
df.query('not (continent == \'EU\') and (`fips code`.isnull())') # Output iso alpha2 iso alpha3 iso numeric fips code \ 30 BQ BES 535 NaN 233 UM UMI 581 NaN name capital areaInSqKm population \ 30 Bonaire, Sint Eustatius, and Saba Kralendijk 328.0 18012 233 U.S. Outlying Islands NaN 0.0 0 continent currency 30 NA USD 233 OC USD
Referirea variabilelor in expresia de filtrare
Chiar daca pana in acest moment am referit la nivelul expresiei de filtrare doar nume de coloane din cadrul structurii de date de tip DataFrame, exista si posibilitatea referirii unor variabile externe.
La nivelul unei variabile currency pastram codul corespunzator unei valute, in acest caz USD. Referim aceasta variabila in expresia de filtrare urmatoare pentru a extrage statele din North America (NA) si South America (SA) care utilizeaza valuta USD. Referirea variabilei la nivelul expresiei de filtrare s-a realizat prin precizarea caracterului @ in fata numelui variabilei.
currency = 'USD' df.query('(continent in ("NA", "SA")) and currency == @currency') # Output iso alpha2 iso alpha3 iso numeric fips code \ 30 BQ BES 535 NaN 64 EC ECU 218 EC 183 PR PRI 630 RQ 211 SV SLV 222 ES 215 TC TCA 796 TK 234 US USA 840 US 240 VG VGB 92 VI 241 VI VIR 850 VQ name capital areaInSqKm \ 30 Bonaire, Sint Eustatius, and Saba Kralendijk 328.0 64 Ecuador Quito 283560.0 183 Puerto Rico San Juan 9104.0 211 El Salvador San Salvador 21040.0 215 Turks and Caicos Islands Cockburn Town 430.0 234 United States Washington 9629091.0 240 British Virgin Islands Road Town 153.0 241 U.S. Virgin Islands Charlotte Amalie 352.0 population continent currency 30 18012 NA USD 64 17084357 SA USD 183 3195153 NA USD 211 6420744 NA USD 215 37665 NA USD 234 327167434 NA USD 240 29802 NA USD 241 106977 NA USD
Intr-un alt exemplu, dorim sa extragem inregistrarile corespunzatoare statelor care utilizeaza valutele euro sau dolar si care au o populatie mai mare decat 90% din celelalte state. In acest scop, calculam a 90-a percentila, adica o valoare care este mai mare decat 90% din celelalte valori de la nivelul coloanei population.
percentile = df.population.quantile(.9) currencies = ['EUR', 'USD'] df.query('(currency == @currencies) and (population > @percentile)') # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 58 DE DEU 276 GM Germany Berlin 76 FR FRA 250 FR France Paris 111 IT ITA 380 IT Italy Rome 234 US USA 840 US United States Washington areaInSqKm population continent currency 58 357021.0 82927922 EU EUR 76 547030.0 66987244 EU EUR 111 301230.0 60431283 EU EUR 234 9629091.0 327167434 NA USD
In expresia de filtrare sunt referite doua variabile, una care precizeaza codurile corespunzatoare celor doua valute (EUR si USD), intr-o structura de tip lista, si o a doua care introduce a 90-a percentila calculata pentru valorile din coloana population, folosind metoda pandas.DataFrame.quantile().
Alte modalitati de filtrare a datelor
Filtrarea datelor pe baza uneia sau mai multor conditii reprezinta o operatie de baza in analiza datelor. Pe langa metoda pandas.DataFrame.query(), in Pandas mai sunt disponibile o serie de alte modalitati care permit extragerea inregistrarilor de la nivelul unei structuri de date de tip DataFrame.
O prima modalitate de filtrare a inregistrarilor dintr-o structura de tip DataFrame o ofera proprietatile loc si iloc. Aceste proprietati pot fi utilizate pentru a extrage inregistrari sau coloane pe baza unei etichete sau a unui index. Astfel, proprietatea loc permite accesarea unui grup de inregistrari sau coloane pe baza unei etichete, in timp ce proprietatea iloc permite accesarea unui grup de inregistrari sau coloane pe baza unui pozitii intregi.
Pentru a extrage de la nivelul setului de date CountryInfo de la Geonames statele a caror populatie a depasit 1 miliard de persoane, putem avea in vedere utilizarea proprietatii loc in urmatoarea forma:
df.loc[df['population'] > 1000000000] # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 48 CN CHN 156 CH China Beijing 106 IN IND 356 IN India New Delhi areaInSqKm population continent currency 48 9596960.0 1392730000 AS CNY 106 3287590.0 1352617328 AS INR
In acest caz dorim sa filtram setul de date in functie de continutul unei anumite coloane, coloana care returneaza o masca cu valoarea True pentru fiecare element din coloana populatie care indeplineste conditia.
mask = df['population'] > 1000000000 mask # Output 0 False 1 False 2 False 3 False 4 False ... 247 False 248 False 249 False 250 False 251 False Name: population, Length: 252, dtype: bool
mask[[48, 106]] # Output 48 True 106 True Name: population, dtype: bool
Masca poate fi apoi aplicata structurii de tip DataFrame, pentru a extrage astfel inregistrarile pentru care conditia este adevarata.
df[mask] # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 48 CN CHN 156 CH China Beijing 106 IN IND 356 IN India New Delhi areaInSqKm population continent currency 48 9596960.0 1392730000 AS CNY 106 3287590.0 1352617328 AS INR
Proprietatea iloc poate fi utilizata si pentru a implementa o noua masca, similara cu cea precedenta. De aceasta data, referirea coloanei populatie se realizeaza pe paza pozitiei acestei coloane la nivelul structurii de tip DataFrame, adica 7.
mask = (df.iloc[:, 7] > 1000000000) df[mask] # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 48 CN CHN 156 CH China Beijing 106 IN IND 356 IN India New Delhi areaInSqKm population continent currency 48 9596960.0 1392730000 AS CNY 106 3287590.0 1352617328 AS INR
In anumite situatii, este necesara extragerea inregistrarilor din structura de tip DataFrame care prezinta cea mai mica/mare valoare pentru o anumita coloana. Astfel de implementari pot fi realizate folosind metodele pandas.DataFrane.nsmallest(), respectiv pandas.DataFrame.nlargest(). Metodele returneaza primele n inregistrari care prezinta cele mai mici/mari valori in coloanele referite, ordonate crescator/descrescator.
df.nlargest(2, 'population') # Output iso alpha2 iso alpha3 iso numeric fips code name capital \ 48 CN CHN 156 CH China Beijing 106 IN IND 356 IN India New Delhi areaInSqKm population continent currency 48 9596960.0 1392730000 AS CNY 106 3287590.0 1352617328 AS INR
Restul coloanelor din structura de tip DataFrame care nu sunt specificate la apelul metodelor sunt returnate, dar nu au rol in ordonare.
De exemplu, pentru a extrage inregistrarile corespunzatoare statelor care includ termenul Island in denumire, sunt parte a continentului Oceania si utilizeaza valuta cu codul USD, este necesara urmatoarea masca. Operatorii logici din limbajul Python (and, or, not) sunt proiectati sa functioneze cu scalari. Din acest motiv in Pandas au fost suprascrisi operatorii logici pe biti pentru a obtine o versiune vectorizata a acestei functionalitati.
mask = (df['name'].str.contains('Island')) & \ (df['continent'].isin(['OC'])) & \ (df['currency'] == 'USD')
Rezultatul aplicarii acestei masti pentru structurura de date de tip DataFrame poate fi observat in cele ce urmeaza:
df[mask] # Output iso alpha2 iso alpha3 iso numeric fips code name \ 144 MH MHL 584 RM Marshall Islands 150 MP MNP 580 CQ Northern Mariana Islands 233 UM UMI 581 NaN U.S. Outlying Islands capital areaInSqKm population continent currency 144 Majuro 181.3 58413 OC USD 150 Saipan 477.0 56882 OC USD 233 NaN 0.0 0 OC USD