File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.49: download - view: text, annotated - select for diffs
Tue Apr 1 17:00:24 2003 UTC (21 years, 3 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Modified to accept a hash ref from loncoursedata::get_problem_statistics.
Fields output is now configured in one place instead of 3.  The @Fields array
holds the description of each field, its format, and much more.
Removed the output options 'Degree of Difficulty Plot' and 'Percent Wrong
Plot' (see below).
Plots are now available from the 'ungrouped statistics' page.
&statistics_html_table_data now loops through @Fields to determine its behavior.
Added &statistics_table_header to output the proper fields and links for the
HTML statistics tables.
&get_statistics (the wrapper for loncoursedata::get_problem_statistics) now
requires the sequence, resource, part, and problem number as inputs in order
to add extra data to the returned hash reference.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonproblemstatistics.pm,v 1.49 2003/04/01 17:00:24 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: #
   29: ###############################################
   30: ###############################################
   31: 
   32: =pod
   33: 
   34: =head1 NAME
   35: 
   36: lonproblemstatistics
   37: 
   38: =head1 SYNOPSIS
   39: 
   40: Routines to present problem statistics to instructors via tables,
   41: Excel files, and plots.
   42: 
   43: =over 4
   44: 
   45: =cut
   46: 
   47: ###############################################
   48: ###############################################
   49: 
   50: package Apache::lonproblemstatistics;
   51: 
   52: use strict;
   53: use Apache::lonnet();
   54: use Apache::lonhtmlcommon;
   55: use Apache::loncoursedata;
   56: use Apache::lonstatistics;
   57: use Spreadsheet::WriteExcel;
   58: 
   59: my @Fields = (
   60:            { name => 'problem_num',
   61:              title => 'P#',
   62:              align => 'right',
   63:              color => '#FFFFE6' },
   64:            { name   => 'container',
   65:              title  => 'Container',
   66:              align  => 'left',
   67:              color  => '#FFFFE6',
   68:              sortable => 'yes' },
   69:            { name   => 'title',
   70:              title  => 'Title',
   71:              align  => 'left',
   72:              color  => '#FFFFE6',
   73:              special  => 'link',
   74:              sortable => 'yes', },
   75:            { name   => 'part', 
   76:              title  => 'Part',
   77:              align  => 'left',
   78:              color  => '#FFFFE6' },
   79:            { name   => 'num_students',
   80:              title  => '#Stdnts',
   81:              align  => 'right',
   82:              color  => '#EEFFCC',
   83:              format => '%d',
   84:              sortable  => 'yes',
   85:              graphable => 'yes',
   86:              long_title => 'Number of Students Attempting Problem' },
   87:            { name   => 'tries',
   88:              title  => 'Tries',
   89:              align  => 'right',
   90:              color  => '#EEFFCC',
   91:              format => '%d',
   92:              sortable  => 'yes',
   93:              graphable => 'yes',
   94:              long_title => 'Total Number of Tries' },
   95:            { name   => 'max_tries',
   96:              title  => 'Max Tries',
   97:              align  => 'right',
   98:              color  => '#DDFFFF',
   99:              format => '%d',
  100:              sortable  => 'yes',
  101:              graphable => 'yes',
  102:              long_title => 'Maximum Number of Tries' },
  103:            { name   => 'mean_tries',
  104:              title  => 'Mean Tries',
  105:              align  => 'right',
  106:              color  => '#DDFFFF',
  107:              format => '%5.2f',
  108:              sortable  => 'yes',
  109:              graphable => 'yes',
  110:              long_title => 'Average Number of Tries' },
  111:            { name   => 'std_tries',
  112:              title  => 'S.D. tries',
  113:              align  => 'right',
  114:              color  => '#DDFFFF',
  115:              format => '%5.2f',
  116:              sortable  => 'yes',
  117:              graphable => 'yes',
  118:              long_title => 'Standard Deviation of Number of Tries' },
  119:            { name   => 'skew_tries',
  120:              title  => 'Skew Tries',
  121:              align  => 'right',
  122:              color  => '#DDFFFF',
  123:              format => '%5.2f',
  124:              sortable  => 'yes',
  125:              graphable => 'yes',
  126:              long_title => 'Skew of Number of Tries' },
  127:            { name   => 'deg_of_diff',
  128:              title  => 'DoDiff',
  129:              align  => 'right',
  130:              color  => '#DDFFFF',
  131:              format => '%5.2f',
  132:              sortable  => 'yes',
  133:              graphable => 'yes',
  134:              long_title => 'Degree of Difficulty' },
  135:            { name   => 'num_solved',
  136:              title  => '#YES',
  137:              align  => 'right',
  138:              color  => '#FFDDDD',
  139:              format => '%d',
  140:              sortable  => 'yes',
  141:              graphable => 'yes',
  142:              long_title => 'Number of Students able to Solve' },
  143:            { name   => 'num_override',
  144:              title  => '#yes',
  145:              align  => 'right',
  146:              color  => '#FFDDDD',
  147:              format => '%d',
  148:              sortable  => 'yes',
  149:              graphable => 'yes',
  150:              long_title => 'Number of Students given Override' },
  151:            { name   => 'per_wrong',
  152:              title  => '%Wrng',
  153:              align  => 'right',
  154:              color  => '#FFFFE6',
  155:              format => '%4.1f',
  156:              sortable  => 'yes',
  157:              graphable => 'yes',
  158:              long_title => 'Percent Wrong' },
  159: );
  160: 
  161: ###############################################
  162: ###############################################
  163: 
  164: =pod 
  165: 
  166: =item &CreateInterface()
  167: 
  168: Create the main intereface for the statistics page.  Allows the user to
  169: select sections, maps, and output.
  170: 
  171: =cut
  172: 
  173: ###############################################
  174: ###############################################
  175: sub CreateInterface {
  176:     my $Str = '';
  177:     $Str .= '<table cellspacing="5">'."\n";
  178:     $Str .= '<tr>';
  179:     $Str .= '<td align="center"><b>Sections</b></td>';
  180:     $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
  181:     $Str .= '<td align="center"><b>Output</b></td>';
  182:     $Str .= '</tr>'."\n";
  183:     #
  184:     $Str .= '<tr><td align="center">'."\n";
  185:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
  186:     $Str .= '</td><td align="center">';
  187:     #
  188:     my $only_seq_with_assessments = sub { 
  189:         my $s=shift;
  190:         if ($s->{'num_assess'} < 1) { 
  191:             return 0;
  192:         } else { 
  193:             return 1;
  194:         }
  195:     };
  196:     $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
  197:                                               $only_seq_with_assessments);
  198:     $Str .= '</td><td>'."\n";
  199:     $Str .= &CreateAndParseOutputSelector();
  200:     $Str .= '</td></tr>'."\n";
  201:     $Str .= '</table>'."\n";
  202:     return $Str;
  203: }
  204: 
  205: #######################################################
  206: #######################################################
  207: 
  208: =pod
  209: 
  210: =item &CreateAndParseOutputSelector()
  211: 
  212: Construct a selection list of options for output and parse output selections.
  213: The current output selected is indicated by the values of the two package
  214: variables $output_mode and $show.  @OutputOptions holds the descriptions of
  215: the output options and the values for $output_mode and $show.
  216: 
  217: Based on code from lonstudentassessment.pm.
  218: 
  219: =cut
  220: 
  221: #######################################################
  222: #######################################################
  223: my $output_mode;
  224: my $show;
  225: 
  226: my @OutputOptions = 
  227:     (
  228:      { name  => 'problem statistics grouped by sequence',
  229:        value => 'HTML problem statistics grouped',
  230:        description => 'Output statistics for the problem parts.',
  231:        mode => 'html',
  232:        show => 'grouped',
  233:      },
  234:      { name  => 'problem statistics ungrouped',
  235:        value => 'HTML problem statistics ungrouped',
  236:        description => 'Output statistics for the problem parts.',
  237:        mode => 'html',
  238:        show => 'ungrouped',
  239:      },
  240:      { name  => 'problem statistics, Excel',
  241:        value => 'Excel problem statistics',
  242:        description => 'Output statistics for the problem parts '.
  243:            'in an Excel workbook',
  244:        mode => 'excel',
  245:        show => 'all',
  246:      },
  247:      );
  248: 
  249: sub OutputDescriptions {
  250:     my $Str = '';
  251:     $Str .= "<h2>Output Modes</h2>\n";
  252:     $Str .= "<dl>\n";
  253:     foreach my $outputmode (@OutputOptions) {
  254: 	$Str .="    <dt>".$outputmode->{'name'}."</dt>\n";
  255: 	$Str .="        <dd>".$outputmode->{'description'}."</dd>\n";
  256:     }
  257:     $Str .= "</dl>\n";
  258:     return $Str;
  259: }
  260: 
  261: sub CreateAndParseOutputSelector {
  262:     my $Str = '';
  263:     my $elementname = 'statsoutputmode';
  264:     #
  265:     # Format for output options is 'mode, restrictions';
  266:     my $selected = 'HTML problem statistics grouped';
  267:     if (exists($ENV{'form.'.$elementname})) {
  268:         if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
  269:             $selected = $ENV{'form.'.$elementname}->[0];
  270:         } else {
  271:             $selected = $ENV{'form.'.$elementname};
  272:         }
  273:     }
  274:     #
  275:     # Set package variables describing output mode
  276:     $output_mode = 'html';
  277:     $show        = 'all';
  278:     foreach my $option (@OutputOptions) {
  279:         next if ($option->{'value'} ne $selected);
  280:         $output_mode = $option->{'mode'};
  281:         $show        = $option->{'show'};
  282:     }
  283:     #
  284:     # Build the form element
  285:     $Str = qq/<select size="5" name="$elementname">/;
  286:     foreach my $option (@OutputOptions) {
  287:         if (exists($option->{'special'}) && 
  288:             $option->{'special'} =~ /do not show/) {
  289:             next;
  290:         }
  291:         $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
  292:         $Str .= " selected " if ($option->{'value'} eq $selected);
  293:         $Str .= ">".$option->{'name'}."<\/option>";
  294:     }
  295:     $Str .= "\n</select>";
  296:     return $Str;
  297: }
  298: 
  299: ###############################################
  300: ###############################################
  301: 
  302: =pod 
  303: 
  304: =item &Gather_Student_Data()
  305: 
  306: Ensures all student data is up to date.
  307: 
  308: =cut
  309: 
  310: ###############################################
  311: ###############################################
  312: sub Gather_Student_Data {
  313:     my ($r) = @_;
  314:     my $c = $r->connection();
  315:     #
  316:     my @Sequences = &Apache::lonstatistics::Sequences_with_Assess();
  317:     #
  318:     my @Students = @Apache::lonstatistics::Students;
  319:     #
  320:     # Open the progress window
  321:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
  322:         ($r,'Statistics Compilation Status',
  323:          'Statistics Compilation Progress', scalar(@Students));
  324:     #
  325:     while (my $student = shift @Students) {
  326:         return if ($c->aborted());
  327:         my ($status,undef) = &Apache::loncoursedata::ensure_current_data
  328:             ($student->{'username'},$student->{'domain'},
  329:              $ENV{'request.course.id'});
  330:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
  331:                                                  'last student');
  332:     }
  333:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
  334:     $r->rflush();
  335: }
  336: 
  337: ###############################################
  338: ###############################################
  339: 
  340: =pod 
  341: 
  342: =item &BuildProblemStatisticsPage()
  343: 
  344: Main interface to problem statistics.
  345: 
  346: =cut
  347: 
  348: ###############################################
  349: ###############################################
  350: sub BuildProblemStatisticsPage {
  351:     my ($r,$c)=@_;
  352:     #
  353:     $output_mode = 'html';
  354:     $show = 'grouped';
  355:     #
  356:     $r->print(&CreateInterface());
  357:     $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
  358:     $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
  359:               '" />');
  360:     $r->print('<input type="hidden" name="plot" value="" />');
  361:     if (! exists($ENV{'form.statsfirstcall'})) {
  362:         $r->print(<<ENDMSG);
  363: <p>
  364: <font size="+1">
  365: Please make your selections in the boxes above and hit 
  366: the button marked &quot;Update&nbsp;Display&quot;.
  367: </font>
  368: </p>
  369: ENDMSG
  370:         return;
  371:     }
  372:     #
  373:     &Gather_Student_Data($r);
  374:     #
  375:     #
  376:     if ($output_mode eq 'html') {
  377:         $r->print("<h2>".
  378:                   $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
  379:                   "</h2>\n");
  380:         $r->print("<h3>".localtime(time)."</h3>");
  381:         $r->rflush();
  382:         if ($show eq 'grouped') {
  383:             &output_html_grouped_by_sequence($r);
  384:         } elsif ($show eq 'ungrouped') {
  385:             &output_html_ungrouped($r);
  386:         }
  387:     } elsif ($output_mode eq 'excel') {
  388:         $r->print("<h2>Preparing Excel Spreadsheet</h2>");
  389:         &output_excel($r);
  390:     } else {
  391:         $r->print("<h1>Not implemented</h1>");
  392:     }
  393:     return;
  394: }
  395: 
  396: ###############################################
  397: ###############################################
  398: 
  399: =pod 
  400: 
  401: =item &output_html_grouped_by_sequence()
  402: 
  403: Presents the statistics data as an html table organized by the order
  404: the assessments appear in the course.
  405: 
  406: =cut
  407: 
  408: ###############################################
  409: ###############################################
  410: sub output_html_grouped_by_sequence {
  411:     my ($r) = @_;
  412:     my $problem_num = 0;
  413:     #$r->print(&ProblemStatisticsLegend());
  414:     foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
  415:         next if ($sequence->{'num_assess'}<1);
  416:         $r->print("<h3>".$sequence->{'title'}."</h3>");
  417:         $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
  418:         $r->print('<table border="0" cellpadding="3">'."\n");
  419:         $r->print('<tr bgcolor="#FFFFE6">');
  420:         my $Str = &statistics_table_header('no container no plots');
  421:         $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
  422:         foreach my $resource (@{$sequence->{'contents'}}) {
  423:             next if ($resource->{'type'} ne 'assessment');
  424:             foreach my $part (@{$resource->{'parts'}}) {
  425:                 $problem_num++;
  426:                 my $data = &get_statistics($sequence,$resource,$part,
  427:                                            $problem_num);
  428:                 my $option = '';
  429:                 $r->print('<tr>'.&statistics_html_table_data($data,
  430:                                                              'no container').
  431:                           "</tr>\n");
  432:             }
  433:         }
  434:         $r->print("</table>\n");
  435:         $r->print("</td></tr></table>\n");
  436:         $r->rflush();
  437:     }
  438:     #
  439:     return;
  440: }
  441: 
  442: ###############################################
  443: ###############################################
  444: 
  445: =pod 
  446: 
  447: =item &output_html_ungrouped()
  448: 
  449: Presents the statistics data in a single html table which can be sorted by
  450: different columns.
  451: 
  452: =cut
  453: 
  454: ###############################################
  455: ###############################################
  456: sub output_html_ungrouped {
  457:     my ($r,$option) = @_;
  458:     #
  459:     if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '') {
  460:         &plot_statistics($r,$ENV{'form.plot'});
  461:     }
  462:     #
  463:     my $problem_num = 0;
  464:     my $show_container = 0;
  465:     my $show_part = 0;
  466:     #$r->print(&ProblemStatisticsLegend());
  467:     my $sortby = undef;
  468:     foreach my $field (@Fields) {
  469:         if ($ENV{'form.sortby'} eq $field->{'name'}) {
  470:             $sortby = $field->{'name'};
  471:         }
  472:     }
  473:     if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
  474:         $sortby = 'container';
  475:     }
  476:     # If there is more than one sequence, list their titles
  477:     my @Sequences = &Apache::lonstatistics::Sequences_with_Assess();
  478:     if (@Sequences < 1) {
  479:         $option .= ' no container';
  480:     }
  481:     #
  482:     # Compile the data
  483:     my @Statsarray;
  484:     foreach my $sequence (@Sequences) {
  485:         next if ($sequence->{'num_assess'}<1);
  486:         foreach my $resource (@{$sequence->{'contents'}}) {
  487:             next if ($resource->{'type'} ne 'assessment');
  488:             foreach my $part (@{$resource->{'parts'}}) {
  489:                 $problem_num++;
  490:                 my $data = &get_statistics($sequence,$resource,$part,
  491:                                            $problem_num);
  492:                 $show_part = 1 if ($part ne '0');
  493:                 #
  494:                 push (@Statsarray,$data);
  495:             }
  496:         }
  497:     }
  498:     #
  499:     # Sort the data
  500:     my @OutputOrder;
  501:     if ($sortby eq 'container') {
  502:         @OutputOrder = @Statsarray;
  503:     } else {
  504:         # $sortby is already defined, so we can charge ahead
  505:         if ($sortby =~ /^(title|part)$/i) {
  506:             # Alpha comparison
  507:             @OutputOrder = sort {
  508:                 lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
  509:                     lc($a->{'title'}) cmp lc($b->{'title'}) ||
  510:                         lc($a->{'part'}) cmp lc($b->{'part'});
  511:             } @Statsarray;
  512:         } else {
  513:             # Numerical comparison
  514:             @OutputOrder = sort {
  515:                 my $retvalue = 0;
  516:                 if ($b->{$sortby} eq 'nan') {
  517:                     if ($a->{$sortby} ne 'nan') {
  518:                         $retvalue = -1;
  519:                     } else {
  520:                         $retvalue = 0;
  521:                     }
  522:                 }
  523:                 if ($a->{$sortby} eq 'nan') {
  524:                     if ($b->{$sortby} ne 'nan') {
  525:                         $retvalue = 1;
  526:                     }
  527:                 }
  528:                 if ($retvalue eq '0') {
  529:                     $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
  530:                                 lc($a->{'title'}) <=> lc($b->{'title'}) ||
  531:                                 lc($a->{'part'})  <=> lc($b->{'part'});
  532:                 }
  533:                 $retvalue;
  534:             } @Statsarray;
  535:         }
  536:     }
  537:     $option .= 'no part' if (! $show_part);
  538:     my $num_output = 0;
  539:     #
  540:     # output the headers
  541:     $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
  542:     $r->print('<table border="0" cellpadding="3">'."\n");
  543:     my $Str = &statistics_table_header($option.' sortable');
  544:     $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
  545:     #
  546:     foreach my $rowdata (@OutputOrder) {
  547:         $num_output++;
  548:         if ($num_output % 25 == 0) {
  549:             $r->print("</table>\n</td></tr></table>\n");
  550:             #
  551:             $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
  552:             $r->print('<table border="0" cellpadding="3">'."\n");
  553:             my $Str = &statistics_table_header($option.' sortable');
  554:             $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
  555:             $r->rflush();
  556:         }
  557:         $r->print('<tr>'.&statistics_html_table_data($rowdata,$option).
  558:                   "</tr>\n");
  559:     }
  560:     $r->print("</table>\n");
  561:     $r->print("</td></tr></table>\n");
  562:     $r->rflush();
  563:     #
  564:     return;
  565: }
  566: 
  567: ###############################################
  568: ###############################################
  569: 
  570: =pod 
  571: 
  572: =item &output_excel()
  573: 
  574: Presents the statistical data in an Excel 95 compatable spreadsheet file.
  575: 
  576: =cut
  577: 
  578: ###############################################
  579: ###############################################
  580: sub output_excel {
  581:     my ($r) = @_;
  582:     my $filename = '/prtspool/'.
  583:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
  584:             time.'_'.rand(1000000000).'.xls';
  585:     #
  586:     my $excel_workbook = undef;
  587:     my $excel_sheet = undef;
  588:     #
  589:     my $rows_output = 0;
  590:     my $cols_output = 0;
  591:     #
  592:     # Create sheet
  593:     $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
  594:     #
  595:     # Check for errors
  596:     if (! defined($excel_workbook)) {
  597:         $r->log_error("Error creating excel spreadsheet $filename: $!");
  598:         $r->print("Problems creating new Excel file.  ".
  599:                   "This error has been logged.  ".
  600:                   "Please alert your LON-CAPA administrator");
  601:         return ;
  602:     }
  603:     #
  604:     # The excel spreadsheet stores temporary data in files, then put them
  605:     # together.  If needed we should be able to disable this (memory only).
  606:     # The temporary directory must be specified before calling 'addworksheet'.
  607:     # File::Temp is used to determine the temporary directory.
  608:     $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
  609:     #
  610:     # Add a worksheet
  611:     my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
  612:     if (length($sheetname) > 31) {
  613:         $sheetname = substr($sheetname,0,31);
  614:     }
  615:     $excel_sheet = $excel_workbook->addworksheet($sheetname);
  616:     #
  617:     # Put the course description in the header
  618:     $excel_sheet->write($rows_output,$cols_output++,
  619:                    $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
  620:     $cols_output += 3;
  621:     #
  622:     # Put a description of the sections listed
  623:     my $sectionstring = '';
  624:     my @Sections = @Apache::lonstatistics::SelectedSections;
  625:     if (scalar(@Sections) > 1) {
  626:         if (scalar(@Sections) > 2) {
  627:             my $last = pop(@Sections);
  628:             $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
  629:         } else {
  630:             $sectionstring = "Sections ".join(' and ',@Sections);
  631:         }
  632:     } else {
  633:         if ($Sections[0] eq 'all') {
  634:             $sectionstring = "All sections";
  635:         } else {
  636:             $sectionstring = "Section ".$Sections[0];
  637:         }
  638:     }
  639:     $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
  640:     $cols_output += scalar(@Sections);
  641:     #
  642:     # Put the date in there too
  643:     $excel_sheet->write($rows_output,$cols_output++,
  644:                         'Compiled on '.localtime(time));
  645:     #
  646:     $rows_output++; 
  647:     $cols_output=0;
  648:     #
  649:     # Add the headers
  650:     foreach my $field (@Fields) {
  651:         next if ($field->{'name'} eq 'problem_num');
  652:         $excel_sheet->write($rows_output,$cols_output++,$field->{'title'});
  653:     }
  654:     $rows_output++;
  655:     #
  656:     # Write the data
  657:     my $problem_num=0;
  658:     foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
  659:         next if ($sequence->{'num_assess'}<1);
  660:         foreach my $resource (@{$sequence->{'contents'}}) {
  661:             next if ($resource->{'type'} ne 'assessment');
  662:             foreach my $part (@{$resource->{'parts'}}) {
  663:                 $cols_output=0;
  664:                 $problem_num++;
  665:                 my $data = &get_statistics($sequence,$resource,$part,
  666:                                            $problem_num);
  667:                 #
  668:                 if (!defined($part) || $part eq '') {
  669:                     $part = ' ';
  670:                 }
  671:                 foreach my $field (@Fields) {
  672:                     next if ($field->{'name'} eq 'problem_num');
  673:                     $excel_sheet->write($rows_output,$cols_output++,
  674:                                         $data->{$field->{'name'}});
  675:                 }
  676:                 $rows_output++;
  677:             }
  678:         }
  679:     }
  680:     #
  681:     # Write the excel file
  682:     $excel_workbook->close();
  683:     # Tell the user where to get their excel file
  684:     $r->print('<br />'.
  685:               '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
  686:     $r->rflush();
  687:     return;
  688: }
  689: 
  690: ###############################################
  691: ###############################################
  692: 
  693: =pod 
  694: 
  695: =item &statistics_html_table_data()
  696: 
  697: Help function used to format the rows for HTML table output.
  698: 
  699: =cut
  700: 
  701: ###############################################
  702: ###############################################
  703: sub statistics_html_table_data {
  704:     my ($data,$options) = @_;
  705:     my $row = '';
  706:     foreach my $field (@Fields) {
  707:         next if ($options =~ /no $field->{'name'}/);
  708:         $row .= '<td bgcolor="'.$field->{'color'}.'"';
  709:         if (exists($field->{'align'})) {
  710:             $row .= ' align="'.$field->{'align'}.'"';
  711:             }
  712:         $row .= '>';
  713:         if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
  714:             $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.
  715:                 ' target="_blank">';
  716:         }
  717:         if (exists($field->{'format'})) {
  718:             $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
  719:         } else {
  720:             $row .= $data->{$field->{'name'}};
  721:         }
  722:         if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
  723:             $row.= '</a>';
  724:         }
  725:         $row .= '</td>';
  726:     }
  727:     return $row;
  728: }
  729: 
  730: sub statistics_table_header {
  731:     my ($options) = @_;
  732:     my $header_row;
  733:     foreach my $field (@Fields) {
  734:         next if ($options =~ /no $field->{'name'}/);
  735:         $header_row .= '<th>';
  736:         if ($options =~ /sortable/ && 
  737:             exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
  738:             $header_row .= '<a href="javascript:'.
  739:                 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
  740:                     ';document.Statistics.submit();">';
  741:         }
  742:         $header_row .= $field->{'title'};
  743:         if ($options =~ /sortable/) {
  744:             $header_row.= '</a>';
  745:         }
  746:         if ($options !~ /no plots/        && 
  747:             exists($field->{'graphable'}) && 
  748:             $field->{'graphable'} eq 'yes') {
  749:             $header_row.=' (';
  750:             $header_row .= '<a href="javascript:'.
  751:                 "document.Statistics.plot.value='$field->{'name'}'".
  752:                     ';document.Statistics.submit();">';
  753:             $header_row .= 'plot</a>)';
  754:         }
  755:         $header_row .= '</th>';
  756:     }
  757:     return $header_row;
  758: }
  759: 
  760: ###############################################
  761: ###############################################
  762: 
  763: =pod 
  764: 
  765: =item &plot_statistics()
  766: 
  767: =cut
  768: 
  769: ###############################################
  770: ###############################################
  771: sub plot_statistics {
  772:     my ($r,$datafield) = @_;
  773:     my @Data;
  774:     #
  775:     #
  776:     my $sortfield = undef;
  777:     my $title = undef;
  778:     foreach my $field (@Fields) {
  779:         if ($datafield eq $field->{'name'} &&
  780:             exists($field->{'graphable'}) && $field->{'graphable'} eq 'yes') {
  781:             $sortfield = $field->{'name'};
  782:             $title = $field->{'long_title'};
  783:         }
  784:     }
  785:     return if (! defined($sortfield) || $sortfield eq '');
  786:     &Apache::lonnet::logthis('data field = '.$datafield);
  787:     #
  788:     my $Max = 0;
  789:     my $problem_num = 0;
  790:     foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
  791:         next if ($sequence->{'num_assess'}<1);
  792:         foreach my $resource (@{$sequence->{'contents'}}) {
  793:             next if ($resource->{'type'} ne 'assessment');
  794:             foreach my $part (@{$resource->{'parts'}}) {
  795:                 my $problem_number++;
  796:                 my $data = &get_statistics($sequence,$resource,$part,
  797:                                            $problem_num);
  798:                 my $value = $data->{$sortfield};
  799:                 $Max = $value if ($Max < $value);
  800:                 push (@Data,$value);
  801:             }
  802:         }
  803:     }
  804:     #
  805:     # Print out plot request
  806:     my $yaxis = '';
  807:     if ($sortfield eq 'per_wrong') {
  808:         $yaxis = 'Percent';
  809:     }
  810:     #
  811:     # Determine appropriate value for $Max
  812:     if ($sortfield eq 'deg_of_diff') {
  813:         if ($Max > 0.5) {
  814:             $Max = 1;
  815:         } elsif ($Max > 0.2) {
  816:             $Max = 0.5;
  817:         } elsif ($Max > 0.1) {
  818:             $Max = 0.2;
  819:         }
  820:     } elsif ($sortfield eq 'per_wrong') {
  821:         if ($Max > 50) {
  822:             $Max = 100;
  823:         } elsif ($Max > 25) {
  824:             $Max = 50;
  825:         } elsif ($Max > 20) {
  826:             $Max = 25;
  827:         } elsif ($Max > 10) {
  828:             $Max = 20;
  829:         } elsif ($Max > 5) {
  830:             $Max = 10;
  831:         } else {
  832:             $Max = 5;
  833:         }
  834:     }
  835:     
  836:     $r->print("<p>".&DrawGraph(\@Data,$title,'Problem Number',$yaxis,
  837:                                $Max)."</p>\n");
  838:     #
  839:     # Print out the data
  840:     $ENV{'form.sortby'} = 'Contents';
  841: #    &output_html_ungrouped($r);
  842:     return;
  843: }
  844: 
  845: ###############################################
  846: ###############################################
  847: 
  848: =pod 
  849: 
  850: =item &DrawGraph()
  851: 
  852: =cut
  853: 
  854: ###############################################
  855: ###############################################
  856: sub DrawGraph {
  857:     my ($values,$title,$xaxis,$yaxis,$Max)=@_;
  858:     $title = '' if (! defined($title));
  859:     $xaxis = '' if (! defined($xaxis));
  860:     $yaxis = '' if (! defined($yaxis));
  861:     #
  862:     my $sendValues = join(',', @$values);
  863:     my $sendCount = scalar(@$values);
  864:     $Max =1 if ($Max < 1);
  865:     if ( int($Max) < $Max ) {
  866:         $Max++;
  867:         $Max = int($Max);
  868:     }
  869:     my @GData = ($title,$xaxis,$yaxis,$Max,$sendCount,$sendValues);
  870:     return '<IMG src="/cgi-bin/graph.png?'.
  871:         (join('&', @GData)).'" border="1" />';
  872: }
  873: 
  874: sub get_statistics {
  875:     my ($sequence,$resource,$part,$problem_num) = @_;
  876:     #
  877:     my $symb = $resource->{'symb'};
  878:     my $courseid = $ENV{'request.course.id'};
  879:     #
  880:     my $students = \@Apache::lonstatistics::Students;
  881:     if ($Apache::lonstatistics::SelectedSections[0] eq 'all') {
  882:         $students = undef;
  883:     }
  884:     my $data = &Apache::loncoursedata::get_problem_statistics
  885:                         ($students,$symb,$part,$courseid);
  886:     $data->{'part'}        = $part;
  887:     $data->{'problem_num'} = $problem_num;
  888:     $data->{'container'}   = $sequence->{'title'};
  889:     $data->{'title'}       = $resource->{'title'};
  890:     $data->{'title.link'}  = $resource->{'src'};
  891:     #
  892:     return $data;
  893: }
  894: 
  895: ###############################################
  896: ###############################################
  897: 
  898: =pod 
  899: 
  900: =item &ProblemStatisticsLegend()
  901: 
  902: =cut
  903: 
  904: ###############################################
  905: ###############################################
  906: sub ProblemStatisticsLegend {
  907:     my $Ptr = '';
  908:     $Ptr = '<table border="0">';
  909:     $Ptr .= '<tr><td>';
  910:     $Ptr .= '<b>#Stdnts</b></td>';
  911:     $Ptr .= '<td>Total number of students attempted the problem.';
  912:     $Ptr .= '</td></tr><tr><td>';
  913:     $Ptr .= '<b>Tries</b></td>';
  914:     $Ptr .= '<td>Total number of tries for solving the problem.';
  915:     $Ptr .= '</td></tr><tr><td>';
  916:     $Ptr .= '<b>Max Tries</b></td>';
  917:     $Ptr .= '<td>Largest number of tries for solving the problem by a student.';
  918:     $Ptr .= '</td></tr><tr><td>';
  919:     $Ptr .= '<b>Mean</b></td>';
  920:     $Ptr .= '<td>Average number of tries. [ Tries / #Stdnts ]';
  921:     $Ptr .= '</td></tr><tr><td>';
  922:     $Ptr .= '<b>#YES</b></td>';
  923:     $Ptr .= '<td>Number of students solved the problem correctly.';
  924:     $Ptr .= '</td></tr><tr><td>';
  925:     $Ptr .= '<b>#yes</b></td>';
  926:     $Ptr .= '<td>Number of students solved the problem by override.';
  927:     $Ptr .= '</td></tr><tr><td>';
  928:     $Ptr .= '<b>%Wrong</b></td>';
  929:     $Ptr .= '<td>Percentage of students who tried to solve the problem ';
  930:     $Ptr .= 'but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]';
  931:     $Ptr .= '</td></tr><tr><td>';
  932:     $Ptr .= '<b>DoDiff</b></td>';
  933:     $Ptr .= '<td>Degree of Difficulty of the problem.  ';
  934:     $Ptr .= '[ 1 - ((#YES+#yes) / Tries) ]';
  935:     $Ptr .= '</td></tr><tr><td>';
  936:     $Ptr .= '<b>S.D.</b></td>';
  937:     $Ptr .= '<td>Standard Deviation of the tries.  ';
  938:     $Ptr .= '[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1) ';
  939:     $Ptr .= 'where Xi denotes every student\'s tries ]';
  940:     $Ptr .= '</td></tr><tr><td>';
  941:     $Ptr .= '<b>Skew.</b></td>';
  942:     $Ptr .= '<td>Skewness of the students tries.';
  943:     $Ptr .= '[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]';
  944:     $Ptr .= '</td></tr><tr><td>';
  945:     $Ptr .= '<b>Dis.F.</b></td>';
  946:     $Ptr .= '<td>Discrimination Factor: A Standard for evaluating the ';
  947:     $Ptr .= 'problem according to a Criterion<br>';
  948:     $Ptr .= '<b>[Criterion to group students into %27 Upper Students - ';
  949:     $Ptr .= 'and %27 Lower Students]</b><br>';
  950:     $Ptr .= '<b>1st Criterion</b> for Sorting the Students: ';
  951:     $Ptr .= '<b>Sum of Partial Credit Awarded / Total Number of Tries</b><br>';
  952:     $Ptr .= '<b>2nd Criterion</b> for Sorting the Students: ';
  953:     $Ptr .= '<b>Total number of Correct Answers / Total Number of Tries</b>';
  954:     $Ptr .= '</td></tr>';
  955:     $Ptr .= '<tr><td><b>Disc.</b></td>';
  956:     $Ptr .= '<td>Number of Students had at least one discussion.';
  957:     $Ptr .= '</td></tr></table>';
  958:     return $Ptr;
  959: }
  960: 
  961: #---- END Problem Statistics Web Page ----------------------------------------
  962: 
  963: 1;
  964: __END__

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