File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.55: download - view: text, annotated - select for diffs
Mon Jul 28 15:30:46 2003 UTC (20 years, 11 months ago) by matthew
Branches: MAIN
CVS tags: version_1_0_3, version_1_0_2, version_1_0_1, version_1_0_0, version_0_99_5, version_0_99_4, HEAD
Bug 1663 - brief explainatory notes about the statistics fields are now
included in Excel output.  Ought to go in to 1.0.

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

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