MDL-14625 more tolerant to missing subquestions
[moodle.git] / mod / quiz / report / statistics / report.php
CommitLineData
0c1c764e 1<?php
2/**
3 * This script lists student attempts
4 *
5 * @version $Id$
6 * @author Martin Dougiamas, Tim Hunt and others.
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package quiz
9 *//** */
10
11require_once($CFG->libdir.'/tablelib.php');
12require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_form.php');
13require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_table.php');
14
15class quiz_report extends quiz_default_report {
16
17 /**
18 * Display the report.
19 */
20 function display($quiz, $cm, $course) {
21 global $CFG, $DB;
22
23 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
24
25 $download = optional_param('download', '', PARAM_ALPHA);
26
27 $pageoptions = array();
28 $pageoptions['id'] = $cm->id;
29 $pageoptions['q'] = $quiz->id;
30 $pageoptions['mode'] = 'statistics';
31
32 $reporturl = new moodle_url($CFG->wwwroot.'/mod/quiz/report.php', $pageoptions);
33
34 $mform = new mod_quiz_report_statistics($reporturl);
35 if ($fromform = $mform->get_data()){
36 $useallattempts = $fromform->useallattempts;
37 if ($fromform->useallattempts){
38 set_user_preference('quiz_report_statistics_useallattempts', $fromform->useallattempts);
39 } else {
40 unset_user_preference('quiz_report_statistics_useallattempts');
41 }
42 } else {
43 $useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0);
44 }
45
46 /// find out current groups mode
47 $currentgroup = groups_get_activity_group($cm, true);
48
49 if (!empty($currentgroup)) {
50 // all users who can attempt quizzes and who are in the currently selected group
51 if (!$groupstudents = get_users_by_capability($context, 'mod/quiz:attempt','','','','',$currentgroup,'',false)){
52 $groupstudents = array();
53 }
54 $groupstudentslist = join(',', array_keys($groupstudents));
55 $allowedlist = $groupstudentslist;
56 }
57
c0250840 58 $questions = question_load_questions(quiz_questions_in_quiz($quiz->questions));
0c1c764e 59
c0250840 60 $table = new quiz_report_statistics_table();
0c1c764e 61 $table->is_downloading($download, get_string('reportstatistics','quiz_statistics'),
62 "$course->shortname ".format_string($quiz->name,true));
63 if (!$table->is_downloading()) {
64 // Only print headers if not asked to download data
65 $this->print_header_and_tabs($cm, $course, $quiz, "statistics");
66 }
67
68 if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
69 if (!$table->is_downloading()) {
70 groups_print_activity_menu($cm, $reporturl->out());
71 }
72 }
73
74
75 // Print information on the number of existing attempts
76 if (!$table->is_downloading()) { //do not print notices when downloading
77 print_heading(get_string('quizinformation', 'quiz_statistics'));
78 $quizinformationtable = new object();
79 $quizinformationtable->align = array('center', 'center');
80 $quizinformationtable->width = '60%';
81 $quizinformationtable->class = 'generaltable titlesleft';
82 $quizinformationtable->data = array();
83 $quizinformationtable->data[] = array(get_string('quizname', 'quiz_statistics'), $quiz->name);
84 $quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $course->fullname);
85 if ($cm->idnumber){
86 $quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $cm->idnumber);
87 }
88 if ($quiz->timeopen){
89 $quizinformationtable->data[] = array(get_string('quizopen', 'quiz'), userdate($quiz->timeopen));
90 }
91 if ($quiz->timeclose){
92 $quizinformationtable->data[] = array(get_string('quizclose', 'quiz'), userdate($quiz->timeclose));
93 }
94 if ($quiz->timeopen && $quiz->timeclose){
95 $quizinformationtable->data[] = array(get_string('duration'), format_time($quiz->timeclose - $quiz->timeopen));
96 }
97 print_table($quizinformationtable);
98 }
99 if (!$table->is_downloading()) {
100 // Print display options
101 $mform->set_data(array('useallattempts' => $useallattempts));
102 $mform->display();
103 }
08a7ead5 104 $fromqa = '{quiz_attempts} qa ';
105 $whereqa = 'quiz = ? AND preview=0 AND timefinish !=0 ';
0c1c764e 106 $sql = 'SELECT (attempt=1) AS isfirst, COUNT(1) AS countrecs, SUM(sumgrades) AS total ' .
08a7ead5 107 'FROM '.$fromqa.
108 'WHERE ' .$whereqa.
0c1c764e 109 'GROUP BY (attempt=1)';
110
040c36e3 111 //Calculating_MEAN_of_grades_for_all_attempts_by_students
112 //http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Calculating_MEAN_of_grades_for_all_attempts_by_students
08a7ead5 113 if (!$attempttotals = $DB->get_records_sql($sql, array($quiz->id))){
0c1c764e 114 print_heading(get_string('noattempts','quiz'));
115 return true;
116 } else {
117 $firstattempt = $attempttotals[1];
118 $allattempts = new object();
119 $allattempts->countrecs = $firstattempt->countrecs +
120 (isset($attempttotals[0])?$attempttotals[0]->countrecs:0);
121 $allattempts->total = $firstattempt->total +
122 (isset($attempttotals[0])?$attempttotals[0]->total:0);
123 }
124
125 if (!$table->is_downloading()) {
126 print_heading(get_string('quizoverallstatistics', 'quiz_statistics'));
127 $quizoverallstatistics = new object();
128 $quizoverallstatistics->align = array('center', 'center');
129 $quizoverallstatistics->width = '60%';
130 $quizoverallstatistics->class = 'generaltable titlesleft';
131 $quizoverallstatistics->data = array();
132 $quizoverallstatistics->data[] = array(get_string('nooffirstattempts', 'quiz_statistics'), $firstattempt->countrecs);
133 $quizoverallstatistics->data[] = array(get_string('noofallattempts', 'quiz_statistics'), $allattempts->countrecs);
134 $quizoverallstatistics->data[] = array(get_string('firstattemptsavg', 'quiz_statistics'), quiz_report_scale_sumgrades_as_percentage($firstattempt->total / $firstattempt->countrecs, $quiz));
135 $quizoverallstatistics->data[] = array(get_string('allattemptsavg', 'quiz_statistics'), quiz_report_scale_sumgrades_as_percentage($allattempts->total / $allattempts->countrecs, $quiz));
136 print_table($quizoverallstatistics);
137 }
138 //get the median
0c1c764e 139 if (!$table->is_downloading()) {
140 if ($useallattempts){
141 $usingattempts = $allattempts;
142 $usingattempts->heading = get_string('statsforallattempts', 'quiz_statistics');
143 $usingattempts->sql = '';
144 } else {
145 $usingattempts = $firstattempt;
146 $usingattempts->heading = get_string('statsforfirstattempts', 'quiz_statistics');
08a7ead5 147 $usingattempts->sql = 'AND qa.attempt=1 ';
0c1c764e 148 }
149 print_heading($usingattempts->heading);
150 if (($usingattempts->countrecs/2)==floor($usingattempts->countrecs/2)){
151 //even number of attempts
152 $limitoffset = (floor($usingattempts->countrecs/2)) - 1;
153 $limit = 2;
154 } else {
155 $limitoffset = ($usingattempts->countrecs/2) - 1;
156 $limit = 1;
157 }
158 $sql = 'SELECT id, sumgrades ' .
08a7ead5 159 'FROM ' .$fromqa.
160 'WHERE ' .$whereqa.
0c1c764e 161 $usingattempts->sql.
162 'ORDER BY sumgrades';
08a7ead5 163 if (!$mediangrades = $DB->get_records_sql_menu($sql, array($quiz->id), $limitoffset, $limit)){
0c1c764e 164 print_error('errormedian', 'quiz_statistics');
165 }
166 if (count($mediangrades)==1){
167 $median = array_shift($mediangrades);
168 } else {
169 $median = array_shift($mediangrades);
170 $median += array_shift($mediangrades);
171 $median = $median /2;
172 }
173 //fetch sum of squared, cubed and power 4d
174 //differences between grades and mean grade
175 $mean = $usingattempts->total / $usingattempts->countrecs;
176 $sql = "SELECT " .
08a7ead5 177 "SUM(POWER((qa.sumgrades - ?),2)) AS power2, " .
178 "SUM(POWER((qa.sumgrades - ?),3)) AS power3, ".
179 "SUM(POWER((qa.sumgrades - ?),4)) AS power4 ".
180 'FROM ' .$fromqa.
181 'WHERE ' .$whereqa.
182 $usingattempts->sql;
0c1c764e 183 $params = array($mean, $mean, $mean, $quiz->id);
184 if (!$powers = $DB->get_record_sql($sql, $params)){
185 print_error('errorpowers', 'quiz_statistics');
186 }
187
188 $s = $usingattempts->countrecs;
189
040c36e3 190 //Standard_Deviation
191 //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Standard_Deviation
192
0c1c764e 193 $sd = sqrt($powers->power2 / ($s -1));
194
040c36e3 195 //Skewness_and_Kurtosis
196 //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Skewness_and_Kurtosis
0c1c764e 197 $m2= $powers->power2 / $s;
198 $m3= $powers->power3 / $s;
199 $m4= $powers->power4 / $s;
200
201 $k2= $s*$m2/($s-1);
202 $k3= $s*$s*$m3/(($s-1)*($s-2));
040c36e3 203 $k4= (($s*$s*$s)/(($s-1)*($s-2)*($s-3)))*((($s+1)*$m4)-(3*($s-1)*$m2*$m2));
0c1c764e 204
205 $skewness = $k3 / (pow($k2, 2/3));
206 $kurtosis = $k4 / ($k2*$k2);
207
208 $quizattsstatistics = new object();
209 $quizattsstatistics->align = array('center', 'center');
210 $quizattsstatistics->width = '60%';
211 $quizattsstatistics->class = 'generaltable titlesleft';
212 $quizattsstatistics->data = array();
213
214 $quizattsstatistics->data[] = array(get_string('median', 'quiz_statistics'), quiz_report_scale_sumgrades_as_percentage($median, $quiz));
215 $quizattsstatistics->data[] = array(get_string('standarddeviation', 'quiz_statistics'), quiz_report_scale_sumgrades_as_percentage($sd, $quiz));
216 $quizattsstatistics->data[] = array(get_string('skewness', 'quiz_statistics'), $skewness);
217 $quizattsstatistics->data[] = array(get_string('kurtosis', 'quiz_statistics'), $kurtosis);
08a7ead5 218
040c36e3 219 //CIC, ER and SE.
220 //http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#CIC.2C_ER_and_SE
08a7ead5 221 $qgradeavgsql = "SELECT qs.question, AVG(qs.grade) FROM " .
222 "{question_sessions} qns, " .
223 "{question_states} qs, " .
224 "{question} q, " .
225 $fromqa.' '.
226 'WHERE ' .$whereqa.
227 'AND qns.attemptid = qa.uniqueid '.
228 'AND qs.question = q.id ' .
229 'AND q.length > 0 '.
230 $usingattempts->sql.
231 'AND qns.newgraded = qs.id GROUP BY qs.question';
232 $qgradeavgs = $DB->get_records_sql_menu($qgradeavgsql, array($quiz->id));
233 $sum = 0;
040c36e3 234 $sql = 'SELECT ' .
235 'SUM(POWER((qs.grade - ?),2)) AS power2 ' .
236 'FROM ' .
237 '{question_sessions} qns, ' .
238 '{question_states} qs, ' .
239 '{question} q, ' .
240 $fromqa.' '.
241 'WHERE ' .$whereqa.
242 'AND qns.attemptid = qa.uniqueid '.
243 'AND qs.question = ? ' .
244 $usingattempts->sql.
245 'AND qns.newgraded = qs.id';
08a7ead5 246 foreach ($qgradeavgs as $qid => $qgradeavg){
08a7ead5 247 $params = array($qgradeavg, $quiz->id, $qid);
248 $power = $DB->get_field_sql($sql, $params);
249 if ($power === false){
250 print_error('errorpowerquestions', 'quiz_statistics');
251 }
252 $sum += $power;
253 }
254 $sumofvarianceforallpositions = $sum / ($usingattempts->countrecs -1);
255 $p = count($qgradeavgs);//no of positions
256 $cic = (100 * $p / ($p -1)) * (1 - ($sumofvarianceforallpositions/$k2));
257 $quizattsstatistics->data[] = array(get_string('cic', 'quiz_statistics'), number_format($cic, $quiz->decimalpoints).' %');
258 $errorratio = 100 * sqrt(1-($cic/100));
259 $quizattsstatistics->data[] = array(get_string('errorratio', 'quiz_statistics'), number_format($errorratio, $quiz->decimalpoints).' %');
260 $standarderror = ($errorratio * $sd / 100);
261 $quizattsstatistics->data[] = array(get_string('standarderror', 'quiz_statistics'),
262 quiz_report_scale_sumgrades_as_percentage($standarderror, $quiz));
0c1c764e 263 print_table($quizattsstatistics);
c0250840 264
265 }
266 if (!$table->is_downloading()){
267 print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
268 }
269 $table->setup($quiz, $cm->id, $reporturl);
270 foreach ($questions as $question){
271 $table->add_data_keyed($table->format_row($question));
08a7ead5 272 }
c0250840 273 $table->finish_output();
0c1c764e 274 return true;
275 }
276
277}
278
279
280?>