]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-15268 "Content for Quiz Statistics report table" seperated quiz item / position...
authorjamiesensei <jamiesensei>
Fri, 18 Jul 2008 14:36:24 +0000 (14:36 +0000)
committerjamiesensei <jamiesensei>
Fri, 18 Jul 2008 14:36:24 +0000 (14:36 +0000)
lang/en_utf8/quiz_statistics.php
lib/questionlib.php
mod/quiz/report/statistics/qstats.php [new file with mode: 0644]
mod/quiz/report/statistics/report.php
mod/quiz/report/statistics/statistics_table.php

index c51bef67a815ecebc450bcafedc78e22ba3872e9..901acd9385aba91e39802be1c047ae377b83f9ad 100644 (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.';
 ?>
\ No newline at end of file
index 0813ffb77c9f4ed416fdb966e9cb65d898614802..88a724c3834c8d4a9e41e0358a7788be0f21517d 100644 (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];
+    }
+}
 ?>
diff --git a/mod/quiz/report/statistics/qstats.php b/mod/quiz/report/statistics/qstats.php
new file mode 100644 (file)
index 0000000..4bad495
--- /dev/null
@@ -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;
+    }
+}
+?>
\ No newline at end of file
index 9e4c2659c4573a1b06b80f42a7c262976a59b9b4..4acff501d972e470b4dccef7a4909d926f3e4b9d 100644 (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;
index ebf425363ff7d8d8718947c6f7833e00b81c57f2..580ce9e031f2ab6100eb28a61334f19c25dbcf25 100644 (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).' %';
     }
 }
 ?>