|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00036 class quiz_statistics_question_stats { 00037 public $questions; 00038 public $subquestions = array(); 00039 00040 protected $s; 00041 protected $summarksavg; 00042 protected $allattempts; 00043 00045 protected $lateststeps; 00046 00047 protected $sumofmarkvariance = 0; 00048 protected $randomselectors = array(); 00049 00056 public function __construct($questions, $s, $summarksavg) { 00057 $this->s = $s; 00058 $this->summarksavg = $summarksavg; 00059 00060 foreach ($questions as $slot => $question) { 00061 $question->_stats = $this->make_blank_question_stats(); 00062 $question->_stats->questionid = $question->id; 00063 $question->_stats->slot = $slot; 00064 } 00065 00066 $this->questions = $questions; 00067 } 00068 00072 protected function make_blank_question_stats() { 00073 $stats = new stdClass(); 00074 $stats->slot = null; 00075 $stats->s = 0; 00076 $stats->totalmarks = 0; 00077 $stats->totalothermarks = 0; 00078 $stats->markvariancesum = 0; 00079 $stats->othermarkvariancesum = 0; 00080 $stats->covariancesum = 0; 00081 $stats->covariancemaxsum = 0; 00082 $stats->subquestion = false; 00083 $stats->subquestions = ''; 00084 $stats->covariancewithoverallmarksum = 0; 00085 $stats->randomguessscore = null; 00086 $stats->markarray = array(); 00087 $stats->othermarksarray = array(); 00088 return $stats; 00089 } 00090 00099 public function load_step_data($quizid, $currentgroup, $groupstudents, $allattempts) { 00100 global $DB; 00101 00102 $this->allattempts = $allattempts; 00103 00104 list($qsql, $qparams) = $DB->get_in_or_equal(array_keys($this->questions), 00105 SQL_PARAMS_NAMED, 'q'); 00106 list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql( 00107 $quizid, $currentgroup, $groupstudents, $allattempts, false); 00108 00109 $this->lateststeps = $DB->get_records_sql(" 00110 SELECT 00111 qas.id, 00112 quiza.sumgrades, 00113 qa.questionid, 00114 qa.slot, 00115 qa.maxmark, 00116 qas.fraction * qa.maxmark as mark 00117 00118 FROM $fromqa 00119 JOIN {question_attempts} qa ON qa.questionusageid = quiza.uniqueid 00120 JOIN ( 00121 SELECT questionattemptid, MAX(id) AS latestid 00122 FROM {question_attempt_steps} 00123 GROUP BY questionattemptid 00124 ) lateststepid ON lateststepid.questionattemptid = qa.id 00125 JOIN {question_attempt_steps} qas ON qas.id = lateststepid.latestid 00126 00127 WHERE 00128 qa.slot $qsql AND 00129 $whereqa", $qparams + $qaparams); 00130 } 00131 00132 public function compute_statistics() { 00133 set_time_limit(0); 00134 00135 $subquestionstats = array(); 00136 00137 // Compute the statistics of position, and for random questions, work 00138 // out which questions appear in which positions. 00139 foreach ($this->lateststeps as $step) { 00140 $this->initial_steps_walker($step, $this->questions[$step->slot]->_stats); 00141 00142 // If this is a random question what is the real item being used? 00143 if ($step->questionid != $this->questions[$step->slot]->id) { 00144 if (!isset($subquestionstats[$step->questionid])) { 00145 $subquestionstats[$step->questionid] = $this->make_blank_question_stats(); 00146 $subquestionstats[$step->questionid]->questionid = $step->questionid; 00147 $subquestionstats[$step->questionid]->allattempts = $this->allattempts; 00148 $subquestionstats[$step->questionid]->usedin = array(); 00149 $subquestionstats[$step->questionid]->subquestion = true; 00150 $subquestionstats[$step->questionid]->differentweights = false; 00151 $subquestionstats[$step->questionid]->maxmark = $step->maxmark; 00152 } else if ($subquestionstats[$step->questionid]->maxmark != $step->maxmark) { 00153 $subquestionstats[$step->questionid]->differentweights = true; 00154 } 00155 00156 $this->initial_steps_walker($step, 00157 $subquestionstats[$step->questionid], false); 00158 00159 $number = $this->questions[$step->slot]->number; 00160 $subquestionstats[$step->questionid]->usedin[$number] = $number; 00161 00162 $randomselectorstring = $this->questions[$step->slot]->category . 00163 '/' . $this->questions[$step->slot]->questiontext; 00164 if (!isset($this->randomselectors[$randomselectorstring])) { 00165 $this->randomselectors[$randomselectorstring] = array(); 00166 } 00167 $this->randomselectors[$randomselectorstring][$step->questionid] = 00168 $step->questionid; 00169 } 00170 } 00171 00172 foreach ($this->randomselectors as $key => $notused) { 00173 ksort($this->randomselectors[$key]); 00174 } 00175 00176 // Compute the statistics of question id, if we need any. 00177 $this->subquestions = question_load_questions(array_keys($subquestionstats)); 00178 foreach ($this->subquestions as $qid => $subquestion) { 00179 $subquestion->_stats = $subquestionstats[$qid]; 00180 $subquestion->maxmark = $subquestion->_stats->maxmark; 00181 $subquestion->_stats->randomguessscore = $this->get_random_guess_score($subquestion); 00182 00183 $this->initial_question_walker($subquestion->_stats); 00184 00185 if ($subquestionstats[$qid]->differentweights) { 00186 // TODO output here really sucks, but throwing is too severe. 00187 global $OUTPUT; 00188 echo $OUTPUT->notification( 00189 get_string('erroritemappearsmorethanoncewithdifferentweight', 00190 'quiz_statistics', $this->subquestions[$qid]->name)); 00191 } 00192 00193 if ($subquestion->_stats->usedin) { 00194 sort($subquestion->_stats->usedin, SORT_NUMERIC); 00195 $subquestion->_stats->positions = implode(',', $subquestion->_stats->usedin); 00196 } else { 00197 $subquestion->_stats->positions = ''; 00198 } 00199 } 00200 00201 // Finish computing the averages, and put the subquestion data into the 00202 // corresponding questions. 00203 00204 // This cannot be a foreach loop because we need to have both 00205 // $question and $nextquestion available, but apart from that it is 00206 // foreach ($this->questions as $qid => $question) { 00207 reset($this->questions); 00208 while (list($slot, $question) = each($this->questions)) { 00209 $nextquestion = current($this->questions); 00210 $question->_stats->allattempts = $this->allattempts; 00211 $question->_stats->positions = $question->number; 00212 $question->_stats->maxmark = $question->maxmark; 00213 $question->_stats->randomguessscore = $this->get_random_guess_score($question); 00214 00215 $this->initial_question_walker($question->_stats); 00216 00217 if ($question->qtype == 'random') { 00218 $randomselectorstring = $question->category.'/'.$question->questiontext; 00219 if ($nextquestion && $nextquestion->qtype == 'random') { 00220 $nextrandomselectorstring = $nextquestion->category . '/' . 00221 $nextquestion->questiontext; 00222 if ($randomselectorstring == $nextrandomselectorstring) { 00223 continue; // Next loop iteration 00224 } 00225 } 00226 if (isset($this->randomselectors[$randomselectorstring])) { 00227 $question->_stats->subquestions = implode(',', 00228 $this->randomselectors[$randomselectorstring]); 00229 } 00230 } 00231 } 00232 00233 // Go through the records one more time 00234 foreach ($this->lateststeps as $step) { 00235 $this->secondary_steps_walker($step, 00236 $this->questions[$step->slot]->_stats); 00237 00238 if ($this->questions[$step->slot]->qtype == 'random') { 00239 $this->secondary_steps_walker($step, 00240 $this->subquestions[$step->questionid]->_stats); 00241 } 00242 } 00243 00244 $sumofcovariancewithoverallmark = 0; 00245 foreach ($this->questions as $slot => $question) { 00246 $this->secondary_question_walker($question->_stats); 00247 00248 $this->sumofmarkvariance += $question->_stats->markvariance; 00249 00250 if ($question->_stats->covariancewithoverallmark >= 0) { 00251 $sumofcovariancewithoverallmark += 00252 sqrt($question->_stats->covariancewithoverallmark); 00253 $question->_stats->negcovar = 0; 00254 } else { 00255 $question->_stats->negcovar = 1; 00256 } 00257 } 00258 00259 foreach ($this->subquestions as $subquestion) { 00260 $this->secondary_question_walker($subquestion->_stats); 00261 } 00262 00263 foreach ($this->questions as $question) { 00264 if ($sumofcovariancewithoverallmark) { 00265 if ($question->_stats->negcovar) { 00266 $question->_stats->effectiveweight = null; 00267 } else { 00268 $question->_stats->effectiveweight = 100 * 00269 sqrt($question->_stats->covariancewithoverallmark) / 00270 $sumofcovariancewithoverallmark; 00271 } 00272 } else { 00273 $question->_stats->effectiveweight = null; 00274 } 00275 } 00276 } 00277 00286 protected function initial_steps_walker($step, $stats, $positionstat = true) { 00287 $stats->s++; 00288 $stats->totalmarks += $step->mark; 00289 $stats->markarray[] = $step->mark; 00290 00291 if ($positionstat) { 00292 $stats->totalothermarks += $step->sumgrades - $step->mark; 00293 $stats->othermarksarray[] = $step->sumgrades - $step->mark; 00294 00295 } else { 00296 $stats->totalothermarks += $step->sumgrades; 00297 $stats->othermarksarray[] = $step->sumgrades; 00298 } 00299 } 00300 00307 protected function initial_question_walker($stats) { 00308 $stats->markaverage = $stats->totalmarks / $stats->s; 00309 00310 if ($stats->maxmark != 0) { 00311 $stats->facility = $stats->markaverage / $stats->maxmark; 00312 } else { 00313 $stats->facility = null; 00314 } 00315 00316 $stats->othermarkaverage = $stats->totalothermarks / $stats->s; 00317 00318 sort($stats->markarray, SORT_NUMERIC); 00319 sort($stats->othermarksarray, SORT_NUMERIC); 00320 } 00321 00330 protected function secondary_steps_walker($step, $stats) { 00331 $markdifference = $step->mark - $stats->markaverage; 00332 if ($stats->subquestion) { 00333 $othermarkdifference = $step->sumgrades - $stats->othermarkaverage; 00334 } else { 00335 $othermarkdifference = $step->sumgrades - $step->mark - 00336 $stats->othermarkaverage; 00337 } 00338 $overallmarkdifference = $step->sumgrades - $this->summarksavg; 00339 00340 $sortedmarkdifference = array_shift($stats->markarray) - $stats->markaverage; 00341 $sortedothermarkdifference = array_shift($stats->othermarksarray) - 00342 $stats->othermarkaverage; 00343 00344 $stats->markvariancesum += pow($markdifference, 2); 00345 $stats->othermarkvariancesum += pow($othermarkdifference, 2); 00346 $stats->covariancesum += $markdifference * $othermarkdifference; 00347 $stats->covariancemaxsum += $sortedmarkdifference * $sortedothermarkdifference; 00348 $stats->covariancewithoverallmarksum += $markdifference * $overallmarkdifference; 00349 } 00350 00356 protected function secondary_question_walker($stats) { 00357 if ($stats->s > 1) { 00358 $stats->markvariance = $stats->markvariancesum / ($stats->s - 1); 00359 $stats->othermarkvariance = $stats->othermarkvariancesum / ($stats->s - 1); 00360 $stats->covariance = $stats->covariancesum / ($stats->s - 1); 00361 $stats->covariancemax = $stats->covariancemaxsum / ($stats->s - 1); 00362 $stats->covariancewithoverallmark = $stats->covariancewithoverallmarksum / 00363 ($stats->s - 1); 00364 $stats->sd = sqrt($stats->markvariancesum / ($stats->s - 1)); 00365 00366 } else { 00367 $stats->markvariance = null; 00368 $stats->othermarkvariance = null; 00369 $stats->covariance = null; 00370 $stats->covariancemax = null; 00371 $stats->covariancewithoverallmark = null; 00372 $stats->sd = null; 00373 } 00374 00375 if ($stats->markvariance * $stats->othermarkvariance) { 00376 $stats->discriminationindex = 100 * $stats->covariance / 00377 sqrt($stats->markvariance * $stats->othermarkvariance); 00378 } else { 00379 $stats->discriminationindex = null; 00380 } 00381 00382 if ($stats->covariancemax) { 00383 $stats->discriminativeefficiency = 100 * $stats->covariance / 00384 $stats->covariancemax; 00385 } else { 00386 $stats->discriminativeefficiency = null; 00387 } 00388 } 00389 00394 protected function get_random_guess_score($questiondata) { 00395 return question_bank::get_qtype( 00396 $questiondata->qtype, false)->get_random_guess_score($questiondata); 00397 } 00398 00403 public function get_sum_of_mark_variance() { 00404 return $this->sumofmarkvariance; 00405 } 00406 }