Merge branch 'MDL-41752-master' of git://github.com/jamiepratt/moodle
This commit is contained in:
@@ -864,11 +864,9 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2012062000.01);
|
||||
}
|
||||
|
||||
|
||||
// Moodle v2.3.0 release upgrade line
|
||||
// Put any upgrade step following this
|
||||
|
||||
|
||||
if ($oldversion < 2012062500.02) {
|
||||
// Drop some old backup tables, not used anymore
|
||||
|
||||
@@ -1497,7 +1495,6 @@ function xmldb_main_upgrade($oldversion) {
|
||||
// Moodle v2.4.0 release upgrade line
|
||||
// Put any upgrade step following this
|
||||
|
||||
|
||||
if ($oldversion < 2012120300.01) {
|
||||
// Make sure site-course has format='site' //MDL-36840
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ class quiz_statistics_calculated {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @var array of names of properties of this class that are cached in db record.
|
||||
*/
|
||||
|
||||
@@ -37,9 +37,7 @@ class quiz_statistics_calculator {
|
||||
* @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 array with two elements:
|
||||
* - integer $s Number of attempts included in the stats.
|
||||
* - object $quizstats The statistics for overall attempt scores.
|
||||
* @return quiz_statistics_calculated $quizstats The statistics for overall attempt scores.
|
||||
*/
|
||||
public function calculate($quizid, $currentgroup, $useallattempts, $groupstudents, $p, $sumofmarkvariance) {
|
||||
|
||||
@@ -92,7 +90,6 @@ class quiz_statistics_calculator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$quizstats->cache(quiz_statistics_qubaids_condition($quizid, $currentgroup, $groupstudents, $useallattempts));
|
||||
|
||||
return $quizstats;
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Post-install script
|
||||
*/
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Quiz statistics report upgrade code.
|
||||
*/
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Serve questiontext files in the question text when they are displayed in this report.
|
||||
*
|
||||
|
||||
@@ -22,14 +22,11 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
|
||||
require_once($CFG->dirroot . '/question/engine/statistics.php');
|
||||
require_once($CFG->dirroot . '/question/engine/responseanalysis.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
|
||||
/**
|
||||
* The quiz statistics report provides summary information about each question in
|
||||
@@ -46,7 +43,7 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/** @var object instance of table class used for main questions stats table. */
|
||||
/** @var quiz_statistics_table instance of table class used for main questions stats table. */
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
@@ -108,7 +105,6 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
|
||||
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
|
||||
|
||||
|
||||
// If recalculate was requested, handle that.
|
||||
if ($recalculate && confirm_sesskey()) {
|
||||
$this->clear_cached_data($qubaids);
|
||||
@@ -131,14 +127,13 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
|
||||
if (!$nostudentsingroup) {
|
||||
// Get the data to be displayed.
|
||||
list($quizstats, $questions, $subquestions) =
|
||||
list($quizstats, $questionstats, $subquestionstats) =
|
||||
$this->get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions);
|
||||
} else {
|
||||
// Or create empty stats containers.
|
||||
$quizstats = new quiz_statistics_calculated($useallattempts);
|
||||
$qstats = new question_statistics($questions);
|
||||
$questions = $qstats->questions;
|
||||
$subquestions = $qstats->subquestions;
|
||||
$questionstats = array();
|
||||
$subquestionstats = array();
|
||||
}
|
||||
$quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
|
||||
|
||||
@@ -174,23 +169,23 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
$this->download_quiz_info_table($quizinfo);
|
||||
|
||||
if ($quizstats->s()) {
|
||||
$this->output_quiz_structure_analysis_table($quizstats->s(), $questions, $subquestions);
|
||||
$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, $useallattempts);
|
||||
}
|
||||
|
||||
foreach ($questions as $question) {
|
||||
foreach ($questions as $slot => $question) {
|
||||
if (question_bank::get_qtype(
|
||||
$question->qtype, false)->can_analyse_responses()) {
|
||||
$this->output_individual_question_response_analysis(
|
||||
$question, $reporturl, $qubaids);
|
||||
$question, $questionstats[$slot]->s, $reporturl, $qubaids);
|
||||
|
||||
} else if (!empty($question->_stats->subquestions)) {
|
||||
$subitemstodisplay = explode(',', $question->_stats->subquestions);
|
||||
} else if (!empty($questionstats[$slot]->subquestions)) {
|
||||
$subitemstodisplay = explode(',', $questionstats[$slot]->subquestions);
|
||||
foreach ($subitemstodisplay as $subitemid) {
|
||||
$this->output_individual_question_response_analysis(
|
||||
$subquestions[$subitemid], $reporturl, $qubaids);
|
||||
$subquestionstats[$subitemid]->question, $subquestionstats[$subitemid]->s, $reporturl, $qubaids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,9 +199,8 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
print_error('questiondoesnotexist', 'question');
|
||||
}
|
||||
|
||||
$this->output_individual_question_data($quiz, $questions[$slot]);
|
||||
$this->output_individual_question_response_analysis(
|
||||
$questions[$slot], $reporturl, $qubaids);
|
||||
$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() . '">' .
|
||||
@@ -219,9 +213,9 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
print_error('questiondoesnotexist', 'question');
|
||||
}
|
||||
|
||||
$this->output_individual_question_data($quiz, $subquestions[$qid]);
|
||||
$this->output_individual_question_response_analysis(
|
||||
$subquestions[$qid], $reporturl, $qubaids);
|
||||
$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() . '">' .
|
||||
@@ -231,7 +225,7 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
} else if ($this->table->is_downloading()) {
|
||||
// Downloading overview report.
|
||||
$this->download_quiz_info_table($quizinfo);
|
||||
$this->output_quiz_structure_analysis_table($quizstats->s(), $questions, $subquestions);
|
||||
$this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
|
||||
$this->table->finish_output();
|
||||
|
||||
} else {
|
||||
@@ -243,7 +237,7 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
echo $this->output_quiz_info_table($quizinfo);
|
||||
if ($quizstats->s()) {
|
||||
echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'));
|
||||
$this->output_quiz_structure_analysis_table($quizstats->s(), $questions, $subquestions);
|
||||
$this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
|
||||
$this->output_statistics_graph($quiz->id, $currentgroup, $useallattempts);
|
||||
}
|
||||
}
|
||||
@@ -255,16 +249,16 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
* Display the statistical and introductory information about a question.
|
||||
* Only called when not downloading.
|
||||
* @param object $quiz the quiz settings.
|
||||
* @param object $question the question to report on.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat the question to report on.
|
||||
* @param moodle_url $reporturl the URL to resisplay this report.
|
||||
* @param object $quizstats Holds the quiz statistics.
|
||||
*/
|
||||
protected function output_individual_question_data($quiz, $question) {
|
||||
protected function output_individual_question_data($quiz, $questionstat) {
|
||||
global $OUTPUT;
|
||||
|
||||
// On-screen display. Show a summary of the question's place in the quiz,
|
||||
// and the question statistics.
|
||||
$datumfromtable = $this->table->format_row($question);
|
||||
$datumfromtable = $this->table->format_row($questionstat);
|
||||
|
||||
// Set up the question info table.
|
||||
$questioninfotable = new html_table();
|
||||
@@ -275,13 +269,13 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
$questioninfotable->data = array();
|
||||
$questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
|
||||
$questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
|
||||
$question->name.' '.$datumfromtable['actions']);
|
||||
$questionstat->question->name.' '.$datumfromtable['actions']);
|
||||
$questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
|
||||
$datumfromtable['icon'] . ' ' .
|
||||
question_bank::get_qtype($question->qtype, false)->menu_name() . ' ' .
|
||||
question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . ' ' .
|
||||
$datumfromtable['icon']);
|
||||
$questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
|
||||
$question->_stats->positions);
|
||||
$questionstat->positions);
|
||||
|
||||
// Set up the question statistics table.
|
||||
$questionstatstable = new html_table();
|
||||
@@ -312,7 +306,7 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
// Display the various bits.
|
||||
echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'));
|
||||
echo html_writer::table($questioninfotable);
|
||||
echo $this->render_question_text($question);
|
||||
echo $this->render_question_text($questionstat->question);
|
||||
echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'));
|
||||
echo html_writer::table($questionstatstable);
|
||||
}
|
||||
@@ -339,8 +333,7 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
* @param moodle_url $reporturl the URL to resisplay this report.
|
||||
* @param qubaid_condition $qubaids
|
||||
*/
|
||||
protected function output_individual_question_response_analysis($question,
|
||||
$reporturl, $qubaids) {
|
||||
protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
|
||||
global $OUTPUT;
|
||||
|
||||
if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
|
||||
@@ -373,10 +366,10 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
}
|
||||
}
|
||||
|
||||
$responesstats = new question_response_analyser($question);
|
||||
$responesstats = new \core_question\statistics\responses\analyser($question);
|
||||
$responesstats->load_cached($qubaids);
|
||||
|
||||
$qtable->question_setup($reporturl, $question, $responesstats);
|
||||
$qtable->question_setup($reporturl, $question, $s, $responesstats);
|
||||
if ($this->table->is_downloading()) {
|
||||
$exportclass->output_headers($qtable->headers);
|
||||
}
|
||||
@@ -415,27 +408,27 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
/**
|
||||
* Output the table that lists all the questions in the quiz with their statistics.
|
||||
* @param int $s number of attempts.
|
||||
* @param array $questions the questions in the quiz.
|
||||
* @param array $subquestions the subquestions of any random questions.
|
||||
* @param \core_question\statistics\questions\calculated[] $questionstats the stats for the main questions in the quiz.
|
||||
* @param \core_question\statistics\questions\calculated_for_subquestion[] $subquestionstats the stats of any random questions.
|
||||
*/
|
||||
protected function output_quiz_structure_analysis_table($s, $questions, $subquestions) {
|
||||
protected function output_quiz_structure_analysis_table($s, $questionstats, $subquestionstats) {
|
||||
if (!$s) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($questions as $question) {
|
||||
// Output the data for this questions.
|
||||
$this->table->add_data_keyed($this->table->format_row($question));
|
||||
foreach ($questionstats as $questionstat) {
|
||||
// Output the data for these question statistics.
|
||||
$this->table->add_data_keyed($this->table->format_row($questionstat));
|
||||
|
||||
if (empty($question->_stats->subquestions)) {
|
||||
if (empty($questionstat->subquestions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// And its subquestions, if it has any.
|
||||
$subitemstodisplay = explode(',', $question->_stats->subquestions);
|
||||
$subitemstodisplay = explode(',', $questionstat->subquestions);
|
||||
foreach ($subitemstodisplay as $subitemid) {
|
||||
$subquestions[$subitemid]->maxmark = $question->maxmark;
|
||||
$this->table->add_data_keyed($this->table->format_row($subquestions[$subitemid]));
|
||||
$subquestionstats[$subitemid]->maxmark = $questionstat->maxmark;
|
||||
$this->table->add_data_keyed($this->table->format_row($subquestionstats[$subitemid]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,43 +512,36 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
* @param array $questions question definitions.
|
||||
* @return array with 4 elements:
|
||||
* - $quizstats The statistics for overall attempt scores.
|
||||
* - $questions The questions, with an additional _stats field.
|
||||
* - $subquestions The subquestions, if any, with an additional _stats field.
|
||||
* - $s Number of attempts included in the stats.
|
||||
* - $questionstats array of \core_question\statistics\questions\calculated objects keyed by slot.
|
||||
* - $subquestionstats array of \core_question\statistics\questions\calculated_for_subquestion objects keyed by question id.
|
||||
*/
|
||||
protected function get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions) {
|
||||
|
||||
$qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
|
||||
|
||||
|
||||
$qstats = new question_statistics($questions);
|
||||
$qcalc = new \core_question\statistics\questions\calculator($questions);
|
||||
|
||||
$quizcalc = new quiz_statistics_calculator();
|
||||
|
||||
if ($quizcalc->get_last_calculated_time($qubaids) === false) {
|
||||
// Recalculate now.
|
||||
$qstats->calculate($qubaids);
|
||||
list($questionstats, $subquestionstats) = $qcalc->calculate($qubaids);
|
||||
|
||||
$quizstats = $quizcalc->calculate($quiz->id, $currentgroup, $useallattempts,
|
||||
$groupstudents, count($questions), $qstats->get_sum_of_mark_variance());
|
||||
|
||||
$questions = $qstats->questions;
|
||||
$subquestions = $qstats->subquestions;
|
||||
$groupstudents, count($questions), $qcalc->get_sum_of_mark_variance());
|
||||
|
||||
if ($quizstats->s()) {
|
||||
$this->calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions);
|
||||
$this->calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
|
||||
}
|
||||
} else {
|
||||
$quizstats = $quizcalc->get_cached($qubaids);
|
||||
$qstats->get_cached($qubaids);
|
||||
$questions = $qstats->questions;
|
||||
$subquestions = $qstats->subquestions;
|
||||
list($questionstats, $subquestionstats) = $qcalc->get_cached($qubaids);
|
||||
}
|
||||
|
||||
return array($quizstats, $questions, $subquestions);
|
||||
return array($quizstats, $questionstats, $subquestionstats);
|
||||
}
|
||||
|
||||
protected function calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions) {
|
||||
protected function calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats) {
|
||||
|
||||
$done = array();
|
||||
foreach ($questions as $question) {
|
||||
@@ -564,18 +550,18 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
}
|
||||
$done[$question->id] = 1;
|
||||
|
||||
$responesstats = new question_response_analyser($question);
|
||||
$responesstats = new \core_question\statistics\responses\analyser($question);
|
||||
$responesstats->calculate($qubaids);
|
||||
}
|
||||
|
||||
foreach ($subquestions as $question) {
|
||||
if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses() ||
|
||||
isset($done[$question->id])) {
|
||||
foreach ($subquestionstats as $subquestionstat) {
|
||||
if (!question_bank::get_qtype($subquestionstat->question->qtype, false)->can_analyse_responses() ||
|
||||
isset($done[$subquestionstat->question->id])) {
|
||||
continue;
|
||||
}
|
||||
$done[$question->id] = 1;
|
||||
$done[$subquestionstat->question->id] = 1;
|
||||
|
||||
$responesstats = new question_response_analyser($question);
|
||||
$responesstats = new \core_question\statistics\responses\analyser($subquestionstat->question);
|
||||
$responesstats->calculate($qubaids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
|
||||
/**
|
||||
* This is the settings form for the quiz statistics report.
|
||||
*
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
require_once(dirname(__FILE__) . '/../../../../config.php');
|
||||
require_once($CFG->libdir . '/graphlib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
|
||||
/**
|
||||
* This table shows statistics about a particular question.
|
||||
*
|
||||
@@ -40,12 +38,12 @@ require_once($CFG->libdir . '/tablelib.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class quiz_statistics_question_table extends flexible_table {
|
||||
/** @var object this question with a _stats field. */
|
||||
/** @var object this question. */
|
||||
protected $questiondata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param $qid the id of the particular question whose statistics are being
|
||||
* @param int $qid the id of the particular question whose statistics are being
|
||||
* displayed.
|
||||
*/
|
||||
public function __construct($qid) {
|
||||
@@ -53,16 +51,14 @@ class quiz_statistics_question_table extends flexible_table {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the columns and headers and other properties of the table and then
|
||||
* call flexible_table::setup() method.
|
||||
*
|
||||
* @param moodle_url $reporturl the URL to redisplay this report.
|
||||
* @param object $question a question with a _stats field
|
||||
* @param bool $hassubqs
|
||||
* @param moodle_url $reporturl
|
||||
* @param object $questiondata
|
||||
* @param integer $s number of attempts on this question.
|
||||
* @param \core_question\statistics\responses\analyser $responesstats
|
||||
*/
|
||||
public function question_setup($reporturl, $questiondata,
|
||||
question_response_analyser $responesstats) {
|
||||
public function question_setup($reporturl, $questiondata, $s, \core_question\statistics\responses\analyser $responesstats) {
|
||||
$this->questiondata = $questiondata;
|
||||
$this->s = $s;
|
||||
|
||||
$this->define_baseurl($reporturl->out());
|
||||
$this->collapsible(false);
|
||||
@@ -137,10 +133,10 @@ class quiz_statistics_question_table extends flexible_table {
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_frequency($response) {
|
||||
if (!$this->questiondata->_stats->s) {
|
||||
if (!$this->s) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->format_percentage($response->count / $this->questiondata->_stats->s);
|
||||
return $this->format_percentage($response->count / $this->s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/tablelib.php');
|
||||
|
||||
|
||||
/**
|
||||
* This table has one row for each question in the quiz, with sub-rows when
|
||||
* random questions appear. There are columns for the various statistics.
|
||||
@@ -134,61 +132,63 @@ class quiz_statistics_table extends flexible_table {
|
||||
|
||||
/**
|
||||
* The question number.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_number($question) {
|
||||
if ($question->_stats->subquestion) {
|
||||
protected function col_number($questionstat) {
|
||||
if ($questionstat->subquestion) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $question->number;
|
||||
return $questionstat->question->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The question type icon.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_icon($question) {
|
||||
return print_question_icon($question, true);
|
||||
protected function col_icon($questionstat) {
|
||||
return print_question_icon($questionstat->question, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions that can be performed on the question by this user (e.g. edit or preview).
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_actions($question) {
|
||||
return quiz_question_action_icons($this->quiz, $this->cmid, $question, $this->baseurl);
|
||||
protected function col_actions($questionstat) {
|
||||
return quiz_question_action_icons($this->quiz, $this->cmid, $questionstat->question, $this->baseurl);
|
||||
}
|
||||
|
||||
/**
|
||||
* The question type name.
|
||||
* @param object $question containst the data to display.
|
||||
*
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_qtype($question) {
|
||||
return question_bank::get_qtype_name($question->qtype);
|
||||
protected function col_qtype($questionstat) {
|
||||
return question_bank::get_qtype_name($questionstat->question->qtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* The question name.
|
||||
* @param object $question containst the data to display.
|
||||
*
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_name($question) {
|
||||
$name = $question->name;
|
||||
protected function col_name($questionstat) {
|
||||
$name = $questionstat->question->name;
|
||||
|
||||
if ($this->is_downloading()) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$url = null;
|
||||
if ($question->_stats->subquestion) {
|
||||
$url = new moodle_url($this->baseurl, array('qid' => $question->id));
|
||||
} else if ($question->_stats->slot && $question->qtype != 'random') {
|
||||
$url = new moodle_url($this->baseurl, array('slot' => $question->_stats->slot));
|
||||
if ($questionstat->subquestion) {
|
||||
$url = new moodle_url($this->baseurl, array('qid' => $questionstat->questionid));
|
||||
} else if ($questionstat->slot && $questionstat->question->qtype != 'random') {
|
||||
$url = new moodle_url($this->baseurl, array('slot' => $questionstat->slot));
|
||||
}
|
||||
|
||||
if ($url) {
|
||||
@@ -196,7 +196,7 @@ class quiz_statistics_table extends flexible_table {
|
||||
array('title' => get_string('detailedanalysis', 'quiz_statistics')));
|
||||
}
|
||||
|
||||
if ($this->is_dubious_question($question)) {
|
||||
if ($this->is_dubious_question($questionstat)) {
|
||||
$name = html_writer::tag('div', $name, array('class' => 'dubious'));
|
||||
}
|
||||
|
||||
@@ -205,82 +205,82 @@ class quiz_statistics_table extends flexible_table {
|
||||
|
||||
/**
|
||||
* The number of attempts at this question.
|
||||
* @param object $question containst the data to display.
|
||||
*
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_s($question) {
|
||||
if (!isset($question->_stats->s)) {
|
||||
protected function col_s($questionstat) {
|
||||
if (!isset($questionstat->s)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $question->_stats->s;
|
||||
return $questionstat->s;
|
||||
}
|
||||
|
||||
/**
|
||||
* The facility index (average fraction).
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_facility($question) {
|
||||
if (is_null($question->_stats->facility)) {
|
||||
protected function col_facility($questionstat) {
|
||||
if (is_null($questionstat->facility)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return number_format($question->_stats->facility*100, 2) . '%';
|
||||
return number_format($questionstat->facility*100, 2) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard deviation of the fractions.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_sd($question) {
|
||||
if (is_null($question->_stats->sd) || $question->_stats->maxmark == 0) {
|
||||
protected function col_sd($questionstat) {
|
||||
if (is_null($questionstat->sd) || $questionstat->maxmark == 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return number_format($question->_stats->sd*100 / $question->_stats->maxmark, 2) . '%';
|
||||
return number_format($questionstat->sd*100 / $questionstat->maxmark, 2) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* An estimate of the fraction a student would get by guessing randomly.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_random_guess_score($question) {
|
||||
if (is_null($question->_stats->randomguessscore)) {
|
||||
protected function col_random_guess_score($questionstat) {
|
||||
if (is_null($questionstat->randomguessscore)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return number_format($question->_stats->randomguessscore * 100, 2).'%';
|
||||
return number_format($questionstat->randomguessscore * 100, 2).'%';
|
||||
}
|
||||
|
||||
/**
|
||||
* The intended question weight. Maximum mark for the question as a percentage
|
||||
* of maximum mark for the quiz. That is, the indended influence this question
|
||||
* on the student's overall mark.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_intended_weight($question) {
|
||||
return quiz_report_scale_summarks_as_percentage(
|
||||
$question->_stats->maxmark, $this->quiz);
|
||||
protected function col_intended_weight($questionstat) {
|
||||
return quiz_report_scale_summarks_as_percentage($questionstat->maxmark, $this->quiz);
|
||||
}
|
||||
|
||||
/**
|
||||
* The effective question weight. That is, an estimate of the actual
|
||||
* influence this question has on the student's overall mark.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_effective_weight($question) {
|
||||
protected function col_effective_weight($questionstat) {
|
||||
global $OUTPUT;
|
||||
|
||||
if ($question->_stats->subquestion) {
|
||||
if ($questionstat->subquestion) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($question->_stats->negcovar) {
|
||||
if ($questionstat->negcovar) {
|
||||
$negcovar = get_string('negcovar', 'quiz_statistics');
|
||||
|
||||
if (!$this->is_downloading()) {
|
||||
@@ -292,49 +292,49 @@ class quiz_statistics_table extends flexible_table {
|
||||
return $negcovar;
|
||||
}
|
||||
|
||||
return number_format($question->_stats->effectiveweight, 2) . '%';
|
||||
return number_format($questionstat->effectiveweight, 2) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Discrimination index. This is the product moment correlation coefficient
|
||||
* between the fraction for this qestion, and the average fraction for the
|
||||
* between the fraction for this question, and the average fraction for the
|
||||
* other questions in this quiz.
|
||||
* @param object $question containst the data to display.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_discrimination_index($question) {
|
||||
if (!is_numeric($question->_stats->discriminationindex)) {
|
||||
return $question->_stats->discriminationindex;
|
||||
protected function col_discrimination_index($questionstat) {
|
||||
if (!is_numeric($questionstat->discriminationindex)) {
|
||||
return $questionstat->discriminationindex;
|
||||
}
|
||||
|
||||
return number_format($question->_stats->discriminationindex, 2) . '%';
|
||||
return number_format($questionstat->discriminationindex, 2) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Discrimination efficiency, similar to, but different from, the Discrimination index.
|
||||
* @param object $question containst the data to display.
|
||||
*
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return string contents of this table cell.
|
||||
*/
|
||||
protected function col_discriminative_efficiency($question) {
|
||||
if (!is_numeric($question->_stats->discriminativeefficiency)) {
|
||||
protected function col_discriminative_efficiency($questionstat) {
|
||||
if (!is_numeric($questionstat->discriminativeefficiency)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return number_format($question->_stats->discriminativeefficiency, 2) . '%';
|
||||
return number_format($questionstat->discriminativeefficiency, 2) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method encapsulates the test for wheter a question should be considered dubious.
|
||||
* @param object question the question object with a property _stats which
|
||||
* includes all the stats for the question.
|
||||
* @param \core_question\statistics\questions\calculated $questionstat stats for the question.
|
||||
* @return bool is this question possibly not pulling it's weight?
|
||||
*/
|
||||
protected function is_dubious_question($question) {
|
||||
if (!is_numeric($question->_stats->discriminativeefficiency)) {
|
||||
protected function is_dubious_question($questionstat) {
|
||||
if (!is_numeric($questionstat->discriminativeefficiency)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $question->_stats->discriminativeefficiency < 15;
|
||||
return $questionstat->discriminativeefficiency < 15;
|
||||
}
|
||||
|
||||
public function wrap_html_start() {
|
||||
|
||||
@@ -23,23 +23,26 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
require_once($CFG->dirroot . '/question/engine/statistics.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
|
||||
|
||||
|
||||
/**
|
||||
* Test helper subclass of question_statistics
|
||||
*
|
||||
* @copyright 2010 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_question_statistics extends question_statistics {
|
||||
class testable_question_statistics extends \core_question\statistics\questions\calculator {
|
||||
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
protected $lateststeps;
|
||||
|
||||
public function set_step_data($states) {
|
||||
$this->lateststeps = $states;
|
||||
}
|
||||
@@ -95,9 +98,9 @@ class quiz_statistics_question_stats_testcase extends basic_testcase {
|
||||
// Data is taken from questions mostly generated by
|
||||
// contrib/tools/generators/generator.php.
|
||||
$questions = $this->get_records_from_csv(__DIR__.'/fixtures/mdl_question.csv');
|
||||
$this->qstats = new testable_question_statistics($questions, 22, 10045.45455);
|
||||
$this->qstats->set_step_data($steps);
|
||||
$this->qstats->calculate(null);
|
||||
$calculator = new testable_question_statistics($questions);
|
||||
$calculator->set_step_data($steps);
|
||||
list($this->qstats, ) = $calculator->calculate(null);
|
||||
|
||||
// Values expected are taken from contrib/tools/quiz_tools/stats.xls.
|
||||
$facility = array(0, 0, 0, 0, null, null, null, 41.19318182, 81.36363636,
|
||||
@@ -125,13 +128,13 @@ class quiz_statistics_question_stats_testcase extends basic_testcase {
|
||||
}
|
||||
|
||||
public function qstats_q_fields($fieldname, $values, $multiplier=1) {
|
||||
foreach ($this->qstats->questions as $question) {
|
||||
foreach ($this->qstats as $qstat) {
|
||||
$value = array_shift($values);
|
||||
if ($value !== null) {
|
||||
$this->assertEquals($question->_stats->{$fieldname} * $multiplier,
|
||||
$this->assertEquals($qstat->{$fieldname} * $multiplier,
|
||||
$value, '', 1E-6);
|
||||
} else {
|
||||
$this->assertEquals($question->_stats->{$fieldname} * $multiplier, $value);
|
||||
$this->assertEquals($qstat->{$fieldname} * $multiplier, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
|
||||
$this->check_attempts_results($csvdata['results'], $attemptids);
|
||||
|
||||
$this->report = new testable_quiz_statistics_report();
|
||||
list($quizstats, $questions, $subquestions) = $this->report->get_stats($this->quiz);
|
||||
list($quizstats, $questionstats, $subquestionstats) = $this->report->get_stats($this->quiz);
|
||||
|
||||
// These quiz stats and the question stats found in qstats00.csv were calculated independently in spreadsheet which is
|
||||
// available in open document or excel format here :
|
||||
@@ -133,7 +133,7 @@ class quiz_report_statistics_from_steps extends mod_quiz_attempt_walkthrough_fro
|
||||
}
|
||||
$slot = $slotqstats['slot'];
|
||||
$delta = abs($slotqstat) * $precision;
|
||||
$actual = $questions[$slot]->_stats->{$statname};
|
||||
$actual = $questionstats[$slot]->{$statname};
|
||||
$this->assertEquals(floatval($slotqstat), $actual, "$statname for slot $slot", $delta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Question statistics calculations class. Used in the quiz statistics report but also available for use elsewhere.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage questionbank
|
||||
* @copyright 2013 Open University
|
||||
* @author Jamie Pratt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_question\statistics\questions;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* This class is used to return the stats as calculated by {@link \core_question\statistics\questions\calculator}
|
||||
*
|
||||
* @copyright 2013 Open University
|
||||
* @author Jamie Pratt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class calculated {
|
||||
|
||||
public $questionid;
|
||||
|
||||
// These first fields are the final fields cached in the db and shown in reports.
|
||||
|
||||
// See : http://docs.moodle.org/dev/Quiz_statistics_calculations#Position_statistics .
|
||||
|
||||
public $slot = null;
|
||||
|
||||
/**
|
||||
* @var bool is this a sub question.
|
||||
*/
|
||||
public $subquestion = false;
|
||||
|
||||
/**
|
||||
* @var int total attempts at this question.
|
||||
*/
|
||||
public $s = 0;
|
||||
|
||||
/**
|
||||
* @var float effective weight of this question.
|
||||
*/
|
||||
public $effectiveweight;
|
||||
|
||||
/**
|
||||
* @var bool is covariance of this questions mark with other question marks negative?
|
||||
*/
|
||||
public $negcovar;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
public $discriminationindex;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
public $discriminativeefficiency;
|
||||
|
||||
/**
|
||||
* @var float standard deviation
|
||||
*/
|
||||
public $sd;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
public $facility;
|
||||
|
||||
/**
|
||||
* @var float max mark achievable for this question.
|
||||
*/
|
||||
public $maxmark;
|
||||
|
||||
/**
|
||||
* @var string comma separated list of the positions in which this question appears.
|
||||
*/
|
||||
public $positions;
|
||||
|
||||
/**
|
||||
* @var null|float The average score that students would have got by guessing randomly. Or null if not calculable.
|
||||
*/
|
||||
public $randomguessscore = null;
|
||||
|
||||
// End of fields in db.
|
||||
|
||||
protected $fieldsindb = array('questionid', 'slot', 'subquestion', 's', 'effectiveweight', 'negcovar', 'discriminationindex',
|
||||
'discriminativeefficiency', 'sd', 'facility', 'subquestions', 'maxmark', 'positions', 'randomguessscore');
|
||||
|
||||
// Fields used for intermediate calculations.
|
||||
|
||||
public $totalmarks = 0;
|
||||
|
||||
public $totalothermarks = 0;
|
||||
|
||||
public $markvariancesum = 0;
|
||||
|
||||
public $othermarkvariancesum = 0;
|
||||
|
||||
public $covariancesum = 0;
|
||||
|
||||
public $covariancemaxsum = 0;
|
||||
|
||||
public $subquestions = '';
|
||||
|
||||
public $covariancewithoverallmarksum = 0;
|
||||
|
||||
public $markarray = array();
|
||||
|
||||
public $othermarksarray = array();
|
||||
|
||||
public $markaverage;
|
||||
|
||||
public $othermarkaverage;
|
||||
|
||||
public $markvariance;
|
||||
public $othermarkvariance;
|
||||
public $covariance;
|
||||
public $covariancemax;
|
||||
public $covariancewithoverallmark;
|
||||
|
||||
/**
|
||||
* @var object full question data
|
||||
*/
|
||||
public $question;
|
||||
|
||||
/**
|
||||
* Set if this record has been retrieved from cache. This is the time that the statistics were calculated.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $timemodified;
|
||||
|
||||
/**
|
||||
* Cache calculated stats stored in this object in 'question_statistics' table.
|
||||
*
|
||||
* @param \qubaid_condition $qubaids
|
||||
*/
|
||||
public function cache($qubaids) {
|
||||
global $DB;
|
||||
$toinsert = new \stdClass();
|
||||
$toinsert->hashcode = $qubaids->get_hash_code();
|
||||
$toinsert->timemodified = time();
|
||||
foreach ($this->fieldsindb as $field) {
|
||||
$toinsert->{$field} = $this->{$field};
|
||||
}
|
||||
$DB->insert_record('question_statistics', $toinsert, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $record Given a record from 'question_statistics' copy stats from record to properties.
|
||||
*/
|
||||
public function populate_from_record($record) {
|
||||
foreach ($this->fieldsindb as $field) {
|
||||
$this->$field = $record->$field;
|
||||
}
|
||||
$this->timemodified = $record->timemodified;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class for storing calculated sub question statistics and intermediate calculation values.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2013 The Open University
|
||||
* @author James Pratt [email protected]
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_question\statistics\questions;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class calculated_for_subquestion extends calculated {
|
||||
public $subquestion = true;
|
||||
|
||||
public $usedin = array();
|
||||
|
||||
public $differentweights = false;
|
||||
|
||||
public $negcovar = 0;
|
||||
}
|
||||
+249
-251
@@ -15,7 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Question statistics calculations class. Used in the quiz statistics report but also available for use elsewhere.
|
||||
* Question statistics calculator class. Used in the quiz statistics report but also available for use elsewhere.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage questionbank
|
||||
@@ -24,10 +24,9 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
namespace core_question\statistics\questions;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* This class has methods to compute the question statistics from the raw data.
|
||||
*
|
||||
@@ -35,61 +34,248 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @author Jamie Pratt <me@jamiep.org>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_statistics {
|
||||
public $questions;
|
||||
public $subquestions = array();
|
||||
class calculator {
|
||||
|
||||
protected $summarksavg;
|
||||
/**
|
||||
* @var calculated[]
|
||||
*/
|
||||
public $questionstats = array();
|
||||
|
||||
/**
|
||||
* @var calculated_for_subquestion[]
|
||||
*/
|
||||
public $subquestionstats = array();
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $sumofmarkvariance = 0;
|
||||
|
||||
protected $randomselectors = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param $questions array the main questions indexed by slot.
|
||||
* @param object[] questions to analyze, keyed by slot, also analyses sub questions for random questions.
|
||||
* we expect some extra fields - slot, maxmark and number on the full question data objects.
|
||||
*/
|
||||
public function __construct($questions) {
|
||||
foreach ($questions as $slot => $question) {
|
||||
$question->_stats = $this->make_blank_question_stats();
|
||||
$question->_stats->questionid = $question->id;
|
||||
$question->_stats->slot = $slot;
|
||||
$this->questionstats[$slot] = new calculated();
|
||||
$this->questionstats[$slot]->questionid = $question->id;
|
||||
$this->questionstats[$slot]->question = $question;
|
||||
$this->questionstats[$slot]->slot = $slot;
|
||||
$this->questionstats[$slot]->positions = $question->number;
|
||||
$this->questionstats[$slot]->maxmark = $question->maxmark;
|
||||
$this->questionstats[$slot]->randomguessscore = $this->get_random_guess_score($question);
|
||||
}
|
||||
|
||||
$this->questions = $questions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object ready to hold all the question statistics.
|
||||
* @param $qubaids \qubaid_condition
|
||||
* @return array containing two arrays calculated[] and calculated_for_subquestion[].
|
||||
*/
|
||||
protected function make_blank_question_stats() {
|
||||
$stats = new stdClass();
|
||||
$stats->slot = null;
|
||||
$stats->s = 0;
|
||||
$stats->totalmarks = 0;
|
||||
$stats->totalothermarks = 0;
|
||||
$stats->markvariancesum = 0;
|
||||
$stats->othermarkvariancesum = 0;
|
||||
$stats->covariancesum = 0;
|
||||
$stats->covariancemaxsum = 0;
|
||||
$stats->subquestion = false;
|
||||
$stats->subquestions = '';
|
||||
$stats->covariancewithoverallmarksum = 0;
|
||||
$stats->randomguessscore = null;
|
||||
$stats->markarray = array();
|
||||
$stats->othermarksarray = array();
|
||||
return $stats;
|
||||
public function calculate($qubaids) {
|
||||
set_time_limit(0);
|
||||
|
||||
list($lateststeps, $summarks, $summarksavg) = $this->get_latest_steps($qubaids);
|
||||
|
||||
if ($lateststeps) {
|
||||
|
||||
// Compute the statistics of position, and for random questions, work
|
||||
// out which questions appear in which positions.
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->initial_steps_walker($step, $this->questionstats[$step->slot], $summarks);
|
||||
|
||||
// If this is a random question what is the real item being used?
|
||||
if ($step->questionid != $this->questionstats[$step->slot]->questionid) {
|
||||
if (!isset($this->subquestionstats[$step->questionid])) {
|
||||
$this->subquestionstats[$step->questionid] = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$step->questionid]->questionid = $step->questionid;
|
||||
$this->subquestionstats[$step->questionid]->maxmark = $step->maxmark;
|
||||
} else if ($this->subquestionstats[$step->questionid]->maxmark != $step->maxmark) {
|
||||
$this->subquestionstats[$step->questionid]->differentweights = true;
|
||||
}
|
||||
|
||||
$this->initial_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks, false);
|
||||
|
||||
$number = $this->questionstats[$step->slot]->question->number;
|
||||
$this->subquestionstats[$step->questionid]->usedin[$number] = $number;
|
||||
|
||||
$randomselectorstring = $this->questionstats[$step->slot]->question->category. '/'
|
||||
.$this->questionstats[$step->slot]->question->questiontext;
|
||||
if (!isset($this->randomselectors[$randomselectorstring])) {
|
||||
$this->randomselectors[$randomselectorstring] = array();
|
||||
}
|
||||
$this->randomselectors[$randomselectorstring][$step->questionid] = $step->questionid;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->randomselectors as $key => $notused) {
|
||||
ksort($this->randomselectors[$key]);
|
||||
}
|
||||
|
||||
// Compute the statistics of question id, if we need any.
|
||||
$subquestions = question_load_questions(array_keys($this->subquestionstats));
|
||||
foreach ($subquestions as $qid => $subquestion) {
|
||||
$this->subquestionstats[$qid]->question = $subquestion;
|
||||
$this->subquestionstats[$qid]->question->maxmark = $this->subquestionstats[$qid]->maxmark;
|
||||
$this->subquestionstats[$qid]->randomguessscore = $this->get_random_guess_score($subquestion);
|
||||
|
||||
$this->initial_question_walker($this->subquestionstats[$qid]);
|
||||
|
||||
if ($this->subquestionstats[$qid]->differentweights) {
|
||||
// TODO output here really sucks, but throwing is too severe.
|
||||
global $OUTPUT;
|
||||
$name = $this->subquestionstats[$qid]->question->name;
|
||||
echo $OUTPUT->notification( get_string('erroritemappearsmorethanoncewithdifferentweight',
|
||||
'quiz_statistics', $name));
|
||||
}
|
||||
|
||||
if ($this->subquestionstats[$qid]->usedin) {
|
||||
sort($this->subquestionstats[$qid]->usedin, SORT_NUMERIC);
|
||||
$this->subquestionstats[$qid]->positions = implode(',', $this->subquestionstats[$qid]->usedin);
|
||||
} else {
|
||||
$this->subquestionstats[$qid]->positions = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Finish computing the averages, and put the subquestion data into the
|
||||
// corresponding questions.
|
||||
|
||||
// This cannot be a foreach loop because we need to have both
|
||||
// $question and $nextquestion available, but apart from that it is
|
||||
// foreach ($this->questions as $qid => $question).
|
||||
reset($this->questionstats);
|
||||
while (list($slot, $questionstat) = each($this->questionstats)) {
|
||||
$nextquestionstats = current($this->questionstats);
|
||||
|
||||
$this->initial_question_walker($questionstat);
|
||||
|
||||
if ($questionstat->question->qtype == 'random') {
|
||||
$randomselectorstring = $questionstat->question->category .'/'. $questionstat->question->questiontext;
|
||||
if ($nextquestionstats && $nextquestionstats->question->qtype == 'random') {
|
||||
$nextrandomselectorstring =
|
||||
$nextquestionstats->question->category .'/'. $nextquestionstats->question->questiontext;
|
||||
if ($randomselectorstring == $nextrandomselectorstring) {
|
||||
continue; // Next loop iteration.
|
||||
}
|
||||
}
|
||||
if (isset($this->randomselectors[$randomselectorstring])) {
|
||||
$questionstat->subquestions = implode(',', $this->randomselectors[$randomselectorstring]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the records one more time.
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->secondary_steps_walker($step, $this->questionstats[$step->slot], $summarks, $summarksavg);
|
||||
|
||||
if ($this->questionstats[$step->slot]->subquestions) {
|
||||
$this->secondary_steps_walker($step, $this->subquestionstats[$step->questionid], $summarks, $summarksavg);
|
||||
}
|
||||
}
|
||||
|
||||
$sumofcovariancewithoverallmark = 0;
|
||||
foreach ($this->questionstats as $questionstat) {
|
||||
$this->secondary_question_walker($questionstat);
|
||||
|
||||
$this->sumofmarkvariance += $questionstat->markvariance;
|
||||
|
||||
if ($questionstat->covariancewithoverallmark >= 0) {
|
||||
$sumofcovariancewithoverallmark += sqrt($questionstat->covariancewithoverallmark);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->subquestionstats as $subquestionstat) {
|
||||
$this->secondary_question_walker($subquestionstat);
|
||||
}
|
||||
|
||||
foreach ($this->questionstats as $questionstat) {
|
||||
if ($sumofcovariancewithoverallmark) {
|
||||
if ($questionstat->negcovar) {
|
||||
$questionstat->effectiveweight = null;
|
||||
} else {
|
||||
$questionstat->effectiveweight = 100 * sqrt($questionstat->covariancewithoverallmark) /
|
||||
$sumofcovariancewithoverallmark;
|
||||
}
|
||||
} else {
|
||||
$questionstat->effectiveweight = null;
|
||||
}
|
||||
}
|
||||
$this->cache_stats($qubaids);
|
||||
}
|
||||
return array($this->questionstats, $this->subquestionstats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $qubaids qubaid_condition
|
||||
* Load cached statistics from the database.
|
||||
*
|
||||
* @param $qubaids \qubaid_condition
|
||||
* @return array containing two arrays calculated[] and calculated_for_subquestion[].
|
||||
*/
|
||||
public function get_cached($qubaids) {
|
||||
global $DB;
|
||||
$timemodified = time() - self::TIME_TO_CACHE;
|
||||
$questionstatrecs = $DB->get_records_select('question_statistics', 'hashcode = ? AND timemodified > ?',
|
||||
array($qubaids->get_hash_code(), $timemodified));
|
||||
|
||||
$questionids = array();
|
||||
foreach ($questionstatrecs as $fromdb) {
|
||||
if (!$fromdb->slot) {
|
||||
$questionids[] = $fromdb->questionid;
|
||||
}
|
||||
}
|
||||
$subquestions = question_load_questions($questionids);
|
||||
foreach ($questionstatrecs as $fromdb) {
|
||||
if ($fromdb->slot) {
|
||||
$this->questionstats[$fromdb->slot]->populate_from_record($fromdb);
|
||||
// Array created in constructor and populated from question.
|
||||
} else {
|
||||
$this->subquestionstats[$fromdb->questionid] = new calculated_for_subquestion();
|
||||
$this->subquestionstats[$fromdb->questionid]->populate_from_record($fromdb);
|
||||
$this->subquestionstats[$fromdb->questionid]->question = $subquestions[$fromdb->questionid];
|
||||
}
|
||||
}
|
||||
return array($this->questionstats, $this->subquestionstats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find time of non-expired statistics in the database.
|
||||
*
|
||||
* @param $qubaids \qubaid_condition
|
||||
* @return integer|boolean Time of cached record that matches this qubaid_condition or false is non found.
|
||||
*/
|
||||
public function get_last_calculated_time($qubaids) {
|
||||
global $DB;
|
||||
|
||||
$timemodified = time() - self::TIME_TO_CACHE;
|
||||
return $DB->get_field_select('question_statistics', 'timemodified', 'hashcode = ? AND timemodified > ? '.
|
||||
'ORDER BY timemodified DESC LIMIT 1',
|
||||
array($qubaids->get_hash_code(), $timemodified));
|
||||
}
|
||||
|
||||
/** @var integer Time after which statistics are automatically recomputed. */
|
||||
const TIME_TO_CACHE = 900; // 15 minutes.
|
||||
|
||||
/**
|
||||
* Used when computing Coefficient of Internal Consistency by quiz statistics.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function get_sum_of_mark_variance() {
|
||||
return $this->sumofmarkvariance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $qubaids \qubaid_condition
|
||||
* @return array with three items
|
||||
* - $lateststeps array of latest step data for the question usages
|
||||
* - $summarks array of total marks for each usage, indexed by usage id
|
||||
* - $summarksavg the average of the total marks over all the usages
|
||||
*/
|
||||
protected function get_latest_steps($qubaids) {
|
||||
$dm = new question_engine_data_mapper();
|
||||
$dm = new \question_engine_data_mapper();
|
||||
|
||||
$fields = " qas.id,
|
||||
qa.questionusageid,
|
||||
@@ -98,7 +284,7 @@ class question_statistics {
|
||||
qa.maxmark,
|
||||
qas.fraction * qa.maxmark as mark";
|
||||
|
||||
$lateststeps = $dm->load_questions_usages_latest_steps($qubaids, array_keys($this->questions), $fields);
|
||||
$lateststeps = $dm->load_questions_usages_latest_steps($qubaids, array_keys($this->questionstats), $fields);
|
||||
$summarks = array();
|
||||
if ($lateststeps) {
|
||||
foreach ($lateststeps as $step) {
|
||||
@@ -115,185 +301,14 @@ class question_statistics {
|
||||
return array($lateststeps, $summarks, $summarksavg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $qubaids qubaid_condition
|
||||
*/
|
||||
public function calculate($qubaids) {
|
||||
set_time_limit(0);
|
||||
|
||||
list($lateststeps, $summarks, $summarksavg) = $this->get_latest_steps($qubaids);
|
||||
|
||||
if ($lateststeps) {
|
||||
$subquestionstats = array();
|
||||
|
||||
// Compute the statistics of position, and for random questions, work
|
||||
// out which questions appear in which positions.
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->initial_steps_walker($step, $this->questions[$step->slot]->_stats, $summarks);
|
||||
|
||||
// If this is a random question what is the real item being used?
|
||||
if ($step->questionid != $this->questions[$step->slot]->id) {
|
||||
if (!isset($subquestionstats[$step->questionid])) {
|
||||
$subquestionstats[$step->questionid] = $this->make_blank_question_stats();
|
||||
$subquestionstats[$step->questionid]->questionid = $step->questionid;
|
||||
$subquestionstats[$step->questionid]->usedin = array();
|
||||
$subquestionstats[$step->questionid]->subquestion = true;
|
||||
$subquestionstats[$step->questionid]->differentweights = false;
|
||||
$subquestionstats[$step->questionid]->maxmark = $step->maxmark;
|
||||
} else if ($subquestionstats[$step->questionid]->maxmark != $step->maxmark) {
|
||||
$subquestionstats[$step->questionid]->differentweights = true;
|
||||
}
|
||||
|
||||
$this->initial_steps_walker($step, $subquestionstats[$step->questionid], $summarks, false);
|
||||
|
||||
$number = $this->questions[$step->slot]->number;
|
||||
$subquestionstats[$step->questionid]->usedin[$number] = $number;
|
||||
|
||||
$randomselectorstring = $this->questions[$step->slot]->category .
|
||||
'/' . $this->questions[$step->slot]->questiontext;
|
||||
if (!isset($this->randomselectors[$randomselectorstring])) {
|
||||
$this->randomselectors[$randomselectorstring] = array();
|
||||
}
|
||||
$this->randomselectors[$randomselectorstring][$step->questionid] =
|
||||
$step->questionid;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->randomselectors as $key => $notused) {
|
||||
ksort($this->randomselectors[$key]);
|
||||
}
|
||||
|
||||
// Compute the statistics of question id, if we need any.
|
||||
$this->subquestions = question_load_questions(array_keys($subquestionstats));
|
||||
foreach ($this->subquestions as $qid => $subquestion) {
|
||||
$subquestion->_stats = $subquestionstats[$qid];
|
||||
$subquestion->maxmark = $subquestion->_stats->maxmark;
|
||||
$subquestion->_stats->randomguessscore = $this->get_random_guess_score($subquestion);
|
||||
|
||||
$this->initial_question_walker($subquestion->_stats);
|
||||
|
||||
if ($subquestionstats[$qid]->differentweights) {
|
||||
// TODO output here really sucks, but throwing is too severe.
|
||||
global $OUTPUT;
|
||||
echo $OUTPUT->notification(
|
||||
get_string('erroritemappearsmorethanoncewithdifferentweight',
|
||||
'quiz_statistics', $this->subquestions[$qid]->name));
|
||||
}
|
||||
|
||||
if ($subquestion->_stats->usedin) {
|
||||
sort($subquestion->_stats->usedin, SORT_NUMERIC);
|
||||
$subquestion->_stats->positions = implode(',', $subquestion->_stats->usedin);
|
||||
} else {
|
||||
$subquestion->_stats->positions = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Finish computing the averages, and put the subquestion data into the
|
||||
// corresponding questions.
|
||||
|
||||
// This cannot be a foreach loop because we need to have both
|
||||
// $question and $nextquestion available, but apart from that it is
|
||||
// foreach ($this->questions as $qid => $question).
|
||||
reset($this->questions);
|
||||
while (list($slot, $question) = each($this->questions)) {
|
||||
$nextquestion = current($this->questions);
|
||||
$question->_stats->positions = $question->number;
|
||||
$question->_stats->maxmark = $question->maxmark;
|
||||
$question->_stats->randomguessscore = $this->get_random_guess_score($question);
|
||||
|
||||
$this->initial_question_walker($question->_stats);
|
||||
|
||||
if ($question->qtype == 'random') {
|
||||
$randomselectorstring = $question->category.'/'.$question->questiontext;
|
||||
if ($nextquestion && $nextquestion->qtype == 'random') {
|
||||
$nextrandomselectorstring = $nextquestion->category . '/' .
|
||||
$nextquestion->questiontext;
|
||||
if ($randomselectorstring == $nextrandomselectorstring) {
|
||||
continue; // Next loop iteration.
|
||||
}
|
||||
}
|
||||
if (isset($this->randomselectors[$randomselectorstring])) {
|
||||
$question->_stats->subquestions = implode(',',
|
||||
$this->randomselectors[$randomselectorstring]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the records one more time.
|
||||
foreach ($lateststeps as $step) {
|
||||
$this->secondary_steps_walker($step, $this->questions[$step->slot]->_stats, $summarks, $summarksavg);
|
||||
|
||||
if ($this->questions[$step->slot]->qtype == 'random') {
|
||||
$this->secondary_steps_walker($step, $this->subquestions[$step->questionid]->_stats, $summarks, $summarksavg);
|
||||
}
|
||||
}
|
||||
|
||||
$sumofcovariancewithoverallmark = 0;
|
||||
foreach ($this->questions as $slot => $question) {
|
||||
$this->secondary_question_walker($question->_stats);
|
||||
|
||||
$this->sumofmarkvariance += $question->_stats->markvariance;
|
||||
|
||||
if ($question->_stats->covariancewithoverallmark >= 0) {
|
||||
$sumofcovariancewithoverallmark +=
|
||||
sqrt($question->_stats->covariancewithoverallmark);
|
||||
$question->_stats->negcovar = 0;
|
||||
} else {
|
||||
$question->_stats->negcovar = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->subquestions as $subquestion) {
|
||||
$this->secondary_question_walker($subquestion->_stats);
|
||||
}
|
||||
|
||||
foreach ($this->questions as $question) {
|
||||
if ($sumofcovariancewithoverallmark) {
|
||||
if ($question->_stats->negcovar) {
|
||||
$question->_stats->effectiveweight = null;
|
||||
} else {
|
||||
$question->_stats->effectiveweight = 100 *
|
||||
sqrt($question->_stats->covariancewithoverallmark) /
|
||||
$sumofcovariancewithoverallmark;
|
||||
}
|
||||
} else {
|
||||
$question->_stats->effectiveweight = null;
|
||||
}
|
||||
}
|
||||
$this->cache_stats($qubaids);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $qubaids qubaid_condition
|
||||
*/
|
||||
protected function cache_stats($qubaids) {
|
||||
global $DB;
|
||||
$cachetime = time();
|
||||
foreach ($this->questions as $question) {
|
||||
$question->_stats->hashcode = $qubaids->get_hash_code();
|
||||
$question->_stats->timemodified = $cachetime;
|
||||
$DB->insert_record('question_statistics', $question->_stats, false);
|
||||
}
|
||||
|
||||
foreach ($this->subquestions as $subquestion) {
|
||||
$subquestion->_stats->hashcode = $qubaids->get_hash_code();
|
||||
$subquestion->_stats->timemodified = $cachetime;
|
||||
$DB->insert_record('question_statistics', $subquestion->_stats, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update $stats->totalmarks, $stats->markarray, $stats->totalothermarks
|
||||
* and $stats->othermarksarray to include another state.
|
||||
*
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param object $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param bool $positionstat whether this is a statistic of position of question.
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param calculated $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param bool $positionstat whether this is a statistic of position of question.
|
||||
*/
|
||||
protected function initial_steps_walker($step, $stats, $summarks, $positionstat = true) {
|
||||
$stats->s++;
|
||||
@@ -314,7 +329,7 @@ class question_statistics {
|
||||
* Perform some computations on the per-question statistics calculations after
|
||||
* we have been through all the states.
|
||||
*
|
||||
* @param object $stats quetsion stats to update.
|
||||
* @param calculated $stats question stats to update.
|
||||
*/
|
||||
protected function initial_question_walker($stats) {
|
||||
$stats->markaverage = $stats->totalmarks / $stats->s;
|
||||
@@ -335,9 +350,9 @@ class question_statistics {
|
||||
* Now we know the averages, accumulate the date needed to compute the higher
|
||||
* moments of the question scores.
|
||||
*
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param object $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param object $step the state to add to the statistics.
|
||||
* @param calculated $stats the question statistics we are accumulating.
|
||||
* @param array $summarks of the sum of marks for each question usage, indexed by question usage id
|
||||
* @param float $summarksavg the average sum of marks for all question usages
|
||||
*/
|
||||
protected function secondary_steps_walker($step, $stats, $summarks, $summarksavg) {
|
||||
@@ -345,14 +360,12 @@ class question_statistics {
|
||||
if ($stats->subquestion) {
|
||||
$othermarkdifference = $summarks[$step->questionusageid] - $stats->othermarkaverage;
|
||||
} else {
|
||||
$othermarkdifference = $summarks[$step->questionusageid] - $step->mark -
|
||||
$stats->othermarkaverage;
|
||||
$othermarkdifference = $summarks[$step->questionusageid] - $step->mark - $stats->othermarkaverage;
|
||||
}
|
||||
$overallmarkdifference = $summarks[$step->questionusageid] - $summarksavg;
|
||||
|
||||
$sortedmarkdifference = array_shift($stats->markarray) - $stats->markaverage;
|
||||
$sortedothermarkdifference = array_shift($stats->othermarksarray) -
|
||||
$stats->othermarkaverage;
|
||||
$sortedothermarkdifference = array_shift($stats->othermarksarray) - $stats->othermarkaverage;
|
||||
|
||||
$stats->markvariancesum += pow($markdifference, 2);
|
||||
$stats->othermarkvariancesum += pow($othermarkdifference, 2);
|
||||
@@ -364,18 +377,24 @@ class question_statistics {
|
||||
/**
|
||||
* Perform more per-question statistics calculations.
|
||||
*
|
||||
* @param object $stats quetsion stats to update.
|
||||
* @param calculated $stats question stats to update.
|
||||
*/
|
||||
protected function secondary_question_walker($stats) {
|
||||
|
||||
if ($stats->s > 1) {
|
||||
$stats->markvariance = $stats->markvariancesum / ($stats->s - 1);
|
||||
$stats->othermarkvariance = $stats->othermarkvariancesum / ($stats->s - 1);
|
||||
$stats->covariance = $stats->covariancesum / ($stats->s - 1);
|
||||
$stats->covariancemax = $stats->covariancemaxsum / ($stats->s - 1);
|
||||
$stats->covariancewithoverallmark = $stats->covariancewithoverallmarksum /
|
||||
($stats->s - 1);
|
||||
($stats->s - 1);
|
||||
$stats->sd = sqrt($stats->markvariancesum / ($stats->s - 1));
|
||||
|
||||
if ($stats->covariancewithoverallmark >= 0) {
|
||||
$stats->negcovar = 0;
|
||||
} else {
|
||||
$stats->negcovar = 1;
|
||||
}
|
||||
} else {
|
||||
$stats->markvariance = null;
|
||||
$stats->othermarkvariance = null;
|
||||
@@ -383,18 +402,19 @@ class question_statistics {
|
||||
$stats->covariancemax = null;
|
||||
$stats->covariancewithoverallmark = null;
|
||||
$stats->sd = null;
|
||||
$stats->negcovar = 0;
|
||||
}
|
||||
|
||||
if ($stats->markvariance * $stats->othermarkvariance) {
|
||||
$stats->discriminationindex = 100 * $stats->covariance /
|
||||
sqrt($stats->markvariance * $stats->othermarkvariance);
|
||||
sqrt($stats->markvariance * $stats->othermarkvariance);
|
||||
} else {
|
||||
$stats->discriminationindex = null;
|
||||
}
|
||||
|
||||
if ($stats->covariancemax) {
|
||||
$stats->discriminativeefficiency = 100 * $stats->covariance /
|
||||
$stats->covariancemax;
|
||||
$stats->covariancemax;
|
||||
} else {
|
||||
$stats->discriminativeefficiency = null;
|
||||
}
|
||||
@@ -405,42 +425,20 @@ class question_statistics {
|
||||
* @return number the random guess score for this question.
|
||||
*/
|
||||
protected function get_random_guess_score($questiondata) {
|
||||
return question_bank::get_qtype(
|
||||
$questiondata->qtype, false)->get_random_guess_score($questiondata);
|
||||
return \question_bank::get_qtype(
|
||||
$questiondata->qtype, false)->get_random_guess_score($questiondata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when computing CIC.
|
||||
* @return number
|
||||
* @param $qubaids \qubaid_condition
|
||||
*/
|
||||
public function get_sum_of_mark_variance() {
|
||||
return $this->sumofmarkvariance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param qubaid_condition $qubaids
|
||||
*/
|
||||
public function get_cached($qubaids) {
|
||||
global $DB;
|
||||
$questionstats = $DB->get_records('question_statistics',
|
||||
array('hashcode' => $qubaids->get_hash_code()));
|
||||
|
||||
$subquestionstats = array();
|
||||
foreach ($questionstats as $stat) {
|
||||
if ($stat->slot) {
|
||||
$this->questions[$stat->slot]->_stats = $stat;
|
||||
} else {
|
||||
$subquestionstats[$stat->questionid] = $stat;
|
||||
}
|
||||
protected function cache_stats($qubaids) {
|
||||
foreach ($this->questionstats as $questionstat) {
|
||||
$questionstat->cache($qubaids);
|
||||
}
|
||||
|
||||
if (!empty($subquestionstats)) {
|
||||
$subqstofetch = array_keys($subquestionstats);
|
||||
$this->subquestions = question_load_questions($subqstofetch);
|
||||
foreach ($this->subquestions as $subqid => $subq) {
|
||||
$this->subquestions[$subqid]->_stats = $subquestionstats[$subqid];
|
||||
$this->subquestions[$subqid]->maxmark = $subq->defaultmark;
|
||||
}
|
||||
foreach ($this->subquestionstats as $subquestionstat) {
|
||||
$subquestionstat->cache($qubaids);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -25,10 +25,9 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
namespace core_question\statistics\responses;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* This class can store and compute the analysis of the responses to a particular
|
||||
* question.
|
||||
@@ -37,7 +36,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @author Jamie Pratt <me@jamiep.org>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_response_analyser {
|
||||
class analyser {
|
||||
/** @var object the data from the database that defines the question. */
|
||||
protected $questiondata;
|
||||
|
||||
@@ -67,9 +66,7 @@ class question_response_analyser {
|
||||
public function __construct($questiondata) {
|
||||
$this->questiondata = $questiondata;
|
||||
|
||||
$this->responseclasses =
|
||||
question_bank::get_qtype($questiondata->qtype)->get_possible_responses(
|
||||
$questiondata);
|
||||
$this->responseclasses = \question_bank::get_qtype($questiondata->qtype)->get_possible_responses($questiondata);
|
||||
foreach ($this->responseclasses as $subpartid => $responseclasses) {
|
||||
foreach ($responseclasses as $responseclassid => $notused) {
|
||||
$this->responses[$subpartid][$responseclassid] = array();
|
||||
@@ -121,11 +118,11 @@ class question_response_analyser {
|
||||
/**
|
||||
* Analyse all the response data for for all the specified attempts at
|
||||
* this question.
|
||||
* @param qubaid_condition $qubaids which attempts to consider.
|
||||
* @param \qubaid_condition $qubaids which attempts to consider.
|
||||
*/
|
||||
public function calculate($qubaids) {
|
||||
// Load data.
|
||||
$dm = new question_engine_data_mapper();
|
||||
$dm = new \question_engine_data_mapper();
|
||||
$questionattempts = $dm->load_attempts_at_question($this->questiondata->id, $qubaids);
|
||||
|
||||
// Analyse it.
|
||||
@@ -138,16 +135,16 @@ class question_response_analyser {
|
||||
|
||||
/**
|
||||
* Analyse the data from one question attempt.
|
||||
* @param question_attempt $qa the data to analyse.
|
||||
* @param \question_attempt $qa the data to analyse.
|
||||
*/
|
||||
protected function add_data_from_one_attempt(question_attempt $qa) {
|
||||
$blankresponse = question_classified_response::no_response();
|
||||
protected function add_data_from_one_attempt(\question_attempt $qa) {
|
||||
$blankresponse = \question_classified_response::no_response();
|
||||
|
||||
$partresponses = $qa->classify_response();
|
||||
foreach ($partresponses as $subpartid => $partresponse) {
|
||||
if (!isset($this->responses[$subpartid][$partresponse->responseclassid]
|
||||
[$partresponse->response])) {
|
||||
$resp = new stdClass();
|
||||
$resp = new \stdClass();
|
||||
$resp->count = 0;
|
||||
if (!is_null($partresponse->fraction)) {
|
||||
$resp->fraction = $partresponse->fraction;
|
||||
@@ -167,7 +164,7 @@ class question_response_analyser {
|
||||
|
||||
/**
|
||||
* Store the computed response analysis in the question_response_analysis table.
|
||||
* @param qubaid_condition $qubaids
|
||||
* @param \qubaid_condition $qubaids
|
||||
* data corresponding to.
|
||||
* @return bool true if cached data was found in the database and loaded, otherwise false, to mean no data was loaded.
|
||||
*/
|
||||
@@ -181,7 +178,7 @@ class question_response_analyser {
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$this->responses[$row->subqid][$row->aid][$row->response] = new stdClass();
|
||||
$this->responses[$row->subqid][$row->aid][$row->response] = new \stdClass();
|
||||
$this->responses[$row->subqid][$row->aid][$row->response]->count = $row->rcount;
|
||||
$this->responses[$row->subqid][$row->aid][$row->response]->fraction = $row->credit;
|
||||
}
|
||||
@@ -190,7 +187,7 @@ class question_response_analyser {
|
||||
|
||||
/**
|
||||
* Store the computed response analysis in the question_response_analysis table.
|
||||
* @param qubaid_condition $qubaids
|
||||
* @param \qubaid_condition $qubaids
|
||||
*/
|
||||
public function store_cached($qubaids) {
|
||||
global $DB;
|
||||
@@ -199,7 +196,7 @@ class question_response_analyser {
|
||||
foreach ($this->responses as $subpartid => $partdata) {
|
||||
foreach ($partdata as $responseclassid => $classdata) {
|
||||
foreach ($classdata as $response => $data) {
|
||||
$row = new stdClass();
|
||||
$row = new \stdClass();
|
||||
$row->hashcode = $qubaids->get_hash_code();
|
||||
$row->questionid = $this->questiondata->id;
|
||||
$row->subqid = $subpartid;
|
||||
@@ -24,10 +24,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Question statistics cron code. Deletes cached stats more than a certain age.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user