[1] Problema orarului școlar echilibrat și limbajul R
[2] V. Bazon - Orare școlare echilibrate și limbajul R https://books.google.ro/books?id=aWrDEAAAQBAJ
Intenționam să salvăm setul CDL
abia în final, după toate simplificările (v. Partea I)…
Dar disciplinele sunt mai încâlcite decât credeam inițial (vom vedea că sunt de făcut nu doar „simplificări”) – așa că redenumim programul precedent "adjust_cls.R
", salvăm CDL
(în urma simplificării denumirii claselor) în fișierul CDL.rds
și înființăm un nou program, în care ne vom ocupa de discipline:
# adjust_obj.R rm(list = ls()) # elimină obiectele din sesiunea precedentă library(tidyverse) CDL <- readRDS("CDL.rds")
Este important, cum denumim disciplinele în cadrul setului de date aferent orarului școlii. Dar nu doar fiindcă denumirile prea lungi (ca aceea oficială "Limba și literatura română") nu încap în orar (chiar dacă-l prezentăm în format "landscape"); trebuie să avem posibilitatea de a face diverse statistici, pe datele respective – deci în primul rând, n-ar trebui să avem mai multe denumiri pentru o aceeași materie.
Avem un număr rezonabil, 35 de discipline:
> CDL$obj %>% unique() %>% sort() [1] "BIOLOGIE" "CHIMIE" [3] "CULTURA CIVICA" "DESEN" [5] "DIRIGENTIE" "ECONOMIE" [7] "EDUCATIE ANTREPRENORIALA" "EDUCATIE TEHNOLOGICA" [9] "FILOSOFIE" "FIZICA" [11] "GEOGRAFIE" "INFORMATICA" [13] "ISTORIA HOLOCAUST" "ISTORIA RELIGIILOR" [15] "ISTORIE" "LAB INFORMATICA" [17] "LATINA" "LI" [19] "LIMBA ENGLEZA" "LIMBA FRANCEZA" [21] "LIMBA GERMANA" "LIMBA ROMANA" [23] "LITERATURA UNIVERSALA" "LOGICA" [25] "M" "MATEMATICA" [27] "MATEMATICA U" "MATEMATIICA" [29] "MUZICA" "PSHILOGIE" [31] "RELIGIE" "SOCIOLOGIE" [33] "SPORT" "STUDII SOCIALE" [35] "TIC"
"Matematică" are în cazul de aici, trei sau patru denumiri (între care și una stâlcită: "MATEMATIICA
"); filtrând CDL
pentru disciplina "M
" (indicată la indexul 25):
> CDL %>% filter(obj=="M") prof obj zi ora cls 1 NA M Mi 3 8B 2 NA M Mi 4 8B
vedem că "M
" se face la o singură clasă 8B
, pe două ore, cu un profesor „necunoscut” NA
. Filtrând CDL
pentru clasa 8B
și apoi, pentru comparație, pentru clasa 8A
– constatăm diferențe numai între numărul de ore pe "MATEMATICA
" (2 ore, cu "NEMES ADRIAN
" – versus 5 ore) și respectiv, LIMBA ROMANA
(5 ore, versus 4 ore); de aceea, înclinăm să credem că "M
" care apare numai la clasa 8B
, reprezintă neapărat "MATEMATICA
" (altfel, ar însemna că „examenul de evaluare națională” pe care îl au de susținut absolvenții claselor a 8-a, disociază între cei care fac 2 ore și respectiv 4 sau 5 ore de „Matematică” – ceea ce nu este adevărat).
Prin următoarea secvență de comenzi înlocuim denumirile de la indecșii 25:28 cu "MATEMATICA
" și zicem că "NA
" vine de la "NEMES ADRIAN
":
> wh <- which(with(CDL, obj %in% c("MATEMATIICA", "MATEMATICA U", "M")) == TRUE) > CDL[wh, "obj"] <- "MATEMATICA" > CDL[which(with(CDL, prof=="NA") == TRUE), "prof"] <- "NEMES ADRIAN"
Procedând analog cazului "M
", constatăm că "LI
" (de la indexul 18) este o altă denumire pentru "LAB INFORMATICA
" – și operăm modificarea necesară în CDL
.
Am rămas cu 31 de materii distincte (acceptând că "INFORMATICA" și "LAB INFORMATICA" desemnează și ele, materii „distincte”) și acum ne putem gândi la simplificarea denumirilor: le vom prescurta (dar sugestiv) și vom abandona stilul „cu majuscule”; dar… mai mult, le vom și codifica prin câte două litere – intenționând să desemnăm profesorii după disciplina principală a fiecăruia (și după numărul de ore).
Mai întâi, factorizăm coloana obj
, afișăm și înscriem noile denumiri în vectorul levels()
asociat factorului respectiv (totodată, le abreviem convenabil):
> CDL <- CDL %>% mutate(obj = factor(obj)) > lev <- str_to_title(levels(CDL$obj)) # "Cultura Civica" pentru "CULTURA CIVICA", etc. > abb <- abbreviate(lev, minlength=2, strict=TRUE) > lev <- c("Biologie", "Chimie", "cultCivică", "Desen", "Dirig", # ... # "Religie", "Sociologie", "Sport", "stdSociale", "TIC") > levels(CDL$obj) <- lev
"TIC
" este un acronim încetățenit, așa că am păstrat scrierea cu majuscule.
Am preferat să abreviem imediat după transformarea prin str_to_title()
, mai „apropiat” de numele inițiale (alte abrevieri obțineam, dacă foloseam abbreviate()
abia în final).
Acum transformăm vectorul lev
într-un „dicționar”, având ca nume codurile de câte două caractere din vectorul abb
și ca valori, denumirile existente în lev
:
> names(lev) <- as.vector(abb) # dicționarul disciplinelor: Bl Ch CC Ds Dr "Biologie" "Chimie" "cultCivică" "Desen" "Dirig" Ec EA ET Fl Fz "Economie" "edAntrep" "edTehnol" "Filosofie" "Fizica" Gg In IH IR Is "Geografie" "Informatică" "istHolocaust" "istReligiilor" "Istorie" LI Lt LE LF LG "labInform" "Latină" "Engleză" "Franceză" "Germană" LR LU Lg Mt Mz "Românâ" "litUnivers" "Logică" "Matematică" "Muzică" Ps Rl Sc Sp SS "Pshilogie" "Religie" "Sociologie" "Sport" "stdSociale" Tc "TIC" > saveRDS(lev, "dict_obj.RDS")
Intern, vom folosi codurile de câte două caractere (și nu coloana obj
!); dar când va fi să afișăm orarul unei clase, va trebui să folosim denumirile asociate codurilor respective – așa că am salvat dicționarul respectiv, în fișierul dict_obj.RDS
.
Obs. Comenzile din secvențele redate mai sus au fost preluate din consolă (fiind precedate de promptul acesteia '>
'); le puteam înscrie ca atare în programul înființat la început adjust_obj.R
– sau mai bine (ca în cazul programului adjust_cls.R
), într-o funcție pe care să o încorporăm acestuia. Am preferat totuși, să tastăm întâi direct în consolă, fiecare dintre comenzile respective – fiindcă astfel putem controla mai ușor lucrurile…
De exemplu: am avut nevoie de o funcție de transformare a numelor scrise cu majuscule; știm că există asemenea funcții (și nu numai în limbajul R), dar (bineînțeles) nu le știm „pe de-a rostul” – știm doar că numele lor începe de obicei cu "str_
"; așa că, lucrând în consolă, am tastat "? str_
" și am apăsat tasta tab – obținând pe ecran lista tuturor funcțiilor care încep astfel (și a fost ușor să depistăm funcția care ne trebuia, str_to_title()
).
Sau, pentru încă un exemplu: pentru funcția abbreviate()
a fost necesar (neștiind-o „pe de-a rostul”) să ne amintim (prin ?
) ce parametri putem folosi.
(A nu învăța pe de rost este un principiu de lucru esențial, pentru a putea deprinde și a putea folosi după caz, mai multe limbaje de programare)
Pe de altă parte – nu prea este cazul de a ambala într-o funcție independentă comenzile respective, dat fiind că modificările vizate sunt particulare setului CDL
(pentru o altă școală fiind de făcut alte modificări, decât cele de mai sus).
Pentru orice eventualitate, salvăm iarăși în CDL.rds
, setul CDL
rezultat mai sus prin „simplificarea” disciplinelor.
„Simplificăm” și numele profesorilor – dar nu (doar) pentru a evita vreo dispută legată de „drepturile personale”… Profesorii sunt încadrați fiecare pe câte o anumită disciplină (plus eventual, câteva ore pe unele discipline „secundare”) – de unde ideea firească de a le asocia câte un cod care ambalează disciplina principală și (cumva) numărul de ore; vizăm astfel și eficiența programelor (vom putea elimina coloana obj
).
Dar vom ambala în codurile asociate profesorilor nu denumirile explicite ale disciplinelor, ci abrevierile la câte două caractere ale acestora:
levels(CDL$obj) <- names(readRDS("dict_obj.RDS"))
În cazul de față, întâi trebuie să verificăm dacă profesorii sunt și bine definiți: mai toți au câte două prenume (separate prin '-
') și uneori este menționat numai unul, alteori – ambele prenume. Dar nu facem caz de asta: listăm profesorii (și cuplajele) în ordine alfabetică și reparăm ad-hoc cele două sau trei asemenea situații:
> CDL %>% pull(prof) %>% unique() %>% sort() # ... # [65] "STANGA MIRELA/RUSET OVIDIU" "STANGA MIRELA/TEROVAN DIANA" # cuplaje [67] "STANGA VIORICA-MIRELA" # ... # > CDL$prof[CDL$prof=="STANGA VIORICA-MIRELA"] <- "STANGA MIRELA"
Pentru cuplaje întâlnim o situație similară celeia de mai sus: uneori apare "Prof1/Prof2/Prof3", alteori apare "Prof2/Prof1/Prof3"; unificăm cele două denumiri distincte:
> CDL$prof[CDL$prof=="INASEL LAURA/GOSA SIMONA/RITIVOIU ADA"] <- "GOSA SIMONA/INASEL LAURA/RITIVOIU ADA"
Desigur, după aceste corecturi pe CDL
, salvăm iarăși în CDL.rds
.
Să despărțim lecțiile obișnuite („cu întreaga clasă”), de cele care decurg „pe grupe” (pentru care în câmpul prof
apare ’/’) și să evidențiem profesorii în fiecare caz:
L12 <- CDL %>% split(grepl("/", .$prof)) P1 <- L12[[1]] %>% pull(prof) %>% unique() %>% sort() # cei care au și ore proprii ("cu clasa întreagă") P2 <- L12[[2]] %>% pull(prof) %>% unique() %>% strsplit(., "/") %>% unlist() %>% unique() %>% sort() # profesorii angajați în cuplaje ("pe grupe")
setdiff(P2, P1)
afișează profesorii care sunt angajați în cuplaje, dar nu au și ore proprii (îi numim „externi”); s-ar fi afișat și "STANGA MIRELA" de exemplu, dacă n-am fi corectat mai sus… În cazul nostru, nu există profesori externi.
Următoarea funcție produce disciplinele pe care este încadrat un profesor (din setul P1
, sau din P2
), în ordinea descrescătoare a numărului de ore:
prof_objs <- function(P, lss) lss %>% filter(grepl(P, prof)) %>% count(obj, sort=TRUE)
Subliniem că prof_objs()
păstrează calitatea de factor a câmpului $obj
și implicit, toate nivelele acestuia; dacă un profesor este încadrat pe mai multe discipline, ne va interesa acum numai cea „principală” (pe care are cel mai multe ore) și va trebui să folosim droplevels()
, pentru a păstra numai nivelele corespunzătoare disciplinelor principale.
Putem obține acum o listă care asociază fiecărei discipline principale, setul profesorilor încadrați pe acea disciplină, ordonați descrescător după numărul de ore:
Pob <- map_dfr(P1, function(P) { OB <- prof_objs(P, L12[[1]])[1, ] if(P %in% P2) { # decide asupra disciplinei principale OB2 <- prof_objs(P, L12[[2]])[1, ] if(OB2$n > OB$n) OB <- OB2 } data.frame(prof = P, obj = OB$obj, no = OB$n) }) %>% droplevels() %>% # ignoră disciplinele secundare arrange(desc(no)) %>% split(.$obj)
Și în sfârșit, putem formula un „tabel” care asociază numelor din P1 coduri de câte 3 caractere, formate din codul disciplinei principale și un sufix care indică numărul de ordine după numărul descrescător al orelor fiecăruia:
Pcd <- map_dfr(seq_along(Pob), function(i) { N <- nrow(Pob[[i]]) # numărul de profesori pe disciplina curentă (< 10) ob <- Pob[[i]]$obj[1] obn <- paste0(ob, 1:N) data.frame(prof = Pob[[i]]$prof, cod = obn) })
Dar bineînțeles că în loc de „tabel”, va fi mai convenabil să lucrăm cu dicționare:
prof_cod <- Pcd$cod names(prof_cod) <- Pcd$prof CPR <- setNames(names(prof_cod), prof_cod) # dicționar nume_profesor -> COD
Folosind dicționarul prof_cod
, putem codifica acum și cuplajele – alipind codurile celor doi sau trei membri (iar în final, reunim toate codurile):
tws <- L12[[2]] %>% pull(prof) %>% unique() %>% sort() # cuplajele de profesori (Prof1/Prof2 sau Prof1/Prof2/Prof3) cup_cod <- vector("character", length(tws)) for(i in seq_along(tws)) { tw <- strsplit(tws[i], "/")[[1]] # "/" leagă numele din cuplaj kod <- as.vector(prof_cod[tw]) # codurile membrilor cup_cod[i] <- ifelse(length(kod)==2, paste0(kod[1], kod[2]), # alipește codurile membrilor paste0(kod[1], kod[2], kod[3])) } cup_cod <- setNames(cup_cod, tws) prof_cod <- c(prof_cod, cup_cod) # dicționar profesor sau cuplaj -> COD saveRDS(prof_cod, "dict_prof.RDS") # util când va fi de afișat orarul final
Dicționarul prof_cod
va fi util — dacă-l vom inversa — când va fi să afișăm orarul final.
Înregistrăm codurile din vectorul prof_cod
, pe toate liniile din setul CDL:
CDL <- CDL %>% mutate(prof = as.vector(prof_cod[prof])) > slice_sample(CDL, n=5) # un eșantion ilustrativ prof obj zi ora cls 1 LG4 LE Ma 6 5A 2 LR7 LR Jo 6 12D 3 Tc1 Tc Vi 6 9D 4 LG3LG1LG4 LG Vi 3 12A 5 Gg1 Gg Ma 3 10B
Fiecare lecție este reprezentată pe câte o linie din setul CDL; pe linia respectivă, codul din câmpul prof
induce pe cel din câmpul obj
– dar numai în cazul disciplinelor principale; comparând pe fiecare linie, valorile prof
și obj
, vom putea depista liniile corespunzătoare disciplinelor secundare.
În mod implicit, operațiile pe un obiect data.frame decurg „pe coloane”; dar funcția dplyr::rowwise()
asigură și posibilitatea de a opera „pe linii”, cum am avea nevoie acum:
scd <- CDL %>% rowwise(prof, obj, cls) %>% filter(! grepl(obj, prof)) # liniile cu discipline "secundare" lSC <- scd %>% split(.$prof) OSE <- map_dfr(seq_along(lSC), function(i) { sec <- lSC[[i]] %>% # pe profesorul curent split(.$obj, drop=TRUE) # desparte pe discipline map_dfr(seq_along(sec), function(j) { Cls <- sec[[j]] %>% pull(cls) data.frame( prof = sec[[j]]$prof[1], # profesorul obj = names(sec)[j], # disciplina secundară cls = paste(Cls, collapse = " ")) # la care clase }) })
Obs. În [1] – de unde am cam copiat mai sus, text și secvențe de cod – procedam greșit (totuși fără consecințe, acolo): în [1] aveam sec <- lSC[[i]]
(fără despărțire pe discipline, ca aici) și foloseam Obj <- sec$obj[j]
, unde j
era indexul uneia dintre disciplinele secundare ale profesorului respectiv; numai că dacă pentru j=1
de exemplu, există mai multe clase, atunci următorul j
(=2) va indica nu linia următoarei discipline secundare, ci linia celei de-a doua dintre clasele de la j
-ul precedent!
Să facem și o verificare, pentru unul dintre cei încadrați și pe discipline secundare:
> OSE %>% filter(prof == "CC1") prof obj cls 1 CC1 Dr 5A 2 CC1 Ec 11A 11B 11C 11E 11E 11F 3 CC1 EA 10B 10E 4 CC1 Sc 11E 11E 11F > dobj <- readRDS("dict_obj.RDS") > CDL %>% filter(prof=="CC1") %>% count(obj) %>% mutate(Disciplina = dobj[obj]) obj n Disciplina 1 CC 8 cultCivică # disciplina principală 2 Dr 1 Dirig 3 Ec 6 Economie 4 EA 2 edAntrep 5 Sc 3 Sociologie
Când va fi de afișat orarul vreunei clase, va trebui să consultăm și OSE:
saveRDS(OSE, "df_obj_sec.RDS")
pentru a stabili ce disciplină afișăm pe lecția curentă a unuia sau altuia dintre profesori.
În final, salvăm iarăși setul CDL
(în care acum, profesorii sunt codificați prin câte 3 caractere, sau în cazul cuplajelor, prin 6 sau prin 9 caractere) în CDL.rds
.
Să ne dumirim, cât de cât deocamdată, asupra cuplajelor de trei profesori:
> CDL %>% filter(prof %in% "LG3LG1LG4") %>% arrange(zi) prof obj zi ora cls 1 LG3LG1LG4 LG Jo 1 11A # dobj["LG"]: "Germană" 2 LG3LG1LG4 LG Jo 1 11B 3 LG3LG1LG4 LG Jo 1 11D 4 LG3LG1LG4 LG Mi 5 12A 5 LG3LG1LG4 LG Mi 5 12B 6 LG3LG1LG4 LG Mi 5 12D 7 LG3LG1LG4 LG Vi 2 11A 8 LG3LG1LG4 LG Vi 2 11B 9 LG3LG1LG4 LG Vi 2 11D 10 LG3LG1LG4 LG Vi 3 12A 11 LG3LG1LG4 LG Vi 3 12B 12 LG3LG1LG4 LG Vi 3 12D
Elevii din clasele 12A
, 12B
și 12D
sunt repartizați (în ora a 5-a din ziua Mi
și apoi, în ora a 3-a din ziua Vi
) în trei grupe (poate „începători”, „avansați” și „cultură”), la care intră (pentru câte o lecție de "Germană") respectiv profesorii LG3
, LG1
și LG4
. La fel, pentru clasele 11A
, 11B
și 11D
, în zilele Jo
(ora 1) și Vi
(ora 2).
—v. Partea a III-a—
vezi Cărţile mele (de programare)