$string['discrimination_index'] = 'Discrimination Index';
$string['discriminative_efficiency'] = 'Discriminative Efficiency';
$string['effective_weight'] = 'Effective weight';
+$string['errorrandom'] = 'Error getting sub item data';
+$string['erroritemappearsmorethanoncewithdifferentweight'] = 'Question ($a) appears more than once with different weights in different positions of the test. This is not currently supported by the statistics report and may make the statistics for this question unreliable.';
?>
\ No newline at end of file
}
return $courseid;
}
+/**
+ * Get the real question id for a random question.
+ * @param object $state with property answer.
+ * @return mixed return integer real question id or false if there was an
+ * error..
+ */
+function question_get_real_questionid($state){
+ $matches = array();
+ if (!preg_match('|^random([0-9]+)-|', $state->answer, $matches)){
+ notify(get_string('errorrandom', 'quiz_statistics'));
+ return false;
+ } else {
+ return $matches[1];
+ }
+}
?>
--- /dev/null
+<?php
+class qstats{
+ /**
+ * @var mixed states from which to calculate stats - iteratable.
+ */
+ var $states;
+
+ var $sumofgradevariance = 0;
+ var $questions;
+ var $subquestions = array();
+
+ function qstats($questions, $s, $sumgradesavg){
+ $this->s = $s;
+ $this->sumgradesavg = $sumgradesavg;
+ foreach (array_keys($questions) as $qid){
+ $questions[$qid]->_stats = $this->stats_init_object();
+ }
+ $this->questions = $questions;
+ }
+ function stats_init_object(){
+ $statsinit = new object();
+ $statsinit->s = 0;
+ $statsinit->totalgrades = 0;
+ $statsinit->totalothergrades = 0;
+ $statsinit->gradevariancesum = 0;
+ $statsinit->othergradevariancesum = 0;
+ $statsinit->covariancesum = 0;
+ $statsinit->covariancemaxsum = 0;
+ $statsinit->covariancewithoverallgradesum = 0;
+ $statsinit->gradearray = array();
+ $statsinit->othergradesarray = array();
+ $statsinit->subitems = array();
+ return $statsinit;
+ }
+ function get_records($fromqa, $whereqa, $usingattempts, $qaparams){
+ global $DB;
+ $sql = 'SELECT qs.id, ' .
+ 'qs.question, ' .
+ 'qa.sumgrades, ' .
+ 'qs.grade, ' .
+ 'qs.answer ' .
+ 'FROM ' .
+ '{question_sessions} qns, ' .
+ '{question_states} qs, '.
+ $fromqa.' '.
+ 'WHERE ' .$whereqa.
+ 'AND qns.attemptid = qa.uniqueid '.
+ $usingattempts.
+ 'AND qns.newgraded = qs.id';
+ $this->states = $DB->get_records_sql($sql, $qaparams);
+ if ($this->states === false){
+ print_error('errorstatisticsquestions', 'quiz_statistics');
+ }
+ }
+
+ function _initial_states_walker($state, &$stats, $positionstat = true){
+ $stats->s++;
+ $stats->totalgrades += $state->grade;
+ if ($positionstat){
+ $stats->totalothergrades += $state->sumgrades - $state->grade;
+ } else {
+ $stats->totalothergrades += $state->sumgrades;
+ }
+ //need to sort these to calculate max covariance :
+ $stats->gradearray[] = $state->grade;
+ if ($positionstat){
+ $stats->othergradesarray[] = $state->sumgrades - $state->grade;
+ } else {
+ $stats->othergradesarray[] = $state->sumgrades;
+ }
+ }
+
+ function _secondary_states_walker($state, &$stats){
+ $gradedifference = ($state->grade - $stats->gradeaverage);
+ $othergradedifference = (($state->sumgrades - $state->grade) - $stats->othergradeaverage);
+ $overallgradedifference = $state->sumgrades - $this->sumgradesavg;
+ $sortedgradedifference = (array_shift($stats->gradearray) - $stats->gradeaverage);
+ $sortedothergradedifference = (array_shift($stats->othergradesarray) - $stats->othergradeaverage);
+ $stats->gradevariancesum += pow($gradedifference,2);
+ $stats->othergradevariancesum += pow($othergradedifference,2);
+ $stats->covariancesum += $gradedifference * $othergradedifference;
+ $stats->covariancemaxsum += $sortedgradedifference * $sortedothergradedifference;
+ $stats->covariancewithoverallgradesum += $gradedifference * $overallgradedifference;
+ }
+
+
+ function _initial_question_walker(&$stats, $grade){
+ $stats->gradeaverage = $stats->totalgrades / $stats->s;
+ $stats->facility = $stats->gradeaverage / $grade;
+ $stats->othergradeaverage = $stats->totalothergrades / $stats->s;
+ sort($stats->gradearray, SORT_NUMERIC);
+ sort($stats->othergradesarray, SORT_NUMERIC);
+ }
+ function _secondary_question_walker(&$stats){
+ $stats->gradevariance = $stats->gradevariancesum / ($stats->s -1);
+ $stats->othergradevariance = $stats->othergradevariancesum / ($stats->s -1);
+ $stats->covariance = $stats->covariancesum / ($stats->s -1);
+ $stats->covariancemax = $stats->covariancemaxsum / ($stats->s -1);
+ $stats->covariancewithoverallgrade = $stats->covariancewithoverallgradesum / ($stats->s-1);
+ $stats->sd = sqrt($stats->gradevariancesum / ($stats->s -1));
+ //avoid divide by zero
+ if ($stats->gradevariance * $stats->othergradevariance){
+ $stats->discriminationindex = 100*$stats->covariance
+ / sqrt($stats->gradevariance * $stats->othergradevariance);
+ } else {
+ $stats->discriminationindex = '';
+ }
+ if ($stats->covariancemax){
+ $stats->discriminativeefficiency = 100*$stats->covariance / $stats->covariancemax;
+ } else {
+ $stats->discriminativeefficiency = '';
+ }
+ }
+
+ function process_states(){
+ foreach ($this->states as $state){
+ $this->_initial_states_walker($state, $this->questions[$state->question]->_stats);
+ //if this is a random question what is the real item being used?
+ if ($this->questions[$state->question]->qtype == 'random'){
+ if ($itemid = question_get_real_questionid($state)){
+ if (!isset($subquestionstats[$itemid])){
+ $subquestionstats[$itemid] = $this->stats_init_object();
+ $subquestionstats[$itemid]->usedin = array();
+ $subquestionstats[$itemid]->differentweights = false;
+ $subquestionstats[$itemid]->grade = $this->questions[$state->question]->grade;
+ } else if ($subquestionstats[$itemid]->grade != $this->questions[$state->question]->grade){
+ $subquestionstats[$itemid]->differentweights = true;
+ }
+ $this->_initial_states_walker($state, $subquestionstats[$itemid], false);
+ $subquestionstats[$itemid]->usedin[] = $state->question;
+ $this->questions[$state->question]->_stats->subitems[] = $itemid;
+ }
+ }
+ }
+ $this->subquestions = question_load_questions(array_keys($subquestionstats));
+ foreach (array_keys($this->subquestions) as $qid){
+ $this->subquestions[$qid]->_stats = $subquestionstats[$qid];
+ $this->subquestions[$qid]->grade = $this->subquestions[$qid]->_stats->grade;
+ $this->subquestions[$qid]->subquestion = true;
+ $this->_initial_question_walker($this->subquestions[$qid]->_stats, $this->subquestions[$qid]->_stats->grade);
+ if ($subquestionstats[$qid]->differentweights){
+ notify(get_string('erroritemappearsmorethanoncewithdifferentweight', 'quiz_statistics', $this->subquestions[$qid]->name));
+ }
+ }
+ foreach (array_keys($this->questions) as $qid){
+ $this->_initial_question_walker($this->questions[$qid]->_stats, $this->questions[$qid]->grade);
+ $this->questions[$qid]->subquestion = false;
+ }
+ //go through the records one more time
+ foreach ($this->states as $state){
+ $this->_secondary_states_walker($state, $this->questions[$state->question]->_stats);
+ if ($this->questions[$state->question]->qtype == 'random'){
+ if ($itemid = question_get_real_questionid($state)){
+ $this->_secondary_states_walker($state, $this->subquestions[$itemid]->_stats);
+ }
+ }
+ }
+ $sumofcovariancewithoverallgrade = 0;
+ foreach (array_keys($this->questions) as $qid){
+ $this->_secondary_question_walker($this->questions[$qid]->_stats);
+ $this->sumofgradevariance += $this->questions[$qid]->_stats->gradevariance;
+ $sumofcovariancewithoverallgrade += sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade);
+ }
+ foreach (array_keys($this->subquestions) as $qid){
+ $this->_secondary_question_walker($this->subquestions[$qid]->_stats);
+ }
+ foreach (array_keys($this->questions) as $qid){
+ $this->questions[$qid]->_stats->effectiveweight = 100 * sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade)
+ / $sumofcovariancewithoverallgrade;
+ }
+ }
+ /**
+ * Needed by quiz stats calculations.
+ */
+ function sum_of_grade_variance(){
+ return $this->sumofgradevariance;
+ }
+}
+?>
\ No newline at end of file
}
}
if ($s){
-/* //CIC, ER and SE.
- //http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#CIC.2C_ER_and_SE
- list($qsql, $sqlparams) = $DB->get_in_or_equal(array_keys($questions), SQL_PARAMS_NAMED);
- $sqlparams += $qaparams;//put quiz id in at beginning of array
- $qgradeavgsql = "SELECT qs.question, " .
- "AVG(qs.grade) AS gradeaverage " .
- "AVG(qa.sumgrades - qs.grade) AS sumgradeaverage " .
- "FROM " .
- "{question_sessions} qns, " .
- "{question_states} qs, " .
- $fromqa.' '.
- 'WHERE ' .$whereqa.
- 'AND qns.attemptid = qa.uniqueid '.
- 'AND qs.question '.$qsql.' ' .
- $usingattempts->sql.
- 'AND qns.newgraded = qs.id GROUP BY qs.question';
- $qgradeavgs = $DB->get_records_sql($qgradeavgsql, $sqlparams);
-
- $sum = 0;
- $sql = 'SELECT COUNT(1) as s,' .
- 'SUM(POWER((qs.grade - :mean),2)) AS power2 ' .
- 'FROM ' .
- '{question_sessions} qns, ' .
- '{question_states} qs, '.
- $fromqa.' '.
- 'WHERE ' .$whereqa.
- 'AND qns.attemptid = qa.uniqueid '.
- 'AND qs.question = :qid ' .
- $usingattempts->sql.
- 'AND qns.newgraded = qs.id';
- foreach (array_keys($questions) as $qid){
- $params = array('mean' => $qgradeavgs[$qid], 'qid' => $qid)+ $qaparams;
- $fromdb = $DB->get_record_sql($sql, $params);
- if ($fromdb === false){
- print_error('errorpowerquestions', 'quiz_statistics');
- }
- $questions[$qid]->s = $fromdb->s;
- if ($s>1){
- $questions[$qid]->facility = $qgradeavgs[$qid] / $questions[$qid]->grade;
- $questions[$qid]->sd = sqrt($fromdb->power2 / ($questions[$qid]->s -1));
- }
- $sum += $fromdb->power2;
- }
- //Discrimination index
- $sql = 'SELECT qs.id, ' .
- 'qs.question, ' .
- 'qa.sumgrades - qs.grade AS sum, ' .
- 'qs.grade ' .
- 'FROM ' .
- '{question_sessions} qns, ' .
- '{question_states} qs, '.
- $fromqa.' '.
- 'WHERE ' .$whereqa.
- 'AND qns.attemptid = qa.uniqueid '.
- $usingattempts->sql.
- 'AND qns.newgraded = qs.id';
- $fromdbrs = $DB->get_recordset_sql($sql, $qaparams);
- if ($fromdbrs === false){
- print_error('errorpowerquestions', 'quiz_statistics');
- }
- foreach ($fromdbrs as $record){
-
- }*/
- $sql = 'SELECT qs.id, ' .
- 'qs.question, ' .
- 'qa.sumgrades, ' .
- 'qs.grade ' .
- 'FROM ' .
- '{question_sessions} qns, ' .
- '{question_states} qs, '.
- $fromqa.' '.
- 'WHERE ' .$whereqa.
- 'AND qns.attemptid = qa.uniqueid '.
- $usingattempts->sql.
- 'AND qns.newgraded = qs.id';
- $fromdbrs = $DB->get_recordset_sql($sql, $qaparams);
- if ($fromdbrs === false){
- print_error('errorstatisticsquestions', 'quiz_statistics');
- }
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->s = 0;
- $questions[$qid]->totalgrades = 0;
- $questions[$qid]->totalothergrades = 0;
- $questions[$qid]->gradevariancesum = 0;
- $questions[$qid]->othergradevariancesum = 0;
- $questions[$qid]->covariancesum = 0;
- $questions[$qid]->covariancemaxsum = 0;
- $questions[$qid]->covariancewithoverallgradesum = 0;
- $questions[$qid]->gradearray = array();
- $questions[$qid]->othergradesarray = array();
- }
-
- foreach ($fromdbrs as $record){
- $questions[$record->question]->s++;
- $questions[$record->question]->totalgrades += $record->grade;
- $questions[$record->question]->totalothergrades += $record->sumgrades - $record->grade;
- //need to sort these to calculate max covariance :
- $questions[$record->question]->gradearray[] = $record->grade;
- $questions[$record->question]->othergradesarray[] = $record->sumgrades - $record->grade;
- }
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->gradeaverage = $questions[$qid]->totalgrades / $s;
- $questions[$qid]->facility = $questions[$qid]->gradeaverage / $questions[$qid]->grade;
- $questions[$qid]->othergradeaverage = $questions[$qid]->totalothergrades / $s;
- sort($questions[$qid]->gradearray, SORT_NUMERIC);
- sort($questions[$qid]->othergradesarray, SORT_NUMERIC);
- }
- //go through the records one more time
- foreach ($fromdbrs as $record){
- $gradedifference = ($record->grade - $questions[$record->question]->gradeaverage);
- $othergradedifference = (($record->sumgrades - $record->grade) - $questions[$record->question]->othergradeaverage);
- $overallgradedifference = $record->sumgrades - $sumgradesavg;
- $sortedgradedifference = (array_shift($questions[$qid]->gradearray) - $questions[$record->question]->gradeaverage);
- $sortedothergradedifference = (array_shift($questions[$qid]->othergradesarray) - $questions[$record->question]->othergradeaverage);
- $questions[$record->question]->gradevariancesum += pow($gradedifference,2);
- $questions[$record->question]->othergradevariancesum += pow($othergradedifference,2);
- $questions[$record->question]->covariancesum += $gradedifference * $othergradedifference;
- $questions[$record->question]->covariancemaxsum += $sortedgradedifference * $sortedothergradedifference;
- $questions[$record->question]->covariancewithoverallgradesum += $gradedifference * $overallgradedifference;
- }
- $sumofcovariancewithoverallgrade = 0;
- $sumofgradevariance =0;
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->gradevariance = $questions[$qid]->gradevariancesum / ($s -1);
- $questions[$qid]->othergradevariance = $questions[$qid]->othergradevariancesum / ($s -1);
- $questions[$qid]->covariance = $questions[$qid]->covariancesum / ($s -1);
- $questions[$qid]->covariancemax = $questions[$qid]->covariancemaxsum / ($s -1);
- $sumofgradevariance += $questions[$qid]->gradevariance;
- $questions[$qid]->covariancewithoverallgrade = $questions[$qid]->covariancewithoverallgradesum / ($s-1);
- $sumofcovariancewithoverallgrade += sqrt($questions[$qid]->covariancewithoverallgrade);
- $questions[$qid]->sd = sqrt($questions[$qid]->gradevariancesum / ($s -1));
- //avoid divide by zero
- if (sqrt($questions[$qid]->gradevariance * $questions[$qid]->othergradevariance)){
- $questions[$qid]->discriminationindex = 100*$questions[$qid]->covariance
- / sqrt($questions[$qid]->gradevariance * $questions[$qid]->othergradevariance);
- } else {
- $questions[$qid]->discriminationindex = '';
- }
- $questions[$qid]->discriminativeefficiency = 100*$questions[$qid]->covariance / $questions[$qid]->covariancemax;
- }
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->effectiveweight = 100 * sqrt($questions[$qid]->covariancewithoverallgrade)/$sumofcovariancewithoverallgrade;
- }
+ require_once("$CFG->dirroot/mod/quiz/report/statistics/qstats.php");
+ $qstats = new qstats($questions, $s, $sumgradesavg);
+ $qstats->get_records($fromqa, $whereqa, $usingattempts->sql, $qaparams);
+ set_time_limit(0);
+ $qstats->process_states();
}
if (!$table->is_downloading()){
if ($s>1){
$p = count($questions);//no of positions
if ($p > 1){
- $cic = (100 * $p / ($p -1)) * (1 - ($sumofgradevariance)/$k2);
+ $cic = (100 * $p / ($p -1)) * (1 - ($qstats->sum_of_grade_variance())/$k2);
$quizattsstatistics->data[] = array(get_string('cic', 'quiz_statistics'), number_format($cic, $quiz->decimalpoints).' %');
$errorratio = 100 * sqrt(1-($cic/100));
$quizattsstatistics->data[] = array(get_string('errorratio', 'quiz_statistics'), number_format($errorratio, $quiz->decimalpoints).' %');
print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
}
$table->setup($quiz, $cm->id, $reporturl, $s);
- foreach ($questions as $question){
- $table->add_data_keyed($table->format_row($question));
+ if (isset($qstats)){
+ foreach ($qstats->questions as $question){
+ $table->add_data_keyed($table->format_row($question));
+ foreach ($question->_stats->subitems as $itemid){
+ $table->add_data_keyed($table->format_row($qstats->subquestions[$itemid]));
+ }
+ }
}
$table->finish_output();
return true;
}
function col_number($question){
- return $question->number;
+ if (!$question->subquestion){
+ return $question->number;
+ } else {
+ return '';
+ }
}
function col_actions($question){
return quiz_question_action_icons($this->quiz, $this->cmid, $question, $this->baseurl);
return quiz_report_scale_sumgrades_as_percentage($question->grade, $this->quiz);
}
function col_effective_weight($question){
- return number_format($question->effectiveweight, 2).' %';
+ if (!$question->subquestion){
+ return number_format($question->_stats->effectiveweight, 2).' %';
+ } else {
+ return '';
+ }
}
function col_discrimination_index($question){
- if (is_numeric($question->discriminationindex)){
- return number_format($question->discriminationindex, 2).' %';
+ if (is_numeric($question->_stats->discriminationindex)){
+ return number_format($question->_stats->discriminationindex, 2).' %';
} else {
- return $question->discriminationindex;
+ return $question->_stats->discriminationindex;
}
}
function col_discriminative_efficiency($question){
- return number_format($question->discriminativeefficiency, 2).' %';
+ if (is_numeric($question->_stats->discriminativeefficiency)){
+ return number_format($question->_stats->discriminativeefficiency, 2).' %';
+ } else {
+ return '';
+ }
}
function col_random_guess_score($question){
$randomguessscore = question_get_random_guess_score($question);
}
function col_sd($question){
- return number_format($question->sd*100 / $question->grade, 2).' %';
+ return number_format($question->_stats->sd*100 / $question->grade, 2).' %';
}
function col_s($question){
- if (isset($question->s)){
- return $question->s;
+ if (isset($question->_stats->s)){
+ return $question->_stats->s;
} else {
return 0;
}
}
function col_facility($question){
- return number_format($question->facility*100, 2).' %';
+ return number_format($question->_stats->facility*100, 2).' %';
}
}
?>