MDL-43369 : quiz statistics - average across attempts where item seen
authorJames Pratt <me@jamiep.org>
Fri, 13 Dec 2013 08:51:07 +0000 (15:51 +0700)
committerJames Pratt <me@jamiep.org>
Mon, 27 Jan 2014 10:30:56 +0000 (17:30 +0700)
and not across all attempts for subqs.

mod/quiz/report/statistics/tests/statistics_test.php
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
question/classes/statistics/questions/calculated.php
question/classes/statistics/questions/calculator.php

index cc29888..e4dd8ac 100644 (file)
@@ -53,10 +53,10 @@ class testable_question_statistics extends \core_question\statistics\questions\c
 
     /**
      * @param $qubaids qubaid_condition is ignored in this test
-     * @return array with three items
+     * @return array with two items
      *              - $lateststeps array of latest step data for the question usages
      *              - $summarks    array of total marks for each usage, indexed by usage id
-     *              - $summarksavg the average of the total marks over all the usages     */
+     */
     protected function get_latest_steps($qubaids) {
         $summarks = array();
         $fakeusageid = 0;
@@ -72,8 +72,7 @@ class testable_question_statistics extends \core_question\statistics\questions\c
             $step->questionusageid = $fakeusageid;
         }
 
-        $summarksavg = array_sum($summarks) / count($summarks);
-        return array($this->lateststeps, $summarks, $summarksavg);
+        return array($this->lateststeps, $summarks);
     }
 
     protected function cache_stats($qubaids) {
index 26b9a82..eb1be28 100644 (file)
@@ -32,7 +32,6 @@ require_once($CFG->dirroot . '/mod/quiz/report/default.php');
 require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
 require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
 
-
 /**
  * Quiz attempt walk through using data from csv file.
  *
@@ -42,7 +41,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
  * @author     Jamie Pratt <me@jamiep.org>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_from_csv_testcase {
+class quiz_report_statistics_from_steps_testcase extends mod_quiz_attempt_walkthrough_from_csv_testcase {
 
     /**
      * @var quiz_statistics_report object to do stats calculations.
@@ -103,9 +102,11 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
             $this->assertTimeCurrent($responesstats->get_last_analysed_time($qubaids));
         }
 
-        // These quiz stats and the question stats found in qstats00.csv were calculated independently in spreadsheet which is
+        // These quiz stats and the question stats found in qstats00.csv were calculated independently in spreadsheets which are
         // available in open document or excel format here :
         // https://github.com/jamiepratt/moodle-quiz-tools/tree/master/statsspreadsheet
+
+        // These quiz stats and the position stats here are calculated in stats.xls and stats.ods available, see above github URL.
         $quizstatsexpected = array(
             'median' => 4.5,
             'firstattemptsavg' => 4.617333332,
@@ -128,22 +129,62 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
             $slotqstats = $csvdata['qstats']->getRow($rowno);
             foreach ($slotqstats as $statname => $slotqstat) {
                 if ($statname !== 'slot') {
-                    switch ($statname) {
-                        case 'covariance' :
-                        case 'discriminationindex' :
-                        case 'discriminativeefficiency' :
-                        case 'effectiveweight' :
-                            $precision = 1e-5;
-                            break;
-                        default :
-                            $precision = 1e-6;
-                    }
-                    $slot = $slotqstats['slot'];
-                    $delta = abs($slotqstat) * $precision;
-                    $actual = $questionstats[$slot]->{$statname};
-                    $this->assertEquals(floatval($slotqstat), $actual, "$statname for slot $slot", $delta);
+                    $this->assert_stat_equals($questionstats, $subquestionstats, $slotqstats['slot'],
+                                              null, null, $statname, (float)$slotqstat);
                 }
             }
         }
+
+        $itemstats = array('s' => 12,
+                          'effectiveweight' => null,
+                          'discriminationindex' => 35.803933,
+                          'discriminativeefficiency' => 39.39393939,
+                          'sd' => 0.514928651,
+                          'facility' => 0.583333333,
+                          'maxmark' => 1,
+                          'positions' => '1',
+                          'slot' => null,
+                          'subquestion' => true);
+        foreach ($itemstats as $statname => $expected) {
+            $this->assert_stat_equals($questionstats, $subquestionstats, 1, null, 'numerical', $statname, $expected);
+        }
+    }
+
+    /**
+     * Check that the stat is as expected within a reasonable tolerance.
+     *
+     * @param \core_question\statistics\questions\calculated[] $questionstats
+     * @param \core_question\statistics\questions\calculated_for_subquestion[] $subquestionstats
+     * @param int                                              $slot
+     * @param int|null                                         $variant if null then not a variant stat.
+     * @param string|null                                      $subqname if null then not an item stat.
+     * @param string                                           $statname
+     * @param float                                            $expected
+     */
+    protected function assert_stat_equals($questionstats, $subquestionstats, $slot, $variant, $subqname, $statname, $expected) {
+
+        if ($variant === null && $subqname === null) {
+            $actual = $questionstats[$slot]->{$statname};
+        } else if ($subqname !== null) {
+            $actual = $subquestionstats[$this->randqids[$slot][$subqname]]->{$statname};
+        } else {
+            $actual = $questionstats[$slot]->variantstats[$variant]->{$statname};
+        }
+        if (is_bool($expected) || is_string($expected)) {
+            $this->assertEquals($expected, $actual, "$statname for slot $slot");
+        } else {
+            switch ($statname) {
+                case 'covariance' :
+                case 'discriminationindex' :
+                case 'discriminativeefficiency' :
+                case 'effectiveweight' :
+                    $precision = 1e-5;
+                    break;
+                default :
+                    $precision = 1e-6;
+            }
+            $delta = abs($expected) * $precision;
+            $this->assertEquals(floatval($expected), $actual, "$statname for slot $slot", $delta);
+        }
     }
 }
index e8e3404..6af6aae 100644 (file)
@@ -99,6 +99,7 @@ class calculated {
      */
     public $randomguessscore = null;
 
+
     // End of fields in db.
 
     protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
@@ -110,6 +111,11 @@ class calculated {
 
     public $totalothermarks = 0;
 
+    /**
+     * @var float The total of marks achieved for all positions in all attempts where this item was seen.
+     */
+    public $totalsummarks = 0;
+
     public $markvariancesum = 0;
 
     public $othermarkvariancesum = 0;
@@ -130,6 +136,11 @@ class calculated {
 
     public $othermarkaverage;
 
+    /**
+     * @var float The average for all attempts, of the sum of the marks for all positions in which this item appeared.
+     */
+    public $summarksaverage;
+
     public $markvariance;
     public $othermarkvariance;
     public $covariance;
index fda92c7..a3e9d98 100644 (file)
@@ -78,7 +78,7 @@ class calculator {
     public function calculate($qubaids) {
         \core_php_time_limit::raise();
 
-        list($lateststeps, $summarks, $summarksavg) = $this->get_latest_steps($qubaids);
+        list($lateststeps, $summarks) = $this->get_latest_steps($qubaids);
 
         if ($lateststeps) {
 
@@ -169,10 +169,10 @@ class calculator {
 
             // Go through the records one more time.
             foreach ($lateststeps as $step) {
-                $this->secondary_steps_walker($step, $this->questionstats[$step->slot], $summarks, $summarksavg);
+                $this->secondary_steps_walker($step, $this->questionstats[$step->slot], $summarks);
 
                 if ($this->questionstats[$step->slot]->subquestions) {
-                    $this->secondary_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks, $summarksavg);
+                    $this->secondary_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks);
                 }
             }
 
@@ -268,10 +268,9 @@ class calculator {
 
     /**
      * @param $qubaids \qubaid_condition
-     * @return array with three items
+     * @return array with two items
      *              - $lateststeps array of latest step data for the question usages
      *              - $summarks    array of total marks for each usage, indexed by usage id
-     *              - $summarksavg the average of the total marks over all the usages
      */
     protected function get_latest_steps($qubaids) {
         $dm = new \question_engine_data_mapper();
@@ -292,12 +291,9 @@ class calculator {
                 }
                 $summarks[$step->questionusageid] += $step->mark;
             }
-            $summarksavg = array_sum($summarks) / count($summarks);
-        } else {
-            $summarksavg = null;
         }
 
-        return array($lateststeps, $summarks, $summarksavg);
+        return array($lateststeps, $summarks);
     }
 
     /**
@@ -341,6 +337,8 @@ class calculator {
 
         $stats->othermarkaverage = $stats->totalothermarks / $stats->s;
 
+        $stats->summarksaverage = $stats->totalsummarks / $stats->s;
+
         sort($stats->markarray, SORT_NUMERIC);
         sort($stats->othermarksarray, SORT_NUMERIC);
     }
@@ -352,16 +350,15 @@ 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 float  $summarksavg the average sum of marks for all question usages
      */
-    protected function secondary_steps_walker($step, $stats, $summarks, $summarksavg) {
+    protected function secondary_steps_walker($step, $stats, $summarks) {
         $markdifference = $step->mark - $stats->markaverage;
         if ($stats->subquestion) {
             $othermarkdifference = $summarks[$step->questionusageid] - $stats->othermarkaverage;
         } else {
             $othermarkdifference = $summarks[$step->questionusageid] - $step->mark - $stats->othermarkaverage;
         }
-        $overallmarkdifference = $summarks[$step->questionusageid] - $summarksavg;
+        $overallmarkdifference = $summarks[$step->questionusageid] - $stats->summarksaverage;
 
         $sortedmarkdifference = array_shift($stats->markarray) - $stats->markaverage;
         $sortedothermarkdifference = array_shift($stats->othermarksarray) - $stats->othermarkaverage;