File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.42: download - view: text, annotated - select for diffs
Fri Oct 17 22:05:45 2003 UTC (20 years, 9 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Many changes, most to internal code structure.  Hopefully future changes
will not require such monsterous rewrites.
Foils analysis now shows two graphs, one for the correct choice on the
foils and the other for the incorrect submissions.
Time analysis no longer has editable plot titles.
Added a few helper functions get_time_from_row, get_tries_from_row, and
Process_Row.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonproblemanalysis.pm,v 1.42 2003/10/17 22:05:45 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: 
   28: package Apache::lonproblemanalysis;
   29: 
   30: use strict;
   31: use Apache::lonnet();
   32: use Apache::loncommon();
   33: use Apache::lonhtmlcommon();
   34: use Apache::loncoursedata();
   35: use Apache::lonstatistics;
   36: use Apache::lonlocal;
   37: use HTML::Entities();
   38: use Time::Local();
   39: 
   40: my $plotcolors = ['#33ff00', 
   41:                   '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
   42:                   '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
   43:                   ]; 
   44: 
   45: sub BuildProblemAnalysisPage {
   46:     my ($r,$c)=@_;
   47:     $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
   48:     $r->print(&CreateInterface());
   49:     #
   50:     my @Students = @Apache::lonstatistics::Students;
   51:     #
   52:     if (exists($ENV{'form.ClearCache'}) || 
   53:         exists($ENV{'form.updatecaches'}) ||
   54:         (exists($ENV{'form.firstanalysis'}) &&
   55:          $ENV{'form.firstanalysis'} ne 'no')) {
   56:         &Apache::lonstatistics::Gather_Full_Student_Data($r);
   57:     }
   58:     if (! exists($ENV{'form.firstanalysis'})) {
   59:         $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
   60:     } else {
   61:         $r->print('<input type="hidden" name="firstanalysis" value="no" />');
   62:     }
   63:     $r->rflush();
   64:     if (exists($ENV{'form.problemchoice'}) && 
   65:         ! exists($ENV{'form.SelectAnother'})) {
   66:         $r->print('<input type="submit" name="ProblemAnalysis" value="'.
   67:                   &mt('Analyze Problem Again').'" />');
   68:         $r->print('&nbsp;'x5);
   69:         $r->print('<input type="submit" name="ClearCache" value="'.
   70:                   &mt('Clear Caches').'" />');
   71:         $r->print('&nbsp;'x5);
   72:         $r->print('<input type="submit" name="updatecaches" value="'.
   73:                   &mt('Update Student Data').'" />');
   74:         $r->print('&nbsp;'x5);
   75:         $r->print('<input type="hidden" name="problemchoice" value="'.
   76:                   $ENV{'form.problemchoice'}.'" />');
   77:         $r->print('<input type="submit" name="SelectAnother" value="'.
   78:                   &mt('Choose a different resource').'" />');
   79:         $r->print('&nbsp;'x5);
   80:         #
   81:         $r->print('<hr />');
   82:         #
   83:         my ($symb,$part,$resid) = &get_problem_symb(
   84:                      &Apache::lonnet::unescape($ENV{'form.problemchoice'})
   85:                                            );
   86:         #
   87:         my $resource = &get_resource_from_symb($symb);
   88:         if (defined($resource)) {
   89:             $r->print('<h3>'.$resource->{'src'}.'</h3>');
   90:             my %Data = &get_problem_data($resource->{'src'});
   91:             my $ORdata = $Data{$part.'.'.$resid};
   92:             ##
   93:             ## Render the problem
   94:             my $base;
   95:             ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
   96:             $base = "http://".$ENV{'SERVER_NAME'}.$base;
   97:             my $rendered_problem = 
   98:                 &Apache::lonnet::ssi_body($resource->{'src'});
   99:             $rendered_problem =~ s/<\s*form\s*/<nop /g;
  100:             $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
  101:             $r->print('<table bgcolor="ffffff"><tr><td>'.
  102:                       '<base href="'.$base.'" />'.
  103:                       $rendered_problem.
  104:                       '</td></tr></table>');
  105:             ##
  106:             ## Analyze the problem
  107:             my $PerformanceData = 
  108:                 &Apache::loncoursedata::get_optionresponse_data
  109:                                            (\@Students,$symb,$resid);
  110:             if (defined($PerformanceData) && 
  111:                 ref($PerformanceData) eq 'ARRAY') {
  112:                 if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
  113:                     my $analysis_html = &tries_analysis($PerformanceData,
  114:                                                          $ORdata);
  115:                     $r->print($analysis_html);
  116:                 } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
  117:                     my $analysis_html = &time_analysis($PerformanceData,
  118:                                                          $ORdata);
  119:                 $r->print($analysis_html);
  120:                 } else {
  121:                     $r->print('<h2>'.
  122:                               &mt('The analysis you have selected is '.
  123:                                          'not supported at this time').
  124:                               '</h2>');
  125:                 }
  126:             } else {
  127:                 $r->print('<h2>'.
  128:                           &mt('There is no student data for this problem.').
  129:                           '</h2>');
  130:             }
  131:         } else {
  132:             $r->print('resource is undefined');
  133:         }
  134:         $r->print('<hr />');
  135:     } else {
  136:         $r->print('<input type="submit" name="ProblemAnalysis" value="'.
  137:                   &mt('Analyze Problem').'" />');
  138:         $r->print('&nbsp;'x5);
  139:         $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
  140:         $r->print(&OptionResponseProblemSelector());
  141:     }
  142: }
  143: 
  144: #########################################################
  145: #########################################################
  146: ##
  147: ##      Misc interface routines use by analysis code
  148: ##
  149: #########################################################
  150: #########################################################
  151: sub build_foil_index {
  152:     my ($ORdata) = @_;
  153:     return if (! exists($ORdata->{'Foils'}));
  154:     my %Foildata = %{$ORdata->{'Foils'}};
  155:     my @Foils = sort(keys(%Foildata));
  156:     my %Concepts;
  157:     foreach my $foilid (@Foils) {
  158:         push(@{$Concepts{$Foildata{$foilid}->{'Concept'}}},
  159:              $foilid);
  160:     }
  161:     undef(@Foils);
  162:     # Having gathered the concept information in a hash, we now translate it
  163:     # into an array because we need to be consistent about order.
  164:     # Also put the foils in order, too.
  165:     my $sortfunction = sub {
  166:         my %Numbers = (one   => 1,
  167:                        two   => 2,
  168:                        three => 3,
  169:                        four  => 4,
  170:                        five  => 5,
  171:                        six   => 6,
  172:                        seven => 7,
  173:                        eight => 8,
  174:                        nine  => 9,
  175:                        ten   => 10,);
  176:         my $a1 = $a; 
  177:         my $b1 = $b;
  178:         if (exists($Numbers{lc($a)})) {
  179:             $a1 = $Numbers{lc($a)};
  180:         }
  181:         if (exists($Numbers{lc($b)})) {
  182:             $b1 = $Numbers{lc($b)};
  183:         }
  184:         $a1 cmp $b1;
  185:     };
  186:     my @Concepts;
  187:     foreach my $concept (sort $sortfunction (keys(%Concepts))) {
  188:         push(@Concepts,{ name => $concept,
  189:                         foils => [@{$Concepts{$concept}}]});
  190:         push(@Foils,(@{$Concepts{$concept}}));
  191:     }
  192:     #
  193:     # Build up the table of row labels.
  194:     my $table = '<table border="1" >'."\n";
  195:     if (@Concepts > 1) {
  196:         $table .= '<tr>'.
  197:             '<th>'.&mt('Concept Number').'</th>'.
  198:             '<th>'.&mt('Concept').'</th>'.
  199:             '<th>'.&mt('Foil Number').'</th>'.
  200:             '<th>'.&mt('Foil Name').'</th>'.
  201:             '<th>'.&mt('Foil Text').'</th>'.
  202:             '<th>'.&mt('Correct Value').'</th>'.
  203:             "</tr>\n";
  204:     } else {
  205:         $table .= '<tr>'.
  206:             '<th>'.&mt('Foil Number').'</th>'.
  207:             '<th>'.&mt('Foil Name').'</th>'.
  208:             '<th>'.&mt('Foil Text').'</th>'.
  209:             '<th>'.&mt('Correct Value').'</th>'.
  210:             "</tr>\n";
  211:     }        
  212:     my $conceptindex = 1;
  213:     my $foilindex = 1;
  214:     foreach my $concept (@Concepts) {
  215:         my @FoilsInConcept = @{$concept->{'foils'}};
  216:         my $firstfoil = shift(@FoilsInConcept);
  217:         if (@Concepts > 1) {
  218:             $table .= '<tr>'.
  219:                 '<td>'.$conceptindex.'</td>'.
  220:                 '<td>'.$concept->{'name'}.'</td>'.
  221:                 '<td>'.$foilindex++.'</td>'.
  222:                 '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
  223:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
  224:                 '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
  225:                 "</tr>\n";
  226:         } else {
  227:             $table .= '<tr>'.
  228:                 '<td>'.$foilindex++.'</td>'.
  229:                 '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
  230:                 '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
  231:                 '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
  232:                 "</tr>\n";
  233:         }
  234:         foreach my $foilid (@FoilsInConcept) {
  235:             if (@Concepts > 1) {
  236:                 $table .= '<tr>'.
  237:                     '<td></td>'.
  238:                     '<td></td>'.
  239:                     '<td>'.$foilindex.'</td>'.
  240:                     '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
  241:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
  242:                     '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
  243:                     "</tr>\n";
  244:             } else {
  245:                 $table .= '<tr>'.
  246:                     '<td>'.$foilindex.'</td>'.
  247:                     '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
  248:                     '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
  249:                     '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
  250:                     "</tr>\n";
  251:             }                
  252:         } continue {
  253:             $foilindex++;
  254:         }
  255:     } continue {
  256:         $conceptindex++;
  257:     }
  258:     $table .= "</table>\n";
  259:     #
  260:     # Build option index with color stuff
  261:     return ($table,\@Foils,\@Concepts);
  262: }
  263: 
  264: sub build_option_index {
  265:     my ($ORdata)= @_;
  266:     my $table = "<table>\n";
  267:     my $optionindex = 0;
  268:     my @Rows;
  269:     foreach my $option (&mt('correct option chosen'),@{$ORdata->{'Options'}}) {
  270:         push (@Rows,
  271:               '<tr>'.
  272:               '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
  273:               ('&nbsp;'x4).'</td>'.
  274:               '<td>'.$option.'</td>'.
  275:               "</tr>\n");
  276:     }
  277:     shift(@Rows); # Throw away 'correct option chosen' color
  278:     $table .= join('',reverse(@Rows));
  279:     $table .= "</table>\n";
  280: }
  281: 
  282: #########################################################
  283: #########################################################
  284: ##
  285: ##         Tries Analysis
  286: ##
  287: #########################################################
  288: #########################################################
  289: sub tries_analysis {
  290:     my ($PerformanceData,$ORdata) = @_;
  291:     my $mintries = 1;
  292:     my $maxtries = $ENV{'form.NumPlots'};
  293:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  294:     if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
  295:         $table = '<h3>'.
  296:             &mt('Not enough data for concept analysis.  '.
  297:                 'Performing Foil Analysis').
  298:             '</h3>'.$table;
  299:         $ENV{'form.AnalyzeAs'} = 'Foils';
  300:     }
  301:     my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
  302:                                                      $mintries,$maxtries);
  303:     my $analysis = '';
  304:     if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
  305:         $analysis = &Tries_Foil_Analysis($mintries,$maxtries,$Foils,
  306:                                          \%ResponseData,$ORdata);
  307:     } else {
  308:         $analysis = &Tries_Concept_Analysis($mintries,$maxtries,
  309:                                             $Concepts,\%ResponseData,$ORdata);
  310:     }
  311:     $table .= $analysis;
  312:     return $table;
  313: }
  314: 
  315: sub Tries_Foil_Analysis {
  316:     my ($mintries,$maxtries,$Foils,$respdat,$ORdata) = @_;
  317:     my %ResponseData = %$respdat;
  318:     #
  319:     # Compute the data neccessary to make the plots
  320:     my @PlotData; 
  321:     foreach my $foilid (@$Foils) {
  322:         for (my $i=$mintries;$i<=$maxtries;$i++) {
  323:             if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
  324:                 push(@{$PlotData[$i]->{'_correct'}},0);
  325:             } else {
  326:                 push(@{$PlotData[$i]->{'_correct'}},
  327:                      100*$ResponseData{$foilid}->[$i]->{'_correct'}/
  328:                      $ResponseData{$foilid}->[$i]->{'_total'});
  329:             }
  330:             foreach my $option (@{$ORdata->{'Options'}}) {
  331:                 push(@{$PlotData[$i]->{'_total'}},
  332:                      $ResponseData{$foilid}->[$i]->{'_total'});
  333:                 if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
  334:                     push (@{$PlotData[$i]->{$option}},0);
  335:                 } else {
  336:                     if ($ResponseData{$foilid}->[$i]->{'_total'} ==
  337:                         $ResponseData{$foilid}->[$i]->{'_correct'}) {
  338:                         push(@{$PlotData[$i]->{$option}},0);
  339:                     } else {
  340:                         push (@{$PlotData[$i]->{$option}},
  341:                               100 * $ResponseData{$foilid}->[$i]->{$option} / 
  342:                               ($ResponseData{$foilid}->[$i]->{'_total'} - 
  343:                                $ResponseData{$foilid}->[$i]->{'_correct'}));
  344:                     }
  345:                 }
  346:             }
  347:         }
  348:     }
  349:     # 
  350:     # Build a table for the plots
  351:     my $analysis_html = "<table>\n";
  352:     my $foilkey = &build_option_index($ORdata);
  353:     for (my $i=$mintries;$i<=$maxtries;$i++) {
  354:         my $count = $ResponseData{'_total'}->[$i];
  355:         if ($count == 0) {
  356:             $count = 'no submissions';
  357:         } elsif ($count == 1) {
  358:             $count = '1 submission';
  359:         } else {
  360:             $count = $count.' submissions';
  361:         }
  362:         my $title = 'Attempt '.$i.', '.$count;
  363:         my @Datasets;
  364:         foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
  365:             next if (! exists($PlotData[$i]->{$option}));
  366:             push(@Datasets,$PlotData[$i]->{$option});
  367:         }
  368:         my $correctgraph = &Apache::loncommon::DrawGraph
  369:             ($title,'Foil Number','Percent Correct',
  370:              100,$plotcolors,$Datasets[0]);
  371:         $analysis_html.= '<tr><td>'.$correctgraph.'</td>';
  372:         ##
  373:         ##
  374:         for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) {
  375:             $Datasets[0]->[$i]=0;
  376:         }
  377:         $count = $ResponseData{'_total'}->[$i]-$ResponseData{'_correct'}->[$i];
  378:         if ($count == 0) {
  379:             $count = 'no submissions';
  380:         } elsif ($count == 1) {
  381:             $count = '1 submission';
  382:         } else {
  383:             $count = $count.' submissions';
  384:         }
  385:         $title = 'Attempt '.$i.', '.$count;
  386:         my $incorrectgraph = &Apache::loncommon::DrawGraph
  387:             ($title,'Foil Number','% Option Chosen Incorrectly',
  388:              100,$plotcolors,@Datasets);
  389:         $analysis_html.= '<td>'.$incorrectgraph.'</td>';
  390:         $analysis_html.= '<td>'.$foilkey."<td></tr>\n";
  391:     }
  392:     $analysis_html .= "</table>\n";
  393:     return $analysis_html;
  394: }
  395: 
  396: sub Tries_Concept_Analysis {
  397:     my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
  398:     my %ResponseData = %$respdat;
  399:     my $analysis_html = "<table>\n";
  400:     #
  401:     # Compute the data neccessary to make the plots
  402:     my @PlotData;
  403:     # Concept analysis
  404:     #
  405:     # Note: we do not bother with characterizing the students incorrect
  406:     # answers at the concept level because an incorrect answer for one foil
  407:     # may be a correct answer for another foil.
  408:     my %ConceptData;
  409:     foreach my $concept (@{$Concepts}) {
  410:         for (my $i=$mintries;$i<=$maxtries;$i++) {
  411:             #
  412:             # Gather the per-attempt data
  413:             my $cdata = $ConceptData{$concept}->[$i];
  414:             foreach my $foilid (@{$concept->{'foils'}}) {
  415:                 $cdata->{'_correct'} += 
  416:                     $ResponseData{$foilid}->[$i]->{'_correct'};
  417:                 $cdata->{'_total'}   += 
  418:                     $ResponseData{$foilid}->[$i]->{'_total'};
  419:             }
  420:             push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
  421:             if ($cdata->{'_total'} == 0) {
  422:                 push (@{$PlotData[$i]->{'_correct'}},0);
  423:             } else {
  424:                 push (@{$PlotData[$i]->{'_correct'}},
  425:                       100*$cdata->{'_correct'}/$cdata->{'_total'});
  426:                 }
  427:         }
  428:     }
  429:     # Build a table for the plots
  430:     for (my $i=$mintries;$i<=$maxtries;$i++) {
  431:         my $minstu = $PlotData[$i]->{'_total'}->[0];
  432:         my $maxstu = $PlotData[$i]->{'_total'}->[0];
  433:         foreach my $count (@{$PlotData[$i]->{'_total'}}) {
  434:             if ($minstu > $count) {
  435:                 $minstu = $count;
  436:             }
  437:             if ($maxstu < $count) {
  438:                 $maxstu = $count;
  439:             }
  440:         }
  441:         $maxstu = 0 if (! defined($maxstu));
  442:         $minstu = 0 if (! defined($minstu));
  443:         my $title;
  444:         my $count = $ResponseData{'_total'}->[$i];
  445:         if ($count == 0) {
  446:             $count = 'no submissions';
  447:         } elsif ($count == 1) {
  448:             $count = '1 submission';
  449:         } else {
  450:             $count = $count.' submissions';
  451:         }
  452:         $title = 'Attempt '.$i.', '.$count;
  453:         my $graphlink = &Apache::loncommon::DrawGraph
  454:             ($title,'Concept Number','Percent Correct',
  455:              100,$plotcolors,$PlotData[$i]->{'_correct'});
  456:         $analysis_html.= '<tr><td>'.$graphlink."</td></tr>\n";
  457:     }
  458:     $analysis_html .= "</table>\n";
  459:     return $analysis_html;
  460: }
  461: 
  462: sub analyze_option_data_by_tries {
  463:     my ($PerformanceData,$mintries,$maxtries) = @_;
  464:     my %Trydata;
  465:     $mintries = 1         if (! defined($mintries) || $mintries < 1);
  466:     $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
  467:     foreach my $row (@$PerformanceData) {
  468:         next if (! defined($row));
  469:         my $tries = &get_tries_from_row($row);
  470:         my %Row   = &Process_Row($row);
  471:         while (my ($foilid,$href) = each(%Row)) {
  472:             if (! ref($href)) { 
  473:                 $Trydata{$foilid}->[$tries] += $href;
  474:                 next;
  475:             }
  476:             while (my ($option,$value) = each(%$href)) {
  477:                 $Trydata{$foilid}->[$tries]->{$option}+=$value;
  478:             }
  479:         }
  480:     }
  481:     return %Trydata;
  482: }
  483: 
  484: #########################################################
  485: #########################################################
  486: ##
  487: ##                 Time Analysis
  488: ##
  489: #########################################################
  490: #########################################################
  491: sub time_analysis {
  492:     my ($PerformanceData,$ORdata) = @_;
  493:     my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
  494:     if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
  495:         $table = '<h3>'.
  496:             &mt('Not enough data for concept analysis.  '.
  497:                 'Performing Foil Analysis').
  498:             '</h3>'.$table;
  499:         $ENV{'form.AnalyzeAs'} = 'Foils';
  500:     }
  501:     my $num_plots = $ENV{'form.NumPlots'};
  502:     my $num_data = scalar(@$PerformanceData)-1;
  503:     my $percent = sprintf('%2f',100/$num_plots);
  504:     #
  505:     $table .= "<table>\n";
  506:     for (my $i=0;$i<$num_plots;$i++) {
  507:         ##
  508:         my $starttime = &Apache::lonhtmlcommon::get_date_from_form
  509:             ('startdate_'.$i);
  510:         my $endtime = &Apache::lonhtmlcommon::get_date_from_form
  511:             ('enddate_'.$i);
  512:         if (! defined($starttime) || ! defined($endtime)) {
  513:             my $sec_in_day = 86400;
  514:             my $last_sub_time = &get_time_from_row($PerformanceData->[-1]);
  515:             my ($sday,$smon,$syear);
  516:             (undef,undef,undef,$sday,$smon,$syear) = 
  517:                 localtime($last_sub_time - $sec_in_day*$i);
  518:             $starttime = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear);
  519:             $endtime = $starttime + $sec_in_day;
  520:             if ($i == ($num_plots -1 )) {
  521:                 $starttime = &get_time_from_row($PerformanceData->[0]);
  522:             }
  523:         }
  524:         my $startdateform = &Apache::lonhtmlcommon::date_setter
  525:             ('Statistics','startdate_'.$i,$starttime);
  526:         my $enddateform = &Apache::lonhtmlcommon::date_setter
  527:             ('Statistics','enddate_'.$i,$endtime);
  528:         #
  529:         my $begin_index;
  530:         my $end_index;
  531:         my $j;
  532:         while (++$j < scalar(@$PerformanceData)) {
  533:             last if (&get_time_from_row($PerformanceData->[$j]) > $starttime);
  534:         }
  535:         $begin_index = $j;
  536:         while (++$j < scalar(@$PerformanceData)) {
  537:             last if (&get_time_from_row($PerformanceData->[$j]) > $endtime);
  538:         }
  539:         $end_index = $j;
  540:         ##
  541:         my $interval = {
  542:             begin_index => $begin_index,
  543:             end_index   => $end_index,
  544:             startdateform => $startdateform,
  545:             enddateform   => $enddateform,
  546:         };
  547:         if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
  548:             $table .= &Foil_Time_Analysis($PerformanceData,$ORdata,$Foils,
  549:                                           $interval);
  550:         } else {
  551:             $table .= &Concept_Time_Analysis($PerformanceData,$ORdata,
  552:                                              $Concepts,$interval);
  553:         }
  554:     }
  555:     #
  556:     return $table;
  557: }
  558: 
  559: sub Foil_Time_Analysis {
  560:     my ($PerformanceData,$ORdata,$Foils,$interval) = @_;
  561:     my $analysis_html;
  562:     my $foilkey = &build_option_index($ORdata);
  563:     my ($begin_index,$end_index) = ($interval->{'begin_index'},
  564:                                     $interval->{'end_index'});
  565:     my %TimeData;
  566:     #
  567:     # Compute the number getting the foils correct or incorrects
  568:     for (my $j=$begin_index;$j<=$end_index;$j++) {
  569:         my $row = $PerformanceData->[$j];
  570:         next if (! defined($row));
  571:         my %Row = &Process_Row($row);
  572:         while (my ($foilid,$href) = each(%Row)) {
  573:             if (! ref($href)) {
  574:                 $TimeData{$foilid} += $href;
  575:                 next;
  576:             }
  577:             while (my ($option,$value) = each(%$href)) {
  578:                 $TimeData{$foilid}->{$option}+=$value;
  579:             }
  580:         }
  581:     }
  582:     my @Plotdata;
  583:     foreach my $foil (@$Foils) {
  584:         my $total = $TimeData{$foil}->{'_total'};
  585:         if ($total == 0) {
  586:             push(@{$Plotdata[0]},0);
  587:         } else {
  588:             push(@{$Plotdata[0]},
  589:                  100 * $TimeData{$foil}->{'_correct'} / $total);
  590:         }
  591:         my $total_incorrect = $total - $TimeData{$foil}->{'_correct'};
  592:         my $optionidx = 1;
  593:         foreach my $option (@{$ORdata->{'Options'}}) {
  594:             if ($total_incorrect == 0) {
  595:                 push(@{$Plotdata[$optionidx]},0);
  596:             } else {
  597:                 push(@{$Plotdata[$optionidx]},
  598:                      100 * $TimeData{$foil}->{$option} / $total_incorrect);
  599:             }
  600:         } continue {
  601:             $optionidx++;
  602:         }
  603:     }
  604:     #
  605:     # Create the plot
  606:     my $count = $end_index-$begin_index;
  607:     my $title;
  608:     if ($count == 0) {
  609:         $title = 'no submissions';
  610:     } elsif ($count == 1) {
  611:         $title = 'one submission';
  612:     } else {
  613:         $title = $count.' submissions';
  614:     }
  615:     my $correctplot = &Apache::loncommon::DrawGraph($title,
  616:                                                     'Foil Number',
  617:                                                     'Percent Correct',
  618:                                                     100,
  619:                                                     $plotcolors,
  620:                                                     $Plotdata[0]);
  621:     for (my $j=0; $j< scalar(@{$Plotdata[0]});$j++) {
  622:         $Plotdata[0]->[$j]=0;
  623:     }
  624:     $count = $end_index-$begin_index-$TimeData{'_correct'};
  625:     if ($count == 0) {
  626:         $title = 'no submissions';
  627:     } elsif ($count == 1) {
  628:         $title = 'one submission';
  629:     } else {
  630:         $title = $count.' submissions';
  631:     }
  632:     my $incorrectplot = &Apache::loncommon::DrawGraph($title,
  633:                                                  'Foil Number',
  634:                                                  'Incorrect Option Choice',
  635:                                                  100,
  636:                                                  $plotcolors,
  637:                                                  @Plotdata);        
  638:     $analysis_html.='<tr>'.
  639:         '<td>'.$correctplot.'</td>'.
  640:         '<td>'.$incorrectplot.'</td>'.
  641:         '<td align="left" valign="top">'.$foilkey.'</td>'."</tr>\n";
  642:     $analysis_html.= '<tr>'.'<td colspan="3">'.
  643:         '<b>Start Time</b>:'.
  644:         ' &nbsp;'.$interval->{'startdateform'}.'<br />'.
  645:         '<b>End Time</b>&nbsp;&nbsp;: '.
  646:         '&nbsp;'.$interval->{'enddateform'}.'<br />'.
  647: #        '<b>Plot Title</b>&nbsp;&nbsp;:'.
  648: #        ("&nbsp;"x3).$interval->{'titleform'}.
  649:         '</td>'.
  650:         "</tr>\n";
  651:     return $analysis_html;
  652: }
  653: 
  654: sub Concept_Time_Analysis {
  655:     my ($PerformanceData,$ORdata,$Concepts,$interval) = @_;
  656:     my $analysis_html;
  657:     ##
  658:     ## Determine starttime, endtime, startindex, endindex
  659:     my ($begin_index,$end_index) = ($interval->{'begin_index'},
  660:                                     $interval->{'end_index'});
  661:     my %TimeData;
  662:     #
  663:     # Compute the number getting the foils correct or incorrects
  664:     for (my $j=$begin_index;$j<=$end_index;$j++) {
  665:         my $row = $PerformanceData->[$j];
  666:         next if (! defined($row));
  667:         my %Row = &Process_Row($row);
  668:         while (my ($foilid,$href) = each(%Row)) {
  669:             if (! ref($href)) {
  670:                 $TimeData{$foilid} += $href;
  671:                 next;
  672:             }
  673:             while (my ($option,$value) = each(%$href)) {
  674:                 $TimeData{$foilid}->{$option}+=$value;
  675:             }
  676:         }
  677:     }
  678:     #
  679:     # Put the data in plottable form
  680:     my @Plotdata;
  681:     foreach my $concept (@$Concepts) {
  682:         my ($total,$correct);
  683:         foreach my $foil (@{$concept->{'foils'}}) {
  684:             $total += $TimeData{$foil}->{'_total'};
  685:             $correct += $TimeData{$foil}->{'_correct'};
  686:         }
  687:         if ($total == 0) {
  688:             push(@Plotdata,0);
  689:         } else {
  690:             push(@Plotdata,100 * $correct / $total);
  691:         }
  692:     }
  693:     #
  694:     # Create the plot
  695:     my $title = ($end_index - $begin_index).' submissions';
  696:     my $correctplot = &Apache::loncommon::DrawGraph($title,
  697:                                                     'Concept Number',
  698:                                                     'Percent Correct',
  699:                                                     100,
  700:                                                     $plotcolors,
  701:                                                     \@Plotdata);
  702:     $analysis_html.='<tr>'.
  703:         '<td>'.$correctplot.'</td>'.
  704:         '<td align="left" valign="top">'.
  705:         '<b>Start Time</b>: &nbsp;'.$interval->{'startdateform'}.'<br />'.
  706:         '<b>End Time</b>&nbsp;&nbsp;: '.
  707:            '&nbsp;'.$interval->{'enddateform'}.'<br />'.
  708: #        '<b>Plot Title</b>&nbsp;&nbsp;:'.("&nbsp;"x3).
  709: #            $interval->{'titleform'}.
  710:         '</td>'.
  711:         "</tr>\n";
  712:     return $analysis_html;
  713: }
  714: 
  715: #########################################################
  716: #########################################################
  717: ##
  718: ##             Excel output 
  719: ##
  720: #########################################################
  721: #########################################################
  722: sub prepare_excel_sheet {
  723:     my ($PerformanceData,$ORdata) = @_;
  724:     my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
  725: }
  726: 
  727: sub process_data_for_excel_sheet {
  728:     my ($PerformanceData,$Foils,$Concepts,$ORdata)=@_;
  729: }
  730: 
  731: #########################################################
  732: #########################################################
  733: ##
  734: ##             Interface 
  735: ##
  736: #########################################################
  737: #########################################################
  738: sub CreateInterface {
  739:     ##
  740:     ## Environment variable initialization
  741:     if (! exists$ENV{'form.AnalyzeOver'}) {
  742:         $ENV{'form.AnalyzeOver'} = 'Tries';
  743:     }
  744:     ##
  745:     ## Build the menu
  746:     my $Str = '';
  747:     $Str .= '<table cellspacing="5">'."\n";
  748:     $Str .= '<tr>';
  749:     $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
  750:     $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
  751: #    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
  752:     $Str .= '<td align="center">&nbsp;</td>';
  753:     $Str .= '</tr>'."\n";
  754:     ##
  755:     ## 
  756:     $Str .= '<tr><td align="center">'."\n";
  757:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
  758:     $Str .= '</td>';
  759:     #
  760:     $Str .= '<td align="center">';
  761:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
  762:     $Str .= '</td>';
  763:     #
  764: #    $Str .= '<td align="center">';
  765:     my $only_seq_with_assessments = sub { 
  766:         my $s=shift;
  767:         if ($s->{'num_assess'} < 1) { 
  768:             return 0;
  769:         } else { 
  770:             return 1;
  771:         }
  772:     };
  773:     &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
  774:                                               $only_seq_with_assessments);
  775:     ##
  776:     ##
  777:     $Str .= '<td>';
  778:     { # These braces are here to organize the code, not scope it.
  779:         {
  780:             $Str .= '<nobr>'.&mt('Analyze Over ');
  781:             $Str .='<select name="AnalyzeOver" >';
  782:             $Str .= '<option value="Tries" ';
  783:             if (! exists($ENV{'form.AnalyzeOver'}) || 
  784:                 $ENV{'form.AnalyzeOver'} eq 'Tries'){
  785:                 # Default to Tries
  786:                 $Str .= ' selected ';
  787:             }
  788:             $Str .= '>'.&mt('Tries').'</option>';
  789:             $Str .= '<option value="Time" ';
  790:             $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
  791:             $Str .= '>'.&mt('Time').'</option>';
  792:             $Str .= '</select></nobr><br />';
  793:         }
  794:         {
  795:             $Str .= '<nobr>'.&mt('Analyze as ');
  796:             $Str .='<select name="AnalyzeAs" >';
  797:             $Str .= '<option value="Concepts" ';
  798:             if (! exists($ENV{'form.AnalyzeAs'}) || 
  799:                 $ENV{'form.AnalyzeAs'} eq 'Concepts'){
  800:                 # Default to Concepts
  801:                 $Str .= ' selected ';
  802:             }
  803:             $Str .= '>'.&mt('Concepts').'</option>';
  804:             $Str .= '<option value="Foils" ';
  805:             $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
  806:             $Str .= '>'.&mt('Foils').'</option>';
  807:             $Str .= '</select></nobr><br />';
  808:         }
  809:         {
  810:             $Str .= '<br /><nobr>'.&mt('Number of Plots:');
  811:             $Str .= '<select name="NumPlots">';
  812:             if (! exists($ENV{'form.NumPlots'}) 
  813:                 || $ENV{'form.NumPlots'} < 1 
  814:                 || $ENV{'form.NumPlots'} > 20) {
  815:                 $ENV{'form.NumPlots'} = 5;
  816:             }
  817:             foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
  818:                 $Str .= '<option value="'.$i.'" ';
  819:                 if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
  820:                 $Str .= '>'.$i.'</option>';
  821:             }
  822:             $Str .= '</select></nobr>';
  823:         }
  824:     }
  825:     $Str .= '</td>';
  826:     ##
  827:     ##
  828:     $Str .= '</tr>'."\n";
  829:     $Str .= '</table>'."\n";
  830:     return $Str;
  831: }
  832: 
  833: sub OptionResponseProblemSelector {
  834:     my $Str;
  835:     $Str = "\n<table>\n";
  836:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
  837:         next if ($seq->{'num_assess'}<1);
  838:         my $seq_str = '';
  839:         foreach my $res (@{$seq->{'contents'}}) {
  840:             next if ($res->{'type'} ne 'assessment');
  841:             foreach my $part (@{$res->{'parts'}}) {
  842:                 my $partdata = $res->{'partdata'}->{$part};
  843:                 if (! exists($partdata->{'option'}) || 
  844:                     $partdata->{'option'} == 0) {
  845:                     next;
  846:                 }
  847:                 for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
  848:                     my $respid = $partdata->{'ResponseIds'}->[$i];
  849:                     my $resptype = $partdata->{'ResponseTypes'}->[$i];
  850:                     if ($resptype eq 'option') {
  851:                         my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
  852:                         my $checked = '';
  853:                         if ($ENV{'form.problemchoice'} eq $value) {
  854:                             $checked = 'checked ';
  855:                         }
  856:                         $seq_str .= '<tr><td>'.
  857:   '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
  858:   '</td><td>'.
  859:   '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
  860:                         if ($partdata->{'option'} > 1) {
  861:                             $seq_str .= &mt('response').' '.$respid;
  862:                         }
  863:                         $seq_str .= "</td></tr>\n";
  864:                     }
  865:                 }
  866:             }
  867:         }
  868:         if ($seq_str ne '') {
  869:             $Str .= '<tr><td>&nbsp</td><td><b>'.$seq->{'title'}.'</b></td>'.
  870:                 "</tr>\n".$seq_str;
  871:         }
  872:     }
  873:     $Str .= "</table>\n";
  874:     return $Str;
  875: }
  876: 
  877: #########################################################
  878: #########################################################
  879: ##
  880: ##              Misc functions
  881: ##
  882: #########################################################
  883: #########################################################
  884: sub get_problem_symb {
  885:     my $problemstring = shift();
  886:     my ($symb,$partid,$resid) = ($problemstring=~ /^(.*):([^:]*):([^:]*)$/);
  887:     return ($symb,$partid,$resid);
  888: }
  889: 
  890: sub get_resource_from_symb {
  891:     my ($symb) = @_;
  892:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
  893:         foreach my $res (@{$seq->{'contents'}}) {
  894:             if ($res->{'symb'} eq $symb) {
  895:                 return $res;
  896:             }
  897:         }
  898:     }
  899:     return undef;
  900: }
  901: 
  902: sub get_time_from_row {
  903:     my ($row) = @_;
  904:     if (ref($row)) {
  905:         return $row->[3];
  906:     } 
  907:     return undef;
  908: }
  909: 
  910: sub get_tries_from_row {
  911:     my ($row) = @_;
  912:     if (ref($row)) {
  913:         return $row->[4];
  914:     }
  915:     return undef;
  916: }
  917: 
  918: sub Process_Row {
  919:     my ($row) = @_;
  920:     my %RowData;
  921:     my ($award,$grading,$submission,$time,$tries) = @$row;
  922:     next if ($award eq 'MISSING_ANSWER');
  923:     if ($award =~ /(APPROX_ANS|EXACT_ANS)/) {
  924:         $RowData{'_correct'} = 1;
  925:     }
  926:     $RowData{'_total'} = 1;
  927:     my @Foilgrades = split('&',$grading);
  928:     my @Foilsubs   = split('&',$submission);
  929:     for (my $j=0;$j<=$#Foilgrades;$j++) {
  930:         my ($foilid,$correct)  = split('=',$Foilgrades[$j]);
  931:         my (undef,$submission) = split('=',$Foilsubs[$j]);
  932:         if ($correct) {
  933:             $RowData{$foilid}->{'_correct'}++;
  934:         } else {
  935:             $submission = &Apache::lonnet::unescape($submission);
  936:             $submission =~ s/\%20/ /g;
  937:             $RowData{$foilid}->{$submission}++;
  938:         }
  939:         $RowData{$foilid}->{'_total'}++;
  940:     }
  941:     return %RowData;
  942: }
  943: 
  944: ##
  945: ## get problem data and put it into a useful data structure.
  946: ## note: we must force each foil and option to not begin or end with
  947: ##       spaces as they are stored without such data.
  948: ##
  949: sub get_problem_data {
  950:     my ($url) = @_;
  951:     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
  952:     (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
  953:     my %Answer;
  954:     %Answer=&Apache::lonnet::str2hash($Answ);
  955:     my %Partdata;
  956:     foreach my $part (@{$Answer{'parts'}}) {
  957:         while (my($key,$value) = each(%Answer)) {
  958:             next if ($key !~ /^$part/);
  959:             $key =~ s/^$part\.//;
  960:             if (ref($value) eq 'ARRAY') {
  961:                 if ($key eq 'options') {
  962:                     $Partdata{$part}->{'Options'}=$value;
  963:                 } elsif ($key eq 'concepts') {
  964:                     $Partdata{$part}->{'Concepts'}=$value;
  965:                 } elsif ($key =~ /^concept\.(.*)$/) {
  966:                     my $concept = $1;
  967:                     foreach my $foil (@$value) {
  968:                         $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
  969:                                                                       $concept;
  970:                     }
  971:                 }
  972:             } else {
  973:                 if ($key=~ /^foil\.text\.(.*)$/) {
  974:                     my $foil = $1;
  975:                     $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
  976:                     $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
  977:                 } elsif ($key =~ /^foil\.value\.(.*)$/) {
  978:                     my $foil = $1;
  979:                     $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
  980:                 }
  981:             }
  982:         }
  983:     }
  984:     return %Partdata;
  985: }
  986: 
  987: 1;
  988: 
  989: __END__

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