mirror of
https://github.com/moodle/moodle.git
synced 2025-02-20 16:15:54 +01:00
663 lines
33 KiB
PHP
663 lines
33 KiB
PHP
<?php
|
|
/**
|
|
* This script calculates various statistics about student attempts
|
|
*
|
|
* @version $Id$
|
|
* @author Martin Dougiamas, Jamie Pratt, Tim Hunt and others.
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
|
* @package quiz
|
|
**/
|
|
|
|
define('QUIZ_REPORT_TIME_TO_CACHE_STATS', MINSECS * 15);
|
|
require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_form.php');
|
|
require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_table.php');
|
|
|
|
class quiz_statistics_report extends quiz_default_report {
|
|
|
|
/**
|
|
* @var object instance of table class used for main questions stats table.
|
|
*/
|
|
var $table;
|
|
|
|
/**
|
|
* Display the report.
|
|
*/
|
|
function display($quiz, $cm, $course) {
|
|
global $CFG, $DB, $QTYPES;
|
|
|
|
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
|
|
|
|
$download = optional_param('download', '', PARAM_ALPHA);
|
|
$everything = optional_param('everything', 0, PARAM_BOOL);
|
|
$recalculate = optional_param('recalculate', 0, PARAM_BOOL);
|
|
//pass the question id for detailed analysis question
|
|
$qid = optional_param('qid', 0, PARAM_INT);
|
|
$pageoptions = array();
|
|
$pageoptions['id'] = $cm->id;
|
|
$pageoptions['q'] = $quiz->id;
|
|
$pageoptions['mode'] = 'statistics';
|
|
|
|
$questions = quiz_report_load_questions($quiz);
|
|
// Load the question type specific information
|
|
if (!get_question_options($questions)) {
|
|
print_error('cannotloadquestion', 'question');
|
|
}
|
|
|
|
|
|
$reporturl = new moodle_url($CFG->wwwroot.'/mod/quiz/report.php', $pageoptions);
|
|
|
|
$mform = new mod_quiz_report_statistics($reporturl);
|
|
if ($fromform = $mform->get_data()){
|
|
$useallattempts = $fromform->useallattempts;
|
|
if ($fromform->useallattempts){
|
|
set_user_preference('quiz_report_statistics_useallattempts', $fromform->useallattempts);
|
|
} else {
|
|
unset_user_preference('quiz_report_statistics_useallattempts');
|
|
}
|
|
} else {
|
|
$useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0);
|
|
}
|
|
|
|
/// find out current groups mode
|
|
$currentgroup = groups_get_activity_group($cm, true);
|
|
|
|
|
|
$nostudentsingroup = false;//true if a group is selected and their is noeone in it.
|
|
if (!empty($currentgroup)) {
|
|
// all users who can attempt quizzes and who are in the currently selected group
|
|
$groupstudents = get_users_by_capability($context, 'mod/quiz:attempt','','','','',$currentgroup,'',false);
|
|
if (!$groupstudents){
|
|
$nostudentsingroup = true;
|
|
}
|
|
} else {
|
|
$groupstudents = array();
|
|
}
|
|
|
|
if ($recalculate){
|
|
if ($todelete = $DB->get_records_menu('quiz_statistics', array('quizid' => $quiz->id, 'groupid'=> (int)$currentgroup, 'allattempts'=>$useallattempts))){
|
|
list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
|
|
if (!$DB->delete_records_select('quiz_statistics', "id $todeletesql", $todeleteparams)){
|
|
print_error('errordeleting', 'quiz_statistics', '', 'quiz_statistics');
|
|
}
|
|
if (!$DB->delete_records_select('quiz_question_statistics', "quizstatisticsid $todeletesql", $todeleteparams)){
|
|
print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_statistics');
|
|
}
|
|
if (!$DB->delete_records_select('quiz_question_response_stats', "quizstatisticsid $todeletesql", $todeleteparams)){
|
|
print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_response_stats');
|
|
}
|
|
}
|
|
redirect($reporturl->out());
|
|
}
|
|
|
|
|
|
$this->table = new quiz_report_statistics_table();
|
|
$filename = "$course->shortname-".format_string($quiz->name,true);
|
|
$this->table->is_downloading($download, $filename, get_string('quizstructureanalysis', 'quiz_statistics'));
|
|
if (!$this->table->is_downloading()) {
|
|
// Only print headers if not asked to download data
|
|
$this->print_header_and_tabs($cm, $course, $quiz, "statistics");
|
|
}
|
|
|
|
if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
|
|
if (!$this->table->is_downloading()) {
|
|
groups_print_activity_menu($cm, $reporturl->out());
|
|
echo '<br />';
|
|
if ($currentgroup && !$groupstudents){
|
|
notify(get_string('nostudentsingroup', 'quiz_statistics'));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$this->table->is_downloading()) {
|
|
// Print display options
|
|
$mform->set_data(array('useallattempts' => $useallattempts));
|
|
$mform->display();
|
|
}
|
|
|
|
list($quizstats, $questions, $subquestions, $s, $usingattemptsstring)
|
|
= $this->quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup,
|
|
$useallattempts, $groupstudents, $questions);
|
|
|
|
if (!$this->table->is_downloading()){
|
|
if ($s==0){
|
|
print_heading(get_string('noattempts','quiz'));
|
|
}
|
|
}
|
|
if ($s){
|
|
$this->table->setup($quiz, $cm->id, $reporturl, $s);
|
|
}
|
|
|
|
if (!$qid){//main page
|
|
$this->output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything);
|
|
$this->output_quiz_structure_analysis_table($s, $questions, $subquestions);
|
|
if (!$this->table->is_downloading() || ($everything && $this->table->is_downloading() == 'xhtml')){
|
|
if ($s > 1){
|
|
$imageurl = $CFG->wwwroot.'/mod/quiz/report/statistics/statistics_graph.php?id='.$quizstats->id;
|
|
print_heading(get_string('statisticsreportgraph', 'quiz_statistics'));
|
|
echo '<div class="mdl-align"><img src="'.$imageurl.'" alt="'.get_string('statisticsreportgraph', 'quiz_statistics').'" /></div>';
|
|
}
|
|
}
|
|
if ($this->table->is_downloading()){
|
|
if ($everything){
|
|
foreach ($questions as $question){
|
|
if ($question->qtype != 'random' && $QTYPES[$question->qtype]->show_analysis_of_responses()){
|
|
$this->output_individual_question_data($quiz, $question, $reporturl, $quizstats);
|
|
} elseif (!empty($question->_stats->subquestions)) {
|
|
$subitemstodisplay = explode(',', $question->_stats->subquestions);
|
|
foreach ($subitemstodisplay as $subitemid){
|
|
$this->output_individual_question_data($quiz, $subquestions[$subitemid], $reporturl, $quizstats);
|
|
}
|
|
}
|
|
}
|
|
$exportclassinstance =& $this->table->export_class_instance();
|
|
} else {
|
|
$this->table->finish_output();
|
|
}
|
|
}
|
|
if ($this->table->is_downloading() && $everything){
|
|
$exportclassinstance->finish_document();
|
|
}
|
|
} else {//individual question page
|
|
$thisquestion = false;
|
|
if (isset($questions[$qid])){
|
|
$thisquestion = $questions[$qid];
|
|
} else if (isset($subquestions[$qid])){
|
|
$thisquestion = $subquestions[$qid];
|
|
} else {
|
|
print_error('questiondoesnotexist', 'question');
|
|
}
|
|
$this->output_individual_question_data($quiz, $thisquestion, $reporturl, $quizstats);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function sort_response_details($detail1, $detail2){
|
|
if ($detail1->credit == $detail2->credit){
|
|
return strcmp($detail1->answer, $detail2->answer);
|
|
}
|
|
return ($detail1->credit > $detail2->credit) ? -1 : 1;
|
|
}
|
|
function sort_answers($answer1, $answer2){
|
|
if ($answer1->rcount == $answer2->rcount){
|
|
return strcmp($answer1->response, $answer2->response);
|
|
} else {
|
|
return ($answer1->rcount > $answer2->rcount)? -1 : 1;
|
|
}
|
|
}
|
|
|
|
function output_individual_question_data($quiz, $question, $reporturl, $quizstats){
|
|
global $CFG, $DB, $QTYPES;
|
|
require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_question_table.php');
|
|
$this->qtable = new quiz_report_statistics_question_table($question->id);
|
|
$downloadtype = $this->table->is_downloading();
|
|
if (!$this->table->is_downloading()){
|
|
$datumfromtable = $this->table->format_row($question);
|
|
|
|
$questioninfotable = new object();
|
|
$questioninfotable->align = array('center', 'center');
|
|
$questioninfotable->width = '60%';
|
|
$questioninfotable->class = 'generaltable titlesleft';
|
|
|
|
$questioninfotable->data = array();
|
|
$questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
|
|
$questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'), $question->name.' '.$datumfromtable['actions']);
|
|
$questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $datumfromtable['icon'].' '.get_string($question->qtype,'quiz').' '.$datumfromtable['icon']);
|
|
$questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'), $question->_stats->positions);
|
|
|
|
$questionstatstable = new object();
|
|
$questionstatstable->align = array('center', 'center');
|
|
$questionstatstable->width = '60%';
|
|
$questionstatstable->class = 'generaltable titlesleft';
|
|
|
|
unset($datumfromtable['number']);
|
|
unset($datumfromtable['icon']);
|
|
$actions = $datumfromtable['actions'];
|
|
unset($datumfromtable['actions']);
|
|
unset($datumfromtable['name']);
|
|
$labels = array('s' => get_string('attempts', 'quiz_statistics'),
|
|
'facility' => get_string('facility', 'quiz_statistics'),
|
|
'sd' => get_string('standarddeviationq', 'quiz_statistics'),
|
|
'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
|
|
'intended_weight'=> get_string('intended_weight', 'quiz_statistics'),
|
|
'effective_weight'=> get_string('effective_weight', 'quiz_statistics'),
|
|
'discrimination_index'=> get_string('discrimination_index', 'quiz_statistics'),
|
|
'discriminative_efficiency'=> get_string('discriminative_efficiency', 'quiz_statistics'));
|
|
foreach ($datumfromtable as $item => $value){
|
|
$questionstatstable->data[] = array($labels[$item], $value);
|
|
}
|
|
print_heading(get_string('questioninformation', 'quiz_statistics'));
|
|
print_table($questioninfotable);
|
|
|
|
print_box(format_text($question->questiontext).$actions, 'boxaligncenter generalbox boxwidthnormal mdl-align');
|
|
|
|
print_heading(get_string('questionstatistics', 'quiz_statistics'));
|
|
print_table($questionstatstable);
|
|
|
|
} else {
|
|
$this->qtable->export_class_instance($this->table->export_class_instance());
|
|
$questiontabletitle = !empty($question->number)?'('.$question->number.') ':'';
|
|
$questiontabletitle .= "\"{$question->name}\"";
|
|
$questiontabletitle = "<em>$questiontabletitle</em>";
|
|
if ($downloadtype == 'xhtml'){
|
|
$questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
|
|
}
|
|
$exportclass =& $this->table->export_class_instance();
|
|
$exportclass->start_table($questiontabletitle);
|
|
}
|
|
if ($QTYPES[$question->qtype]->show_analysis_of_responses()){
|
|
if (!$this->table->is_downloading()){
|
|
print_heading(get_string('analysisofresponses', 'quiz_statistics'));
|
|
}
|
|
$teacherresponses = $QTYPES[$question->qtype]->get_possible_responses($question);
|
|
$this->qtable->setup($reporturl, $question, count($teacherresponses)>1);
|
|
if ($this->table->is_downloading()){
|
|
$exportclass->output_headers($this->qtable->headers);
|
|
}
|
|
|
|
$responses = $DB->get_records('quiz_question_response_stats', array('quizstatisticsid' => $quizstats->id, 'questionid' => $question->id), 'credit DESC, subqid ASC, aid ASC, rcount DESC');
|
|
$responses = quiz_report_index_by_keys($responses, array('subqid', 'aid'), false);
|
|
foreach ($responses as $subqid => $response){
|
|
foreach (array_keys($responses[$subqid]) as $aid){
|
|
uasort($responses[$subqid][$aid], array('quiz_statistics_report', 'sort_answers'));
|
|
}
|
|
if (isset($responses[$subqid]['0'])){
|
|
$wildcardresponse = new object();
|
|
$wildcardresponse->answer = '*';
|
|
$wildcardresponse->credit = 0;
|
|
$teacherresponses[$subqid][0] = $wildcardresponse;
|
|
}
|
|
}
|
|
$first = true;
|
|
$subq = 0;
|
|
foreach ($teacherresponses as $subqid => $tresponsesforsubq){
|
|
$subq++;
|
|
$qhaswildcards = $QTYPES[$question->qtype]->has_wildcards_in_responses($question, $subqid);
|
|
if (!$first){
|
|
$this->qtable->add_separator();
|
|
}
|
|
uasort($tresponsesforsubq, array('quiz_statistics_report', 'sort_response_details'));
|
|
foreach ($tresponsesforsubq as $aid => $teacherresponse){
|
|
$teacherresponserow = new object();
|
|
$teacherresponserow->response = $teacherresponse->answer;
|
|
$teacherresponserow->rcount = 0;
|
|
$teacherresponserow->subq = $subq;
|
|
$teacherresponserow->credit = $teacherresponse->credit;
|
|
if (isset($responses[$subqid][$aid])){
|
|
$singleanswer = count($responses[$subqid][$aid])==1 &&
|
|
($responses[$subqid][$aid][0]->response == $teacherresponserow->response);
|
|
if (!$singleanswer && $qhaswildcards){
|
|
$this->qtable->add_separator();
|
|
}
|
|
foreach ($responses[$subqid][$aid] as $response){
|
|
$teacherresponserow->rcount += $response->rcount;
|
|
}
|
|
if ($aid!=0 || $qhaswildcards){
|
|
$this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow));
|
|
}
|
|
if (!$singleanswer){
|
|
foreach ($responses[$subqid][$aid] as $response){
|
|
if (!$downloadtype || $downloadtype=='xhtml'){
|
|
$indent = ' ';
|
|
} else {
|
|
$indent = ' ';
|
|
}
|
|
$response->response = ($qhaswildcards?$indent:'').$response->response;
|
|
$response->subq = $subq;
|
|
if ((count($responses[$subqid][$aid])<2) || ($response->rcount > ($teacherresponserow->rcount / 10))){
|
|
$this->qtable->add_data_keyed($this->qtable->format_row($response));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow));
|
|
}
|
|
}
|
|
$first = false;
|
|
}
|
|
$this->qtable->finish_output(!$this->table->is_downloading());
|
|
}
|
|
if (!$this->table->is_downloading()){
|
|
$url = $reporturl->out();
|
|
$text = get_string('backtoquizreport', 'quiz_statistics');
|
|
print_box("<a href=\"$url\">$text</a>", 'boxaligncenter generalbox boxwidthnormal mdl-align');
|
|
}
|
|
}
|
|
|
|
function output_quiz_structure_analysis_table($s, $questions, $subquestions){
|
|
if ($s){
|
|
if (!$this->table->is_downloading()){
|
|
print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
|
|
}
|
|
foreach ($questions as $question){
|
|
$this->table->add_data_keyed($this->table->format_row($question));
|
|
if (!empty($question->_stats->subquestions)){
|
|
$subitemstodisplay = explode(',', $question->_stats->subquestions);
|
|
foreach ($subitemstodisplay as $subitemid){
|
|
$subquestions[$subitemid]->maxgrade = $question->maxgrade;
|
|
$this->table->add_data_keyed($this->table->format_row($subquestions[$subitemid]));
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->table->finish_output(!$this->table->is_downloading());
|
|
}
|
|
}
|
|
|
|
function output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring,
|
|
$currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything){
|
|
global $DB;
|
|
// Print information on the number of existing attempts
|
|
$quizinformationtablehtml = print_heading(get_string('quizinformation', 'quiz_statistics'), '', 2, 'main', true);
|
|
$quizinformationtable = new object();
|
|
$quizinformationtable->align = array('center', 'center');
|
|
$quizinformationtable->width = '60%';
|
|
$quizinformationtable->class = 'generaltable titlesleft';
|
|
$quizinformationtable->data = array();
|
|
$quizinformationtable->data[] = array(get_string('quizname', 'quiz_statistics'), $quiz->name);
|
|
$quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $course->fullname);
|
|
if ($cm->idnumber){
|
|
$quizinformationtable->data[] = array(get_string('idnumbermod'), $cm->idnumber);
|
|
}
|
|
if ($quiz->timeopen){
|
|
$quizinformationtable->data[] = array(get_string('quizopen', 'quiz'), userdate($quiz->timeopen));
|
|
}
|
|
if ($quiz->timeclose){
|
|
$quizinformationtable->data[] = array(get_string('quizclose', 'quiz'), userdate($quiz->timeclose));
|
|
}
|
|
if ($quiz->timeopen && $quiz->timeclose){
|
|
$quizinformationtable->data[] = array(get_string('duration', 'quiz_statistics'), format_time($quiz->timeclose - $quiz->timeopen));
|
|
}
|
|
$format = array('firstattemptscount' => '',
|
|
'allattemptscount' => '',
|
|
'firstattemptsavg' => 'sumgrades_as_percentage',
|
|
'allattemptsavg' => 'sumgrades_as_percentage',
|
|
'median' => 'sumgrades_as_percentage',
|
|
'standarddeviation' => 'sumgrades_as_percentage',
|
|
'skewness' => '',
|
|
'kurtosis' => '',
|
|
'cic' => 'number_format',
|
|
'errorratio' => 'number_format',
|
|
'standarderror' => 'sumgrades_as_percentage');
|
|
foreach ($quizstats as $property => $value){
|
|
if (!isset($format[$property])){
|
|
continue;
|
|
}
|
|
if (!is_null($value)){
|
|
switch ($format[$property]){
|
|
case 'sumgrades_as_percentage' :
|
|
$formattedvalue = quiz_report_scale_sumgrades_as_percentage($value, $quiz);
|
|
break;
|
|
case 'number_format' :
|
|
$formattedvalue = quiz_format_grade($quiz, $value).'%';
|
|
break;
|
|
default :
|
|
$formattedvalue = $value;
|
|
}
|
|
$quizinformationtable->data[] = array(get_string($property, 'quiz_statistics', $usingattemptsstring), $formattedvalue);
|
|
}
|
|
}
|
|
if (!$this->table->is_downloading()){
|
|
if (isset($quizstats->timemodified)){
|
|
list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quiz->id, $currentgroup, $groupstudents, $useallattempts);
|
|
$sql = 'SELECT COUNT(1) ' .
|
|
'FROM ' .$fromqa.' '.
|
|
'WHERE ' .$whereqa.' AND qa.timefinish > :time';
|
|
$a = new object();
|
|
$a->lastcalculated = format_time(time() - $quizstats->timemodified);
|
|
if (!$a->count = $DB->count_records_sql($sql, array('time'=>$quizstats->timemodified)+$qaparams)){
|
|
$a->count = 0;
|
|
}
|
|
$quizinformationtablehtml .= print_box_start('boxaligncenter generalbox boxwidthnormal mdl-align', '', true);
|
|
$quizinformationtablehtml .= get_string('lastcalculated', 'quiz_statistics', $a);
|
|
$quizinformationtablehtml .= print_single_button($reporturl->out(true), $reporturl->params()+array('recalculate'=>1),
|
|
get_string('recalculatenow', 'quiz_statistics'), 'post', '', true);
|
|
$quizinformationtablehtml .= print_box_end(true);
|
|
}
|
|
$downloadoptions = $this->table->get_download_menu();
|
|
$quizinformationtablehtml .= '<form action="'. $this->table->baseurl .'" method="post">';
|
|
$quizinformationtablehtml .= '<div class="mdl-align">';
|
|
$quizinformationtablehtml .= '<input type="hidden" name="everything" value="1"/>';
|
|
$quizinformationtablehtml .= '<input type="submit" value="'.get_string('downloadeverything', 'quiz_statistics').'"/>';
|
|
$quizinformationtablehtml .= choose_from_menu ($downloadoptions, 'download', $this->table->defaultdownloadformat, '', '', '', true);
|
|
$quizinformationtablehtml .= helpbutton('tableexportformats', get_string('tableexportformats', 'table'), 'moodle', true, false, '', true);
|
|
$quizinformationtablehtml .= '</div></form>';
|
|
}
|
|
$quizinformationtablehtml .= print_table($quizinformationtable, true);
|
|
if (!$this->table->is_downloading()){
|
|
echo $quizinformationtablehtml;
|
|
} elseif ($everything) {
|
|
$exportclass =& $this->table->export_class_instance();
|
|
if ($download == 'xhtml'){
|
|
echo $quizinformationtablehtml;
|
|
} else {
|
|
$exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
|
|
$headers = array();
|
|
$row = array();
|
|
foreach ($quizinformationtable->data as $data){
|
|
$headers[]= $data[0];
|
|
$row[] = $data[1];
|
|
}
|
|
$exportclass->output_headers($headers);
|
|
$exportclass->add_data($row);
|
|
$exportclass->finish_table();
|
|
}
|
|
}
|
|
}
|
|
|
|
function quiz_stats($nostudentsingroup, $quizid, $currentgroup, $groupstudents, $questions, $useallattempts){
|
|
global $CFG, $DB;
|
|
if (!$nostudentsingroup){
|
|
//Calculating_MEAN_of_grades_for_all_attempts_by_students
|
|
//http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Calculating_MEAN_of_grades_for_all_attempts_by_students
|
|
|
|
list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents);
|
|
|
|
$sql = 'SELECT (CASE WHEN attempt=1 THEN 1 ELSE 0 END) AS isfirst, COUNT(1) AS countrecs, SUM(sumgrades) AS total ' .
|
|
'FROM '.$fromqa.
|
|
'WHERE ' .$whereqa.
|
|
'GROUP BY (attempt=1)';
|
|
|
|
if (!$attempttotals = $DB->get_records_sql($sql, $qaparams)){
|
|
$s = 0;
|
|
$usingattemptsstring = '';
|
|
} else {
|
|
$firstattempt = $attempttotals[1];
|
|
$allattempts = new object();
|
|
$allattempts->countrecs = $firstattempt->countrecs +
|
|
(isset($attempttotals[0])?$attempttotals[0]->countrecs:0);
|
|
$allattempts->total = $firstattempt->total +
|
|
(isset($attempttotals[0])?$attempttotals[0]->total:0);
|
|
if ($useallattempts){
|
|
$usingattempts = $allattempts;
|
|
$usingattempts->attempts = get_string('allattempts', 'quiz_statistics');
|
|
$usingattempts->sql = '';
|
|
} else {
|
|
$usingattempts = $firstattempt;
|
|
$usingattempts->attempts = get_string('firstattempts', 'quiz_statistics');
|
|
$usingattempts->sql = 'AND qa.attempt=1 ';
|
|
}
|
|
$usingattemptsstring = $usingattempts->attempts;
|
|
$s = $usingattempts->countrecs;
|
|
$sumgradesavg = $usingattempts->total / $usingattempts->countrecs;
|
|
}
|
|
} else {
|
|
$s = 0;
|
|
}
|
|
$quizstats = new object();
|
|
if ($s == 0){
|
|
$quizstats->firstattemptscount = 0;
|
|
$quizstats->allattemptscount = 0;
|
|
} else {
|
|
$quizstats->firstattemptscount = $firstattempt->countrecs;
|
|
$quizstats->allattemptscount = $allattempts->countrecs;
|
|
$quizstats->firstattemptsavg = $firstattempt->total / $firstattempt->countrecs;
|
|
$quizstats->allattemptsavg = $allattempts->total / $allattempts->countrecs;
|
|
}
|
|
//recalculate sql again this time possibly including test for first attempt.
|
|
list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $useallattempts);
|
|
|
|
//get the median
|
|
if ($s) {
|
|
|
|
if (($s%2)==0){
|
|
//even number of attempts
|
|
$limitoffset = ($s/2) - 1;
|
|
$limit = 2;
|
|
} else {
|
|
$limitoffset = (floor($s/2));
|
|
$limit = 1;
|
|
}
|
|
$sql = 'SELECT id, sumgrades ' .
|
|
'FROM ' .$fromqa.
|
|
'WHERE ' .$whereqa.
|
|
'ORDER BY sumgrades';
|
|
if (!$mediangrades = $DB->get_records_sql_menu($sql, $qaparams, $limitoffset, $limit)){
|
|
print_error('errormedian', 'quiz_statistics');
|
|
}
|
|
$quizstats->median = array_sum($mediangrades) / count($mediangrades);
|
|
if ($s>1){
|
|
//fetch sum of squared, cubed and power 4d
|
|
//differences between grades and mean grade
|
|
$mean = $usingattempts->total / $s;
|
|
$sql = "SELECT " .
|
|
"SUM(POWER((qa.sumgrades - :mean1),2)) AS power2, " .
|
|
"SUM(POWER((qa.sumgrades - :mean2),3)) AS power3, ".
|
|
"SUM(POWER((qa.sumgrades - :mean3),4)) AS power4 ".
|
|
'FROM ' .$fromqa.
|
|
'WHERE ' .$whereqa;
|
|
$params = array('mean1' => $mean, 'mean2' => $mean, 'mean3' => $mean)+$qaparams;
|
|
if (!$powers = $DB->get_record_sql($sql, $params)){
|
|
print_error('errorpowers', 'quiz_statistics');
|
|
}
|
|
|
|
//Standard_Deviation
|
|
//see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Standard_Deviation
|
|
|
|
$quizstats->standarddeviation = sqrt($powers->power2 / ($s -1));
|
|
|
|
|
|
|
|
//Skewness_and_Kurtosis
|
|
if ($s>2){
|
|
//see http://docs.moodle.org/en/Development: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){
|
|
$quizstats->skewness = $k3 / (pow($k2, 3/2));
|
|
}
|
|
}
|
|
|
|
|
|
if ($s>3){
|
|
$k4= $s*$s*((($s+1)*$m4)-(3*($s-1)*$m2*$m2))/(($s-1)*($s-2)*($s-3));
|
|
if ($k2){
|
|
$quizstats->kurtosis = $k4 / ($k2*$k2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($s){
|
|
require_once("$CFG->dirroot/mod/quiz/report/statistics/qstats.php");
|
|
$qstats = new qstats($questions, $s, $sumgradesavg);
|
|
$qstats->get_records($quizid, $currentgroup, $groupstudents, $useallattempts);
|
|
$qstats->process_states();
|
|
$qstats->process_responses();
|
|
} else {
|
|
$qstats = false;
|
|
}
|
|
if ($s>1){
|
|
$p = count($qstats->questions);//no of positions
|
|
if ($p > 1){
|
|
if (isset($k2)){
|
|
$quizstats->cic = (100 * $p / ($p -1)) * (1 - ($qstats->sum_of_grade_variance())/$k2);
|
|
$quizstats->errorratio = 100 * sqrt(1-($quizstats->cic/100));
|
|
$quizstats->standarderror = ($quizstats->errorratio * $quizstats->standarddeviation / 100);
|
|
}
|
|
}
|
|
}
|
|
return array($s, $usingattemptsstring, $quizstats, $qstats);
|
|
}
|
|
|
|
function quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions){
|
|
global $DB;
|
|
$timemodified = time() - QUIZ_REPORT_TIME_TO_CACHE_STATS;
|
|
$params = array('quizid'=>$quiz->id, 'groupid'=>(int)$currentgroup, 'allattempts'=>$useallattempts, 'timemodified'=>$timemodified);
|
|
if (!$quizstats = $DB->get_record_select('quiz_statistics', 'quizid = :quizid AND groupid = :groupid AND allattempts = :allattempts AND timemodified > :timemodified', $params, '*', true)){
|
|
list($s, $usingattemptsstring, $quizstats, $qstats) = $this->quiz_stats($nostudentsingroup, $quiz->id, $currentgroup, $groupstudents, $questions, $useallattempts);
|
|
if ($s){
|
|
$toinsert = (object)((array)$quizstats + $params);
|
|
$toinsert->timemodified = time();
|
|
$quizstats->id = $DB->insert_record('quiz_statistics', $toinsert);
|
|
foreach ($qstats->questions as $question){
|
|
$question->_stats->quizstatisticsid = $quizstats->id;
|
|
$DB->insert_record('quiz_question_statistics', $question->_stats, false, true);
|
|
}
|
|
foreach ($qstats->subquestions as $subquestion){
|
|
$subquestion->_stats->quizstatisticsid = $quizstats->id;
|
|
$DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true);
|
|
}
|
|
foreach ($qstats->responses as $response){
|
|
$response->quizstatisticsid = $quizstats->id;
|
|
$DB->insert_record('quiz_question_response_stats', $response, false);
|
|
}
|
|
}
|
|
if ($qstats){
|
|
$questions = $qstats->questions;
|
|
$subquestions = $qstats->subquestions;
|
|
} else {
|
|
$questions = array();
|
|
$subquestions = array();
|
|
}
|
|
} else {
|
|
//use cached results
|
|
if ($useallattempts){
|
|
$usingattemptsstring = get_string('allattempts', 'quiz_statistics');
|
|
$s = $quizstats->allattemptscount;
|
|
} else {
|
|
$usingattemptsstring = get_string('firstattempts', 'quiz_statistics');
|
|
$s = $quizstats->firstattemptscount;
|
|
}
|
|
$subquestions = array();
|
|
$questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid'=>$quizstats->id), 'subquestion ASC');
|
|
$questionstats = quiz_report_index_by_keys($questionstats, array('subquestion', 'questionid'));
|
|
if (1 < count($questionstats)){
|
|
list($mainquestionstats, $subquestionstats) = $questionstats;
|
|
$subqstofetch = array_keys($subquestionstats);
|
|
$subquestions = question_load_questions($subqstofetch);
|
|
foreach (array_keys($subquestions) as $subqid){
|
|
$subquestions[$subqid]->_stats = $subquestionstats[$subqid];
|
|
}
|
|
} elseif (count($questionstats)) {
|
|
$mainquestionstats = $questionstats[0];
|
|
}
|
|
if (count($questionstats)) {
|
|
foreach (array_keys($questions) as $qid){
|
|
$questions[$qid]->_stats = $mainquestionstats[$qid];
|
|
}
|
|
}
|
|
}
|
|
return array($quizstats, $questions, $subquestions, $s, $usingattemptsstring);
|
|
}
|
|
}
|
|
function quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $allattempts = true){
|
|
global $DB;
|
|
$fromqa = '{quiz_attempts} qa ';
|
|
$whereqa = 'qa.quiz = :quizid AND qa.preview=0 AND qa.timefinish !=0 ';
|
|
$qaparams = array('quizid'=>$quizid);
|
|
if (!empty($currentgroup) && $groupstudents) {
|
|
list($grpsql, $grpparams) = $DB->get_in_or_equal(array_keys($groupstudents), SQL_PARAMS_NAMED, 'u0000');
|
|
$whereqa .= 'AND qa.userid '.$grpsql.' ';
|
|
$qaparams += $grpparams;
|
|
}
|
|
if (!$allattempts){
|
|
$whereqa .= 'AND qa.attempt=1 ';
|
|
}
|
|
return array($fromqa, $whereqa, $qaparams);
|
|
}
|
|
|
|
?>
|