Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Question statistics calculations class. Used in the quiz statistics report but also available for use elsewhere.
 *
 * @package    core
 * @subpackage questionbank
 * @copyright  2013 Open University
 * @author     Jamie Pratt <me@jamiep.org>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace core_question\statistics\questions;
defined('MOODLE_INTERNAL') || die();

/**
 * This class is used to return the stats as calculated by {@link \core_question\statistics\questions\calculator}
 *
 * @copyright 2013 Open University
 * @author    Jamie Pratt <me@jamiep.org>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class calculated {

    public $questionid;

    // These first fields are the final fields cached in the db and shown in reports.

    // See : http://docs.moodle.org/dev/Quiz_statistics_calculations#Position_statistics .

    public $slot = null;

    /**
     * @var null|integer if this property is not null then this is the stats for a variant of a question or when inherited by
     *                   calculated_for_subquestion and not null then this is the stats for a variant of a sub question.
     */
    public $variant = null;

    /**
     * @var bool is this a sub question.
     */
    public $subquestion = false;

    /**
     * @var string if this stat has been picked as a min, median or maximum facility value then this string says which stat this
     *                  is. Prepended to question name for display.
     */
    public $minmedianmaxnotice = '';

    /**
     * @var int total attempts at this question.
     */
    public $s = 0;

    /**
     * @var float effective weight of this question.
     */
    public $effectiveweight;

    /**
     * @var bool is covariance of this questions mark with other question marks negative?
     */
    public $negcovar;

    /**
     * @var float
     */
    public $discriminationindex;

    /**
     * @var float
     */
    public $discriminativeefficiency;

    /**
     * @var float standard deviation
     */
    public $sd;

    /**
     * @var float
     */
    public $facility;

    /**
     * @var float max mark achievable for this question.
     */
    public $maxmark;

    /**
     * @var string comma separated list of the positions in which this question appears.
     */
    public $positions;

    /**
     * @var null|float The average score that students would have got by guessing randomly. Or null if not calculable.
     */
    public $randomguessscore = null;

    // End of fields in db.

    protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
        'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore', 'variant');

    // Fields used for intermediate calculations.

    public $totalmarks = 0;

    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;

    public $covariancesum = 0;

    public $covariancemaxsum = 0;

    public $subquestions = '';

    public $covariancewithoverallmarksum = 0;

    public $markarray = array();

    public $othermarksarray = array();

    public $markaverage;

    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;
    public $covariancemax;
    public $covariancewithoverallmark;

    /**
     * @var object full question data
     */
    public $question;

    /**
     * An array of calculated stats for each variant of the question. Even when there is just one variant we still calculate this
     * data as there is no way to know if there are variants before we have finished going through the attempt data one time.
     *
     * @var calculated[] $variants
     */
    public $variantstats = array();

    /**
     * Set if this record has been retrieved from cache. This is the time that the statistics were calculated.
     *
     * @var integer
     */
    public $timemodified;

    /**
     * Set up a calculated instance ready to store a question's (or a variant of a slot's question's)
     * stats for one slot in the quiz.
     *
     * @param null|object     $question
     * @param null|int     $slot
     * @param null|int $variant
     */
    public function __construct($question = null, $slot = null, $variant = null) {
        if ($question !== null) {
            $this->questionid = $question->id;
            $this->maxmark = $question->maxmark;
            $this->positions = $question->number;
            $this->question = $question;
        }
        if ($slot !== null) {
            $this->slot = $slot;
        }
        if ($variant !== null) {
            $this->variant = $variant;
        }
    }

    /**
     * Used to determine which random questions pull sub questions from the same pools. Where pool means category and possibly
     * all the sub categories of that category.
     *
     * @return null|string represents the pool of questions from which this question draws if it is random, or null if not.
     */
    public function random_selector_string() {
        if ($this->question->qtype == 'random') {
            return $this->question->category .'/'. $this->question->questiontext;
        } else {
            return null;
        }
    }

    /**
     * Cache calculated stats stored in this object in 'question_statistics' table.
     *
     * @param \qubaid_condition $qubaids
> * @param int|null $timemodified the modified time to store. Defaults to the current time.
*/
< public function cache($qubaids) {
> public function cache($qubaids, $timemodified = null) {
global $DB; $toinsert = new \stdClass(); $toinsert->hashcode = $qubaids->get_hash_code();
< $toinsert->timemodified = time();
> $toinsert->timemodified = $timemodified ?? time();
foreach ($this->fieldsindb as $field) { $toinsert->{$field} = $this->{$field}; } $DB->insert_record('question_statistics', $toinsert, false); if ($this->get_variants()) { foreach ($this->variantstats as $variantstat) {
< $variantstat->cache($qubaids);
> $variantstat->cache($qubaids, $timemodified);
} } } /** * Load properties of this class from db record. * * @param object $record Given a record from 'question_statistics' copy stats from record to properties. */ public function populate_from_record($record) { foreach ($this->fieldsindb as $field) { $this->$field = $record->$field; } $this->timemodified = $record->timemodified; } /** * Sort the variants of this question by variant number. */ public function sort_variants() { ksort($this->variantstats); } /** * Get any sub question ids for this question. * * @return int[] array of sub-question ids or empty array if there are none. */ public function get_sub_question_ids() { if ($this->subquestions !== '') { return explode(',', $this->subquestions); } else { return array(); } } /** * Array of variants that have appeared in the attempt data for this question. Or an empty array if there is only one variant. * * @return int[] the variant nos. */ public function get_variants() { $variants = array_keys($this->variantstats); if (count($variants) > 1 || reset($variants) != 1) { return $variants; } else { return array(); } } /** * Do we break down the stats for this question by variant or not? * * @return bool Do we? */ public function break_down_by_variant() { $qtype = \question_bank::get_qtype($this->question->qtype); return $qtype->break_down_stats_and_response_analysis_by_variant($this->question); } /** * Delete the data structure for storing variant stats. */ public function clear_variants() { $this->variantstats = array(); } }