Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Question statistics calculations class. Used in the quiz statistics report but also available for use elsewhere.
  19   *
  20   * @package    core
  21   * @subpackage questionbank
  22   * @copyright  2013 Open University
  23   * @author     Jamie Pratt <me@jamiep.org>
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  namespace core_question\statistics\questions;
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * This class is used to return the stats as calculated by {@link \core_question\statistics\questions\calculator}
  32   *
  33   * @copyright 2013 Open University
  34   * @author    Jamie Pratt <me@jamiep.org>
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class calculated {
  38  
  39      public $questionid;
  40  
  41      // These first fields are the final fields cached in the db and shown in reports.
  42  
  43      // See : http://docs.moodle.org/dev/Quiz_statistics_calculations#Position_statistics .
  44  
  45      public $slot = null;
  46  
  47      /**
  48       * @var null|integer if this property is not null then this is the stats for a variant of a question or when inherited by
  49       *                   calculated_for_subquestion and not null then this is the stats for a variant of a sub question.
  50       */
  51      public $variant = null;
  52  
  53      /**
  54       * @var bool is this a sub question.
  55       */
  56      public $subquestion = false;
  57  
  58      /**
  59       * @var string if this stat has been picked as a min, median or maximum facility value then this string says which stat this
  60       *                  is. Prepended to question name for display.
  61       */
  62      public $minmedianmaxnotice = '';
  63  
  64      /**
  65       * @var int total attempts at this question.
  66       */
  67      public $s = 0;
  68  
  69      /**
  70       * @var float effective weight of this question.
  71       */
  72      public $effectiveweight;
  73  
  74      /**
  75       * @var bool is covariance of this questions mark with other question marks negative?
  76       */
  77      public $negcovar;
  78  
  79      /**
  80       * @var float
  81       */
  82      public $discriminationindex;
  83  
  84      /**
  85       * @var float
  86       */
  87      public $discriminativeefficiency;
  88  
  89      /**
  90       * @var float standard deviation
  91       */
  92      public $sd;
  93  
  94      /**
  95       * @var float
  96       */
  97      public $facility;
  98  
  99      /**
 100       * @var float max mark achievable for this question.
 101       */
 102      public $maxmark;
 103  
 104      /**
 105       * @var string comma separated list of the positions in which this question appears.
 106       */
 107      public $positions;
 108  
 109      /**
 110       * @var null|float The average score that students would have got by guessing randomly. Or null if not calculable.
 111       */
 112      public $randomguessscore = null;
 113  
 114      // End of fields in db.
 115  
 116      protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
 117          'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore', 'variant');
 118  
 119      // Fields used for intermediate calculations.
 120  
 121      public $totalmarks = 0;
 122  
 123      public $totalothermarks = 0;
 124  
 125      /**
 126       * @var float The total of marks achieved for all positions in all attempts where this item was seen.
 127       */
 128      public $totalsummarks = 0;
 129  
 130      public $markvariancesum = 0;
 131  
 132      public $othermarkvariancesum = 0;
 133  
 134      public $covariancesum = 0;
 135  
 136      public $covariancemaxsum = 0;
 137  
 138      public $subquestions = '';
 139  
 140      public $covariancewithoverallmarksum = 0;
 141  
 142      public $markarray = array();
 143  
 144      public $othermarksarray = array();
 145  
 146      public $markaverage;
 147  
 148      public $othermarkaverage;
 149  
 150      /**
 151       * @var float The average for all attempts, of the sum of the marks for all positions in which this item appeared.
 152       */
 153      public $summarksaverage;
 154  
 155      public $markvariance;
 156      public $othermarkvariance;
 157      public $covariance;
 158      public $covariancemax;
 159      public $covariancewithoverallmark;
 160  
 161      /**
 162       * @var object full question data
 163       */
 164      public $question;
 165  
 166      /**
 167       * An array of calculated stats for each variant of the question. Even when there is just one variant we still calculate this
 168       * data as there is no way to know if there are variants before we have finished going through the attempt data one time.
 169       *
 170       * @var calculated[] $variants
 171       */
 172      public $variantstats = array();
 173  
 174      /**
 175       * Set if this record has been retrieved from cache. This is the time that the statistics were calculated.
 176       *
 177       * @var integer
 178       */
 179      public $timemodified;
 180  
 181      /**
 182       * Set up a calculated instance ready to store a question's (or a variant of a slot's question's)
 183       * stats for one slot in the quiz.
 184       *
 185       * @param null|object     $question
 186       * @param null|int     $slot
 187       * @param null|int $variant
 188       */
 189      public function __construct($question = null, $slot = null, $variant = null) {
 190          if ($question !== null) {
 191              $this->questionid = $question->id;
 192              $this->maxmark = $question->maxmark;
 193              $this->positions = $question->number;
 194              $this->question = $question;
 195          }
 196          if ($slot !== null) {
 197              $this->slot = $slot;
 198          }
 199          if ($variant !== null) {
 200              $this->variant = $variant;
 201          }
 202      }
 203  
 204      /**
 205       * Used to determine which random questions pull sub questions from the same pools. Where pool means category and possibly
 206       * all the sub categories of that category.
 207       *
 208       * @return null|string represents the pool of questions from which this question draws if it is random, or null if not.
 209       */
 210      public function random_selector_string() {
 211          if ($this->question->qtype == 'random') {
 212              return $this->question->category .'/'. $this->question->questiontext;
 213          } else {
 214              return null;
 215          }
 216      }
 217  
 218      /**
 219       * Cache calculated stats stored in this object in 'question_statistics' table.
 220       *
 221       * @param \qubaid_condition $qubaids
 222       * @param int|null $timemodified the modified time to store. Defaults to the current time.
 223       */
 224      public function cache($qubaids, $timemodified = null) {
 225          global $DB;
 226          $toinsert = new \stdClass();
 227          $toinsert->hashcode = $qubaids->get_hash_code();
 228          $toinsert->timemodified = $timemodified ?? time();
 229          foreach ($this->fieldsindb as $field) {
 230              $toinsert->{$field} = $this->{$field};
 231          }
 232          $DB->insert_record('question_statistics', $toinsert, false);
 233  
 234          if ($this->get_variants()) {
 235              foreach ($this->variantstats as $variantstat) {
 236                  $variantstat->cache($qubaids, $timemodified);
 237              }
 238          }
 239      }
 240  
 241      /**
 242       * Load properties of this class from db record.
 243       *
 244       * @param object $record Given a record from 'question_statistics' copy stats from record to properties.
 245       */
 246      public function populate_from_record($record) {
 247          foreach ($this->fieldsindb as $field) {
 248              $this->$field = $record->$field;
 249          }
 250          $this->timemodified = $record->timemodified;
 251      }
 252  
 253      /**
 254       * Sort the variants of this question by variant number.
 255       */
 256      public function sort_variants() {
 257          ksort($this->variantstats);
 258      }
 259  
 260      /**
 261       * Get any sub question ids for this question.
 262       *
 263       * @return int[] array of sub-question ids or empty array if there are none.
 264       */
 265      public function get_sub_question_ids() {
 266          if ($this->subquestions !== '') {
 267              return explode(',', $this->subquestions);
 268          } else {
 269              return array();
 270          }
 271      }
 272  
 273      /**
 274       * Array of variants that have appeared in the attempt data for this question. Or an empty array if there is only one variant.
 275       *
 276       * @return int[] the variant nos.
 277       */
 278      public function get_variants() {
 279          $variants = array_keys($this->variantstats);
 280          if (count($variants) > 1 || reset($variants) != 1) {
 281              return $variants;
 282          } else {
 283              return array();
 284          }
 285      }
 286  
 287      /**
 288       * Do we break down the stats for this question by variant or not?
 289       *
 290       * @return bool Do we?
 291       */
 292      public function break_down_by_variant() {
 293          $qtype = \question_bank::get_qtype($this->question->qtype);
 294          return $qtype->break_down_stats_and_response_analysis_by_variant($this->question);
 295      }
 296  
 297  
 298      /**
 299       * Delete the data structure for storing variant stats.
 300       */
 301      public function clear_variants() {
 302          $this->variantstats = array();
 303      }
 304  }