From b98bc5cb98c6ad89c30fc96a122519c3a07506d2 Mon Sep 17 00:00:00 2001 From: James Pratt Date: Fri, 13 Dec 2013 15:51:07 +0700 Subject: [PATCH] MDL-43369 : quiz statistics - average across attempts where item seen and not across all attempts for subqs. --- .../statistics/tests/statistics_test.php | 7 +- .../stats_from_steps_walkthrough_test.php | 75 ++++++++++++++----- .../statistics/questions/calculated.php | 11 +++ .../statistics/questions/calculator.php | 21 +++--- 4 files changed, 81 insertions(+), 33 deletions(-) diff --git a/mod/quiz/report/statistics/tests/statistics_test.php b/mod/quiz/report/statistics/tests/statistics_test.php index cc2988807fb..e4dd8acb26e 100644 --- a/mod/quiz/report/statistics/tests/statistics_test.php +++ b/mod/quiz/report/statistics/tests/statistics_test.php @@ -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) { diff --git a/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php b/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php index 26b9a8235b3..eb1be288bd0 100644 --- a/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php +++ b/mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php @@ -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 * @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); + } } } diff --git a/question/classes/statistics/questions/calculated.php b/question/classes/statistics/questions/calculated.php index e8e3404b2f9..6af6aae70d5 100644 --- a/question/classes/statistics/questions/calculated.php +++ b/question/classes/statistics/questions/calculated.php @@ -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; diff --git a/question/classes/statistics/questions/calculator.php b/question/classes/statistics/questions/calculator.php index e1919e9be43..ed1ab1cbe8c 100644 --- a/question/classes/statistics/questions/calculator.php +++ b/question/classes/statistics/questions/calculator.php @@ -78,7 +78,7 @@ class calculator { public function calculate($qubaids) { set_time_limit(0); - 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;