У овој лекцији је приказано како управљамо табелама, односно како расподељујемо, филтрирамо, сортирамо или на неки други начин обрађујемо податке који су организовани у табеле. Уведена је основна структура за рад са табеларним подацима у Пајтону (DataFrame) и дати су примери коришћења најважнијих Пајтон функција за рад са табелама.
Обрада података подразумева технички део рада са подацима: прикупљање, организацију, трансформисање, филтрирање и форматирање података закључно са чишћењем података. Први део обраде података не захтева посебно доменско, односно експертско знање о значењу и контексту података. Некад тај посао можемо да поверимо и машини која би то радила по алгоритму. То значи да елементарну обраду података може да ради и неко ко не разуме саму природу података. Чишћење података, ипак, захтева доменско знање о подацима и не може да се ради аутоматски. Обрада података претходи фази анализе података за коју је специјалистичко знање неопходно.
Обрада дигиталних података најчешће почиње тако што их учитамо у програм у ком радимо обраду.
Учитавање података¶
Подаци са којима најчешће радимо у јавној управи углавном се налазе у DOCX (Word), XLSX (Excel), PDF i HTML форматима. То су формати који бележе мноштво метаподатака који се тичу стила, маргина, фуснота итд. Они су визуелно погодни за човека који чита садржај фајла или га уноси, али не и за машину која тражи једноставну структуру података без непотребних естетских детаља. За машинску обраду података важно је да се једнозначно утврђена структура податка доследно користи и тако омогући приступ подацима по унапред утврђеном алгоритму.
Са друге стране, обрада података се увек своди на рад са једноставним, добро документованим и отвореним форматима за које не морамо да користимо било какав комерцијални софтвер. Чак и када се подаци учитавају из врло сложених формата они се трансформишу у табеле које се чувају у свега неколико основних формата. Најчешће се за табеларне податке користи Comma Separated Value (CSV) формат где се вредности одвојене зарезом записане у текстуалном формату.
Библиотека pandas има мноштво функција неопходних за учитавање, манипулацију и чување података. Због тога је готово неизбежна у обради података. Скоро да нема програма за обраду табеларних података у Пајтону који на почетку немају import pandas as pd.
import pandas as pd
То нам омогућава да податке из фајла учитамо у структуру са којом се у Пајтону једноставно ради - DataFrame.
За потребе рада у овој лекцији користићемо два фајла са подацима. Њих можете сами да преузмете са интернета са Global Power Plant Database, али смо их због једноставности поставили у поддиректеријум ./data нашег радног директоријума. Ту се налазе и сви остали фајлови са подацима који ће нам бити потребни у овом курсу. За почетак, учитавамо фајл који садржи списак свих великих електрана у свету.
elektrane=pd.read_csv("data/global_power_plant_database.csv")
Пошто је фајл прилично велики, вероватно ћете добити упозорење да у табели имамо различите типове података и овакво учитавање фајла без спецификације који тип података имамо у којој колони није ефикасан. С обзиром да ћемо овај фајл учитавати само једном, најбоље је да игноришете ово упозорење. Ако баш не можете, објасните рачунару да не мора да се плаши да ће остати без слободне меморије и учитајте податке са
elektrane=pd.read_csv("data/global_power_plant_database.csv", low_memory=False)
У том случају неће приказивати упозорење.
У сваком случају, пожељно је да помоћу функције info() погледамо шта се налази у фајлу, које су то колоне и који је тип података у њим садржано.
elektrane.info()
Одавде видимо, на пример, да DataFrame elektrane има 34936 редова и 36 колона. За сваку колону наведено је какве податке садржи и колико таквих података има. Иако све колоне садрже исти број елемената, могуће је да су неки од њих null, односно да ти подаци недостају. Зато за сваку колону имамо број елемената који нису null, односно оних података који су убележени.
Пајтон често користи општије типове података да се при учитавању не би нешто изгубило. Тако су целобројне вредности меморисане као 64-обитни децимални бројеви (float64), а текст као објекат (object). То није најбоље са аспекта коришћења меморије, али сада не морамо тиме да се бавимо. Ако затреба, моћи ћете касније да промените тип променљиве у колони.
Други начин да видимо шта се налази у табели је да помоћу функције head() погледамо заглавље, тј. називе колона са првих неколико редова. Слично, можемо да погледамо и "зачеље" табеле помоћу функције tail().
elektrane.head()
Избор колона¶
Видимо да DataFrame elektrane има 34936 редова и 36 колона. За учење рада са табелама нам није неопходно да их све имамо у меморији. За потребе ове демонстрације узећемо само првих осам колона, закључно са колоном у којој је примарни тип горива. За то ћемо користити индексе редова и колона помоћу iloc[:,0:8]. Ово прво : значи да узимамо све редове, а 0:8 да узимамо 8 колона почевши од индекса 0 (прва колона). Имајте у виду да се прва колона (за нас људе који гледамо испис на екрану) и не рачуна у колоне. То су само ознаке редова. За Пајтон, прва колона је country и она има индекс 0.
elektrane=elektrane.iloc[:,0:8]
elektrane.head()
На овај начин смо изабрали које колоне желимо да користимо и сачували само то у табели elektrane. Слично смо могли да уместо 0:8 ставимо листу где тачно наведемо које колоне хоћемо, нпр. [0,4,7]. Пробајте.
elektrane.iloc[:,[0,4,7]]
Табулација¶
Један од првих корака у "експлоративној анализи података", односно утврђивању шта све имамо у табели јесте да пребројимо колико чега има. Оно што се стручно зове табулација, крос-табулација или contingency table је заправо обично пребројавање елемената по категоријама. За почетак, узећемо колону са ознакама земаља у којима се налазе електране у пребројати их помоћу функције value_counts(). То ће нам рећи колико има електрана у којој земљи. При томе ће функција која пребројава елементе сортирати низ од највећег ка најмањем па ће на првом месту бити земља са највећим бројем електрана у овој табели.
elektrane.value_counts('country')
Крос-табулација¶
Крос-табулација је пребројавање елемената по две одвојене категорије. Овде ћемо, на пример, пребројати колико се пута помиње која земља за сваки тип примарних горива. Тако ћемо видети ко има колико електрана на ветар, колико нуклеарних електрана итд. Да бисмо то урадили није довољна функција value_counts() већ морамо да раздвојимо колоне које ћемо третирати као посебне категоријалне променљиве. Функција crostab() захтева два аргумента тог типа. То су управо две колоне наше табеле.
zemljа=elektrane['country']
gorivo=elektrane['primary_fuel']
zemljа
pd.crosstab(zemljа,gorivo)
Изгледа да ових типова горива има више него што смо очекивали (15). Свеједно, одавде можемо да видимо ко има колико електрана ког типа. Како се који тип горива зове могли смо да добијемо са elektrane.value_counts('primary_fuel'). Алтернативно, можемо да искористимо функцију columns која ће нам исте податке дати за категоријалну табелу.
pd.crosstab(zemljа,gorivo).columns
Сортирање¶
Мењајући редослед редова и колона у табели можемо да уредимо табелу како нама одговара, а да притом ништа не изгубимо од података. Најчешће се редослед мења тако што сортирамо податке по азбучном/абецедном реду ако су текстуални или по величини ако су нумерички. Свако сортирање може да се врши и хијерархијски. Сетите се фудбалских табела: клубови су сортирани по броју бодова, али ако имају исти број бодова онда их сортирамо по гол-разлици. Ту онда имамо два критеријума сортирања. Важан је и смер сортирања. Нумерички подаци, на пример, могу да се сортирају по растућем или опадајућем редоследу.
Ми ћемо сада податке о светским електранама да сортирамо по снази израженој у мегаватима (колона capacity_mw) почевши од електране која има највећу снагу (тј. по реду који није растући -- ascending=False).
elektrane.sort_values('capacity_mw', ascending=False)
Табела нам показује да хидроелектрана "Три клисуре" у Кини има највећи капацитет: 22,5 гигавата. Неки од нас су у школи учили да Хуверова електрана на реци Колорадо производи највише струје. Давно је то било. Изгледа да се нешто променило. Да бисмо утврдили која је по реду Хуверова електрана данас потребно је да електранама придружимо позицију на ранг-листи. Најбоље је да целој табели додамо колону у којој ће бити ранг електране од највеће до најмање. Ту колону ћемо назвати rank. (И остали називи колона су нам на енглеском, зар не?)
elektrane['rank']=elektrane['capacity_mw'].rank(ascending=False)
Приметите да смо у претходном случају само приказали сортирану табелу, а да смо потом оригиналној табели elektrane придружили колону са ранг-листом. То је само други начин сортирања. Да видимо како сад изгледа заглавље табеле. Прва на табели је авганистанска електрана која је 13036. на ранг-листи. Видимо да негде има и нецелобројних вредности за ранг. То је зато што их има више са истом снагом.
elektrane.head()
Да бисмо сада у називу нашли "Hoover", "Djerdap" или "Gorges" морамо да променимо тип података у колони name тако да буде текст, односно str. За Пајтон је то и даље колона у којој су подаци типа object. То ћемо урадити помоћу функције astype() и аргумента "str".
elektrane['name']= elektrane['name'].astype("str")
Сада кад у колони name имамо податке типа str можемо да питамо где се на ранг-листи налазе електране које нас интересују. То ћемо урадити помоћу функције str.contains() која проверава да ли се неки текст налази у некој текстуалној променљивој.
elektrane[elektrane['name'].str.contains('Hoover')]
Изгледа да се Хуверова електрана води два пута у листи. Једном као Хуверова брана (Аризона), а други пут као Хуверова брана (Невада). У сваком случају, није баш најбоље пласирана данас. Ранг је 1525.
Да видимо где је наш Ђердап.
elektrane[elektrane['name'].str.contains('Djerdap')]
Изгледа да га нема на листи. Барем не под овим именом. Мораћемо да покушамо другачије.
Филтрирање редова и селекција колона¶
Ми можемо да филтрирамо податке и погледамо само оно што нас интересује. 34936 редова је свакако превише за прегледање ред по ред. Изабраћемо сада само онај део табеле у ком је Србија земља у којој се налази електрана. У угласте заграде иза имена табеле ћемо ставити логички исказ elektrane['country']=='SRB'. Онда ће нам од табеле остати само они редови у којима је тај исказ тачан, односно где је земља Србија.
elektrane[elektrane['country']=='SRB']
Сад је јасно зашто малопре нисмо нашли Ђердап. Требало је да га напишемо великим словима. Или, што би било још боље кажемо да str.contains() не буде осетљив на то да ли су слова мала или велика. Свеједно, сад имамо податак да је Ђердап 1 1452. највећа електрана на свету. Термоелектране у Обреновцу су нам нешто боље пласиране. Можда тиме не би требало да се хвалимо, али можемо да констатујемо.
Сложени критеријуми¶
Логички исказ помоћу ког филтрирамо податке у табели може да буде сложенији. Ево примера како користимо два услова: да је електрана у Србији и да је типа "Hydro". Да би резултат логичког исказа био израчунат како треба оба појединачна исказа ставите у заграде и раздвојте знаком &.
elektrane[(elektrane['country']=='SRB') & (elektrane['primary_fuel']=='Hydro')]
Агрегација података¶
За неке анализе је згодно груписати податке. У примеру са електранама можемо, на пример, да урадимо преглед по земљама -- да видимо колико има укупно електрана и колико имају укупно снаге. Први корак је да помоћу функције groupby() групишемо податке у табели у односу да конкретну категорију, тј. податке у конкретној колони.
po_zemljama=elektrane.groupby('country')
Структура po_zemljama је објекат сложенији од табеле и није погодан за приказ. Боље је да направимо DataFrame са конкретним статистикама које нас интересују. Почећемо од колоне у којој за све земље саберемо вредности из колоне capacity_mw.
gdf=pd.DataFrame(po_zemljama['capacity_mw'].sum())
gdf
Табели gdf ћемо придружити још једну колону: укупан број електрана. Ову колону ћемо назвати count.
gdf['count']=po_zemljama['capacity_mw'].count()
Још само да табелу сортирамо по броју електрана.
gdf.sort_values('count', ascending=False)
Сада видимо колика је укупна снага електрана у САД, Кини, Британији... Укупно, за цео свет то може да се сабере и добијамо 5.7 теравата снаге.
gdf.capacity_mw.sum()
Повезивање табела¶
Немамо увек све потребне податке у једној табели. На пример, у табели са електранама немамо податак о томе на ком се континенту налазе електране, а баш нас интересује да видимо колико који континент производи струје. Решење је да нађемо другу табелу у којој имамо податке на ком је континенту која земља па да те две табеле повежемо. Важно је да нађемо табелу која има на исти начин записане вредности по којима хоћемо да повежемо табеле. Ако у једној пише "United Kingdom" а у другој "Great Britain" то ће бити мука за повезивање. Баш зато су међународне агенције стандардизовале називе и ознаке за земље, регије, општине итд. Да видимо шта пише у једној таквој табели.
zemlje=pd.read_csv("https://datahub.io/JohnSnowLabs/country-and-continent-codes-list/r/country-and-continent-codes-list-csv.csv")
Ако скидање табеле са интернета не ради, исту табелу имамо у директоријуму ./data/country-and-continent-codes-list.
zemlje.head(8)
Довољно је бацити поглед на табелу па да видимо да је у једној табели "Afghanistan", а у другој "Afghanistan, Islamic Republic of". Срећом, колоне country у првој и Three_Letter_Country_Code имају исте трословне ознаке: "AFG" је на оба места. Надамо се да се подаци подударају и за друге земље.
Помоћу функције merge() из pandas библиотеке можемо да спојимо две табеле. То можемо да урадимо тако што од две табеле правимо трећу или тако што једну допуњујемо подацима из друге. Свеједно, аргументи функције треба да буду називи табела и колона по којима их повезујемо. Лакше је ако се те колоне зову исто у обе табеле. Онда је довољан један аргумент, нпр. on="country", али то код нас није случај па смо навели оба: left_on и right_on. Имајте у виду да се листе по којима повезујемо табеле можда не поклапају савршено. Подразумевани аргумент је how="inner" што значи да ће нова табела имати само оне редове које се налазе и у првој и у другој. То можемо да мењамо, али се нећемо сад у то упуштати.
gdf=gdf.merge(zemlje, left_on="country", right_on="Three_Letter_Country_Code")
gdf
Како бисмо сад од овога направили табелу са бројем електрана и укупном снагом по континентима? Опет ћемо груписати податке.
po_kontinentima=gdf.groupby('Continent_Name')
gdf2=pd.DataFrame(po_kontinentima['capacity_mw'].sum())
gdf2
Без много анализе видимо да Европа производи седам пута више струје него Африка. Ми овде завршавамо истраживање на тему електрана. Све остало препуштамо вама за самостални рад.
Снимање података¶
Остаје нам још само једна ствар коју морамо да урадимо пре него што буде касно -- да снимимо резултат. Помоћу фукције to_csv() DataFrame ћемо лако снимити као CSV фајл.
gdf2.to_csv("data/po_kontinentima.csv")