You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
8.7 KiB

<?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
*/
public function cache($qubaids) {
global $DB;
$toinsert = new \stdClass();
$toinsert->hashcode = $qubaids->get_hash_code();
$toinsert->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);
}
}
}
/**
* 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();
}
}