momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Un set de date URÂT: rezultatele bacalaureatului (V: sumarizări pe intervale şi judeţe)

bacalaureat | limbajul R
2016 aug

9.7 Nivelul notelor şi mediilor, pe judeţe

Putem obţine imediat (cu o singură comandă) o structură "data.frame" conţinând pentru fiecare judeţ, valorile medii pentru coloanele de note şi de "Medie" din "bac5":

aggregate(cbind(nota.A, nota.C, nota.D, Medie) ~ jud, 
          data = bac5, FUN = mean) -> judACDM
print(judACDM, digits = 3)
               jud nota.A nota.C nota.D Medie
1             Alba   7.74   7.96   8.12  7.94
2             Arad   7.36   7.35   7.76  7.49
3            Argeş   7.22   7.76   7.98  7.65
4            Bacău   7.68   7.78   8.06  7.84
5            Bihor   7.21   7.50   8.05  7.61
6  Bistriţa-Năsăud   7.80   7.48   7.90  7.73
7         Botoşani   7.70   7.53   8.22  7.81
8           Braşov   7.81   7.80   7.97  7.84
9           Brăila   7.55   7.75   7.96  7.75
10           Buzău   7.54   7.64   8.03  7.73
11   Caraş-Severin   7.54   7.32   7.72  7.53
12            Cluj   7.73   7.89   8.03  7.89
13       Constanţa   7.34   7.64   7.90  7.63
14         Covasna   7.15   7.64   7.81  7.66
15       Dâmboviţa   7.80   7.47   8.01  7.76
16            Dolj   7.28   7.56   7.97  7.60
17          Galaţi   7.37   7.85   8.02  7.74
18            Gorj   7.83   7.41   7.82  7.68
19        Harghita   6.55   7.53   7.98  7.46
20       Hunedoara   7.66   7.51   7.88  7.68
21        Ialomiţa   7.38   7.56   7.95  7.63
22            Iaşi   7.76   8.00   8.11  7.95
23           Ilfov   7.03   6.69   7.35  7.02
24       Maramureş   7.42   7.70   7.94  7.68
25       Mehedinţi   7.69   7.52   8.04  7.75
26           Mureş   7.35   7.70   7.96  7.71
27           Neamţ   7.51   7.90   8.10  7.83
28             Olt   7.61   7.35   8.11  7.69
29         Prahova   7.76   7.91   8.03  7.90
30       Satu-Mare   7.60   7.66   7.87  7.71
31           Sălaj   7.46   7.67   7.95  7.72
32           Sibiu   7.69   7.78   8.06  7.83
33         Suceava   7.54   7.63   8.08  7.75
34       Teleorman   7.21   7.15   7.89  7.41
35           Timiş   7.80   7.50   7.87  7.72
36          Tulcea   7.84   7.69   7.92  7.81
37          Vaslui   7.18   7.71   8.05  7.64
38          Vâlcea   7.63   7.66   8.02  7.77
39         Vrancea   7.64   7.90   8.01  7.84
40     M.Bucureşti   7.66   7.75   7.92  7.77
41        Călăraşi   7.37   7.27   7.76  7.46
42         Giurgiu   6.85   7.20   7.75  7.26

Dar oare sunt corecte aceste rezultate?! În coloanele 'nota.A' şi 'nota.C' avem câte o singură valoare sub 7; ar reieşi că mediile pe ţară la probele A şi respectiv C sunt peste 7 - ori boxplot-ul redat în §9.6 ne-a arătat că acestea sunt totuşi sub 7 (media la A este 6.816, iar la C este 6.618)! În treacăt să observăm că se cuvine să ne dăm seama că nu are sens să modelăm şi grafic, mediile judeţene redate mai sus: marea majoritate a acestora, pe toate coloanele sunt între 7 şi 8 (dacă toate barele au cam aceeaşi înălţime - ce sens are să le desenezi pe toate?).

Rezultatele sunt corecte, dar nu pentru ceea ce ni se părea că am vrea să obţinem. Funcţia aggregate() ignoră (în mod implicit) valorile 'NA'; pentru formule cu o singură coloană, rezultatul va fi cel aşteptat - dar aici avem patru coloane şi vor fi păstrate pentru calcul numai înregistrările care nu conţin niciun 'NA' în coloanele respective (de exemplu, (2.50, 5.50, 8.25, NA) va fi exclusă, deşi ea ar conta la calculul mediei pe primele trei coloane).

În coloana 'Medie' avem 'NA' în acele înregistrări pentru care măcar în una dintre coloanele de note la probele respective avem sau 'NA', sau o notă mai mică decât 5. Prin urmare, ceea ce am obţinut mai sus reprezintă mediile judeţene pentru 'nota.A', 'nota.C', 'nota.D' şi 'Medie' luând în consideraţie numai acele valori de pe coloanele respective din "bac5" care sunt cel puţin egale cu 5 (dat fiind că metodologia oficială prevede calculul de 'Medie' numai în cazul când la toate probele s-a obţinut cel puţin 5); în "judACDM", coloana 'Medie' este chiar media aritmetică a celor trei coloane de note.

Dacă totuşi, am vrea să luăm în consideraţie toate notele (inclusiv cele mai mici de 5, excluse mai sus) - cum putem proceda? Cel mai comod este să folosim pachetul dplyr:

require(dplyr)
judACDMw <- bac5 %>%
            group_by( jud ) %>% 
            summarise_at( c(19, 21, 22, 25), mean, na.rm = TRUE )

Operatorul "%>%" comunică obiectul din partea stângă a sa, funcţiei din dreapta - permiţând înlănţuirea unor comenzi, fiecare prelucrând rezultatul celei precedente (de exemplu, în loc de formularea obişnuită head(judACDM) - prin care afişăm primele 6 linii din 'judACDM' - putem folosi bac5 %>% head). Prin comanda "compusă" redată mai sus, se grupează înregistrările din "bac5" după valorile câmpului 'jud' şi apoi se determină pentru fiecare grup valoarea medie pentru fiecare dintre coloanele de note şi medii ('nota.A' este coloana de rang 19 din "bac5", ş.a.m.d.), excluzând de fiecare dată (prin specificarea 'na.rm=TRUE') valorile 'NA' (pe fiecare coloană în parte, nu ca în cazul folosirii funcţiei aggregate(), mai sus).

Pe coloana 'Medie' avem aceleaşi valori ca în "judACDM", fiindcă în ambele cazuri s-au exclus exact aceleaşi valori 'NA'; dar pe celelalte coloane avem acum valori mai mici (fiindcă acum s-au considerat şi notele sub 5), iar 'Medie' nu mai este media aritmetică a coloanelor de note:

print(as.data.frame(judACDMw), digits=3)
               jud nota.A nota.C nota.D Medie
1             Alba   7.20   7.16   7.35  7.94
2             Arad   6.54   6.12   6.79  7.49
3            Argeş   6.60   6.77   7.04  7.65
4            Bacău   7.30   7.21   7.56  7.84
5            Bihor   6.33   6.61   7.32  7.61
6  Bistriţa-Năsăud   7.17   6.66   7.19  7.73
7         Botoşani   7.21   6.74   7.63  7.81
8           Braşov   7.46   7.31   7.48  7.84
9           Brăila   7.20   7.25   7.52  7.75
10           Buzău   6.97   6.65   7.14  7.73
11   Caraş-Severin   6.78   6.02   6.57  7.53
12            Cluj   7.43   7.47   7.59  7.89
13       Constanţa   6.28   6.14   6.49  7.63
14         Covasna   6.07   6.55   6.95  7.66
15       Dâmboviţa   6.87   6.05   6.80  7.76
16            Dolj   6.45   6.29   6.78  7.60
17          Galaţi   6.94   7.20   7.43  7.74
18            Gorj   6.64   5.76   6.19  7.68
19        Harghita   5.25   6.34   6.96  7.46
20       Hunedoara   7.03   6.51   7.05  7.68
21        Ialomiţa   6.83   6.64   7.14  7.63
22            Iaşi   7.32   7.32   7.46  7.95
23           Ilfov   5.84   5.13   6.05  7.02
24       Maramureş   6.67   6.76   7.16  7.68
25       Mehedinţi   6.44   5.63   6.32  7.75
26           Mureş   6.65   6.82   7.22  7.71
27           Neamţ   6.90   7.01   7.25  7.83
28             Olt   6.59   5.73   6.64  7.69
29         Prahova   7.29   7.15   7.29  7.90
30       Satu-Mare   7.06   6.99   7.25  7.71
31           Sălaj   6.60   6.33   6.78  7.72
32           Sibiu   7.27   7.22   7.42  7.83
33         Suceava   6.89   6.68   7.28  7.75
34       Teleorman   5.78   5.07   6.17  7.41
35           Timiş   7.07   6.44   6.95  7.72
36          Tulcea   7.20   6.74   7.17  7.81
37          Vaslui   6.65   6.83   7.26  7.64
38          Vâlcea   6.86   6.40   6.91  7.77
39         Vrancea   7.04   6.94   7.25  7.84
40     M.Bucureşti   7.00   6.81   7.04  7.77
41        Călăraşi   6.44   5.85   6.46  7.46
42         Giurgiu   5.51   5.04   5.83  7.26

Diferenţele coloanelor de acelaşi nume din cele două tabele produc indirect unele informaţii referitoare la notele mai mici ca 5, pentru judeţele respective; de exemplu:

range(judACDM$nota.A - judACDMw$nota.A)
[1] 0.3020285 1.4211119

range() ne dă valoarea minimă şi pe cea maximă din vectorul primit (în cazul de faţă, din vectorul diferenţă a celor două coloane); diferenţa minimă 0.30 corespunde judeţului Cluj şi ar însemna că dintre toate judeţele, în Cluj au fost cele mai puţine sau/şi cele mai mari note sub 5, la proba A; diferenţa maximă 1.42 corespunde judeţului Teleorman - deci aici au fost cele mai multe sau/şi cele mai mici note sub 5 la proba A, dintre toate judeţele.

9.8 Intervalele de note şi de medii, pe judeţe

Desigur, cel mai firesc ar fi să vizăm numai mediile finale, clasificându-le pe intervalele [5, 6), [6, 7), etc. (pe fiecare judeţ); totuşi, complicăm lucrurile - vizând şi coloanele de note (considerând pentru ele şi intervalul [1, 5), exclus la 'Medie'). Pentru un judeţ, putem găsi procentele notelor şi mediilor pe fiecare interval folosind funcţia "pergaps()" din §9.6 - de exemplu:

cluj <- subset(bac5, jud == "Cluj")
pergaps(cluj, materna = TRUE)
[[1]]  # procente, pe intervale de medii          [[2]]  # numărul de participanţi  
     gap nota.A nota.B nota.C nota.D Medie    nota.A nota.B nota.C nota.D Medie
1  [1,5)   3.34   0.73   5.98   7.31  0.00      4884    550   4881   4873  4287
2  [5,6)  15.60   9.82  15.63  11.68  5.97
3  [6,7)  15.38  13.27  13.91  12.64 18.36
4  [7,8)  20.76  23.64  17.78  17.24 24.45
5  [8,9)  26.88  24.55  20.67  23.27 31.26
6 [9,10]  18.04  28.00  26.02  27.87 19.97

Am ales aici judeţul Cluj, pentru că acolo s-a susţinut şi proba de "Limba maternă" (oferindu-ne prilejul de a folosi parametrul 'materna', în apelul funcţiei pergaps()).

Să încercăm să iterăm pergaps() pentru toate cele 42 de judeţe. Teama firească este aceea de a avea o durata excesivă a execuţiei; în plus, rezultatele de la fiecare iteraţie (pentru fiecare judeţ) având o aceeaşi structură - se cuvine să le reunim într-un unic obiect (care va servi şi pentru o eventuală sinteză grafică). Ţinând cont de aceste aspecte, rescriem vechea funcţie pergaps() - încorporând iterarea pe judeţe şi reunirea rezultatelor:

perJudGaps <- function() {
    require(dplyr)  # facilitează gruparea, selectarea coloanelor, ordonarea după judeţe
    cols <- c(19, 21, 22, 25, 26)  # coloanele de note (A, C, D), 'Medie' şi 'jud'
    sjdf <- bac5 %>% group_by(jud) %>% select(cols) %>% arrange(jud) %>% as.data.frame
    lsdf <- split(sjdf, sjdf$jud)  # listă cu 42 de 'data.frame' (câte una pe judeţ)
    breaks = c(1, 5:9, 10.01)  # limitele standard ale intervalelor de note
    nume <- c("A", "C", "D", "mb")  # denumiri simplificate faţă de 'names(bac5)[cols[-5]]'
    for(j in 1:42) {
        sdf <- lsdf[[j]]
        proc <- data.frame(gap = c("[1,5)","[5,6)","[6,7)","[7,8)","[8,9)","[9,10]"))
          # în "proc" vom adăuga coloane cu procentele pe probe şi pe intervalele "gap"
        for(k in 1:3) {  # fără coloana 'Medie' (unde nu avem intervalul [1, 5))
            lim <- cut(sdf[, k], breaks=breaks, right=FALSE)  # clasifică notele pe intervale
            proc[k+1] <- aggregate(sdf[, k] ~ lim, sdf, length)[2]  # contorizează pe intervale
        }
          # pentru coloana "Medie", exclude intervalul "[1, 5)"
        m5 <- aggregate(sdf[, 4] ~ cut(sdf[, 4], breaks=breaks[-1], right=FALSE), sdf, length)[2]
        proc[5] <- c(0, m5[[1]])  # pune 0 pentru procent elevi cu "Medie" < 5
        names(proc)[2:5] <- nume  # redenumeşte coloanele din 'proc'
        nelevi <- lapply(proc[2:5], sum)  # numărul total de elevi, pe fiecare coloană
        proc[2:5] <- round(proc[2:5]/nelevi, 4)*100  # procentul elevilor, pe interval de medii
        lsdf[[j]] <- proc  # înlocuim în lista iniţială
    }
    return(lsdf)  # pe fiecare judeţ - procentele pe intervalele de note şi medii
}

Funcţia "perJudGaps()" constituie şi returnează un obiect de clasă 'list' cu 42 de componente de clasă 'data.frame', fiecare conţinând procentele de note la probe şi de medii corespunzătoare câte unui interval de medii, pentru judeţul respectiv:

lpjg <- perJudGaps()
lpjg[1]  # prima componentă din listă
$Alba  # names(lpjg)[1] (numele elementelor listei - aici, numele judeţelor)
# "data.frame" pentru procentele de note la probe şi de medie finală, pe intervale de medii:
     gap     A     C     D    mb
1  [1,5)  6.87 12.60 12.57  0.00
2  [5,6) 19.39 16.53  9.97  6.09
3  [6,7) 16.23 11.78 13.35 18.63
4  [7,8) 16.30 14.82 15.68 23.30
5  [8,9) 22.49 17.62 21.28 27.77
6 [9,10] 18.71 26.64 27.15 24.21

Să transformăm această listă într-un 'data.frame' (cum cer de obicei funcţiile de reprezentare grafică), în care să mai adăugăm o coloană 'factor' pentru judeţele de care ţin înregistrările:

do.call("rbind", lpjg) -> pjg.df
pjg.df$jud <- as.factor( c( rep(names(lpjg), each = 6) ) )
str(pjg.df)
'data.frame':	252 obs. of  6 variables:
 $ gap : Factor w/ 6 levels "[1,5)","[5,6)",..: 1 2 3 4 5 6 1 2 3 4 ...
 $ A   : num  6.87 19.39 16.23 16.3 22.49 ...
 $ C   : num  12.6 16.5 11.8 14.8 17.6 ...
 $ D   : num  12.57 9.97 13.35 15.68 21.28 ...
 $ mb  : num  0 6.09 18.63 23.3 27.77 ...
 $ jud : Factor w/ 42 levels "Alba","Arad",..: 1 1 1 1 1 1 2 2 2 2 ...

Cu rbind() putem concatena mai multe obiecte data.frame; numai că în cazul de faţă, aceste obiecte sunt ambalate împreună într-un obiect 'list' - de aceea am folosit funcţia do.call(): aceasta primeşte numele unei funcţii (aici, "rbind") şi o listă de argumente (cum avem aici, lista "lpjg") şi pune în execuţie funcţia indicată, transferându-i componentele listei de argumente (adică tocmai obiectele 'data.frame' pe care voiam să le alipim).
Pentru adăugarea coloanei pjg.df$jud am folosit numele de componente existente în structura de listă "lpjg" şi - prin funcţia rep() - am repetat fiecare nume de câte 6 ori (câte corespund în "lpjg" fiecărui judeţ), transformând apoi vectorul rezultat într-un obiect de clasă factor.

Dar la ce folosesc datele obţinute mai sus? Cel mai adesea, servesc pentru comparaţii ("a... stăm bine - uite că sunt şi judeţe mai slabe ca noi!", etc.) - iar compararea după diverse criterii este mult facilitată prin vizualizarea grafică a datelor (cu minimum de detalii). Vom folosi mai jos câteva funcţii din pachetul ggplot2 (vezi eventual şi [2]).

Avem aici de reprezentat valorile din mai multe coloane de numere, pe intervale de medii, pentru fiecare judeţ; în special pentru acest caz (când sunt de reprezentat mai mult de o singură coloană de valori), funcţia ggplot2::ggplot() pretinde ca structura 'data.frame' să-i fie transmisă în format "lung" - în care fiecare înregistrare are forma generică "cheie - variabilă - valoare", unde "cheie - variabilă" o identifică unic în setul de date respectiv, iar "valoare" este valoarea acelei variabile pentru linia corespunzătoare câmpurilor constitutive ale cheii.

De exemplu în cazul nostru, "Alba [1, 5) A" identifică unic valoarea 6.87 din tabelul cu 5 coloane lpjg[[1]] redat mai sus (avem "cheie formată de coloanele 'jud' şi 'gap' şi "variabilă='A').

Pentru a transforma structura 'pjg.df' în format lung, folosim funcţia reshape2::melt():

require(reshape2)
pjg.long <- melt(pjg.df)  # implicit, 'jud' şi 'gap' sunt utilizate ca "ID" (identifică)
head(pjg.long)
   jud    gap variable value
1 Alba  [1,5)        A  6.87
2 Alba  [5,6)        A 19.39
# ş.a.m.d.

După ce receptează setul de date ('pjg.long' în cazul de faţă), ggplot() operează stratificat: mai întâi, vede din parametrii funcţiei aes() - numele vine de la cuvântul "aesthetic" - cărei variabile trebuie să-i reprezinte valorile (numerice) şi deasemenea, cărei variabile (de regulă, de clasă "factor") trebuie să-i reprezinte valorile prin culori diferite, sau prin simboluri şi mărimi diferite; apoi (pe următorul nivel de operare, montat celui precedent prin operatorul "+") se stabileşte "geometria" reprezentării (prin bare, sau ca histogramă, prin linii, puncte, etc.) şi eventual, se calculează anumite statistici de reprezentat (după valorile indicate în parametrul "stat" al funcţiei geom_bar()); pe următoarele straturi se stabilesc "detalii" precum titluri şi inscripţionări, ce paletă de culori să fie folosită, etc., iar dacă este cazul se constituie mai multe panouri grafice (printr-o funcţie ca facet_wrap()) şi eventual, se "răstoarnă" axele de coordonate (prin coord_flip()):

require(ggplot2)
require(RColorBrewer)  # "Creates nice looking color palettes" - help(RColorBrewer)
ggplot(pjg.long, aes(variable, value, fill = gap)) +
    geom_bar(stat = "identity") + 
    ggtitle("Bacalaureat 2015") + 
    xlab("Probele A, C, D şi media finală") + ylab("Procente") +
    scale_fill_brewer(palette = "Set1") +
    facet_wrap(~ jud, nrow = 7) +  # câte un panou pentru fiecare judeţ
    coord_flip()

Vedem că procentul de note [1, 5) - culoarea roşie - la proba "A" este cel mai mic la Braşov, Cluj şi Sibiu (cam în această ordine) şi este cel mai mare - de multe ori mai mare - la Harghita, Giurgiu şi Teleorman; la proba "C" este cel mai mic la Cluj, Braşov şi Brăila şi este cel mai mare la Giurgiu, Teleorman şi Ilfov; iar la proba "D" culoarea roşie ocupă cel mai puţin la Brăila, Cluj şi Bacău şi cel mai mult la Giurgiu, Gorj şi Mehedinţi.

Observând culorile respective pe rândurile şi coloanele imaginii de mai sus, putem face uşor diverse alte constatări asupra procentelor pe fiecare interval de note şi de medii. Sare în ochi de exemplu, că la Ilfov şi Giurgiu culoarea galbenă ocupă cel mai puţin - adică în acestea avem cel mai mic procent de note (la cele trei probe) şi de medii în intervalul [9, 10] (cel mai mare fiind la Iaşi, Alba, Braşov, Cluj).

vezi Cărţile mele (de programare)

docerpro | Prev | Next