[2] V.Bazon - De la seturi de date și limbajul R, la orare școlare (Google Books)
[3] V. Bazon - Orare școlare echilibrate și limbajul R (Google Books)
Pănă acum (v. "partea întâia") am folosit consecvent forma normală a lecțiilor: fiecare lecție este reprezentată pe câte o linie de date, care conține (într-o aceeași ordine) valori ale unor variabile independente (nu se amestecă într-o aceeași celulă, valori de natură diferită). Orice investigație dorim, va decurge folosind operații de filtrare (pentru linii de valori) și de selectare (de variabile, sau coloane), iar pentru facilitare, putem transforma o coloană sau alta în factor (ceea ce asigură și operații de grupare și „splitare” după diverse criterii) — în [1] avem deja numeroase exemplificări.
Forma normală stă la baza unui algoritm elementar care promite obținerea de repartizări „echilibrate” (pe zile, poate și pe orele zilei) a unui set de lecții prof|cls
(doar "promite": repartizarea produsă nu este neapărat, acceptabilă și pot fi necesare diverse retușări).
Pentru exemplu, să considerăm lecțiile de "Română"; să vedem întâi cum sunt distribuite acestea pe zile, în orarul lăsat de [1]:
library(tidyverse) orr8 <- readRDS("orar_8.RDS") %>% # 826 lecții cls|prof|zi|ora mutate(prof = factor(prof)) Zile <- orr8$zi %>% unique() %>% as.factor()
Din capul locului, am transformat prof
în factor; pe lângă posibilitățile de grupare și splitare care apar astfel, va fi ușor (folosind droplevels()
) să „excludem” pe cei care nu satisfac un anumit criteriu (nu au lecții de "Ro
"; nu au lecții în ziua curentă; etc.).
Selectăm lecțiile de "Română" și vedem distribuția pe zile a acestora:
> pRo <- orr8 %>% filter(grepl("Ro", prof)) %>% droplevels() > table(pRo[c("prof", "zi")]) zi prof Lu Ma Mi Jo Vi Ro1 3 6 6 7 0 Ro2 4 3 6 5 0 Ro3 3 4 2 5 4 Ro4 0 6 5 5 5 Ro5 4 4 5 3 0 Ro6 3 0 0 3 0
Subliniem că fără droplevels()
, comanda table()
ar fi produs și liniile (cu valori zero peste tot) aferente profesorilor care nu au "Română" ca obiect principal.
Exceptând poate pe Ro6
(fiindcă are numai 6 ore), toți acești profesori au în orarul curent, distribuții neuniforme de lecții pe zile; iar distribuția zilnică a celor 101 ore de Ro
este extrem de neomogenă: Jo
sunt 28 ore, iar Vi
sunt numai 9 ore. Înghesuind astfel lecțiile, se ajunge aproape inevitabil în situația în care clasele fac câte două ore/zi un același obiect.
Nu este chiar obligatoriu, dar noi urmărim consecvent principiul care pare a fi cel mai favorabil pentru procesul de învățare: distribuim lecțiile în mod echilibrat pe zile, clase și profesori; la fiecare clasă, lecțiile pe un același obiect sunt repartizate câte una pe zi; toate clasele își încep programul zilnic la o aceeași oră a zilei.
Aranjăm liniile din pRo
după prof
și cls
— adică, lecțiile fiecărui profesor sunt pe linii consecutive, iar dintre acestea, cele de la o aceeași clasă sunt și ele, pe linii consecutive; apoi, (re)etichetăm toate lecțiile, de sus în jos, plasând succesiv în coloana zi
vectorul Zile
(sau dacă vrem, o permutare fixată a acestuia):
> Dlz <- pRo %>% select(prof, cls) %>% arrange(prof) %>% mutate(zi = rep_len(Zile, nrow(.))) > Dlz # distribuția pe zile rezultată (pentru cele 101 lecții de "Ro") prof cls zi prof cls zi prof cls zi 1 Ro1 10B Lu 11 Ro1 11E Lu 21 Ro1 7A Lu 2 Ro1 10B Ma 12 Ro1 11E Ma 22 Ro1 7A Ma 3 Ro1 10B Mi 13 Ro1 6A Mi 23 Ro2 10E Mi 4 Ro1 11B Jo 14 Ro1 6A Jo 24 Ro2 10E Jo 5 Ro1 11B Vi 15 Ro1 6A Vi ... ... 6 Ro1 11B Lu 16 Ro1 6A Lu 97 Ro6 12B Ma 7 Ro1 11D Ma 17 Ro1 7A Ma 98 Ro6 12B Mi 8 Ro1 11D Mi 18 Ro1 7A Mi 99 Ro6 12B Jo 9 Ro1 11D Jo 19 Ro1 7A Jo 100 Ro6 12B Vi 10 Ro1 11E Vi 20 Ro1 7A Vi 101 Ro6 9F Lu
Procedând astfel, lecțiile unui profesor la o aceeași clasă cad în zile diferite (exceptând desigur, cazul când are mai mult de 5 ore la acea clasă), iar distribuțiile orare ale profesorilor respectivi sunt toate, uniforme; deasemenea, distribuția pe zile a celor 101 lecții de Ro
este și ea, uniformă (cu 20 sau 21 ore/zi):
> addmargins(table(Dlz[c("prof", "zi")])) zi prof Lu Ma Mi Jo Vi Sum Ro1 5 5 4 4 4 22 Ro2 3 3 4 4 4 18 Ro3 4 4 4 3 3 18 Ro4 4 4 4 5 4 21 Ro5 3 3 3 3 4 16 Ro6 2 1 1 1 1 6 Sum 21 20 20 20 20 101
Înlocuind vectorul Zile
cu o permutare oarecare a sa și repetând etichetarea în coloana zi
, obținem alte repartizări pe zile, care păstrează caracterul de uniformitate.
Câte lecții sunt în orarul curent orr8
, pe fiecare zi în parte ? Formulăm întâi o listă care asociază fiecărei zile, subsetul lecțiilor pe acea zi:
Lz <- map(Zile, function(z) orr8 %>% filter(zi == z) %>% select(-c(zi)) # excludem câmpul 'zi' ) %>% setNames(Zile)
Aplicând nrow()
pe fiecare subset, avem distribuția numărului de lecții pe fiecare zi (dar fără lecțiile cuplate pe "viz/muz", pentru care făcusem în [1] un "orar" separat):
map_int(Zile, function(z) nrow(Lz[[z]])) %>% setNames(Zile) Lu Ma Mi Jo Vi 169 163 176 161 157
Cum era de așteptat (având în vedere „înghesuirea” evidențiată mai sus), avem o distribuție neomogenă; o distribuție uniformă (sau "omogenă") a celor 826 de lecții ar fi avut 4 zile cu câte 165 și una cu 166 lecții.
Desigur, subseturile respective sunt și ele în "formă normală"; de exemplu, Lz[["Lu"]]
este coșcogeamite listă, un tabel cu 169 linii cls|prof|ora
… Folosind direct această formă, nu-i ușor să formulezi o secvență de comenzi prin care să găsești câte ferestre are fiecare profesor, în ziua respectivă.
Pentru calculul numărului de ferestre ne-ar conveni mai mult un format matriceal, reprezentând pe fiecare linie orarul câte unui profesor; am avea numai atâtea linii câți profesori au ore în acea zi și pe de altă parte, văzând șabloanele orare de pe linii putem determina (nu chiar „ușor”, trebuind să ținem seama de cuplaje) numărul de ferestre din ziua respectivă.
Dacă în K
am avea lecțiile dintr-o aceeași zi ale unuia dintre profesori, atunci prin pivot_wider(K,...)
am ajunge la un format matriceal: valorile 1
:8
din coloana ora
ar deveni coloanele „matricei”, iar valorile din câmpul cls
ar fi plasate corespunzător pe aceste coloane (folosind constanta NA
, când în ora respectivă profesorul este liber).
Următoarea funcție produce matricea orară a lecțiilor unei zile (din lista Lz
):
hourly_matrix <- function(Dorar) { # prof|cls|ora (pe ziua curentă) orz <- Dorar %>% droplevels() %>% split(.$prof) %>% map_df(., function(K) pivot_wider(K, names_from="ora", values_from="cls")) M <- orz[, c('prof', sort(colnames(orz)[-1]))] %>% replace(is.na(.), '-') %>% as.matrix() row.names(M) <- M[, 1] # profesorii devin numele de linie ale matricei M[, 2:ncol(M)] } # prof: 1|2|3|4|5|6|7 (linia claselor alocate pe ore)
De exemplu, pentru prima zi, transformând matricea pentru a afișa datele într-un tabel cu două coloane (ordonăm liniile în ordinea alfabetică a numelor de linie, adică în ordinea alfabetică a profesorilor; apoi integrăm numele de linie drept coloană, și constituim un "tabel" cu trei coloane, prima pentru prima jumătate a liniilor matricei, a doua drept separator de coloane și a treia, pentru a doua jumătate a liniilor matricei):
Mz <- hourly_matrix(Lz[["Lu"]]) # matrice [1:45, 1:7] (45 profesori) Mz <- Mz[order(rownames(Mz)), ] # liniile în ordinea alfabetică a profesorilor Mz <- cbind(rownames(Mz), Mz) # copiază numele de linie în coloana V1 data.frame(Mz[1:23, 2:8], rep(" ", 23), rbind(Mz[24:45, ], " "), check.names = FALSE, fix.empty.names = FALSE) %>% print() 1 2 3 4 5 6 7 V1 1 2 3 4 5 6 7 Bl2 10A 5A 6A 6A 9F 7A 9E In2 - - - - 10B 8A 9D Bl3 - 12E 10C 10B 10D 12E - In4 11C 12C 7A 11C 7A 9E - Ch1 7A 9A 8A - 12E 10E 8A Is1 - - - 10F 5A 10C - Ch2 9D 12D - 11E 12C 11D 11A Is2 - - - 9B 8A - - Ec1 10F 11F 12F - 11D 11F 10C Lg1 12A 9D 9E 12B 12F - - En1 - - - 9E 10C 9B 11E Mt1 12D - 12B 10A 11A 12A - En2 - - 11B - 12D 12B - Mt2 12C 10C 10E 12C - - - En3 11F - - 5A - - - Mt3 6A 7A 5A 8A - - - En4En2 - 10A - 12A - - - Mt4 10B 11D 11C 12E - - - En4En3 - - 9A - 9A 9A - Mt5 - 9B 9D 9D - - - En5 - - 9F - - - - Mt6 - 10D 10D - - - - En5En1 11A 11A - - - - - Mt7 11B 11B - - - - - eS1 5A 6A - - 10A 10B - Rl2 9A 11E 11F - 11C 11A 11D Fr1 9E 9E - - - - - Ro1 - - 10B 11B - 6A - Fz1 11D 11C 11E - 9E 12C 12D Ro2 - - - 9F 11F 10F 10E Fz3 10C 9F 10A 10E 10F 10D 10A Ro3 9B 12F - 9A - - - Fz4 12E 10B - - - - - Ro5 - - 12D 10C 12A 9D - Fz5 12B - 9B - - - - Ro6 9F 12B 10F - - - - Gg1 12F 8A 12C 12F 9B - - Sp1 - - 12E - - 11E 7A Gg3 - - - 12D 11E - - Sp2 10E 10F 12A 10D - - - Gr2Gr1 - - - 11D 9D 12D 10D Sp3 - - - - 12B 9F 9B Gr3 - - 11A 7A 6A 10A 9A TI1 - 10E 11D 11F 10E - - In1 - 12A - 11A 11B - -
Pentru primele 23 de linii numele de linie sunt cele din rowname(Mz)[1:23]
și am ignorat copia acestora din coloana "V1
" (reținând numai coloanele 2:8
); pentru celelalte linii (completate cu o linie "vidă", pentru a avea tot 23 de linii), numele profesorilor sunt cele din coloana "V1
" (care corespund acestor linii).
Pe matricea orară a zilei este ușor de văzut ferestrele; de exemplu (pe matricea redată mai sus) Bl2
și Bl3
nu au ferestre, iar Ch1
are o singură fereastră, în ora a 4-a. Nu este greu nici de numărat totalul de ferestre din ziua respectivă — trebuie contorizate aparițiile caracterului "-
" între lecții; doar că în cazul celor angajați în cuplaje, o asemenea apariție nu constituie neapărat, o fereastră…
Să vedem orarul celor angajați în cuplajele pe "En
", existente în ziua Lu
:
> print.table(Mz[grepl('En', rownames(Mz)), 2:8]) 1 2 3 4 5 6 7 En1 - - - 9E 10C 9B 11E En2 - - 11B - 12D 12B - En3 11F - - 5A - - - En4En2 - 10A - 12A - - - En4En3 - - 9A - 9A 9A - En5 - - 9F - - - - En5En1 11A 11A - - - - -
Pe linia lui En1
nu vedem vreo fereastră — și totuși, En1
are o fereastră („ascunsă”) în ora 3, dat fiind că în orele 1 și 2 are de intrat împreună cu En5
la clasa 11A
.
Pe linia lui En2
vedem o fereastră în ora 4 — dar este o fereastră „falsă”, dat fiind că în ora 4 are de intrat în cuplaj cu En4
la clasa 12A
.
En3
are o singură fereastră, în ora 2 (cea din ora 3 este acoperită de cuplajul cu En4
); En4
și En5
au zero ferestre.
În total, pentru ziua Lu
avem 16 ferestre (adică aproape 9.5% din totalul 169 de lecții).
De observat că Gr2
și Gr1
apar numai în cuplaj (nu și singuri, cu ore proprii); altfel spus, aceștia sunt „externi” pentru ziua Lu
.
vezi Cărţile mele (de programare)