momente şi schiţe de informatică şi matematică
anti point—and—click

Repartizarea pe zile a încadrării profesorilor (IV)

R | orar şcolar
2021 jan

Transformarea orarului în „set de date”

În [1] am transformat documentul PDF care prezenta orarul unei şcoli (unul bine făcut), într-un obiect R de clasă "tibble"; redăm primele rânduri dintre cele 300 şi o parte dintre cele 14 coloane (primele cinci şi ultimele două):

> (orar <- readRDS("orar.rds"))
# A tibble: 300 x 14
   prof  zl    `1`           `2`           `3`           ... `11`         `12` 
 1 P01   Lu     NA            NA            NA           ...  NA           NA   
 2 P01   Ma     NA            NA            NA           ...  NA           NA   
 3 P01   Mi     NA            NA            NA           ...  NA           NA   
 4 P01   Jo     NA            NA            NA           ... "Rom\r\n9.C"  NA   
 5 P01   Vi    "Rom\r\n11.D" "Rom\r\n11.D"  NA           ...  NA           NA   
 6 P02   Lu     NA           "Rom\r\n11.B" "Rom\r\n12.C" ...  NA           NA   
 7 P02   Ma     NA            NA            NA           ...  NA           NA   
 8 P02   Mi     NA            NA            NA           ...  NA           NA   
 9 P02   Jo    "Rom\r\n11.B" "Rom\r\n12.E" "Rom\r\n12.E" ...  NA           NA   
10 P02   Vi     NA            NA            NA           ...  NA           NA

Tabelul conţine 300×14=4200 de valori; valorile diferite de NA, din coloanele orelor `1`..`12`, reprezintă disciplina (precum "Rom") împreună cu clasa (precum "11.D", sau "9.C") la care profesorul indicat în coloana prof trebuie să intre în ora respectivă a zilei prevăzute în coloana zl.

Acest tabel reflectă vizualizarea obişnuită a orarului; dacă ştergem NA, recunoaştem paginile din documentul PDF original (cu diferenţe nesemnificative: numele profesorului a fost „anonimizat” şi apare pe prima coloană a tabelului, în loc să fie în titlul subtabelului asociat profesorului respectiv; bineînţeles, lipsesc liniile orizontale şi verticale).

Dar ca „set de date”, acest tabel este defectuos; este inutil în general, să înregistrăm valori neexistente (reprezentate prin NA); apoi, valorile înregistrate ar trebui să aibă fiecare propriul domeniu – ori aici, acestea ţin şi de obiectele de învăţământ şi de clasele existente (şi sunt exprimate în scopul afişării: când întâlneşte "Rom\r\n11.D", vizualizatorul va afişa "Rom" în celula curentă, apoi interpretând caracterele de control "\r\n", va avansa în celula respectivă pe rândul următor şi va scrie "11.D").

Prin programul R următor „normalizăm” (sau explicităm) datele din tabelul 'orar':

library(tidyverse)
orar <- readRDS("orar.rds")
orar <- orar %>%
       gather("ora", "obCls", 3:14) %>%
       separate(obCls, c("obj", "cls"), sep="\r\n") %>%
       filter(!is.na(cls)) %>%
       mutate(cls = sub(".", "", cls, fixed=TRUE))

Prin gather("ora", "obCls", 3:14) s-au instituit două coloane (sau „variabile”): $ora înregistrează repetat secvenţa 1:12 (indicând orele zilei), corespunzătoare numelor `1`..`12` ale coloanelor de ranguri 3:14 iniţiale; $obCls înregistrează valorile din celulele aflate în tabelul iniţial pe coloanele 3:14. Apoi, separate() constituie coloanele $obj şi $cls, pentru obiectele de învăţământ şi clasele care erau îmbinate iniţial prin "\r\n" în coloana $obCls (păstrând însă valorile NA). Apoi, filter() reţine numai liniile pe care în coloana $cls avem valori diferite de NA. În final, mutate() redefineşte coloana $cls, eliminând (cu sub()) caracterul "." care apărea în numele de clasă iniţiale.

Acum avem 5 variabile (cu domenii distincte între ele), cu valori grupate pe câte una dintre 925 de linii distincte două câte două:

> orar  # inspectăm obiectul 'orar' (din consola R) şi îl salvăm
# A tibble: 925 x 5
   prof  zl    ora   obj   cls  
   <chr> <chr> <chr> <chr> <chr>
 1 P01   Vi    1     Rom   11D  
 2 P02   Jo    1     Rom   11B  
 3 P03   Jo    1     Rom   12D  
 4 P04   Mi    1     Rom   11A  
 5 P07   Ma    1     Rom   11E  
 6 P10   Lu    1     Eng   12D  
 7 P11   Lu    1     Eng   11C  
 8 P11   Ma    1     Eng   12F  
 9 P12   Mi    1     Fr    8    
10 P13   Lu    1     Fr    12E  
# … with 915 more rows
> saveRDS(orar, file="orarN.rds")  # orarul „normalizat”

Cu alte cuvinte, avem în total 925 de ore, pentru profesorii, obiectele de învăţământ şi clasele existente – repartizate deja pe zilele săptămânii şi pe orele fiecărei zile.

O excepţie conjuncturală

De fapt, avem o excepţie – dintre cele 925 de ore, următoarele 11 ore au rămas fără alocare de zi (valoarea din $zl fiind NA):

> orar[is.na(orar$zl), ]
# A tibble: 11 x 5
   prof  zl    ora   obj    cls       prof  zl    ora   obj    cls
   P49   NA    7     Ed.Muz 10C       P49   NA    10    Ed.Muz 9D
   P50   NA    7     Ed.Muz 10F       P49   NA    11    Ed.Muz 9B
   P49   NA    8     Ed.Muz 9C        P49   NA    11    Ed.Muz 10A
   P50   NA    8     Ed.Muz 10E       P49   NA    12    Ed.Muz 10D
   P50   NA    9     Ed.Muz 10G       P49   NA    12    Ed.Muz 10B
   P49   NA    10    Ed.Muz 9A

Am observat deja în [1] că profesorii P49 şi P50 (cărora le aparţin aceste 11 ore „nealocate”) sunt cuplaţi cu profesorul P51 pe anumite clase. Să listăm şi orele lui P51 (cele dintr-o aceeaşi parte a zilei ca şi orele redate mai sus):

> orar[orar$prof=="P51" & as.integer(orar$ora) > 6, ]
# A tibble: 16 x 5
   prof  zl    ora   obj    cls       prof  zl    ora   obj    cls
   P51   Lu    7     Ed.Viz 10F       P51   Lu    11    Ed.Viz 5 
   P51   Vi    7     Ed.Viz 10C       P51   Ma    11    Ed.Viz 6 
   P51   Lu    8     Ed.Viz 10E       P51   Mi    11    Ed.Viz 9B
   P51   Vi    8     Ed.Viz 9C        P51   Vi    11    Ed.Viz 10A
   P51   Lu    9     Ed.Viz 10G       P51   Lu    12    Ed.Viz 9F
   P51   Lu    10    Ed.Viz 9E        P51   Ma    12    Ed.Viz 9G
   P51   Mi    10    Ed.Viz 9A        P51   Mi    12    Ed.Viz 10D
   P51   Vi    10    Ed.Viz 9D        P51   Vi    12    Ed.Viz 10B

Cu siguranţă, între condiţiile iniţiale furnizate programului folosit pentru construirea orarului, figurează şi condiţia de separare a unor clase pe grupe de elevi, pentru anumite discipline şcolare şi pentru anumiţi profesori. Ştiind (sau în cazul nostru, deducând) aceste condiţii, putem interpreta cele două tabele redate mai sus; de exemplu, clasa 10C este împărţită, în ziua Vi (cum vedem în al doilea tabel), ora a 7-a, în două grupe: una face Ed.Muz cu P49, iar cealaltă face Ed.Viz cu P51 (să observăm că dacă se punea "Vi" şi în primul tabel, în loc de NA, atunci rezulta o „suprapunere” de ore – ceea ce nu este permis într-un orar).

Noi intenţionăm (ca şi în [1]) să generăm o distribuţie pe zile a orelor (şi nu să o construim pe baza unor condiţii externe prealabile) – încât vom ignora jumătăţile de clasă (deci vom opera numai pe cele 914 ore pentru care valoarea $zl din orarul iniţial nu este NA); la sfârşit, prin anumite operaţii de retuşare a repartizării obţinute, vom putea lua în seamă şi diverse excepţii sau cazuri particulare (inclusiv, existenţa celor 11 jumătăţi de clasă ignorate iniţial).

Calitatea repartizării pe zile a orelor

Aici ne interesează numai repartizarea pe zile (nu şi pe orele zilei), încât vom şterge la un moment dat, coloana $ore; mai mult, vom şterge şi coloana $zl – fiindcă vrem să probăm şi să punem la punct algoritmul de repartizare pe zile vizat în [1].

Dar înainte de aceasta, să vedem pentru orarul iniţial, dacă orele profesorilor au fost repartizate omogen pe zile (pentru clase, distribuţia este omogenă: la fiecare clasă, numărul de ore pe zi variază cu cel mult 1). Ca şi în [1], prevedem o funcţie care să ne tabeleze numărul de ore pe zi pentru fiecare profesor:

byPrClOre <- function(byPC) {
    addmargins(table(byPC[c('prof', 'zl')]))  %>% 
    as.data.frame(.)  %>%  
    spread(., zl, Freq)  %>%  
    .[order(-.$Sum), ]
}
byp <- byPrClOre(orar)  # pentru întregul orar (puteam alege şi doar un subset)

Pentru a-l reda aici, să despărţim tabelul byp (obiect data.frame, cu 60 de linii) furnizat de această funcţie, în trei părţi de câte 20 de rânduri (separate prin câte două spaţii):

D <- data.frame(c1=1:20, c2=rep("  ",20), c3=1:20, c4=rep("  ",20), c5=1:20)
D$c1 <- byp[1:20, ]
D$c3 <- byp[21:40, ]
D$c5 <- byp[41:60, ]
names(D) <- NULL
sink("3col.txt")
print(D, row.names=FALSE, width=100)
sink()

Prin sink(), am redirectat ieşirea pe un fişier, fiindcă pe ecran nu încap toate caracterele unei linii din tabel (şi am mărit la 100, lăţimea uzuală de afişare a liniei):

 prof  Jo  Lu  Ma  Mi  Vi Sum    prof Jo Lu Ma Mi Vi Sum    prof Jo Lu Ma Mi Vi Sum
  Sum 181 187 184 181 181 914     P25  4  4  4  4  4  20     P01  4  3  4  0  2  13
  P13   6   6   6   5   5  28     P26  4  4  4  4  4  20     P46  4  0  3  0  6  13
  P53   6   6   6   5   5  28     P15  4  4  4  5  2  19     P42  3  4  0  4  0  11
  P54   6   6   7   6   3  28     P18  5  4  4  4  2  19     P56  1  2  3  1  2   9
  P41   3   6   6   6   6  27     P29  3  3  3  5  5  19     P35  0  3  2  0  2   7
  P51   4   6   5   6   5  26     P38  4  3  4  4  4  19     P20  0  2  0  0  4   6
  P57   5   6   5   6   4  26     P11  2  5  3  4  4  18     P24  0  2  0  2  1   5
  P05   5   5   5   5   5  25     P23  3  3  3  5  4  18     P39  0  1  2  2  0   5
  P14   5   5   5   4   5  24     P28  4  3  3  4  4  18     P43  0  2  0  0  3   5
  P03   4   4   5   5   5  23     P32  4  4  3  4  3  18     P55  0  3  0  2  0   5
  P17   4   5   5   4   5  23     P33  4  3  4  4  3  18     P44  1  0  2  0  1   4
  P48   5   4   5   4   5  23     P37  3  4  4  4  3  18     P45  4  0  0  0  0   4
  P10   5   4   5   4   4  22     P58  3  4  5  4  2  18     P50  2  2  0  0  0   4
  P19   5   5   4   4   4  22     P12  3  3  3  4  4  17     P52  0  1  1  0  2   4
  P21   4   5   5   4   4  22     P02  3  4  3  2  4  16     P59  2  0  2  0  0   4
  P04   4   2   5   5   5  21     P08  4  3  3  3  3  16     P36  0  0  0  0  3   3
  P09   4   4   5   5   3  21     P16  3  2  4  4  3  16     P49  3  0  0  0  0   3
  P22   5   3   4   5   4  21     P27  3  3  3  3  4  16     P40  0  0  1  1  0   2
  P06   4   5   4   4   3  20     P31  3  4  3  2  4  16     P30  0  1  0  0  0   1
  P07   4   4   2   5   5  20     P47  3  3  3  3  4  16     P34  0  0  0  1  0   1

Totalul de ore acoperit de tabelul obţinut prin funcţia de mai sus este 914 (nu 925), fiindcă table() a produs contingenţa valorilor diferite de NA, din coloanele indicate 'prof' şi 'zl' – încât s-au ignorat cele 11 valori NA din coloana $zl.

La profesorii cu peste 25 de ore pe săptămână, avem două cazuri de repartiţie neuniformă: P54 şi P41 au într-o zi 3 ore şi în celelalte câte 6 sau 7 ore (ar mai fi două cazuri, dar mai puţin importante: 4 ore într-o zi şi 6 în altele). La cei 14 profesori cu numărul de ore între 20 şi 25, avem 4 repartiţii neomogene (două fiind să zicem, mai „grave” – cu 2 ore într-o zi şi 5 în altele).

Avem situaţii similare acestora şi pentru cei 19 profesori cu 16-19 ore pe săptămână, etc. Dar să nu uităm că repartizarea redată mai sus este aceea din orarul final, ţinând seama deci şi de diversele restricţionări existente asupra zilelor alocate profesorilor (mai ales pentru cei cu puţine ore pe săptămână); mai încolo vom obţine şi noi o distribuţie a celor 914 ore pe zilele săptămânii, dar necondiţionată (şi nici finalizată într-un orar propriu-zis) – deci intenţia iniţială de a compara cele două repartizări pe zile a orelor, ar fi nelalocul ei.

vezi Cărţile mele (de programare)

docerpro | Prev | Next