item analysis report contributed by Enrique Castro

This commit is contained in:
gustav_delius
2005-05-15 14:40:38 +00:00
parent 339c1d1153
commit 0fc5939983
9 changed files with 712 additions and 11 deletions
@@ -0,0 +1,17 @@
<p align="center"><b>Analysis Options</b></p>
<p>The range of quiz attempts that are procesed and analyzed can be selected.
There are two parameters to specify which attempts are used: </p>
<p><b>Attempt selection:</b></p>
It may be convenient to analyze just one quiz attempt for each user.
This particular attempt may be the one with the highest overall score,
the first attempt or the last attempt of those performed. Or all attempts data may
be collected for a cummulative analysis.</p>
<p><b>Rejection of low scores:</b></p>
<p>Sometimes the users are just browsing the quiz, exploring it,
and not going through it for a grade. It is common that such 'trial' attempts
get very low scores. These byassing data can be excluden from analysis by
setting a low limit for the score of the attempts to analyze. This limit is
specified as a percentage (0-100) of the maximum grade achievable in the quiz.</p>
@@ -0,0 +1,61 @@
<p align="center"><b>Item Anaysis</b></p>
<p>This table presents processed quiz data in a way suitable for anayzing and judging the
performance of each question for the function of assessment. The statistical parameters used are
calculated as explained by classical test theory (ref. 1)</p>
<p><b>Facility Index (% Correct)</b></p>
<p>This is a measure of how easy or difficult is a question for quiz-takers.
It is calculated as:
<br>
FI = (X<sub>average</sub>) / X<sub>max</sub>
<br>
where X<sub>average</sub> is the mean credit obtained by all users attempting the item, <br>
and X<sub>max</sub> is the maximum credit achievable for that item.<br>
If questions can be distributed dicotomically into correct / uncorrect categories,
this parameter coincides with the percentage of users that answer the question correctly. </p>
</p>
<p><b>Standard Deviation (SD)</b></p>
<p>This parameter measures the spread of answers in the response population. If all users
answers the same, then SD=0. SD is calculated as the statistical stadard deviation for the
sample of fractional scores (achieved/maximum) at each particular question.
</p>
<p><b>Discrimination Index (DI)</b></p>
<p>This provides a rough indicator of the performance of each item to separate proficient
<i>vs.</i> less-proficient users. This parameter is calculated by first dividing learners into thirds
based on the overall score in the quiz. Then the average score at the analyzed item is calculated for
the groups of top and bottom performers, and the average scored substracted. The matematical expression is:
<br/>
DI = (X<sub>top</sub> - X<sub>bottom</sub>)/ N
<br/>
where X<sub>top</sub> is the sum of the fractional credit (achieved/maximum) obtained at this item by the 1/3 of users having tha highest
grades in the whole quiz (i.e. number of correct responses in this group), <br/>
and X<sub>bottom</sub>) is the analog sum for users with the lower 1/3 grades for the whole quiz.</p>
<p>This parameter can take values between +1 and -1.
If the index goes below 0.0 it means that more of the weaker learners got the item right than the stronger learners.
Such items should be discarded as worthless. In fact, they reduce the accuracy of the overall score for the quiz. </p>
<p><b>Discrimination Coefficient (DC)</b></p>
<p>This is another measure of the separating power of the item to distinguish proficient from weak learners.<br/>
The discrimination coefficient is a correlation coefficient between scores at the item and at the whole quiz. Here it is calculated as:
<br/>
DC = Sum(xy)/ (N * s<sub>x</sub> * s<sub>y</sub>)
<br/>
where Sun(xy) is the sum of the products of deviations for item scores and overall quiz scores, <br>
N is the number of responses given to this question<br/>
s<sub>x</sub> is the standard deviation of fractional scores for this question and, <br/>
s<sub>y</sub> is the standard deviation of scores at the quiz as a whole.
</p>
<p>Again, this parameter can take values between +1 and -1. Positive values indicate items that
do discriminate proficient learners, whereas negative indices mark items that are answered best by
those with lowest grades. Items with negative DC are answered incorrectly by the seasoned learners
and thus they are actually a penalty against the most proficient learners. Those items should be avoided.<br/>
The advantage of Discrimination Coefficient vs. Discrimitation Index is that the former uses information
from the whole population of learners, not just the extreme upper and lower thirds. Thus, this parameter may be
more sensitive to detect item performance.</p>
+7
View File
@@ -77,6 +77,7 @@ $string['penaltyfactor'] = 'Penalty factor';
$string['penaltyscheme'] = 'Apply penalties';
$string['previewquiz'] = 'Preview quiz';
$string['previous'] = 'Previous state';
$string['questiontype'] = 'Question type $a';
$string['readytosend'] = 'You are about to finalize this quiz attempt. Are you sure you want to continue?';
$string['rendering'] = 'Rendering';
$string['renderingserver'] = 'Rendering Server';
@@ -115,4 +116,10 @@ $string['validate'] = 'Validate';
$string['withselected'] = 'With selected';
$string['wronguse'] = 'You can not use this page like that';
$string['analysisoptions'] = 'Analysis options';
$string['attemptselection'] = 'Select which attempts to analyze per user ';
$string['lowmarkslimit'] = " Don't analyze if score is less than: ";
$string['reportanalysis'] = 'Item analysis';
$string['allattempts'] = 'all attempts';
$string['analysistitle'] = 'Item Analysis Table';
?>
+133 -9
View File
@@ -399,6 +399,71 @@ class quiz_default_questiontype {
return null;
}
/**
* Return an array of values with the texts for all possible responses stored
* for the question
*
* All answers are found and their text values isolated
* @return array An array of values giving the responses corresponding
* to all answers to the question. Answer ids are used as keys
* @param object $question The question for which the answers are to
* be retrieved. Question type specific information is
* available.
*/
// ULPGC ecastro
function get_all_responses($question, $state) {
unset($answers);
if (is_array($question->options->answers)) {
foreach ($question->options->answers as $aid=>$answer) {
unset ($r);
$r->answer = $answer->answer;
$r->credit = $answer->fraction;
$answers[$aid] = $r;
}
} else {
$answers[]="error"; // just for debugging, eliminate
}
$result->id = $question->id;
$result->responses = $answers;
return $result;
}
/**
* Return the actual response to the question in a given attempt
* for the question
*
* @return compond object fractiongrade & actual response .
*
* @param object $question The question for which the correct answer is to
* be retrieved. Question type specific information is
* available.
* @param object $state The state object that corresponds to the question,
* for which a correct answer is needed. Question
* type specific information is included.
*/
// ULPGC ecastro
function get_actual_response($question, $state) {
/* The default implementation only returns the raw ->responses.
may be overridden by each type*/
//unset($resp);
if (isset($state->responses)) {
return $state->responses;
} else {
return null;
}
}
// ULPGC ecastro
function get_fractional_grade($question, $state) {
$maxgrade = $question->maxgrade;
$grade = $state->grade;
if ($maxgrade) {
return (float)($grade/$maxgrade);
} else {
return (float)$grade;
}
}
/**
* Prints the question including the number, grading details, content,
* feedback and interactions
@@ -1711,25 +1776,44 @@ function quiz_print_correctanswer($text) {
* @param object $question The question object for which the icon is required
* @param boolean $editlink If true then the icon is a link to the question
* edit page.
* @param boolean $return If true the functions returns the link as a string
*/
function quiz_print_question_icon($question, $editlink=true) {
// Prints a question icon
function quiz_print_question_icon($question, $editlink=true, $return = false) {
// returns a question icon
global $QUIZ_QUESTION_TYPE;
global $QUIZ_QTYPES;
$html = '<img border="0" height="16" width="16" src="questiontypes/'.
$QUIZ_QTYPES[$question->qtype]->name().'/icon.gif" alt="'.
get_string($QUIZ_QTYPES[$question->qtype]->name(), 'quiz').'" />';
if ($editlink) {
echo "<a href=\"question.php?id=$question->id\" title=\""
.$QUIZ_QTYPES[$question->qtype]->name()."\">";
$html = "<a href=\"question.php?id=$question->id\" title=\""
.$QUIZ_QTYPES[$question->qtype]->name()."\">".
$html."</a>\n";
}
echo '<img border="0" height="16" width="16" src="questiontypes/';
echo $QUIZ_QTYPES[$question->qtype]->name().'/icon.gif" alt="';
echo get_string($QUIZ_QTYPES[$question->qtype]->name(), 'quiz').'" />';
if ($editlink) {
echo "</a>\n";
if ($return) {
return $html;
} else {
echo $html;
}
}
// ULPGc ecastro
function quiz_get_question_review($quiz, $question) {
// returns a question icon
$qnum = $question->id;
$context = $quiz->id ? '&amp;contextquiz='.$quiz->id : '';
$quiz_id = $quiz->id ? '&amp;quizid=' . $quiz->id : '';
return "<a title=\"$strpreview\" href=\"javascript:void();\" onClick=\"openpopup('/mod/quiz/preview.php?id=$qnum$quiz_id','$strpreview','scrollbars=yes,resizable=yes,width=700,height=480', false)\">
<img src=\"../../pix/t/preview.gif\" border=\"0\" alt=\"$strpreview\" /></a>";
}
/**
* Print the question image if there is one
*
@@ -1891,6 +1975,46 @@ function quiz_print_quiz_question(&$question, &$state, $number, $quiz, $options=
$quiz, $options);
}
/**
* Gets all teacher stored answers for a given question
*
* Simply calls the question type specific get_all_responses() method.
*/
// ULPGC ecastro
function quiz_get_question_responses($question, $state) {
global $QUIZ_QTYPES;
$r = $QUIZ_QTYPES[$question->qtype]->get_all_responses($question, $state);
return $r;
}
/**
* Gets the response given by the user in a particular attempt
*
* Simply calls the question type specific get_actual_response() method.
*/
// ULPGC ecastro
function quiz_get_question_actual_response($question, $state) {
global $QUIZ_QTYPES;
$r = $QUIZ_QTYPES[$question->qtype]->get_actual_response($question, $state);
return $r;
}
/**
* Gets the response given by the user in a particular attempt
*
* Simply calls the question type specific get_actual_response() method.
*/
// ULPGc ecastro
function quiz_get_question_fraction_grade($question, $state) {
global $QUIZ_QTYPES;
$r = $QUIZ_QTYPES[$question->qtype]->get_fractional_grade($question, $state);
return $r;
}
/**
* Gets the default category in a course
*
@@ -291,6 +291,41 @@ class quiz_match_qtype extends quiz_default_questiontype {
return true;
}
// ULPGC ecastro for stats report
function get_all_responses($question, $state) {
unset($answers);
if (is_array($question->options->subquestions)) {
foreach ($question->options->subquestions as $aid=>$answer) {
unset ($r);
$r->answer = $answer->questiontext." : ".$answer->answertext;
$r->credit = 1;
$answers[$aid] = $r;
}
} else {
$answers[]="error"; // just for debugging, eliminate
}
$result->id = $question->id;
$result->responses = $answers;
return $result;
}
// ULPGC ecastro
function get_actual_response($question, $state) {
unset($results);
if (isset($state->responses)) {
foreach($state->responses as $left=>$right){
$lpair = $question->options->subquestions[$left]->questiontext;
$rpair = $question->options->subquestions[$right]->answertext;
$results[$left] = $lpair." : ".$rpair;
}
return $results;
} else {
return null;
}
}
}
//// END OF CLASS ////
@@ -371,6 +371,28 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
return true;
}
// ULPGC ecastro
function get_actual_response($question, $state) {
$answers = $question->options->answers;
/*
if (!$question->options->single) {
echo "Objeto State-responses: <br>tamaño= ".count($state->responses)."<br> ";
print_object($state->responses);
foreach ($state->responses as $resp){
echo "esto es response id= ".$rid." <br>";
print_object($resp);
echo " Tamaño interno=".count($resp);
foreach ($resp as $r){
print_object($r);
}
}
}*/
foreach ($state->responses as $rid){
$answer = $answers[$rid]->answer;
$responses[] = $answer;
}
return $responses;
}
}
//// END OF CLASS ////
+18 -1
View File
@@ -163,7 +163,24 @@ class quiz_random_qtype extends quiz_default_questiontype {
->get_correct_responses($wrappedquestion, $state);
}
function print_question(&$question, &$state, $number, $quiz, $options) {
// ULPGC ecastro
function get_all_responses($question, $state){
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->get_all_responses($wrappedquestion, $state);
}
// ULPGC ecastro
function get_actual_response($question, $state){
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->get_actual_response($wrappedquestion, $state);
}
function print_question(&$question, &$state, &$number, $quiz, $options) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
$QUIZ_QTYPES[$wrappedquestion->qtype]
+366
View File
@@ -0,0 +1,366 @@
<?php // $Id$
/// Item analysis displays a table of quiz questions and their performance
require_once("locallib.php");
require_once($CFG->libdir.'/tablelib.php');
function stats_sumx($sum, $data){
$sum[0] += $data[0];
$sum[1] += $data[1];
return $sum;
}
function stats_sumx2($sum, $data){
$sum[0] += $data[0]*$data[0];
$sum[1] += $data[1]*$data[1];
return $sum;
}
function stats_sumxy($sum, $data){
$sum[0] += $data[0]*$data[1];
return $sum;
}
/// Item analysis displays a table of quiz questions and their performance
class quiz_report extends quiz_default_report {
function display($quiz, $cm, $course) { /// This function just displays the report
global $CFG, $SESSION, $db, $QUIZ_QTYPES;
define(QUIZ_ALLATTEMPTS, 0);
define(QUIZ_HIGHESTATTEMPT, 1);
define(QUIZ_FIRSTATTEMPT, 2);
define(QUIZ_LASTATTEMPT, 3);
$strnoquiz = get_string('noquiz','quiz');
$strnoattempts = get_string('noattempts','quiz');
$strtimeformat = get_string('strftimedatetime');
if (!$quiz->questions) {
print_heading($strnoattempts);
return true;
}
/// Check to see if groups are being used in this quiz
if ($groupmode = groupmode($course, $cm)) { // Groups are being used
$currentgroup = setup_and_print_groups($course, $groupmode, "report.php?id=$cm->id&amp;mode=overview");
} else {
$currentgroup = false;
}
/// Get all users: teachers and students
if ($currentgroup) {
$users = get_group_users($currentgroup);
}
else {
$users = get_course_users($course->id);
}
/// Remove teachers
$teachers = get_course_teachers($course->id);
if(!empty($teachers)) {
$keys = array_keys($teachers);
}
foreach($keys as $key) {
unset($users[$key]);
}
if(empty($users)) {
print_heading($strnoattempts);
return true;
}
// set tTable and Analysis stats options
if(!isset($SESSION->quiz_analysis_table)) {
$SESSION->quiz_analysis_table = array('attemptselection' => 0, 'lowmarklimit' => 0);
}
foreach($SESSION->quiz_analysis_table as $option => $value) {
$urlparam = optional_param($option, NULL);
if($urlparam === NULL) {
$$option = $value;
}
else {
$$option = $SESSION->quiz_analysis_table[$option] = $urlparam;
}
}
$scorelimit = $quiz->grade * $lowmarklimit/ 100;
// ULPGC ecastro DEBUG this is here to allow for differnt SQL to select attempts
switch ($attemptselection) {
case QUIZ_ALLATTEMPTS :
$limit = '';
$group = '';
break;
case QUIZ_HIGHESTATTEMPT :
$limit = ', max(qa.sumgrades) ';
$group = ' GROUP BY qa.userid ';
break;
case QUIZ_FIRSTATTEMPT :
$limit = ', min(qa.timemodified) ';
$group = ' GROUP BY qa.userid ';
break;
case QUIZ_LASTATTEMPT :
$limit = ', max(qa.timemodified) ';
$group = ' GROUP BY qa.userid ';
break;
}
$select = 'SELECT qa.* '.$limit;
$sql = 'FROM '.$CFG->prefix.'user u '.
'LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON u.id = qa.userid '.
'LEFT JOIN '.$CFG->prefix.'quiz_states qr ON qr.attempt = qa.id '. // es posible
'WHERE u.id IN ('.implode(',', array_keys($users)).') AND ( qa.quiz = '.$quiz->id.') '. // ULPGC ecastro
' AND ( qa.sumgrades >= '.$scorelimit.' ) ';
// ^^^^^^ es posible seleccionar aquí TODOS los quizzes, como quiere Jussi,
// pero habría que llevar la cuenta ed cada quiz para restaura las preguntas (quizquestions, states)
//$options = ' AND qa.sumgrades > '.$SESSION->quiz_analysis_table->minscore.' ';
/// Fetch the attempts
$attempts = get_records_sql($select.$sql.$group);
if(empty($attempts)) {
print_heading($strnoattempts);
$this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit);
return true;
}
/// Here we rewiew all attempts and record data to construct the table
unset($questions);
unset($statstable);
foreach ($attempts as $attempt) {
//print_object($attempt); // ULPGC ecastro debug eliminar
// print "<br/>Layout= ".$attempt->layout." <br/>";
$questionlist = quiz_questions_in_quiz($attempt->layout);
$sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
" FROM {$CFG->prefix}quiz_questions q,".
" {$CFG->prefix}quiz_question_instances i".
" WHERE i.quiz = '$quiz->id' AND q.id = i.question".
" AND q.id IN ($questionlist)";
if (!$quizquestions = get_records_sql($sql)) {
error('No questions found');
}
// Load the question type specific information
if (!quiz_get_question_options($quizquestions)) {
error('Could not load question options');
}
// Restore the question sessions to their most recent states
// creating new sessions where required
if (!$states = quiz_restore_question_sessions($quizquestions, $quiz, $attempt)) {
error('Could not restore question sessions');
}
$numbers = explode(',', $questionlist);
unset($statsrow);
foreach ($numbers as $i) {
$accepted = array(SHORTANSWER, TRUEFALSE, MULTICHOICE, RANDOM, MATCH, NUMERICAL, CALCULATED);
if (!in_array ($quizquestions[$i]->qtype, $accepted)){
continue;
}
$q = quiz_get_question_responses($quizquestions[$i], $states[$i]);
$qid = $q->id;
if (!isset($questions[$qid])) {
$questions[$qid]->id = $qid;
foreach ($q->responses as $answer => $r) {
$r->count = 0;
$questions[$qid]->responses[$answer] = $r->answer;
$questions[$qid]->counts[$answer] = 0;
$questions[$qid]->credits[$answer] = $r->credit;
$statsrow[$qid] = 0;
}
}
$responses = quiz_get_question_actual_response($quizquestions[$i], $states[$i]);
foreach ($responses as $resp){
//echo "resp= ".$resp." <br/>"; //debug
//print_object($questions[$qid]->responses);
if ($key = array_search($resp, $questions[$qid]->responses)) {
$questions[$qid]->counts[$key]++;
} else {
$questions[$qid]->responses[] = $resp;
$questions[$qid]->counts[] = 1;
$test->responses[''] = $resp;
//echo "Question type= ".
if ($QUIZ_QTYPES[$quizquestions[$i]->qtype]->compare_responses($quizquestions[$i], $states[$i], $test)) {
$questions[$qid]->credits[] = 1;
} else {
$questions[$qid]->credits[] = 0;
}
}
}
//echo "terminado el foreach <br/>"; //debug
$fraction = quiz_get_question_fraction_grade($quizquestions[$i], $states[$i]);
$statsrow[$qid] += $fraction;
}
$attemptscores[$attempt->id] = $attempt->sumgrades;
$statstable[$attempt->id] = $statsrow;
} // Statistics Data table built
$tablecolumns = array('type', 'question', 'responses', 'counts', 'facility', 'sd','discrimination_index', 'discrimination_coeff');
$tableheaders = array("Q#", 'Question', ' Answers ', ' Counts ', 'Facility<br/> % Correct ', 'SD',' Disc.<br/>Index', 'Disc.<br/>Coeff.');
$table = new flexible_table('mod-quiz-report-itemanalysis-report');
$table->define_columns($tablecolumns);
$table->define_headers($tableheaders);
$table->define_baseurl($CFG->wwwroot.'/mod/quiz/report.php?q='.$quiz->id.'&mode=analysis');
$table->sortable(true);
$table->collapsible(true);
$table->initialbars(true);
//$table->column_class('number', 'numcol');
$table->column_class('type', 'numcol');
$table->column_class('facility', 'numcol');
$table->column_class('sd', 'numcol');
$table->column_class('discrimination_index', 'numcol');
$table->column_class('discrimination_coeff', 'numcol');
$table->set_attribute('cellspacing', '0');
//$table->set_attribute('border', '1');
$table->set_attribute('id', 'itemanalysis');
$table->set_attribute('class', 'generaltable generalbox');
//$table->set_attribute('align', 'center');
// Start working -- this is necessary as soon as the niceties are over
$table->setup();
$top = max($attemptscores);
$bottom = min($attemptscores);
$gap = ($top - $bottom)/3;
$top -=$gap;
$bottom +=$gap;
foreach ($questions as $q){
$question = get_record('quiz_questions', 'id', $q->id);
$qnumber = " (".link_to_popup_window('/mod/quiz/question.php?id='.$q->id,'editquestion', $q->id, 450, 550, get_string('edit'), 'none', true ).") ";
$qname = '<div class="qname">'.format_text($question->name." : ", $question->questiontextformat, NULL, $quiz->course).'</div>';
$qicon = quiz_print_question_icon($question, false, true);
$qreview = quiz_get_question_review($quiz, $question);
$qtext = format_text($question->questiontext, $question->questiontextformat, NULL, $quiz->course);
$qquestion = $qname."<br/>\n".$qtext."\n";
$qstats = $this->report_question_stats($attemptscores, $statstable, $q->id, $top, $bottom);
$responses = "";
$counts = "";
foreach ($q->responses as $aid=>$resp){
$credit = " (".format_float($q->credits[$aid],2).") ";
$text = format_text("$resp"."$credit", FORMAT_MOODLE, NULL, $quiz->course);
if ($q->credits[$aid] <= 0) {
$qclass = 'uncorrect';
} elseif ($q->credits[$aid] == 1) {
$qclass = 'correct';
} else {
$qclass = 'partialcorrect';
}
$responses .= str_replace("<p>", "\n".'<p class="'.$qclass.'">', $text);
$count = $q->counts[$aid].'/'.$qstats->count.' ('.format_float($q->counts[$aid]/$qstats->count*100,0).'%) ';
$text = format_text("$count", FORMAT_MOODLE, NULL, $quiz->course);
$counts .= str_replace("<p>", "\n".'<p class="'.$qclass.'">', $text);
}
$facility = format_float($qstats->facility*100,0)." %";
$qsd = format_float($qstats->qsd,3);
$di = format_float($qstats->disc_index,2);
$dc = format_float($qstats->disc_coeff,2);
//$table->add_data(array($qnumber."<br/>\n".$qicon."<br>\n ".$qreview, $qquestion, $responses, $counts, $facility, $qsd, $di, $dc));
$table->add_data(array($qnumber."\n<p>".$qicon."<p/>\n ".$qreview, $qquestion, $responses, $counts, $facility, $qsd, $di, $dc));
}
echo '<div id="titlecontainer" class="quiz-report-title">';
echo get_string("analysistitle", "quiz");
helpbutton("itemanalysis", get_string("reportanalysis","quiz"), "quiz");
echo '</div>';
echo '<div id="tablecontainer">';
$table->print_html();
$this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit);
return true;
}
function print_options_form($quiz, $cm, $attempts, $lowlimit) {
echo '<div class="controls">';
echo '<form method="report.php">';
echo '<p>'.get_string('analysisoptions', 'quiz').': ';
echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
echo '<input type="hidden" name="q" value="'.$quiz->id.'" />';
echo '<input type="hidden" name="mode" value="analysis" />';
echo '<label for="attemptselection">'.get_string('attemptselection', 'quiz').'</label>';
$options = array ( QUIZ_ALLATTEMPTS => get_string("allattempts", "quiz"),
QUIZ_HIGHESTATTEMPT => get_string("gradehighest", "quiz"),
QUIZ_FIRSTATTEMPT => get_string("attemptfirst", "quiz"),
QUIZ_LASTATTEMPT => get_string("attemptlast", "quiz"));
choose_from_menu($options, "attemptselection", "$attempts", "");
echo '<label for="lowmarklimit">'.get_string('lowmarkslimit', 'quiz').'</label> <input type="text" id="lowmarklimit" name="lowmarklimit" size="1" value="'.$lowlimit.'" /> % ';
echo '<input type="submit" value="'.get_string('go').'" />';
helpbutton("analysisoptions", get_string("analysisoptions","quiz"), "quiz");
echo '</p>';
echo '</form>';
echo '</div>';
}
function report_question_stats(&$attemptscores, &$questionscores, $qid, $top, $bottom){
unset($qstats);
$top_scores = $top_count = 0;
$bottom_scores = $bottom_count = 0;
foreach ($questionscores as $aid => $qrow){
if (isset($qrow[$qid])){
$qstats[] = array($attemptscores[$aid],$qrow[$qid]);
if ($attemptscores[$aid]>=$top){
$top_scores +=$qrow[$qid];
$top_count++;
}
if ($attemptscores[$aid]<=$bottom){
$bottom_scores +=$qrow[$qid];
$bottom_count++;
}
}
}
$n = count($qstats);
$sumx = array_reduce($qstats, "stats_sumx");
$sumg = $sumx[0];
$sumq = $sumx[1];
$sumx2 = array_reduce($qstats, "stats_sumx2");
$sumg2 = $sumx2[0];
$sumq2 = $sumx2[1];
$sumxy = array_reduce($qstats, "stats_sumxy");
$sumgq = $sumxy[0];
$result->count = $n;
$result->facility = $sumq/$n;
if ($n<2) {
$result->qsd = sqrt(($sumq2 - $sumq*$sumq/$n)/($n));
} else {
$result->qsd = sqrt(($sumq2 - $sumq*$sumq/$n)/($n-1));
}
$result->disc_index = ($top_scores - $bottom_scores)/max($top_count, $bottom_count);
$gsd = sqrt(($sumg2 - $sumg*$sumg/$n)/($n-1));
$div = $n*$gsd*$result->qsd;
if ($div!=0) {
$result->disc_coeff = ($sumgq - $sumg*$sumq/$n)/($n*$gsd*$result->qsd);
} else {
$result->disc_coeff = -999;
}
return $result;
}
}
?>
+53 -1
View File
@@ -3,8 +3,15 @@
display:block;
}
.quiz-report-title {
text-align: center;
font-weight : bold;
}
body#mod-quiz-attempts table#attempts,
body#mod-quiz-attempts table#commands
body#mod-quiz-attempts table#commands,
body#mod-quiz-report table#commands,
body#mod-quiz-report table#itemanalysis
{
width: 80%;
margin: auto;
@@ -63,6 +70,51 @@ body#mod-quiz-attempts table#attempts .r1 {
body#mod-quiz-report table#itemanalysis {
margin: 20px auto;
}
body#mod-quiz-report table#itemanalysis .header,
body#mod-quiz-report table#itemanalysis .cell
{
padding: 4px;
}
body#mod-quiz-report table#itemanalysis .header .commands {
display: inline;
}
body#mod-quiz-report table#itemanalysis td {
border-width: 1px;
border-style: solid;
}
body#mod-quiz-report table#itemanalysis .header {
text-align: left;
}
body#mod-quiz-report table#itemanalysis .numcol {
text-align: center;
! vertical-align : middle;
}
body#mod-quiz-report table#itemanalysis .uncorrect {
color: red;
}
body#mod-quiz-report table#itemanalysis .correct {
color: blue;
font-weight : bold;
}
body#mod-quiz-report table#itemanalysis .partialcorrect {
!color: green;
}
body#mod-quiz-report table#itemanalysis .qname {
!color: green;
}
body#mod-quiz-report .controls {
text-align: center;
}
.editorhelptext {
font-size:0.7em;