File:  [LON-CAPA] / loncom / interface / statistics / lonproblemstatistics.pm
Revision 1.51: download - view: text, annotated - select for diffs
Mon Jun 2 13:58:43 2003 UTC (21 years, 1 month ago) by matthew
Branches: MAIN
CVS tags: conference_2003, HEAD
Change table headings to match the rest of the interface.

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

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