weekly release 3.10.4+
[moodle.git] / question / behaviour / deferredcbm / behaviourtype.php
CommitLineData
f3460297
TH
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 behaviour type for deferred feedback with CBM behaviour.
19 *
20 * @package qbehaviour_deferredcbm
21 * @copyright 2012 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25
26defined('MOODLE_INTERNAL') || die();
27
1fcf0ca8 28require_once(__DIR__ . '/../deferredfeedback/behaviourtype.php');
f3460297
TH
29
30
31/**
32 * Question behaviour type information for deferred feedback with CBM behaviour.
33 *
34 * @copyright 2012 The Open University
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class qbehaviour_deferredcbm_type extends qbehaviour_deferredfeedback_type {
38 public function adjust_random_guess_score($fraction) {
39 return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());
40 }
e74aa0aa
TH
41
42 public function summarise_usage(question_usage_by_activity $quba, question_display_options $options) {
a5687852 43 global $OUTPUT;
e74aa0aa
TH
44 $summarydata = parent::summarise_usage($quba, $options);
45
46 if ($options->marks < question_display_options::MARK_AND_MAX) {
47 return $summarydata;
48 }
49
50 // Prepare accumulators to hold the data we are about to collect.
51 $notansweredcount = 0;
52 $notansweredweight = 0;
53 $attemptcount = array(
54 question_cbm::HIGH => 0,
55 question_cbm::MED => 0,
56 question_cbm::LOW => 0,
57 );
58 $totalweight = array(
59 question_cbm::HIGH => 0,
60 question_cbm::MED => 0,
61 question_cbm::LOW => 0,
62 );
63 $totalrawscore = array(
64 question_cbm::HIGH => 0,
65 question_cbm::MED => 0,
66 question_cbm::LOW => 0,
67 );
68 $totalcbmscore = array(
69 question_cbm::HIGH => 0,
70 question_cbm::MED => 0,
71 question_cbm::LOW => 0,
72 );
73
74 // Loop through the data, and add it to the accumulators.
75 foreach ($quba->get_attempt_iterator() as $qa) {
76 if (strpos($qa->get_behaviour_name(), 'cbm') === false || $qa->get_max_mark() < 0.0000005) {
77 continue;
78 }
79
80 $gradedstep = $qa->get_last_step_with_behaviour_var('_rawfraction');
81
82 if (!$gradedstep->has_behaviour_var('_rawfraction')) {
83 $notansweredcount += 1;
84 $notansweredweight += $qa->get_max_mark();
85 continue;
86 }
87
88 $certainty = $qa->get_last_behaviour_var('certainty');
2bf83cb2
TH
89 if (is_null($certainty) || $certainty == -1) {
90 // Certainty -1 has never been used in standard Moodle, but is
91 // used in Tony-Gardiner Medwin's patches to mean 'No idea' which
92 // we intend to implement: MDL-42077. In the mean time, avoid
93 // errors for people who have used TGM's patches.
e74aa0aa
TH
94 $certainty = question_cbm::default_certainty();
95 }
96
97 $attemptcount[$certainty] += 1;
98 $totalweight[$certainty] += $qa->get_max_mark();
99 $totalrawscore[$certainty] += $qa->get_max_mark() * $gradedstep->get_behaviour_var('_rawfraction');
100 $totalcbmscore[$certainty] += $qa->get_mark();
101 }
102
103 // Hence compute some statistics.
104 $totalquestions = $notansweredcount + array_sum($attemptcount);
105 $grandtotalweight = $notansweredweight + array_sum($totalweight);
106 $accuracy = array_sum($totalrawscore) / $grandtotalweight;
107 $averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
108 $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
109 $accuracyandbonus = $accuracy + $cbmbonus;
110
a5687852
TH
111 // Add a note to explain the max mark.
112 $summarydata['qbehaviour_cbm_grade_explanation'] = array(
113 'title' => '',
114 'content' => html_writer::tag('i', get_string('cbmgradeexplanation', 'qbehaviour_deferredcbm')) .
115 $OUTPUT->help_icon('cbmgrades', 'qbehaviour_deferredcbm'),
116 );
117
e74aa0aa
TH
118 // Now we can start generating some of the summary: overall values.
119 $summarydata['qbehaviour_cbm_entire_quiz_heading'] = array(
120 'title' => '',
121 'content' => html_writer::tag('h3',
122 get_string('forentirequiz', 'qbehaviour_deferredcbm', $totalquestions),
123 array('class' => 'qbehaviour_deferredcbm_summary_heading')),
124 );
125 $summarydata['qbehaviour_cbm_entire_quiz_cbm_average'] = array(
126 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
127 'content' => format_float($averagecbm, $options->markdp),
128 );
129 $summarydata['qbehaviour_cbm_entire_quiz_accuracy'] = array(
130 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
131 'content' => $this->format_probability($accuracy, 1),
132 );
133 $summarydata['qbehaviour_cbm_entire_quiz_cbm_bonus'] = array(
134 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
135 'content' => $this->format_probability($cbmbonus, 1),
136 );
137 $summarydata['qbehaviour_cbm_entire_quiz_accuracy_and_bonus'] = array(
138 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
139 'content' => $this->format_probability($accuracyandbonus, 1),
140 );
141
142 if ($notansweredcount && array_sum($attemptcount) > 0) {
143 $totalquestions = array_sum($attemptcount);
144 $grandtotalweight = array_sum($totalweight);
145 $accuracy = array_sum($totalrawscore) / $grandtotalweight;
146 $averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
147 $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
148 $accuracyandbonus = $accuracy + $cbmbonus;
149
150 $summarydata['qbehaviour_cbm_answered_quiz_heading'] = array(
151 'title' => '',
152 'content' => html_writer::tag('h3',
153 get_string('foransweredquestions', 'qbehaviour_deferredcbm', $totalquestions),
154 array('class' => 'qbehaviour_deferredcbm_summary_heading')),
155 );
156 $summarydata['qbehaviour_cbm_answered_quiz_cbm_average'] = array(
157 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
158 'content' => format_float($averagecbm, $options->markdp),
159 );
160 $summarydata['qbehaviour_cbm_answered_quiz_accuracy'] = array(
161 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
162 'content' => $this->format_probability($accuracy, 1),
163 );
164 $summarydata['qbehaviour_cbm_answered_quiz_cbm_bonus'] = array(
165 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
166 'content' => $this->format_probability($cbmbonus, 1),
167 );
168 $summarydata['qbehaviour_cbm_answered_quiz_accuracy_and_bonus'] = array(
169 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
170 'content' => $this->format_probability($accuracyandbonus, 1),
171 );
172 }
173
174 // Now per-certainty level values.
175 $summarydata['qbehaviour_cbm_judgement_heading'] = array(
176 'title' => '',
177 'content' => html_writer::tag('h3', get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'),
178 array('class' => 'qbehaviour_deferredcbm_summary_heading')),
179 );
180
181 foreach ($attemptcount as $certainty => $count) {
182 $key = 'qbehaviour_cbm_judgement' . $certainty;
183 $title = question_cbm::get_short_string($certainty);
184
185 if ($count == 0) {
186 $summarydata[$key] = array(
187 'title' => $title,
188 'content' => get_string('noquestions', 'qbehaviour_deferredcbm'),
189 );
190 continue;
191 }
192
193 $lowerlimit = question_cbm::optimal_probablility_low($certainty);
194 $upperlimit = question_cbm::optimal_probablility_high($certainty);
195 $fraction = $totalrawscore[$certainty] / $totalweight[$certainty];
196
197 $a = new stdClass();
198 $a->responses = $count;
199 $a->idealrangelow = $this->format_probability($lowerlimit);
200 $a->idealrangehigh = $this->format_probability($upperlimit);
201 $a->fraction = html_writer::tag('span', $this->format_probability($fraction),
202 array('class' => 'qbehaviour_deferredcbm_actual_percentage'));
203
204 if ($fraction < $lowerlimit - 0.0000005) {
205 if ((pow($fraction - $lowerlimit, 2) * $count) > 0.5) { // Rough indicator of significance: t > 1.5 or 1.8.
206 $judgement = 'overconfident';
207 } else {
208 $judgement = 'slightlyoverconfident';
209 }
210 } else if ($fraction > $upperlimit + 0.0000005) {
211 if ((pow($fraction - $upperlimit, 2) * $count) > 0.5) {
212 $judgement = 'underconfident';
213 } else {
214 $judgement = 'slightlyunderconfident';
215 }
216 } else {
217 $judgement = 'judgementok';
218 }
219 $a->judgement = html_writer::tag('span', get_string($judgement, 'qbehaviour_deferredcbm'),
220 array('class' => 'qbehaviour_deferredcbm_' . $judgement));
221
222 $summarydata[$key] = array(
223 'title' => $title,
224 'content' => get_string('judgementsummary', 'qbehaviour_deferredcbm', $a),
225 );
226 }
227
228 return $summarydata;
229 }
230
231 protected function format_probability($probability, $dp = 0) {
232 return format_float($probability * 100, $dp) . '%';
233 }
234
235 public function calculate_bonus($total, $accuracy) {
236 $expectedforaccuracy = max(
237 $accuracy * question_cbm::adjust_fraction(1, question_cbm::LOW) +
238 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::LOW),
239 $accuracy * question_cbm::adjust_fraction(1, question_cbm::MED) +
240 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::MED),
241 $accuracy * question_cbm::adjust_fraction(1, question_cbm::HIGH) +
242 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::HIGH)
243 );
244 // The constant 0.1 here is determinted empirically from looking at lots
245 // for CBM quiz results. See www.ucl.ac.uk/~ucgbarg/tea/IUPS_2013a.pdf.
246 // It approximately maximises the reliability of accuracy + bonus.
247 return 0.1 * ($total - $expectedforaccuracy);
248 }
f3460297 249}