File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.54: download - view: text, annotated - select for diffs
Mon Jun 16 15:54:58 2003 UTC (21 years, 1 month ago) by matthew
Branches: MAIN
CVS tags: version_0_99_3, HEAD
Interface changes - get rid of drop down box of reports.  Move
'Clear Caches' button to below chart/stat selection boxes.

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

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