Средњошколци у Републици Србији¶
У овом темату позабавићемо се подацима о средњошколском образовању у Републици Србији који су нам доступни кроз отворене податке Министарства за просвету, науку и технолошки развој: http://opendata.mpn.gov.rs. Фајлови о средњошколцима и средњим школама у .csv формату (преузети 01.12.2019.) налазе се у локалном фолдеру и у редовима који следе, бавићемо се кратком прерадом тих података за лакшу обраду у наредним радним свескама. Специјално, користећи библиотеку pandas за учитавање и анализу табеларних података ћемо:
- брисати непотребне колоне
- додавати нове колоне на основу рачунских операцијама над постојећим подадацма
- уз помоћ библиотеке cyrtranslit ћемо мењати текстуалне вредности из латиничног у ћирилично писмо и обратно
- груписати податке и спајати их у нове табеле
- сачувати припремљен фајл за даљу обраду
import pandas as pd
import numpy as np
import cyrtranslit
Учитавање и обрада табеларних података је изузетно једноставна и практична у библиотеци pandas због чега је она изузетно популарна. Податке о средњошколцима и средњим школама преузели смо у .csv формату (вредности раздвојене зарезом, енг. coma separated values), па ћемо их учитати функцијом read_csv:
data = pd.read_csv('data/srednjoskolci data/raw data/MPNTRopendata_ss.csv')
У учитане податке брзо можемо завирити на пар следећих начина:
Функција head() излистава првих 5 редова табеле, док head(n) излистава n првих редова:
data.head(1)
Како учитана табела има чак 42 колоне, не можемо их видети све. Међутим, функцијом info() можемо сазнати које све колоне табела има, колико има уноса, као и које типове података је препознао пајтон у учитавању:
data.info()
Овај сет података садржи информације о срењим школама и образовним профилима у њима. Поред географских одредница школе (општина, округ), за сваки образовни профил имамо информације о броју ученика по разредима, броју одељења и слично што можете видети из назива колона. Овај интересантан сет података у наредним радним свескама користићемо да сазнамо који су то популарни смерови, да ли се то разликује по окрузима и слично.
Функцијом describe() још детаљније можемо завирити у колоне које садрже нумеричке податке. Она нам даје преглед основне статистике - број уноса, просечну вредност (mean), стандардну девијацију (std), најмању (min), највећу (max), медијалну вредност (50%), кaо и вредности које су веће од 25% других и 75% других у одређеној нумеричкој колони.
data.describe()
Како је табела написана ћирилицом, користићемо библиотеку cyrtranslit да све текстуалне податке у табели преиначимо у латиничо писмо (то нам је посебно битно за називе колона како бисмо лакше баратали подацима). За почетак ћемо то урадити са називима колона које добијамо позивајући data.columns.
data.columns = [cyrtranslit.to_latin(text,'sr') for text in list(data.columns)]
data.head(1)
А у наредних пар редова пролазимо кроз целокупан садржај појединачних колона које смо препознали да су текстуалне и да су нам од користи у наставку, а затим њихов садржај мењамо у латинично писмо. Урадићемо за почетак то за колону 'Власништво'.
data['Vlasništvo'] = [cyrtranslit.to_latin(text,'sr') for text in list(data['Vlasništvo'])]
data.head(1)
Наредне колоне можемо обрадити тако што ћемо претходни ред написати више пута, за све различите колоне које желимо да преименујемо у латинично писмо, или тако што ћемо овај ред написати у одговарајућој петљи којом ћемо проћи кроз све колоне које желимо да обрадимо:
tekstualne_kolone = ['Vlasništvo', 'Okrug','Opština','Naziv ustanove', 'Područje rada', 'Obrazovni profil', 'Jezik nastave']
for kolona in tekstualne_kolone:
data[kolona] = [cyrtranslit.to_latin(text,'sr') for text in list(data[kolona])]
data.head(1)
Колоне које нам нису неопходне за предстојећу анализу избацићемо из табеле користећи функцију drop на следећи начин:
data=data.drop(columns=['#','Broj IOP-a 1 - prvi razred', 'Broj IOP-a 2 - prvi razred',
'Broj IOP-a 3 - prvi razred','Broj IOP-a 1 - drugi razred', 'Broj IOP-a 2 - drugi razred',
'Broj IOP-a 3 - drugi razred','Broj IOP-a 1 - treći razred', 'Broj IOP-a 2 - treći razred',
'Broj IOP-a 3 - treći razred','Broj IOP-a 1 - četvrti razred', 'Broj IOP-a 2 - četvrti razred',
'Broj IOP-a 3 - četvrti razred','Broj kombinovanih odeljenja - prvi razred',
'Broj kombinovanih odeljenja - drugi razred',
'Broj kombinovanih odeljenja - treći razred',
'Broj kombinovanih odeljenja - četvrti razred',
'Broj specijalnih odeljenja - prvi razred',
'Broj specijalnih odeljenja - drugi razred',
'Broj specijalnih odeljenja - treći razred',
'Broj specijalnih odeljenja - četvrti razred'])
data.head()
Како имамо на располагању колону о власништву школе, можемо проверити уносе за које је у овој колони означено приватно власништво. То радимо уз помоћ селектовања одређеног дела табеле, односно оног дела табеле за који важи да је data.Vlasništvo=='Privatno':
data[data.Vlasništvo=='Privatno']
Како су у питању само три уноса, за једну исту приватну школу, то значи да немамо репрезентативне информације о школама у приватном власништву. Стога ћемо се у наставку фокусирати само на школе које су у државном власништву и избацићемо из табеле ова три реда а затим и колону власништво:
data = data[data.Vlasništvo!='Privatno']
data = data.drop(columns=['Vlasništvo'])
data.head(2)
Како имамо податке о броју одељења по разреду и слично број ученика о разреду, у наставку ћемо додати још пар колона у којима ће се налазити подаци о укупном броју ученика, одељења сумирајући одговарајуће постојеће колоне.
data['Ukupno odeljenja'] = data['Broj odeljenja - prvi razred']+data['Broj odeljenja - drugi razred']+data['Broj odeljenja - treći razred']+data['Broj odeljenja - četvrti razred']
data['Ukupno učenika'] = data['Broj učenika - prvi razred']+data['Broj učenika - drugi razred']+data['Broj učenika - treći razred']+data['Broj učenika - četvrti razred']
Из колона са укупним бројем ученика и одељења по образовним профилима и смеровима, можемо израчунати и просечан број ученика у одељењу на сваком од профила:
data['Prosečan broj učenika u odeljenju'] = data['Ukupno učenika']/data['Ukupno odeljenja']
Међу подацима које обрађујемо имамо информације и о полу ученика, те ћемо из укупног броја ученика и укупног броја девојчица срачунати и број дечака. Поред укупног броја ученика по полу, израчунажемо и њихове процентуалне заступљености. За процентуалну заступљеност искористили смо и функцију round како бисмо ограничили број децимала на 2.
data['Ukupno devojcica'] = data['Broj devojčica - prvi razred']+data['Broj devojčica - drugi razred']+data['Broj devojčica - treći razred']+data['Broj devojčica - četvrti razred']
data['Ukupno decaka'] = data['Ukupno učenika']-data['Ukupno devojcica']
data['Procenat devojcica'] = round(100*data['Ukupno devojcica']/data['Ukupno učenika'],2)
data['Procenat decaka'] = 100-data['Procenat devojcica']
За анализу коју имамо у плану, неће нам бити потребни подаци о ученицима по разреду, стога ћемо и те колоне избрисати. (Ако имате на уму и неку анализу за коју је потребно да сачувате податке по разредима, прекочите наредну команду!)
data = data.drop(columns=['Broj odeljenja - prvi razred',
'Broj učenika - prvi razred', 'Broj devojčica - prvi razred',
'Broj odeljenja - drugi razred', 'Broj učenika - drugi razred',
'Broj devojčica - drugi razred', 'Broj odeljenja - treći razred',
'Broj učenika - treći razred', 'Broj devojčica - treći razred',
'Broj odeljenja - četvrti razred', 'Broj učenika - četvrti razred',
'Broj devojčica - četvrti razred'])
data.head(1)
Ову нову табелу можемо сачувати у .csv формату користећи функцију to_csv:
data.to_csv('MPNTRopendata_ss_pripremljeni.csv',index=False)
Искористили смо и аргумент index да проследимо информацију функцији да не желимо да сачувамо колону индекс обзиром да се у њој налазе само редни бројеви и не носе додатно интересантне податке о средњошколцима.
Табела са подацима о средњим школама¶
Поред информација о заступљености одређених средњошколских профила и њиховој заузетости која је описана у претходној табели, имамо на располагању и неке опште податке о средњим школама, учитаћемо и припремити и те податке:
srednjeskole = pd.read_csv('data/srednjoskolci data/raw data/MPNTRopendata_ss_opsti.csv')
srednjeskole.head(2)
Да бисмо видели све доступне колоне, искористићемо функцију columns:
srednjeskole.columns
Као и претходно, обрисаћемо неке од колона за које нам се чини да их нећемо даље анализирати:
srednjeskole = srednjeskole.drop(columns=['#','Школска управа', 'Насеље','Адреса', 'Поштански број',
'Адреса електронске поште', 'Телефон', 'Сајт', 'Матични број', 'ПИБ',
'Датум оснивања', 'Примарна делатност', 'Број ученика - ИОП 1', 'Број ученика - ИОП 2',
'Број ученика - ИОП 3'])
srednjeskole.head()
Промена писма у називима и садржају колона:
srednjeskole.columns=[cyrtranslit.to_latin(text,'sr') for text in list(srednjeskole.columns)]
srednjeskole['Okrug']=[cyrtranslit.to_latin(text,'sr') for text in list(srednjeskole['Okrug'])]
srednjeskole['Opština']=[cyrtranslit.to_latin(text,'sr') for text in list(srednjeskole['Opština'])]
srednjeskole['Naziv ustanove']=[cyrtranslit.to_latin(text,'sr') for text in list(srednjeskole['Naziv ustanove'])]
srednjeskole['Vlasništvo']=[cyrtranslit.to_latin(text,'sr') for text in list(srednjeskole['Vlasništvo'])]
И као што смо претходно закључили, како немамо довољно података о школама у приватном власништву фокусираћемо се само на школе које су у државном власништву:
srednjeskole = srednjeskole[srednjeskole['Vlasništvo']=='Državno']
srednjeskole = srednjeskole.drop(columns=['Vlasništvo'])
srednjeskole.head(2)
И ову табелу ћемо сачувати у csv формату:
srednjeskole.to_csv('MPNTRopendata_sskole_pripremljeni.csv',index=False)
Припрема података за анализу и визуализацију по окрузима:¶
Претхнодна табела о средњим школама садржи информације о округу и општини на којој се школа налази што се може искористити да се подаци агрегирају на нивоу ових просторних јединица и на тај начин можемо припремити податке за даљу анализу. За груписање по округу, користићемо фукцију groupby. Ова функција креира подтабеле редова који имају исту вредност по колони коју користимо као аргумент функције (на пример, ако групишемо по Округу, ишод су подтабеле за сваки округ). Ако не желимо целе подтабеле, често сумирамо по одређеним колонама (на пример када желимо да знамо укупан број ђака у сваком округу) или бројимо колико има вредности (на пример када желимо да знамо број школа или образовних профила). Хајде да видимо како то ради:
skolepookruzima = srednjeskole.groupby(['Okrug'])[['Broj učenika','Broj devojčica','Broj odeljenja','Broj nastavnika - bez zamena','Ukupna norma nastavnikaa - bez zamena']].sum()
skolepookruzima.head(3)
Проверите да ли су бројеви које смо на овај начин добили тачни.
(Сугестија: селектујте делове табеле, нпр. skolepookruzima[skolepookruzima['Okrug']=='Borski upravni okrug']
ће излистати све редове у којима је округ Борски, користећи функцију sum можете проверити горње вредности.)
Како је овој новој табели индекс колона округ по коме смо груписали претходну табелу, ресетоваћемо индекс (користећи функцију reset_index), тј. индекс ће постати број:
skolepookruzima = skolepookruzima.reset_index()
skolepookruzima.head(3)
Слично користећи функцију groupby и size за величину сваке од група, можемо у новој табели сачивати вредности о броју средњих школа по округу:
brsskolapookrugu = srednjeskole.groupby(['Okrug']).size().reset_index(name='Broj skola')
brsskolapookrugu.head()
Претходно креиране две табеле skolepookruzima и brsskolapookrugu имају заједничку колону округ, па њу можемо искористи да на основу ње спојимо податке у једну табелу. То радимо уз помоћ функције merge којој је поред две табеле које спајамо потребно проследити и аргумент on којим дефинишемо по којој колони спајамо табеле:
infopookruzima = pd.merge(skolepookruzima,brsskolapookrugu,on='Okrug')
infopookruzima.head()
Направићемо још једну табелу која ће по окрузима пребројати број различитих опција за средњошколско образовање. Ове различите опције - број различитих подручја рада и број различитих образовних профила добићемо функцијом nunique:
opcijepookruzima = data.groupby(['Okrug'])[['Područje rada','Obrazovni profil']].nunique()
opcijepookruzima = opcijepookruzima.reset_index()
opcijepookruzima.head(3)
И ове податке ћемо спојити са претходном табелом у којој су до сада сачувани прикупљени подаци по окрузима:
infopookruzima = pd.merge(infopookruzima,opcijepookruzima,on='Okrug')
infopookruzima.head()
Коначно, један тип средњих школа има често специјалан статус - гимназије - па ћемо прикупити и податке колико се ученика школује у гимназијама на територији различитих округа:
gimnazijalci = data[data['Područje rada']=='Gimnazija'].groupby('Okrug')['Ukupno učenika'].sum()
gimnazijalci = gimnazijalci.reset_index()
gimnazijalci = gimnazijalci.rename(columns={'Ukupno učenika':'Ukupno gimnazijalaca'})
gimnazijalci.head(2)
И њих ћемо додати у табелу infopookruzima:
infopookruzima = pd.merge(infopookruzima,gimnazijalci,on='Okrug')
infopookruzima.head()
Табелу infopookruzima која сада садржи прегршт агрегираних података на нивоу округа ћемо сачувати у .csv за даљу анализу:
infopookruzima.to_csv('MPNTR_podaci_po_okruzima.csv',encoding='UTF-16',index=False)
Припрема података за анализу и визуализацију по општинама:¶
Слично ћемо поновити и са општинама. Како постоје примери оштина истог назива (нпр. Палилула), груписање ћемо сада вршити по пару колона - округ и општина:
skolepoopstinama = srednjeskole.groupby(['Okrug','Opština'])[['Broj učenika','Broj devojčica','Broj odeljenja','Broj nastavnika - bez zamena','Ukupna norma nastavnikaa - bez zamena']].sum()
skolepoopstinama = skolepoopstinama.reset_index()
skolepoopstinama.head(3)
brsskolapoopstini = srednjeskole.groupby(['Okrug','Opština']).size()
brsskolapoopstini = brsskolapoopstini.reset_index()
brsskolapoopstini = brsskolapoopstini.rename(columns={0:'Broj skola'})
brsskolapoopstini.head()
infopoopstinama = pd.merge(skolepoopstinama,brsskolapoopstini,on=['Okrug','Opština'])
infopoopstinama.head()
opcijepoopstinama = data.groupby(['Okrug','Opština'])[['Područje rada','Obrazovni profil']].nunique()
opcijepoopstinama = opcijepoopstinama.reset_index()
opcijepoopstinama.head(3)
infopoopstinama = pd.merge(infopoopstinama,opcijepoopstinama,on=['Okrug','Opština'])
infopoopstinama.head()
gimnazijalci = data[data['Područje rada']=='Gimnazija'].groupby(['Okrug','Opština'])['Ukupno učenika'].sum()
gimnazijalci = gimnazijalci.reset_index()
gimnazijalci = gimnazijalci.rename(columns={'Ukupno učenika':'Ukupno gimnazijalaca'})
gimnazijalci.head(2)
infopoopstinama = pd.merge(infopoopstinama,gimnazijalci,on=['Okrug','Opština'])
infopoopstinama.head()
trogodisnjepoopstini = data[data['Trajanje obrazovanja']==3].groupby(['Okrug','Opština'])['Ukupno učenika'].sum()
trogodisnjepoopstini = trogodisnjepoopstini.reset_index()
trogodisnjepoopstini = trogodisnjepoopstini.rename(columns={'Ukupno učenika':'Ukupno u trogodisnjim ss'})
trogodisnjepoopstini.head()
infopoopstinama = pd.merge(infopoopstinama,trogodisnjepoopstini,on=['Okrug','Opština'])
infopoopstinama.head(2)
infopoopstinama.to_csv('MPNTR_podaci_po_opstinama.csv',encoding='UTF-16',index=False)
У овој радној свесци испробали смо различите функције у оквиру pandas библиотеке, неке једноставне (као read_csv, head, info, drop, rename) али и неке захтевније (нпр. groupby, merge) све са циљем припреме података за даљу анализу и визуализације. А сада, хајде да видимо детљније шта нам говоре ови подаци!