En avaluar les respostes a les preguntes numèriques, Moodle té en compte dos valors que s’han establert quan s’ha creat la pregunta, que són la solució i la tolerància, i dóna per bona la resposta quan \(|resposta-solució|< tolerància\).
Les darreres línies de l’arxiu Rmd (o l’equivalent Rnw) que crea una pregunta contenen metadades sobre el tipus de preguntes, les solucions i les toleràncies (i més coses que no venen al cas).
En una pregunta numèrica simple, les metadades poden tenir aquest aspecte (exemple tret de https://www.r-exams.org/assets/posts/2018-05-15-fruit//fruit.Rmd):
Meta-information
================
exname: Fruit baskets (numeric)
extype: num
exsolution: `r fmt(sol)`
extol: 0
En aquesta mostra la tolerància és fixa (zero) i la solució és una expressió de R que produeix un número, però la tolerància també podria ser una expressió de R (si volguéssim calcular la tolerància per cada versió de la pregunta) o la solució aparèixer com un número (si totes les versions de la pregunta tinguessin la mateixa solució).
En preguntes cloze (o sigui, exercicis amb diverses preguntes com els
que fem servir darrament a l’assignatura), aquests paràmetres es donen
per cada una de les preguntes, separant-los amb barres verticals
|. Si la tolerància és la mateixa per totes les preguntes
es pot donar com un sol número en comptes de repetir-lo per cada
pregunta. Per exemple (tret de https://www.r-exams.org/assets/posts/2022-11-21-lm3//lm3.Rmd):
Meta-information
================
exname: Linear regression
extype: cloze
exsolution: OLS|01001|`r fmt(ahat, 3)`|`r fmt(bhat, 3)`|`r mchoice2string(bsol)`|nil|nil
exclozetype: string|mchoice|num|num|schoice|essay|file
extol: 0.01
Normalment és més fàcil crear un vector amb les solucions i un vector amb les toleràncies i fer-los servir per construir les metadades, sobretot si no totes les toleràncies són iguals:
Meta-information
================
exname: combinatoria_pregunta_repetida
extype: cloze
exsolution: `r paste(sol, collapse = "|")`
exclozetype: `r paste(rep("num", length(sol)), collapse = "|")`
extol: `r paste(tol, collapse = "|")`
En cas que posem una tolerància en preguntes que no siguin numèriques, R-exams la ignora, cosa que pot ser pràctic. En qualsevol cas, els paràmetres que especifiquem per cada pregunta (exsolution, exclozetype i extol) han de tenir tants valors com preguntes.
Idealment, la tolerància ha de ser prou gran com per acceptar totes les respostes que estiguin ben calculades amb un arrodoniment raonable i prou petita com per no acceptar cap resultat erroni del problema, però a la pràctica molts problemes tenen solucions errònies amb resultats que no s’allunyen gaire de la solució correcta de manera que qualsevol tolerància que triem és una solució de compromís.
La tolerància que convé demanar en una pregunta pot dependre del problema, però el més important és explicitar clarament una tolerància mínima demanada a l’enunciat i que aquesta que diem sigui igual o més estricta que la que apliquem a la correcció.
No hi ha una tolerància que serveixi per totes les preguntes. Per la majoria de casos pot ser adient una tolerància del 1% del valor de la resposta amb un mínim que pot ser 0,00001 explicitant a l’enunciat que es demana un mínim de tres xifres significatives correctes. També es pot ser una mica més estricte i establir una tolerància del 0,1% del valor de la resposta i demanar quatre xifres significatives.
Alternativament, es pot demanar una tolerància absoluta (per exemple, demanar dos o tres decimals i establir la tolerància com 0,01 o 0,001) quan tots els resultats siguin del mateix ordre, com quan totes les respostes d’un problema són probabilitats relativament grans.
Ara bé, hi ha problemes en que això pot ser massa o massa poc.
Per exemple: En una pregunta sobre correlació, respondre 0 quan la resposta és 0,0001 és equivalent i hauria d’entrar a la tolerància, de manera que demanar dos o tres decimals pot ser suficient.
En canvi, hi ha problemes on demanar dos o tres decimals no distingeix entre respostes correctes i respostes molt incorrectes. Per exemple, si la probabilitat que exploti una central nuclear en un dia donat és 0,0001 respondre 0 no és equivalent i no ho hauríem de donar per bo tot i encertar tres decimals. El mateix passa si demanem coses com la probabilitat de treure un póquer d’asos agafant cinc cartes d’una baralla francesa sense comodins (0.0000185).
També pot passar que demanar un nombre de xifres significatives no sigui adient, com quan el resultat surt de sumar un número gros i un de petit que s’ha de calcular (com en un interval de confiança amb una mitjana molt gran i una desviació estàndard petita o un problema prediccions en un model de regressió on el terme independent sigui molt més gran que el pendent pel rang del predictor), que aleshores cal demanar un nombre de decimals perquè fer el problema bé o malament fa canviar només els últims decimals, que poden ser molt menys que un 1% de la resposta.
En resum, no crec que hi hagi una solució única com ara dir que tot el curs farem servir un nombre de decimals o de xifres significatives sinó que val més donar el missatge que no arrodoneixin i a cada exercici deixar clar amb quina precisió han de respondre com a mínim, i que aquesta precisió sigui adequada al problema i es pugui assolir a partir de les dades que es donen.
Les toleràncies relatives es poden calcular com:
# exemple 1%
abs(sol)*.01
Quan es fan servir toleràncies relatives convé posar una tolerància absoluta mínima (que no cal esmentar a l’enunciat) per tal d’evitar que una solució molt propera a zero tingui una tolerància molt petita (què vol dir “molt propera a zero” pot dependre del problema).
tol <- pmax(abs(sol)*.01, 1e-4)
Aquest càlcul també serveix si es demana una tolerància relativa o una tolerància absoluta. Per exemple, tres xifres significatives o dos decimals:
tol <- pmax(abs(sol)*.01, .01) # 1% o 0.01
En alguns casos pot convenir demanar una tolerància relativa i una d’absoluta. Per exemple, tres xifres significatives i dos decimals:
pmin(abs(sol)*0.01, .01) # 1% i 0.01
En aquests casos convé afegir-hi una tolerància absoluta mínima (que no cal esmentar a l’enunciat) per si hi ha solucions molt properes a zero:
tol <- pmax(pmin(abs(sol)*0.01, .01), 1e-4)
Si algunes preguntes no són numèriques, aleshores el vector
sol serà un vector de tipus caràcter on la resposta a les
preguntes numèriques serà un número posat com a text. En aquesta
situació podem fer les mateixes operacions afegint-hi la funció
as.numeric:
tol <- pmax(abs(as.numeric(sol))*.01, .01) # 1% o 0.01
Donat que les preguntes de simple opció la resposta es codifica com
una cadena de zeros i uns (com ara "0100"), la tolerància
d’aquestes preguntes calculada així serà un número que R-exams ignorarà.
Si hi haguéssin preguntes de text o d’altres tipus (que habitualment no
fem servir), as.numeric podria produir valors
NA que caldria substituir per qualsevol número.
En alguns casos especials (com els de més avall) pot caldre calcular la tolerància per separat per cada pregunta. En aquests casos només cal concatenar les toleràncies de cada pregunta per obtenir el vector de toleràncies.
Si es permet que un problema es resolgui amb la taula de probabilitat de la distribució normal (o una altra) cal que la tolerància de la pregunta doni per correcta la resposta obtinguda amb la precisió que permet la taula. Donat que diferents arrodoniments més o menys correctes són possibles en calcular les taules i que normalment també es permet resoldre el problema amb R, jo dono com a resposta correcta la més exacta i poso una tolerància que accepti les altres.
El càlcul d’aquesta tolerància es complica més com més vegades calgui consultar la taula, però poso un exemple senzill:
# Les dades
# part 1
mu <- 10
sigma <- 3
z <- runif(1, -2.5, 2.5)
(x <- round(mu+z*sigma, 2))
## [1] 2.95
# part 2
(p <- round(runif(1, .04, .96), 3))
## [1] 0.526
El problema d’exemple:
Una variable aleatòria X segueix una distribució N(10, 3). Calculeu:
Les solucions exactes:
sol1 <- pnorm(x, mu, sigma)
sol2 <- qnorm(p, mu, sigma)
(sol <- c(sol1, sol2))
## [1] 0.009386706 10.195655619
Com a tolerància deguda a la taula agafaré la diferència entre els resultats que s’obtenen en dues caselles consecutives. Això ve a ser admetre llegir qualsevol de les dues caselles més properes al valor buscat. A la taula de la normal Z va en increments de 0.01:
# part 1
z1_exacta <- (x-mu)/sigma
(tol1_taula <- pnorm(z1_exacta+.005)-pnorm(z1_exacta-.005))
## [1] 0.000252187
# part 2
tol2_taula <- .01*sigma
# lliguem
(tol_taula <- abs(c(tol1_taula, tol2_taula)))
## [1] 0.000252187 0.030000000
Naturalment, a més de l’error per causa de la taula cal tenir en compte la tolerància que digui l’enunciat. Es poden combinar de diferents maneres però una opció conservadora és sumar-les. Per exemple, si s’han demanat tres decimals
(tol <- tol_taula + .002) # estrictament, 3 decimals seria .001 però val més que sigui més estricte el que diem que el que fem
## [1] 0.002252187 0.032000000
Si hi ha mes d’una manera correcta de fer el problema, la tolerància hauria de ser suficient per acceptar totes les maneres correctes (a menys que l’enunciat digui que només s’acceptarà una manera). Per exemple:
Tirem una moneda a l’aire 50 vegades. Calculeu la probabilitat que surtin menys de 20 cares.
Si volem acceptar la solució exacta (binomial) i l’aproximada (amb la normal):
(sol3 <- pbinom(19, 50, .5)) # exacta
## [1] 0.05946023
(sol3_norm <- pnorm(19.5, 50*.5, sqrt(50*.5^2))) # aproximada
## [1] 0.05989747
(tol3_norm <- abs(sol3-sol3_norm))
## [1] 0.0004372389
I si a més volem que es pugui fer servir la taula també cal tenir-ne en compte l’arrodoniment:
z3_exacta <- (19.5 - 50*.5)/sqrt(50*.5^2)
(tol3_taula <- pnorm(z3_exacta+.005)-pnorm(z3_exacta-.005))
## [1] 0.001189642
Tot i que sumar toleràncies pot ser encara més conservador, així cobrim tots els casos possibles:
(tol3 <- tol3_norm + tol3_taula + 0.0002) # 4 decimals
## [1] 0.001826881
Donat que és fàcil embolicar-se amb les toleràncies i és difícil de comprovar que el codi estigui bé, a mi em va bé posar els resultats i les toleràncies a la retroacció de la pregunta. És útil tant per comprovar si hi ha algun problema quan elaborem la pregunta com per tenir a mà els marges d’error quan volem veure per què el qüestionari ha donat com a incorrecta (o no) una resposta d’un estudiant.
# solucions i toleràncies dels dos exemples de més amunt
sol <- c(sol, sol3)
tol <- c(tol, tol3)
# taula amb resultats i toleràncies
data.frame(sol=sol, tol=tol, min=sol-tol, max=sol+tol)
## sol tol min max
## 1 0.009386706 0.002252187 0.007134519 0.01163889
## 2 10.195655619 0.032000000 10.163655619 10.22765562
## 3 0.059460226 0.001826881 0.057633345 0.06128711
Si hi ha preguntes no numèriques, aleshores pot ser que calgui fer
servir as.numeric:
data.frame(sol=as.numeric(sol), tol=tol, min=as.numeric(sol)-tol, max=tol+as.numeric(sol))