MDL-41758 quiz statistics : link to full break down of stats for slots
[moodle.git] / question / classes / statistics / questions / calculated.php
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/>.
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  */
27 namespace core_question\statistics\questions;
28 defined('MOODLE_INTERNAL') || die();
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 {
39     public $questionid;
41     // These first fields are the final fields cached in the db and shown in reports.
43     // See : http://docs.moodle.org/dev/Quiz_statistics_calculations#Position_statistics .
45     public $slot = null;
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;
53     /**
54      * @var bool is this a sub question.
55      */
56     public $subquestion = false;
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 = '';
64     /**
65      * @var int total attempts at this question.
66      */
67     public $s = 0;
69     /**
70      * @var float effective weight of this question.
71      */
72     public $effectiveweight;
74     /**
75      * @var bool is covariance of this questions mark with other question marks negative?
76      */
77     public $negcovar;
79     /**
80      * @var float
81      */
82     public $discriminationindex;
84     /**
85      * @var float
86      */
87     public $discriminativeefficiency;
89     /**
90      * @var float standard deviation
91      */
92     public $sd;
94     /**
95      * @var float
96      */
97     public $facility;
99     /**
100      * @var float max mark achievable for this question.
101      */
102     public $maxmark;
104     /**
105      * @var string comma separated list of the positions in which this question appears.
106      */
107     public $positions;
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;
114     // End of fields in db.
116     protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
117         'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore', 'variant');
119     // Fields used for intermediate calculations.
121     public $totalmarks = 0;
123     public $totalothermarks = 0;
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;
130     public $markvariancesum = 0;
132     public $othermarkvariancesum = 0;
134     public $covariancesum = 0;
136     public $covariancemaxsum = 0;
138     public $subquestions = '';
140     public $covariancewithoverallmarksum = 0;
142     public $markarray = array();
144     public $othermarksarray = array();
146     public $markaverage;
148     public $othermarkaverage;
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;
155     public $markvariance;
156     public $othermarkvariance;
157     public $covariance;
158     public $covariancemax;
159     public $covariancewithoverallmark;
161     /**
162      * @var object full question data
163      */
164     public $question;
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();
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;
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     }
204     /**
205      * @return null|string a string that represents the pool of questions from this question draws if it random or null if not.
206      */
207     public function random_selector_string() {
208         if ($this->question->qtype == 'random') {
209             return $this->question->category .'/'. $this->question->questiontext;
210         } else {
211             return null;
212         }
213     }
215     /**
216      * Cache calculated stats stored in this object in 'question_statistics' table.
217      *
218      * @param \qubaid_condition $qubaids
219      */
220     public function cache($qubaids) {
221         global $DB;
222         $toinsert = new \stdClass();
223         $toinsert->hashcode = $qubaids->get_hash_code();
224         $toinsert->timemodified = time();
225         foreach ($this->fieldsindb as $field) {
226             $toinsert->{$field} = $this->{$field};
227         }
228         $DB->insert_record('question_statistics', $toinsert, false);
230         if (count($this->variantstats) > 1) {
231             foreach ($this->variantstats as $variantstat) {
232                 $variantstat->cache($qubaids);
233             }
234         }
235     }
237     /**
238      * @param object $record Given a record from 'question_statistics' copy stats from record to properties.
239      */
240     public function populate_from_record($record) {
241         foreach ($this->fieldsindb as $field) {
242             $this->$field = $record->$field;
243         }
244         $this->timemodified = $record->timemodified;
245     }
247     public function sort_variants() {
248         ksort($this->variantstats);
249     }
251     /**
252      * @return int[] array of sub-question ids or empty array if there are none.
253      */
254     public function get_sub_question_ids() {
255         if ($this->subquestions !== '') {
256             return explode(',', $this->subquestions);
257         } else {
258             return array();
259         }
260     }
262     /**
263      * Array of variants that have appeared in the attempt data for this question.
264      *
265      * @return int[]
266      */
267     public function get_variants() {
268         $variants = array_keys($this->variantstats);
269         if (count($variants) > 1) {
270             return $variants;
271         } else {
272             return array();
273         }
274     }