$string['effective_weight'] = 'Effective weight';
$string['errorrandom'] = 'Error getting sub item data';
$string['erroritemappearsmorethanoncewithdifferentweight'] = 'Question ($a) appears more than once with different weights in different positions of the test. This is not currently supported by the statistics report and may make the statistics for this question unreliable.';
+$string['lastcalculated'] = 'Last calculated $a->lastcalculated ago there have been $a->count attempts since then.';
+$string['recalculatenow'] = 'Recalculate now';
+$string['detailedanalysis'] = 'More detailed analysis of the responses to this question';
+$string['errordeletingquizstats'] = 'Error deleting old quiz_statistics records.';
+$string['errordeletingqstats'] = 'Error deleting old quiz_question_statistics records.';
+$string['questionname'] = 'Question Name';
+$string['questiontype'] = 'Question Type';
+$string['positions'] = 'Position(s)';
+$string['position'] = 'Position';
+$string['questioninformation'] = 'Question information';
+$string['questionstatistics'] = 'Question statistics';
+$string['analysisofresponses'] = 'Analysis of responses';
?>
\ No newline at end of file
return $courseid;
}
/**
- * Get the real question id for a random question.
+ * Get the real state - the correct question id and answer - for a random
+ * question.
* @param object $state with property answer.
* @return mixed return integer real question id or false if there was an
* error..
*/
-function question_get_real_questionid($state){
+function question_get_real_state($state){
+ $realstate = clone($state);
$matches = array();
- if (!preg_match('|^random([0-9]+)-|', $state->answer, $matches)){
+ if (!preg_match('|^random([0-9]+)-(.+)|', $state->answer, $matches)){
notify(get_string('errorrandom', 'quiz_statistics'));
return false;
} else {
- return $matches[1];
+ $realstate->question = $matches[1];
+ $realstate->answer = $matches[2];
+ return $realstate;
}
}
+
?>
*/
function is_downloading($download = null, $filename='', $sheettitle=''){
if ($download!==null){
- $this->filename = clean_filename($filename);
$this->sheettitle = $sheettitle;
$this->is_downloadable(true);
$this->download = $download;
- if (!empty($download)){
- $classname = 'table_'.$download.'_export_format';
- $this->exportclass = new $classname($this);
- }
+ $this->filename = clean_filename($filename);
+ $this->export_class_instance();
}
return $this->download;
}
+
+ function export_class_instance(){
+ if (is_null($this->exportclass) && !empty($this->download)){
+ $classname = 'table_'.$this->download.'_export_format';
+ $this->exportclass = new $classname($this);
+ if (!$this->exportclass->document_started()){
+ $this->exportclass->start_document($this->filename);
+ }
+ }
+ return $this->exportclass;
+ }
+
+
/**
* Probably don't need to call this directly. Calling is_downloading with a
* param automatically sets table as downloadable.
* data to the table with add_data or add_data_keyed.
*
*/
- function finish_output(){
+ function finish_output($closeexportclassdoc = true){
if ($this->exportclass!==null){
- $this->exportclass->finish_output();
+ $this->exportclass->finish_table();
+ if ($closeexportclassdoc){
+ $this->exportclass->finish_document();
+ }
}else{
$this->finish_html();
}
function start_output(){
$this->started_output = true;
if ($this->exportclass!==null){
- $this->exportclass->start_output($this->filename, $this->sheettitle);
+ $this->exportclass->start_table($this->sheettitle);
$this->exportclass->output_headers($this->headers);
} else {
$this->start_html();
* object from which to export data.
*/
var $table;
+
+ /**
+ * @var boolean output started. Keeps track of whether any output has been
+ * started yet.
+ */
+ var $documentstarted = false;
function table_default_export_format_parent(&$table){
$this->table =& $table;
}
+
+ function set_table(&$table){
+ $this->table =& $table;
+ }
function add_data($row) {
return false;
function add_seperator() {
return false;
}
- function finish_output(){
+ function document_started(){
+ return $this->documentstarted;
}
}
*/
function define_workbook(){
}
- function start_output($filename, $sheettitle){
- $this->filename = $filename.'.'.$this->fileextension;
+ function start_document($filename){
+ $filename = $filename.'.'.$this->fileextension;
$this->define_workbook();
- // Creating the first worksheet
- $this->worksheet =& $this->workbook->add_worksheet();
// format types
$this->formatnormal =& $this->workbook->add_format();
$this->formatnormal->set_bold(0);
$this->formatheaders =& $this->workbook->add_format();
$this->formatheaders->set_bold(1);
$this->formatheaders->set_align('center');
-
// Sending HTTP headers
- $this->workbook->send($this->filename);
- // Creating the first worksheet
-
+ $this->workbook->send($filename);
+ $this->documentstarted = true;
+ }
+ function start_table($sheettitle){
+ $this->worksheet =& $this->workbook->add_worksheet($sheettitle);
$this->rownum=0;
}
function output_headers($headers){
$this->rownum++;
return true;
}
- function finish_output(){
+
+ function finish_table(){
+ }
+ function finish_document(){
$this->workbook->close();
exit;
}
class table_text_export_format_parent extends table_default_export_format_parent{
var $seperator = "\t";
- function start_output($filename, $sheettitle){
+ function start_document($filename){
$this->filename = $filename.".txt";
-
header("Content-Type: application/download\n");
- header("Content-Disposition: attachment; filename=\"$this->filename\"");
+ header("Content-Disposition: attachment; filename=\"{$filename}.txt\"");
header("Expires: 0");
header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
header("Pragma: public");
+ $this->documentstarted = true;
+ }
+ function start_table($sheettitle){
+ //nothing to do here
}
function output_headers($headers){
- echo implode($this->seperator, $headers)." \n";
+ echo implode($this->seperator, $headers)."\n";
}
function add_data($row){
- echo implode($this->seperator, $row)." \n";
+ echo implode($this->seperator, $row)."\n";
return true;
}
- function finish_output(){
+ function finish_table(){
+ echo "\n\n";
+ }
+ function finish_document(){
exit;
}
}
}
class table_xhtml_export_format extends table_default_export_format_parent{
- var $seperator = "\t";
- function start_output($filename, $sheettitle){
- $this->table->sortable(false);
- $this->table->collapsible(false);
- $this->filename = $filename.".html";
-
+ function start_document($filename){
header("Content-Type: application/download\n");
- header("Content-Disposition: attachment; filename=\"$this->filename\"");
+ header("Content-Disposition: attachment; filename=\"$filename.html\"");
header("Expires: 0");
header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
header("Pragma: public");
-
//html headers
-
echo <<<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
margin:auto;
}
-h1{
+h1, h2{
text-align:center;
}
.bold {
/*]]>*/</style>
-<head>
- <title>$sheettitle</title>
-</head>
<body>
-<h1>$sheettitle</h1>
EOF;
+ $this->documentstarted = true;
+ }
+ function start_table($sheettitle){
+ $this->table->sortable(false);
+ $this->table->collapsible(false);
+ echo "<h2>{$sheettitle}</h2>";
$this->table->start_html();
}
+
+
function output_headers($headers){
$this->table->print_headers();
}
$this->table->print_row(NULL);
return true;
}
- function finish_output(){
+ function finish_table(){
$this->table->finish_html();
+ }
+ function finish_document(){
echo '</body>';
exit;
}
} else {
$grade = 0;
}
- return $grade.' %';
+ return $grade.'%';
}
?>
function quiz_report_statistics_cron(){
global $DB;
if ($todelete = $DB->get_records_select_menu('quiz_statistics', 'timemodified < ?', array(time()-5*HOURSECS))){
- list($todeletesql, $todeleteparams) = $DB->get_in_or_equal($todelete);
+ list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
if (!$DB->delete_records_select('quiz_statistics', "id $todeletesql", $todeleteparams)){
mtrace('Error deleting out of date quiz_statistics records.');
}
<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/quiz/report/statistics/db" VERSION="20080725" COMMENT="XMLDB file for Moodle mod/quiz/report/statistics"
+<XMLDB PATH="mod/quiz/report/statistics/db" VERSION="20080728" COMMENT="XMLDB file for Moodle mod/quiz/report/statistics"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<FIELD NAME="discriminativeefficiency" TYPE="number" LENGTH="15" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" PREVIOUS="discriminationindex" NEXT="sd"/>
<FIELD NAME="sd" TYPE="number" LENGTH="15" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" DECIMALS="10" PREVIOUS="discriminativeefficiency" NEXT="facility"/>
<FIELD NAME="facility" TYPE="number" LENGTH="15" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" DECIMALS="10" PREVIOUS="sd" NEXT="subquestions"/>
- <FIELD NAME="subquestions" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="facility"/>
+ <FIELD NAME="subquestions" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="facility" NEXT="maxgrade"/>
+ <FIELD NAME="maxgrade" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="subquestions" NEXT="positions"/>
+ <FIELD NAME="positions" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="positions in which this item appears. Only used for random questions." PREVIOUS="maxgrade"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
}
}
+ if ($result && $oldversion < 2008072800) {
+
+ /// Define field maxgrade to be added to quiz_question_statistics
+ $table = new xmldb_table('quiz_question_statistics');
+ $field = new xmldb_field('maxgrade', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'subquestions');
+
+ /// Conditionally launch add field maxgrade
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ }
+
+ if ($result && $oldversion < 2008072801) {
+
+ /// Define field positions to be added to quiz_question_statistics
+ $table = new xmldb_table('quiz_question_statistics');
+ $field = new xmldb_field('positions', XMLDB_TYPE_TEXT, 'medium', null, null, null, null, null, null, 'maxgrade');
+
+ /// Conditionally launch add field positions
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ }
+
return $result;
}
} else {
$stats->othergradesarray[] = $state->sumgrades;
}
+
}
function _secondary_states_walker($state, &$stats){
+ global $QTYPES;
$gradedifference = ($state->grade - $stats->gradeaverage);
$othergradedifference = (($state->sumgrades - $state->grade) - $stats->othergradeaverage);
$overallgradedifference = $state->sumgrades - $this->sumgradesavg;
$stats->covariancesum += $gradedifference * $othergradedifference;
$stats->covariancemaxsum += $sortedgradedifference * $sortedothergradedifference;
$stats->covariancewithoverallgradesum += $gradedifference * $overallgradedifference;
+
+
}
- function _initial_question_walker(&$stats, $grade){
+ function _initial_question_walker(&$stats){
$stats->gradeaverage = $stats->totalgrades / $stats->s;
- $stats->facility = $stats->gradeaverage / $grade;
+ $stats->facility = $stats->gradeaverage / $stats->maxgrade;
$stats->othergradeaverage = $stats->totalothergrades / $stats->s;
sort($stats->gradearray, SORT_NUMERIC);
sort($stats->othergradesarray, SORT_NUMERIC);
}
function _secondary_question_walker(&$stats){
- $stats->gradevariance = $stats->gradevariancesum / ($stats->s -1);
- $stats->othergradevariance = $stats->othergradevariancesum / ($stats->s -1);
- $stats->covariance = $stats->covariancesum / ($stats->s -1);
- $stats->covariancemax = $stats->covariancemaxsum / ($stats->s -1);
- $stats->covariancewithoverallgrade = $stats->covariancewithoverallgradesum / ($stats->s-1);
- $stats->sd = sqrt($stats->gradevariancesum / ($stats->s -1));
+ if ($stats->s > 1){
+ $stats->gradevariance = $stats->gradevariancesum / ($stats->s -1);
+ $stats->othergradevariance = $stats->othergradevariancesum / ($stats->s -1);
+ $stats->covariance = $stats->covariancesum / ($stats->s -1);
+ $stats->covariancemax = $stats->covariancemaxsum / ($stats->s -1);
+ $stats->covariancewithoverallgrade = $stats->covariancewithoverallgradesum / ($stats->s-1);
+ $stats->sd = sqrt($stats->gradevariancesum / ($stats->s -1));
+ } else {
+ $stats->gradevariance = null;
+ $stats->othergradevariance = null;
+ $stats->covariance = null;
+ $stats->covariancemax = null;
+ $stats->covariancewithoverallgrade = null;
+ $stats->sd = null;
+ }
//avoid divide by zero
if ($stats->gradevariance * $stats->othergradevariance){
$stats->discriminationindex = 100*$stats->covariance
$this->_initial_states_walker($state, $this->questions[$state->question]->_stats);
//if this is a random question what is the real item being used?
if ($this->questions[$state->question]->qtype == 'random'){
- if ($itemid = question_get_real_questionid($state)){
- if (!isset($subquestionstats[$itemid])){
- $subquestionstats[$itemid] = $this->stats_init_object();
- $subquestionstats[$itemid]->usedin = array();
- $subquestionstats[$itemid]->subquestion = true;
- $subquestionstats[$itemid]->differentweights = false;
- $subquestionstats[$itemid]->maxgrade = $this->questions[$state->question]->maxgrade;
- } else if ($subquestionstats[$itemid]->maxgrade != $this->questions[$state->question]->maxgrade){
- $subquestionstats[$itemid]->differentweights = true;
+ if ($realstate = question_get_real_state($state)){
+ if (!isset($subquestionstats[$realstate->question])){
+ $subquestionstats[$realstate->question] = $this->stats_init_object();
+ $subquestionstats[$realstate->question]->usedin = array();
+ $subquestionstats[$realstate->question]->subquestion = true;
+ $subquestionstats[$realstate->question]->differentweights = false;
+ $subquestionstats[$realstate->question]->maxgrade = $this->questions[$state->question]->maxgrade;
+ } else if ($subquestionstats[$realstate->question]->maxgrade != $this->questions[$state->question]->maxgrade){
+ $subquestionstats[$realstate->question]->differentweights = true;
}
- $this->_initial_states_walker($state, $subquestionstats[$itemid], false);
- $subquestionstats[$itemid]->usedin[$state->question] = $state->question;
+ $this->_initial_states_walker($realstate, $subquestionstats[$realstate->question], false);
+ $number = $this->questions[$state->question]->number;
+ $subquestionstats[$realstate->question]->usedin[$number] = $number;
$randomselectorstring = $this->questions[$state->question]->category.'/'.$this->questions[$state->question]->questiontext;
if (!isset($this->randomselectors[$randomselectorstring])){
$this->randomselectors[$randomselectorstring] = array();
}
- $this->randomselectors[$randomselectorstring][$itemid] = $itemid;
+ $this->randomselectors[$randomselectorstring][$realstate->question] = $realstate->question;
}
}
}
foreach (array_keys($this->subquestions) as $qid){
$this->subquestions[$qid]->_stats = $subquestionstats[$qid];
$this->subquestions[$qid]->_stats->questionid = $qid;
- $this->subquestions[$qid]->maxgrade = $this->subquestions[$qid]->_stats->maxgrade;
- $this->_initial_question_walker($this->subquestions[$qid]->_stats, $this->subquestions[$qid]->_stats->maxgrade);
+ $this->_initial_question_walker($this->subquestions[$qid]->_stats);
if ($subquestionstats[$qid]->differentweights){
notify(get_string('erroritemappearsmorethanoncewithdifferentweight', 'quiz_statistics', $this->subquestions[$qid]->name));
}
+ if ($this->subquestions[$qid]->_stats->usedin){
+ sort($this->subquestions[$qid]->_stats->usedin, SORT_NUMERIC);
+ $this->subquestions[$qid]->_stats->positions = join($this->subquestions[$qid]->_stats->usedin, ',');
+ } else {
+ $this->subquestions[$qid]->_stats->positions = '';
+ }
}
reset($this->questions);
do{
list($qid, $question) = each($this->questions);
$nextquestion = current($this->questions);
$this->questions[$qid]->_stats->questionid = $qid;
- $this->_initial_question_walker($this->questions[$qid]->_stats, $this->questions[$qid]->maxgrade);
+ $this->questions[$qid]->_stats->positions = $this->questions[$qid]->number;
+ $this->questions[$qid]->_stats->maxgrade = $question->maxgrade;
+ $this->_initial_question_walker($this->questions[$qid]->_stats);
if ($question->qtype == 'random'){
$randomselectorstring = $question->category.'/'.$question->questiontext;
if ($nextquestion){
foreach ($this->states as $state){
$this->_secondary_states_walker($state, $this->questions[$state->question]->_stats);
if ($this->questions[$state->question]->qtype == 'random'){
- if ($itemid = question_get_real_questionid($state)){
- $this->_secondary_states_walker($state, $this->subquestions[$itemid]->_stats);
+ if ($realstate = question_get_real_state($state)){
+ $this->_secondary_states_walker($realstate, $this->subquestions[$realstate->question]->_stats);
}
}
}
$this->_secondary_question_walker($this->subquestions[$qid]->_stats);
}
foreach (array_keys($this->questions) as $qid){
- $this->questions[$qid]->_stats->effectiveweight = 100 * sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade)
- / $sumofcovariancewithoverallgrade;
+ if ($sumofcovariancewithoverallgrade){
+ $this->questions[$qid]->_stats->effectiveweight = 100 * sqrt($this->questions[$qid]->_stats->covariancewithoverallgrade)
+ / $sumofcovariancewithoverallgrade;
+ } else {
+ $this->questions[$qid]->_stats->effectiveweight = null;
+ }
}
}
/**
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.
$download = optional_param('download', '', PARAM_ALPHA);
$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;
/// 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)) {
} else {
$groupstudents = array();
}
-
- $table = new quiz_report_statistics_table();
- $table->is_downloading($download, get_string('reportstatistics','quiz_statistics'),
- "$course->shortname ".format_string($quiz->name,true));
- if (!$table->is_downloading()) {
+ 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('errordeletingquizstats', 'quiz_statistics');
+ }
+ if (!$DB->delete_records_select('quiz_question_statistics', "quizstatisticsid $todeletesql", $todeleteparams)){
+ print_error('errordeletingqstats', 'quiz_statistics');
+ }
+ }
+ 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 (!$table->is_downloading()) {
+ if (!$this->table->is_downloading()) {
groups_print_activity_menu($cm, $reporturl->out());
+ echo '<br />';
if ($currentgroup && !$groupstudents){
notify(get_string('nostudentsingroup', 'quiz_statistics'));
}
}
}
- if (!$table->is_downloading()) {
+ if (!$this->table->is_downloading()) {
// Print display options
$mform->set_data(array('useallattempts' => $useallattempts));
$mform->display();
}
- // Print information on the number of existing attempts
- if (!$table->is_downloading()) { //do not print notices when downloading
- print_heading(get_string('quizinformation', 'quiz_statistics'));
- $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('coursename', 'quiz_statistics'), $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));
- }
- }
- $timemodified = time() - QUIZ_REPORT_TIME_TO_CACHE_STATS;
- $params = array('quizid'=>$quiz->id, 'groupid'=>$currentgroup, 'allattempts'=>$useallattempts, 'timemodified'=>$timemodified);
- if ($recalculate || !$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);
- $toinsert = (object)((array)$quizstats + $params);
- $toinsert->timemodified = time();
- $quizstatisticsid = $DB->insert_record('quiz_statistics', $toinsert);
- foreach ($qstats->questions as $question){
- $question->_stats->quizstatisticsid = $quizstatisticsid;
- $DB->insert_record('quiz_question_statistics', $question->_stats, false, true);
- }
- foreach ($qstats->subquestions as $subquestion){
- $subquestion->_stats->quizstatisticsid = $quizstatisticsid;
- $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true);
- }
- if (isset($qstats)){
- $questions = $qstats->questions;
- $subquestions = $qstats->subquestions;
- } else {
- $questions = array();
- $subquestions = array();
+ 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){
+ $this->output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl);
+ $this->output_question_stats_table($s, $questions, $subquestions);
} else {
- if ($useallattempts){
- $usingattemptsstring = get_string('allattempts', 'quiz_statistics');
- $s = $quizstats->allattemptscount;
+ $thisquestion = false;
+ if (isset($questions[$qid])){
+ $thisquestion = $questions[$qid];
+ } else if (isset($subquestions[$qid])){
+ $thisquestion = $subquestions[$qid];
} else {
- $usingattemptsstring = get_string('firstattempts', 'quiz_statistics');
- $s = $quizstats->firstattemptscount;
+ print_error('questiondoesnotexist', 'question');
}
- $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];
+ $this->output_question_info_table($quiz, $thisquestion);
+ }
+ return true;
+ }
+
+ function output_question_info_table($quiz, $question){
+ $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'), $question->qtype.' '.$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);
+
+ print_heading(get_string('analysisofresponses', 'quiz_statistics'));
+
+ }
+
+ function output_question_stats_table($s, $questions, $subquestions){
+ if (!$this->table->is_downloading()){
+ print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
+ }
+ if ($s){
+ 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]));
+ }
}
- } else {
- $mainquestionstats = $questionstats[0];
- $subquestions = array();
- }
- foreach (array_keys($questions) as $qid){
- $questions[$qid]->_stats = $mainquestionstats[$qid];
}
+
+ $this->table->finish_output();
}
- if (!$table->is_downloading()){
- if ($s==0){
- print_heading(get_string('noattempts','quiz'));
+ }
+
+ function output_quiz_stats_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl){
+ 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('coursename', 'quiz_statistics'), $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;
}
- $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 = number_format($value, $quiz->decimalpoints).' %';
+ $formattedvalue = number_format($value, $quiz->decimalpoints).'%';
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) ' .
$a->lastcalculated = format_time(time() - $quizstats->timemodified);
if (!$a->count = $DB->count_records_sql($sql, array('time'=>$quizstats->timemodified)+$qaparams)){
$a->count = 0;
- }
- print_box_start('boxaligncenter generalbox boxwidthnormal mdl-align');
- echo get_string('lastcalculated', 'quiz_statistics', $a);
- print_single_button($reporturl->out(true), $reporturl->params()+array('recalculate'=>1),
- get_string('recalculatenow', 'quiz_statistics'), 'post');
- print_box_end();
+ }
+ $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);
}
- print_table($quizinformationtable);
-
}
- if (!$table->is_downloading()){
- print_heading(get_string('quizstructureanalysis', 'quiz_statistics'));
- }
- if ($s){
- $table->setup($quiz, $cm->id, $reporturl, $s);
-
- foreach ($questions as $question){
- $table->add_data_keyed($table->format_row($question));
- if (!empty($question->_stats->subquestions)){
- $subitemstodisplay = explode(',', $question->_stats->subquestions);
- foreach ($subitemstodisplay as $subitemid){
- $subquestions[$subitemid]->maxgrade = $question->maxgrade;
- $table->add_data_keyed($table->format_row($subquestions[$subitemid]));
- }
+ $quizinformationtablehtml .= print_table($quizinformationtable, true);
+ if (!$this->table->is_downloading()){
+ echo $quizinformationtablehtml;
+ } else {
+ $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();
}
-
- $table->finish_output();
}
- return true;
}
-
-
+
function quiz_stats($nostudentsingroup, $quizid, $currentgroup, $groupstudents, $questions, $useallattempts){
global $CFG, $DB;
if (!$nostudentsingroup){
$k2= $s*$m2/($s-1);
$k3= $s*$s*$m3/(($s-1)*($s-2));
-
- $quizstats->skewness = $k3 / (pow($k2, 2/3));
+ if ($k2){
+ $quizstats->skewness = $k3 / (pow($k2, 2/3));
+ }
}
if ($s>3){
$k4= (($s*$s*$s)/(($s-1)*($s-2)*($s-3)))*((($s+1)*$m4)-(3*($s-1)*$m2*$m2));
-
- $quizstats->kurtosis = $k4 / ($k2*$k2);
+ if ($k2){
+ $quizstats->kurtosis = $k4 / ($k2*$k2);
+ }
}
}
}
require_once("$CFG->dirroot/mod/quiz/report/statistics/qstats.php");
$qstats = new qstats($questions, $s, $sumgradesavg);
$qstats->get_records($quizid, $currentgroup, $groupstudents, $useallattempts);
- set_time_limit(0);
$qstats->process_states();
} else {
$qstats = false;
if ($s>1){
$p = count($qstats->questions);//no of positions
if ($p > 1){
- $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);
-
+ if ($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);
+ $toinsert = (object)((array)$quizstats + $params);
+ $toinsert->timemodified = time();
+ $quizstatisticsid = $DB->insert_record('quiz_statistics', $toinsert);
+ foreach ($qstats->questions as $question){
+ $question->_stats->quizstatisticsid = $quizstatisticsid;
+ $DB->insert_record('quiz_question_statistics', $question->_stats, false, true);
+ }
+ foreach ($qstats->subquestions as $subquestion){
+ $subquestion->_stats->quizstatisticsid = $quizstatisticsid;
+ $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true);
+ }
+ if (isset($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;
+ }
+ $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];
+ }
+ } else {
+ $mainquestionstats = $questionstats[0];
+ $subquestions = array();
+ }
+ 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;
}
return array($fromqa, $whereqa, $qaparams);
}
+function quiz_report_safe_divider($dividend, $divisor){
+ if ($divisor == 0){
+ return null;
+ }
+ return $dividend / $divisor;
+}
?>
--- /dev/null
+<?php // $Id$
+include '../../../../config.php';
+include $CFG->dirroot."/lib/graphlib.php";
+include $CFG->dirroot."/mod/quiz/locallib.php";
+include $CFG->dirroot."/mod/quiz/report/reportlib.php";
+function graph_get_new_colour(){
+ static $colourindex = 0;
+ $colours = array('red', 'green', 'yellow', 'orange', 'purple', 'black', 'maroon', 'blue', 'ltgreen', 'navy', 'ltred', 'ltltgreen', 'ltltorange', 'olive', 'gray', 'ltltred', 'ltorange', 'lime', 'ltblue', 'ltltblue');
+ $colour = $colours[$colourindex];
+ $colourindex++;
+ if ($colourindex > (count($colours)-1)){
+ $colourindex =0;
+ }
+ return $colour;
+}
+$quizstatisticsid = required_param('id', PARAM_INT);
+
+$quizstatistics = $DB->get_record('quiz_statistics', array('id' => $quizstatisticsid));
+$questionstatistics = $DB->get_records('quiz_question_statistics', array('quizstatisticsid' => $quizstatistics->id, 'subquestion' => 0));
+$quiz = $DB->get_record('quiz', array('id' => $quizstatistics->quizid));
+require_login($quiz->course);
+$questions = quiz_report_load_questions($quiz);
+$cm = get_coursemodule_from_instance('quiz', $quiz->id);
+if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
+ $groups = groups_get_activity_allowed_groups($cm);
+} else {
+ $groups = false;
+}
+if ($quizstatistics->groupid){
+ if (!in_array($quizstatistics->groupid, $groups)){
+ print_error('groupnotamember', 'group');
+ }
+}
+$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
+require_capability('mod/quiz:viewreports', $modcontext);
+
+$line = new graph(800,600);
+$line->parameter['title'] = '';
+$line->parameter['y_label_left'] = '%';
+$line->parameter['x_label'] = get_string('position', 'quiz_statistics');
+$line->parameter['y_label_angle'] = 90;
+$line->parameter['x_label_angle'] = 0;
+$line->parameter['x_axis_angle'] = 60;
+
+$line->parameter['legend'] = 'outside-top';
+$line->parameter['legend_border'] = 'black';
+$line->parameter['legend_offset'] = 4;
+
+
+$line->parameter['bar_size'] = 1.2;
+$line->parameter['bar_spacing'] = 10;
+
+$fieldstoplot = array('facility' => get_string('facility', 'quiz_statistics'), 'discriminativeefficiency' => get_string('discriminative_efficiency', 'quiz_statistics'));
+$fieldstoplotfactor = array('facility' => 100, 'discriminativeefficiency' => 1);
+
+$line->x_data = array();
+foreach (array_keys($fieldstoplot) as $fieldtoplot){
+ $line->y_data[$fieldtoplot] = array();
+ $line->y_format[$fieldtoplot] =
+ array('colour' => graph_get_new_colour(), 'bar' => 'fill', 'shadow_offset' => 1, 'legend' => $fieldstoplot[$fieldtoplot]);
+}
+foreach ($questionstatistics as $questionstatistic){
+ $line->x_data[] = $questions[$questionstatistic->questionid]->number;
+ foreach (array_keys($fieldstoplot) as $fieldtoplot){
+ $value = !is_null($questionstatistic->$fieldtoplot)?$questionstatistic->$fieldtoplot:0;
+ $value = $value * $fieldstoplotfactor[$fieldtoplot];
+ $line->y_data[$fieldtoplot][$questions[$questionstatistic->questionid]->number] = $value;
+ }
+}
+ksort($line->x_data);
+$max = 0;
+$min = 0;
+foreach (array_keys($fieldstoplot) as $fieldtoplot){
+ ksort($line->y_data[$fieldtoplot]);
+ $line->y_data[$fieldtoplot] = array_values($line->y_data[$fieldtoplot]);
+ $max = (max($line->y_data[$fieldtoplot])> $max)? max($line->y_data[$fieldtoplot]): $max;
+ $min = (min($line->y_data[$fieldtoplot])> $min)? min($line->y_data[$fieldtoplot]): $min;
+}
+$line->y_order = array_keys($fieldstoplot);
+
+
+$line->parameter['y_min_left'] = $min; // start at 0
+$line->parameter['y_max_left'] = $max;
+$line->parameter['y_decimal_left'] = 0;
+
+
+$line->draw();
+?>
$columns[]= 'discriminative_efficiency';
$headers[]= get_string('discriminative_efficiency', 'quiz_statistics');
-
+
$this->define_columns($columns);
$this->define_headers($headers);
$this->sortable(false);
function col_name($question){
- return $question->name;
+ if (!$this->is_downloading() && $question->qtype!='random'){
+ $tooltip = get_string('detailedanalysis', 'quiz_statistics');
+ $url = $this->baseurl .'&qid='.$question->id;
+ return "<a title=\"$tooltip\" href=\"$url\">".$question->name."</a>";
+ } else {
+ return $question->name;
+ }
}
return $question->qtype;
}
function col_intended_weight($question){
- return quiz_report_scale_sumgrades_as_percentage($question->maxgrade, $this->quiz);
+ return quiz_report_scale_sumgrades_as_percentage($question->_stats->maxgrade, $this->quiz);
}
function col_effective_weight($question){
if (!$question->_stats->subquestion){
- return number_format($question->_stats->effectiveweight, 2).' %';
+ return number_format($question->_stats->effectiveweight, 2).'%';
} else {
return '';
}
}
function col_discrimination_index($question){
if (is_numeric($question->_stats->discriminationindex)){
- return number_format($question->_stats->discriminationindex, 2).' %';
+ return number_format($question->_stats->discriminationindex, 2).'%';
} else {
return $question->_stats->discriminationindex;
}
}
function col_discriminative_efficiency($question){
if (is_numeric($question->_stats->discriminativeefficiency)){
- return number_format($question->_stats->discriminativeefficiency, 2).' %';
+ return number_format($question->_stats->discriminativeefficiency, 2).'%';
} else {
return '';
}
function col_random_guess_score($question){
$randomguessscore = question_get_random_guess_score($question);
if (is_numeric($randomguessscore)){
- return number_format($randomguessscore * 100, 2).' %';
+ return number_format($randomguessscore * 100, 2).'%';
} else {
return $randomguessscore; // empty string returned by random question.
}
}
function col_sd($question){
- return number_format($question->_stats->sd*100 / $question->maxgrade, 2).' %';
+ return number_format($question->_stats->sd*100 / $question->_stats->maxgrade, 2).'%';
}
function col_s($question){
if (isset($question->_stats->s)){
}
}
function col_facility($question){
- return number_format($question->_stats->facility*100, 2).' %';
+ return number_format($question->_stats->facility*100, 2).'%';
}
+
}
?>
<?php
-$plugin->version = 2008072500; // The (date) version of this module
+$plugin->version = 2008072801; // The (date) version of this module
?>
\ No newline at end of file