File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.53: download - view: text, annotated - select for diffs
Fri Jun 13 20:27:17 2003 UTC (21 years ago) by matthew
Branches: MAIN
CVS tags: version_0_99_2, HEAD
Bug 1611 - links to problems on stats page now work.

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

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