Omogenizarea repartiției pe zile a lecțiilor

library(recastlessons)

Într-o școală avem trei categorii de profesori: cei care au numai “ore proprii”, cei angajați și în “cuplaje”, respectiv cei constituiți și în “tuplaje”. În R123 avem o repartiție pe zile a tuturor lecțiilor, iar în TPL sunt specificate tuplajele asociate acestei repartiții:

dplyr::glimpse(R123)
#> Rows: 928
#> Columns: 3
#> $ prof <ord> Rl2, En3, En3, Ps1, Is3, Is3, Mt2, Mt2, Mt2, Mt2, Sp2, Sp2, Gg1, …
#> $ cls  <chr> "10A", "10A", "10A", "10A", "10A", "10A", "10A", "10A", "10A", "1…
#> $ zl   <fct> Lu, Jo, Ma, Mi, Vi, Lu, Jo, Ma, Mi, Vi, Lu, Jo, Ma, Mi, Vi, Lu, J…
str(TPL)
#> 'data.frame':    15 obs. of  3 variables:
#>  $ prof: chr  "Gr3 Gr4" "Gr3 Gr1 Gr4" "Gr3 Gr1 Gr4" "Gr2 Gr3" ...
#>  $ cls : chr  "10E 10F" "11A 11B 11D" "12A 12B 12D" "11E 11F" ...
#>  $ zl  : chr  "Mi" "Vi Jo" "Lu Ma" "Mi Vi Jo" ...

Profesorii sunt numiți după șablonul: disciplină, abreviată pe două litere, plus un număr de ordine între cei încadrați pe o aceeași disciplină; un cuplaj este reprezentat printr-un “profesor fictiv”, cu numele rezultat prin alipirea codurilor de câte 3 caractere ale membrilor (iar cei câțiva profesori fictivi apar și ei, între cele 66 niveluri din R123$prof).
Mai sus, glimpse() ne-a arătat că Rl2, En3, etc., fac lecțiile la 10A respectiv în zilele Lu, Jo, Ma, etc.

"Gr3 Gr4"/"10E 10F" este un tuplaj, însemnând că lecțiile “Gr3 10E” și “Gr4 10F” trebuie să fie plasate într-o aceeași zi, anume în ziua Mi (și apoi, într-o aceeași oră a zilei), unde “10E” și “10F” au fost formate ad-hoc partiționând după criterii specifice școlii, ansamblul elevilor celor două clase inițiale.

R123 este un exemplu de repartiție relativ echilibrată (care ne-a rezultat, plecând de la încadrarea prof|cls dintr-o anumită școală, prin https://cran.r-project.org/package=days2lessons); o bună parte a distribuțiilor individuale (pe profesori și pe clase) a numărului de ore/zi sunt distribuții omogene (uniforme), dar există altele care sunt doar cvasi-omogene (cu diferență de două ore —dar nu mai mult— de la o zi la alta).
Prezentăm (parțial, aici) un set de funcții care depistează și expun în diverse formate (sau grupări), distribuțiile individuale “defectuoase”, permițând prin corelare, omogenizarea treptată a acestora.

Să vedem întâi la care clase au rămas distribuții cvasi-omogene (dar aici, listăm doar trei):

cls_quasihom(R123) %>% head(3)
#>     Lu Ma Mi Jo Vi
#> 10A  6  7  5  6  7
#> 10C  6  7  5  6  5
#> 10D  6  7  6  5  5

Pentru a omogeniza distribuția la clasa 10A ar trebui să mutăm o lecție fie din ziua Ma, fie din ziua Vi, în ziua Mi (ar rezulta 4 zile cu câte 6 ore și o zi cu 7 ore, deci o distribuție omogenă). Să vedem ce lecții sunt în ziua Vi la 10A și nu sunt în ziua Mi (evităm să apară la clasă într-o aceeași zi, două lecții identice):

prof_2days(R123, "10A", "Vi", "Mi")
#>         zl
#> prof     Lu Ma Mi Jo Vi
#>   Is3     2  3  3  4  4
#>   In2In1  1  1  0  0  1
#>   Tc2     2  3  3  2  3
#>   Gr2     5  4  4  5  5

Să lăsăm în pace deocamdată, pe Is3 și pe Gr2 (distribuția lui Is3 ar deveni omogenă aducându-i încă o lecție nu în ziua Mi, ci în ziua Lu; iar lecția Gr2 10A trebuie să rămână Vi, fiindcă apare în tuplajul "Gr2 Gr1"/"10A 10B"/"Vi Jo"). La Tc2 avem distribuție omogenă (dacă nu cumva este implicat și în cuplaje); ne rămâne In2In1, dar acesta este un cuplaj — să vedem cum ar fi afectați membrii săi, dacă am muta lecția respectivă din ziua Vi în ziua Mi:

coupled_dis(R123)
#>         zl
#> prof     Lu Ma Mi Jo Vi
#>   In2In1  1  1  0  0  1
#>   In2     1  2  2  3  2
#>   In2In3  1  1  1  0  0
#>   In1     2  3  1  2  2
#>   In1In3  0  0  1  1  1
#>   Tc2In1  1  0  1  0  1
#>   Tc2     2  3  3  2  3
#>   In3     2  2  2  2  2

Constatăm că In2 are (singur sau în cuplaj) 3 ore Mi și 3 ore Vi, iar In1 are 3 și respectiv, 5 ore — distribuții care, după mutarea intenționată, ar deveni 2/4 și respectiv 4/4 și deocamdată, le putem accepta:

> DZ <- change_day(R123, "In2In1", "10A", "Vi", "Mi")

DZ diferă de R123 numai prin faptul că In2In1 apare Mi și nu Vi; în DZ, clasa 10A a căpătat o distribuție omogenă a numărului de ore/zi. N-avem dacât să continuăm, operând cam ca mai sus, dar asupra repartiției curente DZ, pentru clasele rămase cu distribuții cvasi-omogene.

Dar pentru a echilibra distribuțiile orare ale celor implicați în cuplaje este mai bine să folosim funcția coupled_list(), care listează matricea distribuțiilor în care este implicat un profesor care are și ore proprii (pe clase neimplicate în cuplaje):

coupled_list(R123)[["In1"]]
#>         zl
#> prof     Lu Ma Mi Jo Vi
#>   In2In1  1  1  0  0  1
#>   In1     2  3  1  2  2
#>   In1In3  0  0  1  1  1
#>   Tc2In1  1  0  1  0  1
#>   Sum     4  4  3  3  5

Pe matricea afișată vedem că pe Vi, In1 cumulează 5 ore, dintre care două sunt ore proprii; deci (în loc de a muta cuplajul In2In1, cum am făcut mai sus) am putea muta una dintre aceste două ore proprii, din ziua Vi în ziua Mi, ceea ce i-ar omogeniza distribuția cumulată a orelor sale.

Cândva… lucrând cu atenție și fără grabă, cls_quasihom(DZ) va returna NULL — însemnând că în acel moment toate clasele au căpătat (în repartiția curentă DZ) distribuții omogene pentru numărul de ore/zi; se poate întâmpla (fiindcă… am lucrat într-adevăr, cu mare atenție, mai și inspectând una-alta, din când în când), se poate întâmpla ca și distribuția pe zile a totalului orelor, să fi devenit omogenă:

> addmargins(table(DZ[c('cls','zl')]))["Sum", ]
 Lu  Ma  Mi  Jo  Vi Sum 
186 186 186 185 185 928

(da!… este necesară oleacă de inspirație, sau experiență, pentru alegerea unor schimbări succesive de lecții dintr-o zi în alta, care “deodată”, să omogenizeze distribuțiile pe clase și până la urmă și pe totalul orelor)

Să vedem acum care profesori din afara cuplajelor, cu mai mult de câte 8 ore/săptămână, au rămas cu distribuții cvasi-omogene:

prof_bad_dis(DZ)
   Ch1 Ch2 En3 ET1 Fl1 Fz2 Gr1 Gr3 Gr4 Mt1 Mt3 Mz1 Ro2 Ro7 Sp1
Lu   3   5   3   1   4   4   4   5   3   5   4   4   4   4   4
Ma   4   3   4   3   4   3   4   3   4   6   5   2   4   2   4
Mi   3   3   4   2   4   2   3   4   5   4   3   4   3   3   5
Jo   3   4   3   1   4   4   5   3   4   4   5   3   5   3   3
Vi   5   3   5   3   2   4   5   4   5   4   4   4   3   4   5

Ne atrage atenția distribuția lui ET1: are numai 10 ore, dar are două zile cu câte o singură oră. Am putea să-i grupăm orele în 4 zile, dacă ora de Lu și cea de Jo sunt la clase diferite:

> cls2prof(DZ, "ET1")
   ET1       
Lu "8A"      
Ma "5A 5B 6A"
Mi "6B 7B"   
Jo "7A"      
Vi "5A 5B 8B"

Fiindcă ținem să nu modificăm distribuțiile, deja omogene, ale claselor și nici distribuția totalului de ore — căutăm vreun profesor cu care ET1interschimbe lecția de Lu (la 8A) cu cea de Jo (la 7A):

> prof_swap(DZ, "ET1", "Lu", "Jo")
   Ro4          
Lu "7A 7B 8B"   
Ma "7A 7B 8A 8B"
Mi "7A 7B 8A 8B"
Jo "7A 7B 8A 8B"
Vi "7A 7B 8A 8B"

8A apare la Ro4 în ziua Jo și nu apare în ziua Lu; pe de altă parte, mutarea acestei clase păstrează omogenitatea distribuției orelor lui Ro4, încât putem decide să facem interschimbarea respectivă:

> DZ <- change_day(DZ, "ET1", "8A", "Lu", "Jo")
> DZ <- change_day(DZ, "Ro4", "8A", "Jo", "Lu")
> saveRDS(DZ, "../adjR123.RDS")  # salvăm din când în când, repartiția curentă

Pentru încă un exemplu, să încercăm să omogenizăm distribuția (3 4 3 3 5) a lui Ch1:

> cls2prof(DZ, "Ch1")
   Ch1                 
Lu "10A 10C 12A"       
Ma "10B 11A 11D 12B"   
Mi "10C 10D 11D"       
Jo "10A 10D 9C"        
Vi "10B 10C 10D 11D 9C"

Trebuie să căutăm profesori cu care Ch1 să interschimbe o clasă din ziua Vi într-una dintre zilele Lu, Mi sau Jo, fără a deteriora alte distribuții individuale. prof_swap(DZ, "Ch1", "Vi", "Jo") ne arată între altele, distribuția cvasi-omogenă (4 4 3 5 3):

   Ro2              
Lu "10B 5A 5B 6B"   
Ma "5A 5B 6B 9B"    
Mi "10B 5B 9B"      
Jo "10B 5A 5B 6B 9B"
Vi "5A 6B 9B"

Putem muta clasa 10B de pe Jo pe Vi la Ro2 și de pe Vi pe Jo la Ch1, folosind iarăși change_day(); ca urmare, devin omogene ambele distribuții (și la Ch1 și la Ro2).

La fel, folosind succesiv cls2prof(), prof_swap() și apoi, după ce facem alegerile potrivite, change_day() — omogenizăm pe rând și distribuțiile cvasi-omogene rămase profesorilor din afara tuplajelor.
În final, prof_bad_dis() ne-a dat:

   ET1 Gr1 Gr3 Gr4 Mz1
Lu   0   4   5   3   4
Ma   3   4   3   4   2
Mi   2   3   4   5   4
Jo   2   5   3   4   3
Vi   3   5   4   5   4

Exceptând ET1 (căruia mai sus, am voit să-i plasăm orele în 4 zile), cei 4 profesori rămași cu distribuții cvasi-omogene sunt membri ai unor tuplaje și preferăm să nu le modificăm distribuțiile (trebuie păstrată ziua alocată din start pentru fiecare clasă care este implicată în tuplaje, deci la cei 4 profesori am putea muta într-o altă zi numai clase ale lor care nu sunt printre cele existente în tuplaje — ceea ce necesită o analiză care probabil că nu este prea grea, dar de care ne putem și lipsi).

Trebuie totuși să verificăm dacă, tot mutând lecții pe alte zile, am reușit să păstrăm în repartiția curentă DZ, alocările pe zile existente în setul inițial ‘R123’ pentru tuplaje: invocarea keep_toupled(DZ, TPL) ar trebui să returneze TRUE.
De altfel, putem vizualiza (în format TSV) întreaga repartizare pe zile a lecțiilor vreunui subset de profesori, folosind funcția tidy2tsv(); de exemplu, chiar pentru cei din tuplaje:

library(dplyr, warn.conflicts = FALSE)
Tpl_prof <- TPL %>% pull(prof) %>% unique() %>%
    sapply(function(P) strsplit(P, " ")[[1]]) %>% 
    unlist() %>% unique()
DZ %>% filter(prof %in% Tpl_prof) %>% 
    tidy2tsv() %>% select(1:5)  # câte coloane încap aici...
#>   prof                Lu             Ma                Mi                Jo
#> 1  Ds1      11F 7A 9E 9C     11E 8B 10A      5A 6A 8A 10B      12E 6B 7B 9A
#> 2  Gr1     6A 12B 10D 9B  6B 12B 10D 9B        6B 12F 10E 6A 9D 11B 10B 12F
#> 3  Gr2   8A 8B 9E 10C 9A   8A 9E 10C 9A     5B 11E 9E 12E 5A 5B 11E 10A 12E
#> 4  Gr3 11C 12C 7B 12A 9F     11C 12A 9F     7A 10E 11F 9F        7B 11A 11F
#> 5  Gr4       10E 10F 12D 10E 10F 7A 12D 10F 7A 7B 10F 10F     10E 5A 9C 11D
#> 6  Mz1      12F 6B 8B 9D         5A 10C      6A 8A 9E 10D          5B 7A 9B

Putem verifica astfel, direct, că am respectat repartizarea pe zile din ‘TPL’; vedem de exemplu, că Gr3, Gr1 și Gr4 intră la 12A, 12B, respectiv 12D în zilele Lu și Ma, iar Ds1 și Mz1 intră la 9A și 9B în ziua Jo (exact cum precizează ‘TPL’). Putem observa că la Mz1 am putea muta 12F (sau 6B, sau 8B, aflate și ele în afara tuplajelor) din ziua Lu în ziua Ma — ceea ce ar omogeniza distribuția respectivă (la fel ne putem gândi să omogenizăm distribuțiile celorlalți profesori din tuplaje, folosind iarăși prof_swap() și change_day()).

În final… probabil că putem fi mulțumiți: repartiția pe zile din DZ este omogenă și pe clase și pe profesori — exceptând unii membri ai cuplajelor și tuplajelor existente, pentru care a rămas totuși acceptabilă, fiind cvasi-omogenă (iar numărul total de lecții din DZ are o distribuție uniformă pe zile); desigur, ar mai fi de analizat și poate de corectat, distribuțiile celor cu mai puțin de 8 ore/săptămână (acestea pot fi depistate prin prof_bad_dis(DZ, at_least = 1)).
Desigur, pentru aceasta ne-au fost necesare vreo două sesiuni de lucru în consola R, cam de câte o “oră” bună… Pentru o eventuală edificare, am adăugat în pachet și repartizarea finală DZ; dar bineînțeles că dacă am lua-o de la capăt, procedând cam cum am arătat mai sus asupra repartiției inițiale ‘R123’, am ajunge la o a doua repartiție DZ, probabil “mai bună” ca prima (am profita desigur, de experiența primeia).