Skip to content

Commit

Permalink
MDL-41752 question statistics class moved and improved
Browse files Browse the repository at this point in the history
quiz_question_statistics_stats renamed to question_statistics_calculator
separate class question_statistics used to store calculated stats
and api changed, also code generally cleaned up.
  • Loading branch information
jamiepratt committed Sep 27, 2013
1 parent 522bef8 commit 515b3ae
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 363 deletions.
4 changes: 1 addition & 3 deletions mod/quiz/report/statistics/classes/calculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ class quiz_statistics_calculator {
* @param array $groupstudents students in this group.
* @param int $p number of positions (slots).
* @param float $sumofmarkvariance sum of mark variance, calculated as part of question statistics
* @return array with two elements:
* - integer $s Number of attempts included in the stats.
* - object $quizstats The statistics for overall attempt scores.
* @return quiz_statistics_calculated $quizstats The statistics for overall attempt scores.
*/
public function calculate($quizid, $currentgroup, $useallattempts, $groupstudents, $p, $sumofmarkvariance) {

Expand Down
115 changes: 52 additions & 63 deletions mod/quiz/report/statistics/report.php

Large diffs are not rendered by default.

22 changes: 10 additions & 12 deletions mod/quiz/report/statistics/statistics_question_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,27 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_statistics_question_table extends flexible_table {
/** @var object this question with a _stats field. */
/** @var object this question. */
protected $questiondata;

/**
* Constructor.
* @param $qid the id of the particular question whose statistics are being
* @param int $qid the id of the particular question whose statistics are being
* displayed.
*/
public function __construct($qid) {
parent::__construct('mod-quiz-report-statistics-question-table' . $qid);
}

/**
* Set up the columns and headers and other properties of the table and then
* call flexible_table::setup() method.
*
* @param moodle_url $reporturl the URL to redisplay this report.
* @param object $question a question with a _stats field
* @param bool $hassubqs
* @param moodle_url $reporturl
* @param object $questiondata
* @param integer $s number of attempts on this question.
* @param \core_question\statistics\responses\analyser $responesstats
*/
public function question_setup($reporturl, $questiondata,
question_response_analyser $responesstats) {
public function question_setup($reporturl, $questiondata, $s, \core_question\statistics\responses\analyser $responesstats) {
$this->questiondata = $questiondata;
$this->s = $s;

$this->define_baseurl($reporturl->out());
$this->collapsible(false);
Expand Down Expand Up @@ -137,10 +135,10 @@ protected function col_fraction($response) {
* @return string contents of this table cell.
*/
protected function col_frequency($response) {
if (!$this->questiondata->_stats->s) {
if (!$this->s) {
return '';
}

return $this->format_percentage($response->count / $this->questiondata->_stats->s);
return $this->format_percentage($response->count / $this->s);
}
}
124 changes: 63 additions & 61 deletions mod/quiz/report/statistics/statistics_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,69 +134,71 @@ public function statistics_setup($quiz, $cmid, $reporturl, $s) {

/**
* The question number.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_number($question) {
if ($question->_stats->subquestion) {
protected function col_number($questionstat) {
if ($questionstat->subquestion) {
return '';
}

return $question->number;
return $questionstat->question->number;
}

/**
* The question type icon.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_icon($question) {
return print_question_icon($question, true);
protected function col_icon($questionstat) {
return print_question_icon($questionstat->question, true);
}

/**
* Actions that can be performed on the question by this user (e.g. edit or preview).
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_actions($question) {
return quiz_question_action_icons($this->quiz, $this->cmid, $question, $this->baseurl);
protected function col_actions($questionstat) {
return quiz_question_action_icons($this->quiz, $this->cmid, $questionstat->question, $this->baseurl);
}

/**
* The question type name.
* @param object $question containst the data to display.
*
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_qtype($question) {
return question_bank::get_qtype_name($question->qtype);
protected function col_qtype($questionstat) {
return question_bank::get_qtype_name($questionstat->question->qtype);
}

/**
* The question name.
* @param object $question containst the data to display.
*
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_name($question) {
$name = $question->name;
protected function col_name($questionstat) {
$name = $questionstat->question->name;

if ($this->is_downloading()) {
return $name;
}

$url = null;
if ($question->_stats->subquestion) {
$url = new moodle_url($this->baseurl, array('qid' => $question->id));
} else if ($question->_stats->slot && $question->qtype != 'random') {
$url = new moodle_url($this->baseurl, array('slot' => $question->_stats->slot));
if ($questionstat->subquestion) {
$url = new moodle_url($this->baseurl, array('qid' => $questionstat->questionid));
} else if ($questionstat->slot && $questionstat->question->qtype != 'random') {
$url = new moodle_url($this->baseurl, array('slot' => $questionstat->slot));
}

if ($url) {
$name = html_writer::link($url, $name,
array('title' => get_string('detailedanalysis', 'quiz_statistics')));
}

if ($this->is_dubious_question($question)) {
if ($this->is_dubious_question($questionstat)) {
$name = html_writer::tag('div', $name, array('class' => 'dubious'));
}

Expand All @@ -205,82 +207,82 @@ protected function col_name($question) {

/**
* The number of attempts at this question.
* @param object $question containst the data to display.
*
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_s($question) {
if (!isset($question->_stats->s)) {
protected function col_s($questionstat) {
if (!isset($questionstat->s)) {
return 0;
}

return $question->_stats->s;
return $questionstat->s;
}

/**
* The facility index (average fraction).
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_facility($question) {
if (is_null($question->_stats->facility)) {
protected function col_facility($questionstat) {
if (is_null($questionstat->facility)) {
return '';
}

return number_format($question->_stats->facility*100, 2) . '%';
return number_format($questionstat->facility*100, 2) . '%';
}

/**
* The standard deviation of the fractions.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_sd($question) {
if (is_null($question->_stats->sd) || $question->_stats->maxmark == 0) {
protected function col_sd($questionstat) {
if (is_null($questionstat->sd) || $questionstat->maxmark == 0) {
return '';
}

return number_format($question->_stats->sd*100 / $question->_stats->maxmark, 2) . '%';
return number_format($questionstat->sd*100 / $questionstat->maxmark, 2) . '%';
}

/**
* An estimate of the fraction a student would get by guessing randomly.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_random_guess_score($question) {
if (is_null($question->_stats->randomguessscore)) {
protected function col_random_guess_score($questionstat) {
if (is_null($questionstat->randomguessscore)) {
return '';
}

return number_format($question->_stats->randomguessscore * 100, 2).'%';
return number_format($questionstat->randomguessscore * 100, 2).'%';
}

/**
* The intended question weight. Maximum mark for the question as a percentage
* of maximum mark for the quiz. That is, the indended influence this question
* on the student's overall mark.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_intended_weight($question) {
return quiz_report_scale_summarks_as_percentage(
$question->_stats->maxmark, $this->quiz);
protected function col_intended_weight($questionstat) {
return quiz_report_scale_summarks_as_percentage($questionstat->maxmark, $this->quiz);
}

/**
* The effective question weight. That is, an estimate of the actual
* influence this question has on the student's overall mark.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_effective_weight($question) {
protected function col_effective_weight($questionstat) {
global $OUTPUT;

if ($question->_stats->subquestion) {
if ($questionstat->subquestion) {
return '';
}

if ($question->_stats->negcovar) {
if ($questionstat->negcovar) {
$negcovar = get_string('negcovar', 'quiz_statistics');

if (!$this->is_downloading()) {
Expand All @@ -292,49 +294,49 @@ protected function col_effective_weight($question) {
return $negcovar;
}

return number_format($question->_stats->effectiveweight, 2) . '%';
return number_format($questionstat->effectiveweight, 2) . '%';
}

/**
* Discrimination index. This is the product moment correlation coefficient
* between the fraction for this qestion, and the average fraction for the
* between the fraction for this question, and the average fraction for the
* other questions in this quiz.
* @param object $question containst the data to display.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_discrimination_index($question) {
if (!is_numeric($question->_stats->discriminationindex)) {
return $question->_stats->discriminationindex;
protected function col_discrimination_index($questionstat) {
if (!is_numeric($questionstat->discriminationindex)) {
return $questionstat->discriminationindex;
}

return number_format($question->_stats->discriminationindex, 2) . '%';
return number_format($questionstat->discriminationindex, 2) . '%';
}

/**
* Discrimination efficiency, similar to, but different from, the Discrimination index.
* @param object $question containst the data to display.
*
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return string contents of this table cell.
*/
protected function col_discriminative_efficiency($question) {
if (!is_numeric($question->_stats->discriminativeefficiency)) {
protected function col_discriminative_efficiency($questionstat) {
if (!is_numeric($questionstat->discriminativeefficiency)) {
return '';
}

return number_format($question->_stats->discriminativeefficiency, 2) . '%';
return number_format($questionstat->discriminativeefficiency, 2) . '%';
}

/**
* This method encapsulates the test for wheter a question should be considered dubious.
* @param object question the question object with a property _stats which
* includes all the stats for the question.
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
* @return bool is this question possibly not pulling it's weight?
*/
protected function is_dubious_question($question) {
if (!is_numeric($question->_stats->discriminativeefficiency)) {
protected function is_dubious_question($questionstat) {
if (!is_numeric($questionstat->discriminativeefficiency)) {
return false;
}

return $question->_stats->discriminativeefficiency < 15;
return $questionstat->discriminativeefficiency < 15;
}

public function wrap_html_start() {
Expand Down
21 changes: 13 additions & 8 deletions mod/quiz/report/statistics/tests/statistics_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

global $CFG;
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/engine/statistics.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');

Expand All @@ -39,7 +38,13 @@
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_question_statistics extends question_statistics {
class testable_question_statistics extends \core_question\statistics\questions\calculator {

/**
* @var object[]
*/
protected $lateststeps;

public function set_step_data($states) {
$this->lateststeps = $states;
}
Expand Down Expand Up @@ -95,9 +100,9 @@ public function test_qstats() {
// Data is taken from questions mostly generated by
// contrib/tools/generators/generator.php.
$questions = $this->get_records_from_csv(__DIR__.'/fixtures/mdl_question.csv');
$this->qstats = new testable_question_statistics($questions, 22, 10045.45455);
$this->qstats->set_step_data($steps);
$this->qstats->calculate(null);
$calculator = new testable_question_statistics($questions);
$calculator->set_step_data($steps);
list($this->qstats, ) = $calculator->calculate(null);

// Values expected are taken from contrib/tools/quiz_tools/stats.xls.
$facility = array(0, 0, 0, 0, null, null, null, 41.19318182, 81.36363636,
Expand Down Expand Up @@ -125,13 +130,13 @@ public function test_qstats() {
}

public function qstats_q_fields($fieldname, $values, $multiplier=1) {
foreach ($this->qstats->questions as $question) {
foreach ($this->qstats as $qstat) {
$value = array_shift($values);
if ($value !== null) {
$this->assertEquals($question->_stats->{$fieldname} * $multiplier,
$this->assertEquals($qstat->{$fieldname} * $multiplier,
$value, '', 1E-6);
} else {
$this->assertEquals($question->_stats->{$fieldname} * $multiplier, $value);
$this->assertEquals($qstat->{$fieldname} * $multiplier, $value);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function test_walkthrough_from_csv($quizsettings, $csvdata) {
$this->check_attempts_results($csvdata['results'], $attemptids);

$this->report = new testable_quiz_statistics_report();
list($quizstats, $questions, $subquestions) = $this->report->get_stats($this->quiz);
list($quizstats, $questionstats, $subquestionstats) = $this->report->get_stats($this->quiz);

// These quiz stats and the question stats found in qstats00.csv were calculated independently in spreadsheet which is
// available in open document or excel format here :
Expand Down Expand Up @@ -133,7 +133,7 @@ public function test_walkthrough_from_csv($quizsettings, $csvdata) {
}
$slot = $slotqstats['slot'];
$delta = abs($slotqstat) * $precision;
$actual = $questions[$slot]->_stats->{$statname};
$actual = $questionstats[$slot]->{$statname};
$this->assertEquals(floatval($slotqstat), $actual, "$statname for slot $slot", $delta);
}
}
Expand Down
Loading

0 comments on commit 515b3ae

Please sign in to comment.