/** * Compute the quiz statistics. * * @param int $quizid the quiz id. * @param int $whichattempts which attempts to use, represented internally as one of the constants as used in * $quiz->grademethod ie. * QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST * we calculate stats based on which attempts would affect the grade for each student. * @param array $groupstudents students in this group. * @param int $p number of positions (slots). * @param float $sumofmarkvariance sum of mark variance, calculated as part of question statistics * @return quiz_statistics_calculated $quizstats The statistics for overall attempt scores. */ public function calculate($quizid, $whichattempts, $groupstudents, $p, $sumofmarkvariance) { $quizstats = new quiz_statistics_calculated($whichattempts); $countsandaverages = $this->attempt_counts_and_averages($quizid, $groupstudents); foreach ($countsandaverages as $propertyname => $value) { $quizstats->{$propertyname} = $value; } $s = $quizstats->s(); if ($s == 0) { return $quizstats; } // Recalculate sql again this time possibly including test for first attempt. list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts); $quizstats->median = $this->median($s, $fromqa, $whereqa, $qaparams); if ($s > 1) { $powers = $this->sum_of_powers_of_difference_to_mean($quizstats->avg(), $fromqa, $whereqa, $qaparams); $quizstats->standarddeviation = sqrt($powers->power2 / ($s - 1)); // Skewness. if ($s > 2) { // See http://docs.moodle.org/dev/Quiz_item_analysis_calculations_in_practise#Skewness_and_Kurtosis. $m2 = $powers->power2 / $s; $m3 = $powers->power3 / $s; $m4 = $powers->power4 / $s; $k2 = $s * $m2 / ($s - 1); $k3 = $s * $s * $m3 / (($s - 1) * ($s - 2)); if ($k2 != 0) { $quizstats->skewness = $k3 / pow($k2, 3 / 2); // Kurtosis. if ($s > 3) { $k4 = $s * $s * (($s + 1) * $m4 - 3 * ($s - 1) * $m2 * $m2) / (($s - 1) * ($s - 2) * ($s - 3)); $quizstats->kurtosis = $k4 / ($k2 * $k2); } if ($p > 1) { $quizstats->cic = 100 * $p / ($p - 1) * (1 - $sumofmarkvariance / $k2); $quizstats->errorratio = 100 * sqrt(1 - $quizstats->cic / 100); $quizstats->standarderror = $quizstats->errorratio * $quizstats->standarddeviation / 100; } } } } $quizstats->cache(quiz_statistics_qubaids_condition($quizid, $groupstudents, $whichattempts)); return $quizstats; }
/** * Display the report. */ public function display($quiz, $cm, $course) { global $CFG, $DB, $OUTPUT, $PAGE; $this->context = context_module::instance($cm->id); if (!quiz_questions_in_quiz($quiz->questions)) { $this->print_header_and_tabs($cm, $course, $quiz, 'statistics'); echo quiz_no_questions_message($quiz, $cm, $this->context); return true; } // Work out the display options. $download = optional_param('download', '', PARAM_ALPHA); $everything = optional_param('everything', 0, PARAM_BOOL); $recalculate = optional_param('recalculate', 0, PARAM_BOOL); // A qid paramter indicates we should display the detailed analysis of a sub question. $qid = optional_param('qid', 0, PARAM_INT); $slot = optional_param('slot', 0, PARAM_INT); $whichattempts = optional_param('whichattempts', $quiz->grademethod, PARAM_INT); $pageoptions = array(); $pageoptions['id'] = $cm->id; $pageoptions['mode'] = 'statistics'; $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions); $mform = new quiz_statistics_settings_form($reporturl); $mform->set_data(array('whichattempts' => $whichattempts)); if ($fromform = $mform->get_data()) { $whichattempts = $fromform->whichattempts; } if ($whichattempts != $quiz->grademethod) { $reporturl->param('whichattempts', $whichattempts); } // Find out current groups mode. $currentgroup = $this->get_current_group($cm, $course, $this->context); $nostudentsingroup = false; // True if a group is selected and there is no one in it. if (empty($currentgroup)) { $currentgroup = 0; $groupstudents = array(); } else { if ($currentgroup == self::NO_GROUPS_ALLOWED) { $groupstudents = array(); $nostudentsingroup = true; } else { // All users who can attempt quizzes and who are in the currently selected group. $groupstudents = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', $currentgroup, '', false); if (!$groupstudents) { $nostudentsingroup = true; } } } $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts); // If recalculate was requested, handle that. if ($recalculate && confirm_sesskey()) { $this->clear_cached_data($qubaids); redirect($reporturl); } // Set up the main table. $this->table = new quiz_statistics_table(); if ($everything) { $report = get_string('completestatsfilename', 'quiz_statistics'); } else { $report = get_string('questionstatsfilename', 'quiz_statistics'); } $courseshortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id))); $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name); $this->table->is_downloading($download, $filename, get_string('quizstructureanalysis', 'quiz_statistics')); $questions = $this->load_and_initialise_questions_for_calculations($quiz); if (!$nostudentsingroup) { // Get the data to be displayed. list($quizstats, $questionstats, $subquestionstats) = $this->get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions); } else { // Or create empty stats containers. $quizstats = new quiz_statistics_calculated($whichattempts); $questionstats = array(); $subquestionstats = array(); } // Set up the table, if there is data. if ($quizstats->s()) { $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s()); } // Print the page header stuff (if not downloading. if (!$this->table->is_downloading()) { $this->print_header_and_tabs($cm, $course, $quiz, 'statistics'); if (groups_get_activity_groupmode($cm)) { groups_print_activity_menu($cm, $reporturl->out()); if ($currentgroup && !$groupstudents) { $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics')); } } if (!$this->table->is_downloading() && $quizstats->s() == 0) { echo $OUTPUT->notification(get_string('noattempts', 'quiz')); } // Print display options form. $mform->display(); } if ($everything) { // Implies is downloading. // Overall report, then the analysis of each question. $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz); $this->download_quiz_info_table($quizinfo); if ($quizstats->s()) { $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats); if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) { $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts); } foreach ($questions as $slot => $question) { if (question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) { $this->output_individual_question_response_analysis($question, $questionstats[$slot]->s, $reporturl, $qubaids); } else { if (!empty($questionstats[$slot]->subquestions)) { $subitemstodisplay = explode(',', $questionstats[$slot]->subquestions); foreach ($subitemstodisplay as $subitemid) { $this->output_individual_question_response_analysis($subquestionstats[$subitemid]->question, $subquestionstats[$subitemid]->s, $reporturl, $qubaids); } } } } } $this->table->export_class_instance()->finish_document(); } else { if ($slot) { // Report on an individual question indexed by position. if (!isset($questions[$slot])) { print_error('questiondoesnotexist', 'question'); } $this->output_individual_question_data($quiz, $questionstats[$slot]); $this->output_individual_question_response_analysis($questions[$slot], $questionstats[$slot]->s, $reporturl, $qubaids); // Back to overview link. echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' . get_string('backtoquizreport', 'quiz_statistics') . '</a>', 'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align'); } else { if ($qid) { // Report on an individual sub-question indexed questionid. if (!isset($subquestionstats[$qid])) { print_error('questiondoesnotexist', 'question'); } $this->output_individual_question_data($quiz, $subquestionstats[$qid]); $this->output_individual_question_response_analysis($subquestionstats[$qid]->question, $subquestionstats[$qid]->s, $reporturl, $qubaids); // Back to overview link. echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' . get_string('backtoquizreport', 'quiz_statistics') . '</a>', 'boxaligncenter generalbox boxwidthnormal mdl-align'); } else { if ($this->table->is_downloading()) { // Downloading overview report. $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz); $this->download_quiz_info_table($quizinfo); $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats); $this->table->finish_output(); } else { // On-screen display of overview report. echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3); echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $reporturl); echo $this->everything_download_options(); $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz); echo $this->output_quiz_info_table($quizinfo); if ($quizstats->s()) { echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3); $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats); $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts); } } } } } return true; }