mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 09:26:35 +02:00
MDL-41756 (2) quiz statistics : break down question stats by variant
This commit is contained in:
parent
aa05ae5da3
commit
1239d28776
11 changed files with 649 additions and 483 deletions
|
@ -44,6 +44,12 @@ class calculated {
|
|||
|
||||
public $slot = null;
|
||||
|
||||
/**
|
||||
* @var null|integer if this property is not null then this is the stats for a variant of a question or when inherited by
|
||||
* calculated_for_subquestion and not null then this is the stats for a variant of a sub question.
|
||||
*/
|
||||
public $variant = null;
|
||||
|
||||
/**
|
||||
* @var bool is this a sub question.
|
||||
*/
|
||||
|
@ -103,7 +109,7 @@ class calculated {
|
|||
// End of fields in db.
|
||||
|
||||
protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
|
||||
'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore');
|
||||
'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore', 'variant');
|
||||
|
||||
// Fields used for intermediate calculations.
|
||||
|
||||
|
@ -152,6 +158,14 @@ class calculated {
|
|||
*/
|
||||
public $question;
|
||||
|
||||
/**
|
||||
* An array of calculated stats for each variant of the question. Even when there is just one variant we still calculate this
|
||||
* data as there is no way to know if there are variants before we have finished going through the attempt data one time.
|
||||
*
|
||||
* @var calculated[] $variants
|
||||
*/
|
||||
public $variantstats = array();
|
||||
|
||||
/**
|
||||
* Set if this record has been retrieved from cache. This is the time that the statistics were calculated.
|
||||
*
|
||||
|
@ -173,6 +187,12 @@ class calculated {
|
|||
$toinsert->{$field} = $this->{$field};
|
||||
}
|
||||
$DB->insert_record('question_statistics', $toinsert, false);
|
||||
|
||||
if (count($this->variantstats) > 1) {
|
||||
foreach ($this->variantstats as $variantstat) {
|
||||
$variantstat->cache($qubaids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,4 +34,9 @@ class calculated_for_subquestion extends calculated {
|
|||
public $differentweights = false;
|
||||
|
||||
public $negcovar = 0;
|
||||
|
||||
/**
|
||||
* @var int only set immediately before display in the table. The order of display in the table.
|
||||
*/
|
||||
public $subqdisplayorder;
|
||||
}
|
||||
|
|
|
@ -73,16 +73,41 @@ class calculator {
|
|||
$this->progress = $progress;
|
||||
|
||||
foreach ($questions as $slot => $question) {
|
||||
$this->questionstats[$slot] = new calculated();
|
||||
$this->questionstats[$slot]->questionid = $question->id;
|
||||
$this->questionstats[$slot]->question = $question;
|
||||
$this->questionstats[$slot]->slot = $slot;
|
||||
$this->questionstats[$slot]->positions = $question->number;
|
||||
$this->questionstats[$slot]->maxmark = $question->maxmark;
|
||||
$this->questionstats[$slot]->randomguessscore = $this->get_random_guess_score($question);
|
||||
$this->questionstats[$slot] = $this->new_slot_stats($question, $slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a calculated instance ready to store a questions stats.
|
||||
*
|
||||
* @param $question
|
||||
* @param $slot
|
||||
* @return calculated
|
||||
*/
|
||||
protected function new_slot_stats($question, $slot) {
|
||||
$toreturn = new calculated();
|
||||
$toreturn->questionid = $question->id;
|
||||
$toreturn->maxmark = $question->maxmark;
|
||||
$toreturn->question = $question;
|
||||
$toreturn->slot = $slot;
|
||||
$toreturn->positions = $question->number;
|
||||
$toreturn->randomguessscore = $this->get_random_guess_score($question);
|
||||
return $toreturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a calculated instance ready to store a randomly selected question's stats.
|
||||
*
|
||||
* @param $step
|
||||
* @return calculated_for_subquestion
|
||||
*/
|
||||
protected function new_subq_stats($step) {
|
||||
$toreturn = new calculated_for_subquestion();
|
||||
$toreturn->questionid = $step->questionid;
|
||||
$toreturn->maxmark = $step->maxmark;
|
||||
return $toreturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $qubaids \qubaid_condition
|
||||
* @return array containing two arrays calculated[] and calculated_for_subquestion[].
|
||||
|
@ -98,21 +123,39 @@ class calculator {
|
|||
// Compute the statistics of position, and for random questions, work
|
||||
// out which questions appear in which positions.
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->progress->increment_progress();
|
||||
$this->initial_steps_walker($step, $this->questionstats[$step->slot], $summarks);
|
||||
|
||||
// If this is a random question what is the real item being used?
|
||||
if ($step->questionid != $this->questionstats[$step->slot]->questionid) {
|
||||
$this->progress->increment_progress();
|
||||
|
||||
$israndomquestion = ($step->questionid != $this->questionstats[$step->slot]->questionid);
|
||||
// If this is a variant we have not seen before create a place to store stats calculations for this variant.
|
||||
if (!$israndomquestion && !isset($this->questionstats[$step->slot]->variantstats[$step->variant])) {
|
||||
$this->questionstats[$step->slot]->variantstats[$step->variant] =
|
||||
$this->new_slot_stats($this->questionstats[$step->slot]->question, $step->slot);
|
||||
$this->questionstats[$step->slot]->variantstats[$step->variant]->variant = $step->variant;
|
||||
}
|
||||
|
||||
|
||||
// Step data walker for main question.
|
||||
$this->initial_steps_walker($step, $this->questionstats[$step->slot], $summarks, true, !$israndomquestion);
|
||||
|
||||
// If this is a random question do the calculations for sub question stats.
|
||||
if ($israndomquestion) {
|
||||
if (!isset($this->subquestionstats[$step->questionid])) {
|
||||
$this->subquestionstats[$step->questionid] = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$step->questionid]->questionid = $step->questionid;
|
||||
$this->subquestionstats[$step->questionid]->maxmark = $step->maxmark;
|
||||
$this->subquestionstats[$step->questionid] = $this->new_subq_stats($step);
|
||||
} else if ($this->subquestionstats[$step->questionid]->maxmark != $step->maxmark) {
|
||||
$this->subquestionstats[$step->questionid]->differentweights = true;
|
||||
}
|
||||
|
||||
// If this is a variant of this subq we have not seen before create a place to store stats calculations for it.
|
||||
if (!isset($this->subquestionstats[$step->questionid]->variantstats[$step->variant])) {
|
||||
$this->subquestionstats[$step->questionid]->variantstats[$step->variant] = $this->new_subq_stats($step);
|
||||
$this->subquestionstats[$step->questionid]->variantstats[$step->variant]->variant = $step->variant;
|
||||
}
|
||||
|
||||
$this->initial_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks, false);
|
||||
|
||||
// Extra stuff we need to do in this loop for subqs to keep track of where they need to be displayed later.
|
||||
|
||||
$number = $this->questionstats[$step->slot]->question->number;
|
||||
$this->subquestionstats[$step->questionid]->usedin[$number] = $number;
|
||||
|
||||
|
@ -137,10 +180,15 @@ class calculator {
|
|||
$this->progress->start_progress('', count($subquestions), 1);
|
||||
foreach ($subquestions as $qid => $subquestion) {
|
||||
$this->progress->increment_progress();
|
||||
$subquestion->maxmark = $this->subquestionstats[$qid]->maxmark;
|
||||
$this->subquestionstats[$qid]->question = $subquestion;
|
||||
$this->subquestionstats[$qid]->question->maxmark = $this->subquestionstats[$qid]->maxmark;
|
||||
$this->subquestionstats[$qid]->randomguessscore = $this->get_random_guess_score($subquestion);
|
||||
|
||||
foreach ($this->subquestionstats[$qid]->variantstats as $variantstat) {
|
||||
$variantstat->question = $subquestion;
|
||||
$variantstat->randomguessscore = $this->get_random_guess_score($subquestion);
|
||||
}
|
||||
|
||||
$this->initial_question_walker($this->subquestionstats[$qid]);
|
||||
|
||||
if ($this->subquestionstats[$qid]->differentweights) {
|
||||
|
@ -168,12 +216,13 @@ class calculator {
|
|||
// foreach ($this->questions as $qid => $question).
|
||||
reset($this->questionstats);
|
||||
$this->progress->start_progress('', count($this->questionstats), 1);
|
||||
while (list($slot, $questionstat) = each($this->questionstats)) {
|
||||
while (list(, $questionstat) = each($this->questionstats)) {
|
||||
$this->progress->increment_progress();
|
||||
$nextquestionstats = current($this->questionstats);
|
||||
|
||||
$this->initial_question_walker($questionstat);
|
||||
|
||||
// The rest of this loop is again to work out where randomly selected question stats should be displayed.
|
||||
if ($questionstat->question->qtype == 'random') {
|
||||
$randomselectorstring = $questionstat->question->category .'/'. $questionstat->question->questiontext;
|
||||
if ($nextquestionstats && $nextquestionstats->question->qtype == 'random') {
|
||||
|
@ -194,7 +243,8 @@ class calculator {
|
|||
$this->progress->start_progress('', count($lateststeps), 1);
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->progress->increment_progress();
|
||||
$this->secondary_steps_walker($step, $this->questionstats[$step->slot], $summarks);
|
||||
$israndomquestion = ($this->questionstats[$step->slot]->question->qtype == 'random');
|
||||
$this->secondary_steps_walker($step, $this->questionstats[$step->slot], $summarks, !$israndomquestion);
|
||||
|
||||
if ($this->questionstats[$step->slot]->subquestions) {
|
||||
$this->secondary_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks);
|
||||
|
@ -257,19 +307,36 @@ class calculator {
|
|||
|
||||
$questionids = array();
|
||||
foreach ($questionstatrecs as $fromdb) {
|
||||
if (!$fromdb->slot) {
|
||||
if (is_null($fromdb->variant) && !$fromdb->slot) {
|
||||
$questionids[] = $fromdb->questionid;
|
||||
}
|
||||
}
|
||||
$subquestions = question_load_questions($questionids);
|
||||
foreach ($questionstatrecs as $fromdb) {
|
||||
if ($fromdb->slot) {
|
||||
$this->questionstats[$fromdb->slot]->populate_from_record($fromdb);
|
||||
// Array created in constructor and populated from question.
|
||||
} else {
|
||||
$this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb);
|
||||
$this->subquestionstats[$fromdb->questionid]->question = $subquestions[$fromdb->questionid];
|
||||
if (is_null($fromdb->variant)) {
|
||||
if ($fromdb->slot) {
|
||||
$this->questionstats[$fromdb->slot]->populate_from_record($fromdb);
|
||||
// Array created in constructor and populated from question.
|
||||
} else {
|
||||
$this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb);
|
||||
$this->subquestionstats[$fromdb->questionid]->question = $subquestions[$fromdb->questionid];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add cached variant stats to data structure.
|
||||
foreach ($questionstatrecs as $fromdb) {
|
||||
if (!is_null($fromdb->variant)) {
|
||||
if ($fromdb->slot) {
|
||||
$newcalcinstance = new calculated();
|
||||
$this->questionstats[$fromdb->slot]->variantstats[$fromdb->variant] = $newcalcinstance;
|
||||
$newcalcinstance->question = $this->questionstats[$fromdb->slot]->question;
|
||||
} else {
|
||||
$newcalcinstance = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$fromdb->questionid]->variantstats[$fromdb->variant] = $newcalcinstance;
|
||||
$newcalcinstance->question = $subquestions[$fromdb->questionid];
|
||||
}
|
||||
$newcalcinstance->populate_from_record($fromdb);
|
||||
}
|
||||
}
|
||||
return array($this->questionstats, $this->subquestionstats);
|
||||
|
@ -313,6 +380,7 @@ class calculator {
|
|||
$fields = " qas.id,
|
||||
qa.questionusageid,
|
||||
qa.questionid,
|
||||
qa.variant,
|
||||
qa.slot,
|
||||
qa.maxmark,
|
||||
qas.fraction * qa.maxmark as mark";
|
||||
|
@ -335,12 +403,13 @@ class calculator {
|
|||
* Update $stats->totalmarks, $stats->markarray, $stats->totalothermarks
|
||||
* and $stats->othermarksarray to include another state.
|
||||
*
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param calculated $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param bool $positionstat whether this is a statistic of position of question.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param bool $positionstat whether this is a statistic of position of question.
|
||||
* @param bool $dovariantalso do we also want to do the same calculations for this variant?
|
||||
*/
|
||||
protected function initial_steps_walker($step, $stats, $summarks, $positionstat = true) {
|
||||
protected function initial_steps_walker($step, $stats, $summarks, $positionstat = true, $dovariantalso = true) {
|
||||
$stats->s++;
|
||||
$stats->totalmarks += $step->mark;
|
||||
$stats->markarray[] = $step->mark;
|
||||
|
@ -353,15 +422,20 @@ class calculator {
|
|||
$stats->totalothermarks += $summarks[$step->questionusageid];
|
||||
$stats->othermarksarray[] = $summarks[$step->questionusageid];
|
||||
}
|
||||
if ($dovariantalso) {
|
||||
$this->initial_steps_walker($step, $stats->variantstats[$step->variant], $summarks, $positionstat, false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some computations on the per-question statistics calculations after
|
||||
* we have been through all the states.
|
||||
* we have been through all the step data.
|
||||
*
|
||||
* @param calculated $stats question stats to update.
|
||||
* @param bool $dovariantsalso do we also want to do the same calculations for the variants?
|
||||
*/
|
||||
protected function initial_question_walker($stats) {
|
||||
protected function initial_question_walker($stats, $dovariantsalso = true) {
|
||||
$stats->markaverage = $stats->totalmarks / $stats->s;
|
||||
|
||||
if ($stats->maxmark != 0) {
|
||||
|
@ -376,6 +450,12 @@ class calculator {
|
|||
|
||||
sort($stats->markarray, SORT_NUMERIC);
|
||||
sort($stats->othermarksarray, SORT_NUMERIC);
|
||||
|
||||
if ($dovariantsalso) {
|
||||
foreach ($stats->variantstats as $variantstat) {
|
||||
$this->initial_question_walker($variantstat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -385,8 +465,9 @@ class calculator {
|
|||
* @param object $step the state to add to the statistics.
|
||||
* @param calculated $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param bool $dovariantalso do we also want to do the same calculations for the variant?
|
||||
*/
|
||||
protected function secondary_steps_walker($step, $stats, $summarks) {
|
||||
protected function secondary_steps_walker($step, $stats, $summarks, $dovariantalso = true) {
|
||||
$markdifference = $step->mark - $stats->markaverage;
|
||||
if ($stats->subquestion) {
|
||||
$othermarkdifference = $summarks[$step->questionusageid] - $stats->othermarkaverage;
|
||||
|
@ -403,14 +484,19 @@ class calculator {
|
|||
$stats->covariancesum += $markdifference * $othermarkdifference;
|
||||
$stats->covariancemaxsum += $sortedmarkdifference * $sortedothermarkdifference;
|
||||
$stats->covariancewithoverallmarksum += $markdifference * $overallmarkdifference;
|
||||
|
||||
if ($dovariantalso) {
|
||||
$this->secondary_steps_walker($step, $stats->variantstats[$step->variant], $summarks, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform more per-question statistics calculations.
|
||||
*
|
||||
* @param calculated $stats question stats to update.
|
||||
* @param bool $dovariantsalso do we also want to do the same calculations for the variants?
|
||||
*/
|
||||
protected function secondary_question_walker($stats) {
|
||||
protected function secondary_question_walker($stats, $dovariantsalso = true) {
|
||||
|
||||
if ($stats->s > 1) {
|
||||
$stats->markvariance = $stats->markvariancesum / ($stats->s - 1);
|
||||
|
@ -449,6 +535,13 @@ class calculator {
|
|||
} else {
|
||||
$stats->discriminativeefficiency = null;
|
||||
}
|
||||
|
||||
|
||||
if ($dovariantsalso) {
|
||||
foreach ($stats->variantstats as $variantstat) {
|
||||
$this->secondary_question_walker($variantstat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -464,9 +557,9 @@ class calculator {
|
|||
* @param $qubaids \qubaid_condition
|
||||
*/
|
||||
protected function cache_stats($qubaids) {
|
||||
foreach ($this->questionstats as $questionstat) {
|
||||
foreach ($this->questionstats as $questionstat) {
|
||||
$questionstat->cache($qubaids);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->subquestionstats as $subquestionstat) {
|
||||
$subquestionstat->cache($qubaids);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue