O linie din fişierul "evna.csv
" - ajustat în [1] după EVNAT-2015.csv - arată astfel:
11096387,F,2001-03-11,URBAN,4061103713,Prezent,,Prezent,1.75,,4,NU,,NU,,NU,,1.75,,4,2.87
Al cincilea câmp reprezintă codul SIIIR, format din zece caractere de şablon "\d
" (cifră în baza 10), dintre care ne interesează numai primele două (codul judeţului); pentru a identifica acest câmp în cadrul liniei putem folosi şablonul de expresie regulată ",\d{10}
" - în care virgula iniţială serveşte pentru a exclude primul câmp (care ar putea să aibă tot 10 cifre, dar nu este precedat de ','). Următoarea comandă Perl suprimă ultimele 8 cifre din codul SIIIR, pe fiecare linie a fişierului:
vb@Home:~/Evna$ perl -pi -e 's/,(\d\d)\d{8}/,$1/g' evna.csv
În [1] am folosit direct read.csv("evna.csv") şi am văzut că în structura de date constituită de R (un "data.frame
") unele câmpuri (coloane, sau variabile) au un alt tip decât cel care ne-ar conveni. De fapt, revăzând rezultatul produs de funcţia str() constatăm că ar fi de forţat tipul numai pentru două sau trei variabile, încât putem proceda astfel:
evna <- read.csv("evna.csv") # evna$Cod_siiir este de tip 'numeric' (şi vrem 'factor')
# evna$Data_nastere este 'factor', vrem 'Date'
# a inspecta cu 'str(evna)' şi după următoarea comandă, cu 'str(classes)'
classes <- sapply(evna, class) # tipul fiecărei variabile, indexând prin numele câmpului
classes[names(classes) %in% c("Cod_siiir")] <- "factor"
classes[names(classes) %in% c("Data_nastere")] <- "Date"
evna <- read.csv("evna.csv", colClasses=classes) # acum evna$Cod_siiir este 'factor'
# iar evna$Data_nastere este 'Date'
Câmpul final "Media
" rămâne de tip "factor
" (deşi am fi vrut iniţial, să forţăm "numeric
"):
> str(evna$Media)
Factor w/ 705 levels "","1","10","1.02",..: 135 313 261 467 419 367 303 513 ...
Nu am putut forţa variabila "Media
" pe valori de tip "numeric
", fiindcă există linii pe care "Media
" are valoarea "Absent
" (şi nu puţine, încât nu se cuvine să le ignorăm); acest termen ("Absent
") apare ca valoare şi în alte coloane, încât deocamdată preferăm să nu-l modificăm.
Cele trei coloane "Status_
" vor fi servit pe parcursul desfăşurării examenului (dacă tot numărând zilnic tezele, comisia de examen găseşte mai puţin decât numărul de "Prezent
", atunci se produce panica gravă de rătăcire a unor teze); poate că aceste coloane de date vor fi servit şi pentru a înscrie "Absent
" în loc de medie, acelor elevi care nu s-au prezentat la vreuna dintre probe:
> table(evna$Status_romana)
Absent Eliminat cu nota 1 Prezent
4390 6 159022
> table(evna$Status_limba_materna)
Absent Prezent
153845 216 9357
> table(evna$Status_matematica)
Absent Eliminat cu nota 1 Prezent
4828 6 158584
Dar după definitivarea examenului, coloanele "Status_
" sunt aproape inutile şi sunt oricum, prea puţin interesante din punct de vedere statistic. De exemplu, "Status_limba_materna
" redat mai sus ne arată că au fost cam 9500 de candidaţi din rândul naţionalităţilor conlocuitoare - dar acest lucru poate fi dedus în finalul examenului pe baza datelor din coloana "Nota_finala_lb_materna
".
Putem obţine o situaţie statistică suficient de relevantă privind prezenţa la examen, pe baza datelor din coloanele "Media
" şi "Cod_siiir
" - procedând de exemplu astfel:
jud.all <- subset(evna, select=c('Cod_siiir', 'Media'))
jud.abs <- subset(jud.all, Media=='Absent' | Media=='')
package(plyr)
jud.all <- count(jud.all, c('Cod_siiir')) # număr candidaţi înscrişi, pe judeţe
jud.abs <- count(jud.abs, c('Cod_siiir')) # absenţi (una sau mai multe probe) pe judeţe
Am extras cele două coloane din "evna
", într-o nouă structură de date "jud.all
" şi apoi, am extras liniile din "jud.all
" corespunzătoare absenţilor, într-o nouă structură de date "jud.abs
"; apoi, prin funcţia plyr::count() am contorizat liniile cu aceeaşi valoare "Cod_siiir
" - obţinând pe fiecare judeţ, numărul total de candidaţi şi respectiv, numărul candidaţilor care au absentat la cel puţin o probă.
În [1] am obţinut un fişier "42jud.csv
", conţinând numele corect al judeţului, codul acestuia, numărul de şcoli şi numărul de locuitori (pentru fiecare dintre cele 42 de judeţe, în ordinea alfabetică); încărcăm datele respective în "jud.csp
" şi ordonăm liniile respective după codul judeţului - adică în aceeaşi ordine în care avem liniile în "jud.all
" şi în "jud.abs
". Apoi, adăugăm coloana "pabs
" pe care înscriem procentul de absenţi - calculat pe baza câmpurilor "freq
" din "tabelele" obţinute mai sus; în final, reordonăm alfabetic şi rescriem fişierul:
jud.csp <- read.csv("42jud.csv",
colClass=c("character", "character", "integer", "integer"))
jud.csp <- jud.csp[order(jud.csp$cod), ] # în ordinea codurilor judeţelor
jud.csp$pabs <- round(jud.abs$freq / jud.all$freq * 100, 3) # procentul de absenţi
jud.csp <- jud.csp[order(jud.csp$judeţ), ] # în ordinea alfabetică a judeţelor
write.csv(jud.csp, file="42jud.csv", row.names=FALSE)
Fie prin inspectare directă a datelor obţinute astfel, fie "citind" histograma produsă de exemplu prin comanda hist(jud.csp$
pabs) - putem face câteva constatări interesante: judeţul Olt are 11.278% absenţi, însemnând de aproape patru ori media pe celelalte judeţe; următorul cel mai mare procent de absenţi este la distanţă de 6 procente (Sălaj cu 5.298% şi Maramureş cu 5.275%). Un număr de 9 judeţe au procentul de absenţi între 4.09% şi 5.3%, alte 10 judeţe au între 0.5% şi 1.88%, iar celelalte 22 de judeţe au între 2.01% şi 3.88% absenţi - adică dacă excludem Olt, repartiţia absenţilor este apropiată de o repartiţie normală:
> summary(jud.csp$pabs[-28]) # exclude judeţul Olt
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.514 2.012 2.860 2.911 3.869 5.298
În [1] am văzut că este foarte plauzibilă o relaţie liniară între numărul de şcoli şi numărul de locuitori şi că în raport cu dreapta de regresie respectivă, Olt prezenta surplusul cel mai mare (138 de şcoli "mai mult decât s-ar cuveni" conform regresiei - cu mult peste media surplusurilor altor judeţe) în timp ce M.Bucureşti prezenta "lipsa" de şcoli cea mai mare (mai mult de 400 de şcoli în minus, faţă de valoarea liniei de regresie); iar acum vedem că Olt are şi procentul cel mai mare de absenţi la examen (de 3.8 ori media), în timp ce M.Bucureşti are procentul cel mai scăzut de absenţi, 0.514%.
Aceste observaţii sugerează eventual existenţa unei anumite relaţii între procentul de absenţi şi valoarea reziduală a regresiei întreprinse în [1], valoare exprimată pe coloana "resid
" din tabelul "jud41
" redat la sfârşitul lui [1]…
Următorul mic experiment arată că evna$Data_nastere
are valori de tip "Date
" (dată calendaristică), care apar în formatul "an-lună-zi" dar sunt reprezentate intern ca diferenţa în zile faţă de data (luată ca origine) 1970-01-01:
> str(evna$Data_nastere)
## Date[1:163418], format: "2001-03-11" "2000-11-15" "2000-05-29" "2000-08-20" ...
> unclass(evna$Data_nastere[1])
## [1] 11392 # (numărul de zile de la originea 1970-01-01 până la 2001-03-11)
> evna$Data_nastere[1] - as.Date("1970-01-01")
## Time difference of 11392 days
Am putea determina vârsta candidaţilor împărţind la 365 (sau mai bine, la 365.25) diferenţa dintre data susţinerii examenului şi vectorul evna$Data_nastere
(de exemplu, 11392 / 365.25 = 31.1896 - însemnând că între datele menţionate mai sus au trecut 31 de ani); dar convertind datele la clasa R POSIXlt dispunem de "accesorii" pentru an, lună şi zi, încât putem formula următoarea funcţie (dar deloc nouă) pentru a determina numărul de ani cuprins între două date calendaristice:
age = function(fromDate, toDate) {
from = as.POSIXlt(fromDate)
to = as.POSIXlt(toDate)
age = to$year - from$year # Vârsta măsurată în ani, dar se va
# scădea 1 an dacă 'toDate' este înaintea aniversării zilei de naştere
ifelse(to$mon < from$mon | (to$mon == from$mon & to$mday < from$mday),
age - 1, age)
}
Extragem coloanele "Data_nastere
", "Cod_siiir
" şi "Media
", calculăm vârsta în ani folosind funcţia age()
introdusă mai înainte şi contorizăm după vârstă:
> varsta <- subset(evna, select=c('Data_nastere', 'Cod_siiir', 'Media'))
> varsta$Data_nastere <- age(varsta$Data_nastere, as.Date('2015-06-01'))
> varsta.count <- count(varsta, c('Data_nastere'))
Schimbăm totuşi numele primei coloane şi apoi filăm (şi rearanjăm aici) rezultatul:
> colnames(varsta.count)[1] <- "vârsta" > varsta.count vârsta freq vârsta freq 10 1 25 14 12 2 ... 13 4783 33 10 14 101401 34 9 15 54289 35 12 16 2279 ... 17 432 55 1 18 74 108 1 19 20 114 1 ... 115 1
Constatăm că marea majoritate a candidaţilor au vârsta standard (14 sau 15 ani), sau apropiată de aceasta (13 ani, sau 16 ani); există şi excepţii (sub 13 ani, sau 17 ani, sau 18..35 de ani) pe care probabil că numai Ministerul le-ar putea eventual explica. Vedem însă că există şi candidaţi cu vârsta de peste 100 de ani - însemnând că s-au strecurat anumite erori la înregistrarea datelor.
Fiindcă am reţinut şi codul judeţului, putem găsi eventual "vinovaţii":
> subset(varsta, vârsta < 13 | vârsta > 100) vârsta Cod_siiir Media 1822 12 40 9.5 # 40 este codul pentru 'M.Bucureşti' 18084 12 40 6.97 77868 114 16 7.22 # 16 este codul pentru 'Dolj' 104217 108 17 4.37 142867 115 37 5.1 # 37 este codul pentru 'Vaslui' 150656 10 33 5.65
În structurile de tip "data.frame
" şi liniile au "nume" (nu numai coloanele), iar acestea se "moştenesc"; de exemplu "numele" 1822
al primeia dintre liniile de date redate mai sus reprezintă (de vreme ce nu am prevăzut alte denumiri) numărul de ordine al acelei linii din structura "varsta
" - moştenit de fapt de la "evna
", din care aceasta a provenit - care conţine datele 12, 40, 9.5
în coloanele vizate mai sus:
# Linia 1823 din fişierul "evna.csv"
10659537,F,2002-07-21,URBAN,40,Prezent,,Prezent,9.5,,9.5,NU,,NU,,NU,,9.5,,9.5,9.5
Desigur, am căutat linia 1823 în loc de 1822, fiindcă prima linie din fişier este linia de antet, iar liniile de date încep de la linia 2. Vedem că este vorba de o elevă (din Bucureşti) care va împlini 13 ani puţin după încheierea examenului şi care a fost notată cu 9.50 la ambele probe.
vezi Cărţile mele (de programare)