MDL-15268 "Content for Quiz Statistics report table" seperated quiz item / position stats code into a seperate class in qstats.php and now calculating item statistics too.

This commit is contained in:
jamiesensei 2008-07-18 14:36:24 +00:00
parent b230cac1da
commit 4f5ffac022
5 changed files with 231 additions and 156 deletions

View File

@ -42,4 +42,6 @@ $string['nostudentsingroup'] = 'There are no students in this group yet';
$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.';
?>

View File

@ -2479,4 +2479,19 @@ function get_filesdir_from_context($context){
}
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];
}
}
?>

View File

@ -0,0 +1,179 @@
<?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;
}
}
?>

View File

@ -244,155 +244,17 @@ class quiz_statistics_report extends quiz_default_report {
}
}
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).' %');
@ -409,8 +271,13 @@ class quiz_statistics_report extends quiz_default_report {
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;

View File

@ -108,7 +108,11 @@ class quiz_report_statistics_table extends flexible_table {
}
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);
@ -120,17 +124,25 @@ class quiz_report_statistics_table extends flexible_table {
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);
@ -142,17 +154,17 @@ class quiz_report_statistics_table extends flexible_table {
}
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).' %';
}
}
?>