File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.40: download - view: text, annotated - select for diffs
Tue Feb 25 20:47:47 2003 UTC (21 years, 4 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
lonstatistics.pm:
   POD cleanups
   @SelectedSections is set when we read the classlist.
   Added variables $top_map, @Sequences, and @Assessments.  These are
      initialized by &PrepareCourseData($r).
   Added &PrepareCourseData($r) which is essentially a wrapper for
      &Apache::loncoursedata::get_sequence_assessment_data().
   Added &MapSelect() to output a <select> box for sequences.  Not tested.
   Added &SectionSelect(..) to output a <select> box for sections.  Tested.
   Added &DisplayClasslist($r) which displays a table of the current classlist
      that is sortable by each column.  Does not bother with 'update time' as
      this should not be an issue for the user.  Tested.
   Added call to &PrepareCourseData by the handler.
lonpercentage.pm, lonproblemanalysis.pm, lonproblemstatistics.pm,
lonstudentassessment.pm: modified to call &Apache::lonstatistics::SectionSelect
   instead of the method in lonhtmlcommon.pm.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonproblemstatistics.pm,v 1.40 2003/02/25 20:47:47 matthew Exp $
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: #
    9: # LON-CAPA is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2 of the License, or
   12: # (at your option) any later version.
   13: #
   14: # LON-CAPA is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with LON-CAPA; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22: #
   23: # /home/httpd/html/adm/gpl.txt
   24: #
   25: # http://www.lon-capa.org/
   26: #
   27: # (Navigate problems for statistical reports
   28: # YEAR=2001
   29: # 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei
   30: # 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei
   31: # YEAR=2002
   32: # 1/22,2/1,2/6,2/25,3/2,3/26,4/7,5/6 Behrouz Minaei
   33: # 5/12,5/26,7/16,7/29,8/5,10/31  Behrouz Minaei
   34: #
   35: ###
   36: 
   37: package Apache::lonproblemstatistics;
   38: 
   39: use strict;
   40: use Apache::lonnet();
   41: use Apache::lonhtmlcommon;
   42: use Apache::loncoursedata;
   43: use GDBM_File;
   44: 
   45: 
   46: sub InitializeProblemStatistics {
   47:     my ($cacheDB, $students, $courseID, $c, $r)=@_;
   48:     my %cache;
   49: 
   50:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
   51:         $r->print('Unable to tie database1.');
   52:         return ('ERROR', undef);
   53:     }
   54: 
   55:     # Remove students who don't have the proper section.
   56:     my @sectionsSelected = split(':',$cache{'sectionsSelected'});
   57:     for(my $studentIndex=((scalar @$students)-1); $studentIndex>=0;
   58:         $studentIndex--) {
   59:         my $value = $cache{$students->[$studentIndex].':section'};
   60:         my $found = 0;
   61:         foreach (@sectionsSelected) {
   62:             if($_ eq 'none') {
   63:                 if($value eq '' || !defined($value) || $value eq ' ') {
   64:                     $found = 1;
   65:                     last;
   66:                 }
   67:             } else {
   68:                 if($value eq $_) {
   69:                     $found = 1;
   70:                     last;
   71:                 }
   72:             }
   73:         }
   74:         if($found == 0) {
   75:             splice(@$students, $studentIndex, 1);
   76:         }
   77:     }
   78: 
   79:     my $isNotCached = 0;
   80:     my $lastStatus = (defined($cache{'StatisticsLastStatus'})) ?
   81:                      $cache{'StatisticsLastStatus'} : 'Nothing';
   82:     my $whichStudents = join(':::',sort(@$students));
   83:     if(!defined($cache{'StatisticsCached'}) ||
   84:        $lastStatus ne $cache{'Status'} ||
   85:        $whichStudents ne $cache{'StatisticsWhichStudents'}) {
   86:         $isNotCached = 1;
   87:     }
   88: 
   89:     untie(%cache);
   90:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
   91:         $r->print('Unable to tie database.2');
   92:         return ('ERROR', undef);
   93:     }
   94:     if($isNotCached && defined($cache{'StatisticsCached'})) {
   95:         my @statkeys = split(':::', $cache{'StatisticsKeys'});
   96:         delete $cache{'StatisticsKeys'};
   97:         delete $cache{'StatisticsCached'};
   98:         foreach(@statkeys) {
   99:             delete $cache{$_};
  100:         }
  101:     }
  102: 
  103:     untie(%cache);
  104:     if($isNotCached) {
  105:         &Apache::loncoursedata::DownloadStudentCourseDataSeparate($students,
  106:                                                                   'true',
  107:                                                                   $cacheDB,
  108:                                                                   'true',
  109:                                                                   'true',
  110:                                                                   $courseID,
  111:                                                                   $r, $c);
  112:     }
  113:     if($c->aborted()) { return ('ERROR', undef); }
  114: 
  115:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  116:         $r->print('Unable to tie database.3');
  117:         return ('ERROR', undef);
  118:     }
  119:     my $problemData;
  120:     if($isNotCached) {
  121:         ($problemData) = &ExtractStudentData(\%cache, $students);
  122:         &CalculateStatistics($problemData, \%cache, $courseID);
  123:     }
  124:     untie(%cache);
  125: 
  126:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
  127:         $r->print('Unable to tie database.4');
  128:         return ('ERROR', undef);
  129:     }
  130:     if($isNotCached) {
  131:         foreach(keys(%$problemData)) {
  132:             $cache{$_} = $problemData->{$_};
  133:         }
  134:         $cache{'StatisticsKeys'} = join(':::', keys(%$problemData));
  135:         $cache{'StatisticsCached'} = 'true';
  136:         $cache{'StatisticsLastStatus'} = $cache{'Status'};
  137:         $cache{'StatisticsWhichStudents'} = $whichStudents;
  138:     }
  139:     untie(%cache);
  140: 
  141:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  142:         $r->print('Unable to tie database.5');
  143:         return ('ERROR', undef);
  144:     }
  145: 
  146:     my $orderedProblems = &SortProblems(\%cache,
  147:                                         $cache{'ProblemStatisticsSort'},
  148:                                         $cache{'SortProblems'},
  149:                                         $cache{'ProblemStatisticsAscend'});
  150:     untie(%cache);
  151: 
  152:     return ('OK', $orderedProblems);
  153: }
  154: 
  155: sub BuildProblemStatisticsPage {
  156:     my ($cacheDB, $students, $courseID, $c, $r)=@_;
  157: 
  158:     my @Header = ("Homework Sets Order","#Stdnts","Tries","Mod",
  159:                   "Mean","#YES","#yes","%Wrng","DoDiff",
  160:                   "S.D.","Skew.","D.F.1st","D.F.2nd");
  161:     my $color=&setbgcolor(0);
  162:     my %cache;
  163: 
  164:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  165:         $r->print('Unable to tie database.6');
  166:         return;
  167:     }
  168:     my $Ptr = '';
  169:     $Ptr .= '<table border="0" cellspacing="5"><tbody>';
  170:     $Ptr .= '<tr><td align="right"><b>Select Map</b></td>'."\n";
  171:     $Ptr .= '<td align="left">';
  172:     $Ptr .= &Apache::lonhtmlcommon::MapOptions(\%cache, 'Statistics',
  173:                                                'Statistics');
  174:     $Ptr .= '</td></tr>'."\n";
  175:     $Ptr .= '<tr><td align="right"><b>Sorting Type:</b></td>'."\n";
  176:     $Ptr .= '<td align="left">'."\n";
  177:     $Ptr .= &Apache::lonhtmlcommon::AscendOrderOptions(
  178:                                            $cache{'ProblemStatisticsAscend'},
  179:                                            'ProblemStatistics',
  180:                                            'Statistics');
  181:     $Ptr .= '</td></tr>'."\n";
  182:     $Ptr .= '<tr><td align="right"><b>Select Sections</b>';
  183:     $Ptr .= '</td>'."\n";
  184:     $Ptr .= '<td align="left">'."\n";
  185:     my @sections = split(':',$cache{'sectionList'});
  186:     my @sectionsSelected = split(':',$cache{'sectionsSelected'});
  187:     $Ptr .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
  188:     $Ptr .= '</td></tr>'."\n";
  189:     $Ptr .= &ProblemStatisticsButtons($cache{'DisplayFormat'},
  190:                                       $cache{'DisplayLegend'},
  191:                                       $cache{'SortProblems'});
  192:     $Ptr .= '</table>';
  193:     if($cache{'DisplayLegend'} eq 'Show Legend') {
  194:         $Ptr .= &ProblemStatisticsLegend();
  195:     }
  196:     $r->print($Ptr);
  197:     $r->rflush();
  198:     untie(%cache);
  199: 
  200:     my ($result, $orderedProblems) =
  201:         &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r);
  202:     if($result ne 'OK') {
  203:         return;
  204:     }
  205: 
  206:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  207:         $r->print('Unable to tie database.6');
  208:         return;
  209:     }
  210:     &BuildStatisticsTable(\%cache, $cache{'DisplayFormat'},
  211:                           $cache{'SortProblems'}, $orderedProblems,
  212:                           \@Header, $r, $color);
  213:     untie(%cache);
  214: 
  215:     return;
  216: }
  217: 
  218: sub BuildGraphicChart {
  219:     my ($graph,$cacheDB,$courseDescription,$students,$courseID,$r,$c)=@_;
  220:     my %cache;
  221:     my $max;
  222: 
  223:     my $title = '';
  224:     if($graph eq 'DoDiffGraph') {
  225: 	$title = 'Degree-of-Difficulty';
  226:     } else {
  227: 	$title = 'Wrong-Percentage';
  228:     }
  229: 
  230:     my $currentSequence = -1;
  231:     my $sortProblems = 'Sort Within Sequence';
  232: 
  233:     my ($result, $orderedProblems) =
  234:         &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r);
  235:     if($result ne 'OK') {
  236:         return;
  237:     }
  238: 
  239:     my @values = ();
  240: 
  241:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  242:         return 'Unable to tie database.7';
  243:     }
  244: 
  245:     foreach(@$orderedProblems) {
  246:         my ($sequence,$problem,$part)=split(':', $_);
  247:         if($cache{'StatisticsMaps'} ne 'All Maps'  &&
  248:            $cache{'StatisticsMaps'} ne $cache{$sequence.':title'}) {
  249:              next;
  250:         }
  251: 
  252:         if( $currentSequence == -1 ||
  253:             ($sortProblems eq 'Sort Within Sequence' &&
  254:             $currentSequence != $sequence)) {
  255: 	    if($currentSequence != -1) {
  256: 		&DrawGraph(\@values,$courseDescription,$title,$max,$r);
  257: 	    }
  258:             if($sortProblems eq 'Sort Within Sequence') {
  259:                 $r->print('<br><b>'.$cache{$sequence.':title'}.'</b>'."\n");
  260:             }
  261: 
  262:             $currentSequence = $sequence;
  263:             @values = ();
  264: 	    $max=0;
  265:         }
  266:         my $data = 0;
  267:         if($graph eq 'DoDiffGraph') {
  268:             $data = sprintf("%.2f", $cache{$_.':degreeOfDifficulty'}),
  269:         } else {
  270:             $data = sprintf("%.1f", $cache{$_.':percentWrong'}),
  271:         }
  272:         if($max < $data) {
  273:             $max = $data;
  274:         }
  275:         push(@values, $data);
  276:     }
  277:     untie(%cache);
  278: 
  279:     &DrawGraph(\@values,$courseDescription,$title,$max,$r);
  280: 
  281:     return;
  282: }
  283: 
  284: 
  285: sub DrawGraph {
  286:     my ($values,$courseDescription,$title,$Max,$r)=@_;
  287:     my $sendValues = join(',', @$values);
  288:     my $sendCount = scalar(@$values);
  289:     $r->print("<br>The Maximum Value is: $Max");
  290:     if ( $Max > 1 ) {
  291: 	if ($Max % 10) {
  292:             if ( int($Max) < $Max ) {
  293: 	    	$Max++;
  294: 		$Max = int($Max);
  295: 	    }
  296: 	}
  297:     #(10 - $Max % 10);
  298:     } else { $Max = 1; }
  299: 
  300:     my @GData = ('','Problem_number',$title,$Max,$sendCount,$sendValues);
  301: 
  302: #    $r->print('</form>'."\n");
  303:     $r->print('<br>'."\n");
  304:     $r->print('<IMG src="/cgi-bin/graph.png?'.
  305:               (join('&', @GData)).'" border="1" />');
  306: #    $r->print('<form>'."\n");
  307:     $r->print('<br>'."\n");
  308: }
  309: 
  310: #---- Problem Statistics Web Page ---------------------------------------
  311: 
  312: sub CreateProblemStatisticsTableHeading {
  313:     my ($headings,$r)=@_;
  314: 
  315:     my $Str='';
  316:     $Str .= '<tr>'."\n";
  317:     $Str .= '<th bgcolor="#ffffe6">P#</th>'."\n";
  318:     foreach(@$headings) {
  319: 	$Str .= '<th bgcolor="#ffffe6">';
  320:         $Str .= '<a href="/adm/statistics?reportSelected=';
  321:         $Str .= &Apache::lonnet::escape('Problem Statistics');
  322:         $Str .= '&ProblemStatisticsSort=';
  323:         $Str .= &Apache::lonnet::escape($_).'">'.$_.'</a>&nbsp</th>'."\n";
  324:     }
  325:     $Str .= "\n".'</tr>'."\n";
  326: 
  327:     return $Str;
  328: }
  329: 
  330: sub BuildStatisticsTable {
  331:     my ($cache,$displayFormat,$sortProblems,$orderedProblems,$headings,
  332:         $r,$color)=@_;
  333: 
  334:     my $count = 1;
  335:     my $currentSequence = -1;
  336:     foreach(@$orderedProblems) {
  337:         my ($sequence,$problem,$part)=split(':', $_);
  338:         if($cache->{'StatisticsMaps'} ne 'All Maps'  &&
  339:            $cache->{'StatisticsMaps'} ne $cache->{$sequence.':title'}) {
  340:             next;
  341:         }
  342: 
  343:         if($currentSequence == -1 ||
  344:            ($sortProblems eq 'Sort Within Sequence' &&
  345:             $currentSequence != $sequence)) {
  346:             if($displayFormat ne 'Display CSV Format') {
  347:                 if($currentSequence ne -1) {
  348:                     $r->print('</table>');
  349:                     $r->print('</td></tr></table><br>');
  350:                 }
  351:                 if($sortProblems eq 'Sort Within Sequence') {
  352:                     $r->print('<b>'.$cache->{$sequence.':title'}.'</b>');
  353:                 }
  354:                 $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
  355:                 $r->print('<table border="0" cellpadding="3">'."\n");
  356:                 $r->print(&CreateProblemStatisticsTableHeading($headings, $r));
  357:             } else {
  358:                 if($sortProblems eq 'Sort Within Sequence') {
  359:                     $r->print('"'.$cache->{$sequence.':title'}.'"');
  360:                 }
  361:                 $r->print('<br>');
  362:             }
  363:             $currentSequence = $sequence;
  364:         }
  365: 
  366:         my $ref = '<a href="'.$cache->{$problem.':source'}.
  367:                   '" target="_blank">'.$cache->{$problem.':title'}.'</a>';
  368:         my $title = $cache->{$problem.':title'};
  369:         if($part != 0) {
  370:             $title .= ' Part '.$part;
  371:         }
  372:         my $source = $cache->{$problem.':source'};
  373:         my $tableData = join('&', $ref, $title, $source,
  374:                        $cache->{$_.':studentCount'},
  375:                        $cache->{$_.':totalTries'},
  376:                        $cache->{$_.':maxTries'},
  377:                        $cache->{$_.':mean'},
  378:                        $cache->{$_.':correct'},
  379:                        $cache->{$_.':correctByOverride'},
  380:                        $cache->{$_.':percentWrong'},
  381:                        $cache->{$_.':degreeOfDifficulty'},
  382:                        $cache->{$_.':standardDeviation'},
  383:                        $cache->{$_.':skewness'},
  384:                        $cache->{$_.':discriminationFactor1'},
  385:                        $cache->{$_.':discriminationFactor2'});
  386: 
  387:         &TableRow($displayFormat,$tableData,$count,$r,$color);
  388: 
  389:         $count++;
  390:     }
  391:     if($displayFormat ne 'Display CSV Format') {
  392:         $r->print('</table>'."\n");
  393:         $r->print('</td></tr></table>');
  394:     } else {
  395:         $r->print('<br>');
  396:     }
  397: 
  398:     return;
  399: }
  400: 
  401: sub TableRow {
  402:     my ($displayFormat,$Str,$RealIdx,$r,$color)=@_;
  403:     my($ref,$title,$source,$StdNo,$TotalTries,$MxTries,$Avg,$YES,$Override,
  404:        $Wrng,$DoD,$SD,$Sk,$_D1,$_D2)=split(/\&/,$Str);	
  405:     my $Ptr;
  406:     if($displayFormat eq 'Display CSV Format') {
  407:         $Ptr='"'.$RealIdx.'",'."\n".
  408:              '"'.$title.'",'."\n".
  409:              '"'.$source.'",'."\n".
  410:              '"'.$StdNo.'",'."\n".
  411:              '"'.$TotalTries.'",'."\n".
  412:              '"'.$MxTries.'",'."\n".
  413:              '"'.$Avg.'",'."\n".
  414:              '"'.$YES.'",'."\n".
  415:              '"'.$Override.'",'."\n".
  416:              '"'.$Wrng.'",'."\n".
  417:              '"'.$DoD.'",'."\n".
  418:              '"'.$SD.'",'."\n".
  419:              '"'.$Sk.'",'."\n".
  420:              '"'.$_D1.'",'."\n".
  421:              '"'.$_D2.'"'."\n".
  422:              "<br>\n";
  423: 
  424:         $r->print("\n".$Ptr);
  425:     } else {
  426:         $Ptr='<tr>'."\n".
  427:              '<td bgcolor="#ffffe6">'.$RealIdx.'</td>'."\n".
  428:              '<td bgcolor="#ffffe6">'.$ref.'</td>'."\n".
  429:              '<td bgcolor='.$color->{"yellow"}.'> '.$StdNo.'</td>'."\n".
  430:              '<td bgcolor='.$color->{"yellow"}.'>'.$TotalTries.'</td>'."\n".
  431:              '<td bgcolor='.$color->{"yellow"}.'>'.$MxTries.'</td>'."\n".
  432:              '<td bgcolor='.$color->{"gb"}.'>'.$Avg.'</td>'."\n".
  433:              '<td bgcolor='.$color->{"gb"}.'> '.$YES.'</td>'."\n".
  434:              '<td bgcolor='.$color->{"gb"}.'> '.$Override.'</td>'."\n".
  435:              '<td bgcolor='.$color->{"red"}.'> '.$Wrng.'</td>'."\n".
  436:              '<td bgcolor='.$color->{"red"}.'> '.$DoD.'</td>'."\n".
  437:              '<td bgcolor='.$color->{"green"}.'> '.$SD.'</td>'."\n".
  438:              '<td bgcolor='.$color->{"green"}.'> '.$Sk.'</td>'."\n".
  439:              '<td bgcolor='.$color->{"purple"}.'> '.$_D1.'</td>'."\n".
  440: 	     '<td bgcolor='.$color->{"purple"}.'> '.$_D2.'</td>'."\n";
  441:         $r->print($Ptr.'</tr>'."\n");
  442:     }
  443: 
  444:     return;
  445: }
  446: 
  447: # For loading the colored table for display or un-colored for print
  448: sub setbgcolor {
  449:     my $PrintTable=shift;
  450:     my %color;
  451:     if ($PrintTable){
  452: 	$color{"gb"}="#FFFFFF";
  453: 	$color{"red"}="#FFFFFF";
  454: 	$color{"yellow"}="#FFFFFF";
  455: 	$color{"green"}="#FFFFFF";
  456: 	$color{"purple"}="#FFFFFF";
  457:     } else {
  458: 	$color{"gb"}="#DDFFFF";
  459: 	$color{"red"}="#FFDDDD";
  460: 	$color{"yellow"}="#EEFFCC";
  461: 	$color{"green"}="#DDFFDD";
  462: 	$color{"purple"}="#FFDDFF";
  463:     }
  464: 
  465:     return \%color;
  466: }
  467: 
  468: sub ProblemStatisticsButtons {
  469:     my ($displayFormat, $displayLegend, $sortProblems)=@_;
  470: 
  471:     my $Ptr = '<tr><td></td><td align="left">';
  472:     $Ptr .= '<input type="submit" name="DoDiffGraph" ';
  473:     $Ptr .= 'value="Plot Degree of Difficulty" />'."\n";
  474:     $Ptr .= '</td><td align="left">';
  475:     $Ptr .= '<input type="submit" name="PercentWrongGraph" ';
  476:     $Ptr .= 'value="Plot Percent Wrong" />'."\n";
  477:     $Ptr .= '</td></tr><tr><td></td><td>'."\n";
  478:     $Ptr .= '<input type="submit" name="SortProblems" ';
  479:     if($sortProblems eq 'Sort All Problems') {
  480:         $Ptr .= 'value="Sort Within Sequence" />'."\n";
  481:     } else {
  482:         $Ptr .= 'value="Sort All Problems" />'."\n";
  483:     }
  484:     $Ptr .= '</td><td align="left">';
  485:     $Ptr .= '<input type="submit" name="DisplayLegend" ';
  486:     if($displayLegend eq 'Show Legend') {
  487:         $Ptr .= 'value="Hide Legend" />'."\n";
  488:     } else {
  489:         $Ptr .= 'value="Show Legend" />'."\n";
  490:     }
  491:     $Ptr .= '</td><td align="left">';
  492:     $Ptr .= '<input type="submit" name="DisplayCSVFormat" ';
  493:     if($displayFormat eq 'Display CSV Format') {
  494:         $Ptr .= 'value="Display Table Format" />'."\n";
  495:     } else {
  496:         $Ptr .= 'value="Display CSV Format" />'."\n";
  497:     }
  498:     $Ptr .= '</td></tr>';
  499: 
  500:     return $Ptr;
  501: }
  502: 
  503: sub ProblemStatisticsLegend {
  504:     my $Ptr = '';
  505:     $Ptr = '<table border="0">';
  506:     $Ptr .= '<tr><td>';
  507:     $Ptr .= '<b>#Stdnts</b></td>';
  508:     $Ptr .= '<td>Total number of students attempted the problem.';
  509:     $Ptr .= '</td></tr><tr><td>';
  510:     $Ptr .= '<b>Tries</b></td>';
  511:     $Ptr .= '<td>Total number of tries for solving the problem.';
  512:     $Ptr .= '</td></tr><tr><td>';
  513:     $Ptr .= '<b>Mod</b></td>';
  514:     $Ptr .= '<td>Largest number of tries for solving the problem by a student.';
  515:     $Ptr .= '</td></tr><tr><td>';
  516:     $Ptr .= '<b>Mean</b></td>';
  517:     $Ptr .= '<td>Average number of tries. [ Tries / #Stdnts ]';
  518:     $Ptr .= '</td></tr><tr><td>';
  519:     $Ptr .= '<b>#YES</b></td>';
  520:     $Ptr .= '<td>Number of students solved the problem correctly.';
  521:     $Ptr .= '</td></tr><tr><td>';
  522:     $Ptr .= '<b>#yes</b></td>';
  523:     $Ptr .= '<td>Number of students solved the problem by override.';
  524:     $Ptr .= '</td></tr><tr><td>';
  525:     $Ptr .= '<b>%Wrong</b></td>';
  526:     $Ptr .= '<td>Percentage of students who tried to solve the problem ';
  527:     $Ptr .= 'but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]';
  528:     $Ptr .= '</td></tr><tr><td>';
  529:     $Ptr .= '<b>DoDiff</b></td>';
  530:     $Ptr .= '<td>Degree of Difficulty of the problem.  ';
  531:     $Ptr .= '[ 1 - ((#YES+#yes) / Tries) ]';
  532:     $Ptr .= '</td></tr><tr><td>';
  533:     $Ptr .= '<b>S.D.</b></td>';
  534:     $Ptr .= '<td>Standard Deviation of the tries.  ';
  535:     $Ptr .= '[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1) ';
  536:     $Ptr .= 'where Xi denotes every student\'s tries ]';
  537:     $Ptr .= '</td></tr><tr><td>';
  538:     $Ptr .= '<b>Skew.</b></td>';
  539:     $Ptr .= '<td>Skewness of the students tries.';
  540:     $Ptr .= '[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]';
  541:     $Ptr .= '</td></tr><tr><td>';
  542:     $Ptr .= '<b>Dis.F.</b></td>';
  543:     $Ptr .= '<td>Discrimination Factor: A Standard for evaluating the ';
  544:     $Ptr .= 'problem according to a Criterion<br>';
  545:     $Ptr .= '<b>[Criterion to group students into %27 Upper Students - ';
  546:     $Ptr .= 'and %27 Lower Students]</b><br>';
  547:     $Ptr .= '<b>1st Criterion</b> for Sorting the Students: ';
  548:     $Ptr .= '<b>Sum of Partial Credit Awarded / Total Number of Tries</b><br>';
  549:     $Ptr .= '<b>2nd Criterion</b> for Sorting the Students: ';
  550:     $Ptr .= '<b>Total number of Correct Answers / Total Number of Tries</b>';
  551:     $Ptr .= '</td></tr>';
  552:     $Ptr .= '<tr><td><b>Disc.</b></td>';
  553:     $Ptr .= '<td>Number of Students had at least one discussion.';
  554:     $Ptr .= '</td></tr></table>';
  555: 
  556:     return $Ptr;
  557: }
  558: 
  559: sub ExtractStudentData {
  560:     my ($cache, $students)=@_;
  561: 
  562:     my @problemList=();
  563:     my %problemData;
  564:     foreach my $sequence (split(':', $cache->{'orderedSequences'})) {
  565:         foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {
  566:             foreach my $part (split(/\:/,$cache->{$sequence.':'.
  567:                                                   $problemID.
  568:                                                   ':parts'})) {
  569:                 my $id = $sequence.':'.$problemID.':'.$part;
  570:                 push(@problemList, $id);
  571:                 my $totalTries = 0;
  572:                 my $totalAwarded = 0;
  573:                 my $correct = 0;
  574:                 my $correctByOverride = 0;
  575:                 my $studentCount = 0;
  576:                 my $maxTries = 0;
  577:                 my $totalFirst = 0;
  578:                 my @studentTries=();
  579:                 foreach(@$students) {
  580:                     my $code = $cache->{"$_:$problemID:$part:code"};
  581: 
  582:                     if(defined($cache->{$_.':error'}) || $code eq ' ' ||
  583:                        $cache->{"$_:$problemID:NoVersion"} eq 'true') {
  584:                         next;
  585:                     }
  586: 
  587:                     $studentCount++;
  588:                     my $tries =  $cache->{"$_:$problemID:$part:tries"};
  589:                     if($maxTries < $tries) {
  590:                         $maxTries = $tries;
  591:                     }
  592:                     $totalTries += $tries;
  593:                     push(@studentTries, $tries);
  594: 
  595:                     my $awarded = $cache->{"$_:$problemID:$part:awarded"};
  596:                     $totalAwarded += $awarded;
  597: 
  598:                     if($code eq '*') {
  599:                         $correct++;
  600:                         if($tries == 1) {
  601:                             $totalFirst++;
  602:                         }
  603:                     } elsif($code eq '+') {
  604:                         $correctByOverride++;
  605:                     }
  606:                 }
  607: 
  608:                 my $studentTriesJoined = join(':::', @studentTries);
  609:                 $problemData{$id.':sequenceTitle'} =
  610:                     $cache->{$sequence.':title'};
  611:                 $problemData{$id.':studentCount'} = $studentCount;
  612:                 $problemData{$id.':totalTries'} = $totalTries;
  613:                 $problemData{$id.':studentTries'} = $studentTriesJoined;
  614:                 $problemData{$id.':totalAwarded'} = $totalAwarded;
  615:                 $problemData{$id.':correct'} = $correct;
  616:                 $problemData{$id.':correctByOverride'} = $correctByOverride;
  617:                 $problemData{$id.':wrong'} = $studentCount - 
  618:                                              ($correct + $correctByOverride);
  619:                 $problemData{$id.':maxTries'} = $maxTries;
  620:                 $problemData{$id.':totalFirst'} = $totalFirst;
  621:             }
  622:         }
  623:     }
  624: 
  625:     my @upperStudents1=();
  626:     my @lowerStudents1=();
  627:     my @upperStudents2=();
  628:     my @lowerStudents2=();
  629:     my $upperCount = int(0.27*scalar(@$students));
  630:     # Discriminant Factor criterion 1
  631:     my $sortedStudents = &SortDivideByTries($students,$cache,':totalAwarded');
  632: 
  633:     for(my $i=0; $i<$upperCount; $i++) {
  634:         push(@lowerStudents1, $sortedStudents->[$i]);
  635:         push(@upperStudents1, $sortedStudents->[(scalar(@$students)-$i-1)]);
  636:     }
  637: 
  638:     $problemData{'studentsUpperListCriterion1'}=join(':::', @upperStudents1);
  639:     $problemData{'studentsLowerListCriterion1'}=join(':::', @lowerStudents1);
  640: 
  641:     # Discriminant Factor criterion 2
  642:     $sortedStudents = &SortDivideByTries($students, $cache, ':totalSolved');
  643: 
  644:     for(my $i=0; $i<$upperCount; $i++) {
  645:         push(@lowerStudents2, $sortedStudents->[$i]);
  646:         push(@upperStudents2, $sortedStudents->[(scalar(@$students)-$i-1)]);
  647:     }
  648:     $problemData{'studentsUpperListCriterion2'}=join(':::', @upperStudents2);
  649:     $problemData{'studentsLowerListCriterion2'}=join(':::', @lowerStudents2);
  650: 
  651:     $problemData{'problemList'} = join(':::', @problemList);
  652: 
  653:     return \%problemData;
  654: }
  655: 
  656: sub SortDivideByTries {
  657:     my ($toSort, $data, $sortOn)=@_;
  658:     my @orderedData = sort { ($data->{$a.':totalTries'}) ?
  659:                              ($data->{$a.$sortOn}/$data->{$a.':totalTries'}):0
  660:                              <=>
  661:                              ($data->{$b.':totalTries'}) ?
  662:                              ($data->{$b.$sortOn}/$data->{$b.':totalTries'}):0
  663:                            } @$toSort;
  664: 
  665:     return \@orderedData;
  666: }
  667: 
  668: sub SortProblems {
  669:     my ($problemData,$sortBy,$sortProblems,$ascend)=@_;
  670: 
  671:     my @problems = split(':::', $problemData->{'problemList'});
  672:     if($sortBy eq "Homework Sets Order") {
  673:         return \@problems;
  674:     }
  675: 
  676:     my $data;
  677: 
  678:     if   ($sortBy eq "#Stdnts") { $data = ':studentCount'; }
  679:     elsif($sortBy eq "Tries")   { $data = ':totalTries'; }
  680:     elsif($sortBy eq "Mod")     { $data = ':maxTries'; }
  681:     elsif($sortBy eq "Mean")    { $data = ':mean'; }
  682:     elsif($sortBy eq "#YES")    { $data = ':correct'; }
  683:     elsif($sortBy eq "#yes")    { $data = ':correctByOverride'; }
  684:     elsif($sortBy eq "%Wrng")   { $data = ':percentWrong'; }
  685:     elsif($sortBy eq "DoDiff")  { $data = ':degreeOfDifficulty'; }
  686:     elsif($sortBy eq "S.D.")    { $data = ':standardDeviation'; }
  687:     elsif($sortBy eq "Skew.")   { $data = ':skewness'; }
  688:     elsif($sortBy eq "D.F.1st") { $data = ':discriminationFactor1'; }
  689:     elsif($sortBy eq "D.F.2nd") { $data = ':discriminationFactor2'; }
  690:     else                        { return \@problems; }
  691: 
  692:     my %temp;
  693:     my @sequenceList=();
  694:     foreach(@problems) {
  695:         my ($sequence) = split(':', $_);
  696: 
  697:         my @array=();
  698:         my $tempArray;
  699:         if(defined($temp{$sequence})) {
  700:             $tempArray = $temp{$sequence};
  701:         } else {
  702:             push(@sequenceList, $sequence);
  703:             $tempArray = \@array;
  704:             $temp{$sequence} = $tempArray;
  705:         }
  706: 
  707:         push(@$tempArray, $_);
  708:     }
  709: 
  710:     my @orderedProblems;
  711:     if($sortProblems eq "Sort Within Sequence") {
  712:         foreach(keys(%temp)) {
  713:             my $tempArray = $temp{$_};
  714:             my @tempOrder =
  715:                 sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
  716:             @$tempArray;
  717:             $temp{$_} = \@tempOrder;
  718:         }
  719:         foreach(@sequenceList) {
  720:             my $tempArray = $temp{$_};
  721:             @orderedProblems = (@orderedProblems, @$tempArray);
  722:         }
  723:     } else {
  724:         @orderedProblems = 
  725:             sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
  726:         @problems;
  727:     }
  728: 
  729:     if($ascend eq 'Descending') {
  730:         @orderedProblems = reverse(@orderedProblems);
  731:     }
  732: 
  733:     return \@orderedProblems;
  734: }
  735: 
  736: sub CalculateStatistics {
  737:     my ($data, $cache, $courseID)=@_;
  738: 
  739:     my @problems = split(':::', $data->{'problemList'});
  740:     foreach(@problems) {
  741:         # Mean
  742:         my $mean = ($data->{$_.':studentCount'}) ? 
  743:             ($data->{$_.':totalTries'} / $data->{$_.':studentCount'}) : 0;
  744:         $data->{$_.':mean'} = sprintf("%.2f", $mean);
  745: 
  746:         # %Wrong
  747:         my $pw = ($data->{$_.':studentCount'}) ?
  748:             (($data->{$_.':wrong'} / $data->{$_.':studentCount'}) * 100.0) : 
  749:             100.0;
  750:         $data->{$_.':percentWrong'} = sprintf("%.1f", $pw);
  751: 
  752:         # Degree of Difficulty
  753:         my $dod = ($data->{$_.':totalTries'}) ?
  754:             (1 - (($data->{$_.':correct'} + $data->{$_.':correctByOverride'}) /
  755:                   $data->{$_.':totalTries'})) : 0;
  756: 
  757:         $data->{$_.':degreeOfDifficulty'} = sprintf("%.2f", $dod);
  758: 
  759:         # Factor in mean
  760:         my @studentTries = split(':::', $data->{$_.':studentTries'});
  761:         foreach(my $index=0; $index < scalar(@studentTries); $index++) {
  762:             $studentTries[$index] -= $mean;
  763:         }
  764:         my $sumSquared = 0;
  765:         my $sumCubed = 0;
  766:         foreach(@studentTries) {
  767:             my $squared = ($_ * $_);
  768:             my $cubed = ($squared * $_);
  769:             $sumSquared += $squared;
  770:             $sumCubed += $cubed;
  771:         }
  772: 
  773:         # Standard deviation
  774:         my $standardDeviation;
  775:         if($data->{$_.':studentCount'} - 1 > 0) {
  776:             $standardDeviation = (sqrt($sumSquared)) / 
  777:                                  ($data->{$_.':studentCount'} - 1);
  778:         } else {
  779:             $standardDeviation =  0.0;
  780:         }
  781:         $data->{$_.':standardDeviation'} = sprintf("%.1f", $standardDeviation);
  782: 
  783:         # Skewness
  784:         my $skew;
  785:         if($standardDeviation > 0.0999 && $data->{$_.':studentCount'} > 0) {
  786:             $skew = (((sqrt($sumSquared)) / $data->{$_.':studentCount'}) / 
  787:                      ($standardDeviation * 
  788:                       $standardDeviation * 
  789:                       $standardDeviation));
  790:         } else {
  791:             $skew = 0.0;
  792:         }
  793: 
  794:         $data->{$_.':skewness'} = sprintf("%.1f", $skew);
  795: 
  796:         # Discrimination Factor 1
  797:         my ($sequence, $problem, $part) = split(':', $_);
  798: 
  799:         my @upper1 = split(':::', $data->{'studentsUpperListCriterion1'});
  800:         my @lower1 = split(':::', $data->{'studentsLowerListCriterion1'});
  801: 
  802:         my $upper1Sum=0;
  803:         foreach my $name (@upper1) {
  804:             $upper1Sum += $cache->{"$name:$problem:$part:awarded"};
  805:         }
  806:         $upper1Sum = (scalar(@upper1)) ? ($upper1Sum/(scalar(@upper1))) : 0;
  807: 
  808:         my $lower1Sum=0;
  809:         foreach my $name (@lower1) {
  810:             $lower1Sum += $cache->{"$name:$problem:$part:awarded"};
  811:         }
  812:         $lower1Sum = (scalar(@lower1)) ? ($lower1Sum/(scalar(@lower1))) : 0;
  813: 
  814:         my $df1 = $upper1Sum - $lower1Sum;
  815:         $data->{$_.':discriminationFactor1'} = sprintf("%.2f", $df1);
  816: 
  817:         # Discrimination Factor 2
  818:         my @upper2 = split(':::', $data->{'studentsUpperListCriterion2'});
  819:         my @lower2 = split(':::', $data->{'studentsLowerListCriterion2'});
  820: 
  821:         my $upper2Sum=0;
  822:         foreach my $name (@upper2) {
  823:             $upper2Sum += $cache->{"$name:$problem:$part:awarded"};
  824:         }
  825:         $upper2Sum = (scalar(@upper2)) ? ($upper2Sum/(scalar(@upper2))) : 0;
  826: 
  827:         my $lower2Sum=0;
  828:         foreach my $name (@lower2) {
  829:             $lower2Sum += $cache->{"$name:$problem:$part:awarded"};
  830:         }
  831:         $lower2Sum = (scalar(@lower2)) ? ($lower2Sum/(scalar(@lower2))) : 0;
  832: 
  833:         my $df2 = $upper2Sum - $lower2Sum;
  834:         $data->{$_.':discriminationFactor2'} = sprintf("%.2f", $df2);
  835: 
  836:         my %storestats;
  837:         my $Average = ($data->{$_.':studentCount'}) ? 
  838:             $data->{$_.':totalTries'}/$data->{$_.':studentCount'} : 0;
  839:         $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
  840:                         '___timestamp'}=time;
  841:         $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
  842:                         '___stdno'}=$data->{$_.':studentCount'};
  843:         $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
  844:                         '___avetries'}=$Average;
  845:         $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
  846:                         '___difficulty'}=$data->{$_.':degreeOfDifficulty'};
  847:         $cache->{$sequence.':source'} =~ /^(\w+)\/(\w+)/;
  848:         if($data->{$_.':studentCount'}) { 
  849:             &Apache::lonnet::put('nohist_resevaldata',\%storestats,$1,$2);
  850:         }
  851:     }
  852: 
  853:     return;
  854: }
  855: 
  856: #---- END Problem Statistics Web Page ----------------------------------------
  857: 
  858: 1;
  859: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>