File:  [LON-CAPA] / loncom / interface / statistics / lonsubmissiontimeanalysis.pm
Revision 1.37: download - view: text, annotated - select for diffs
Tue Nov 10 19:28:32 2020 UTC (3 years, 7 months ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Load CSS file(s) for resource when using (a) Student Submission Reports
  (only one problem selected), or and (b)Detailed Problem Analysis when
  "Show problem" is checked, or when using Submission Time Plots.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonsubmissiontimeanalysis.pm,v 1.37 2020/11/10 19:28:32 raeburn 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::lonsubmissiontimeanalysis;
   29: 
   30: use strict;
   31: use Apache::lonnet;
   32: use Apache::loncommon();
   33: use Apache::lonhtmlcommon();
   34: use Apache::lonquickgrades();
   35: use Apache::loncoursedata();
   36: use Apache::lonstatistics;
   37: use Apache::lonstathelpers;
   38: use Apache::lonlocal;
   39: use HTML::Entities();
   40: use Time::Local();
   41: 
   42: my $plotcolors = ['#33ff00', 
   43:                   '#ff33cc', '#990000', '#aaaa66', '#663399', '#ff9933',
   44:                   '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
   45:                   ]; 
   46: 
   47: my @SubmitButtons = (
   48:                      { name => 'PrevProblemAnalysis',
   49:                        text => 'Previous Problem' },
   50:                      { name => 'ProblemAnalysis',
   51:                        text => 'Analyze Problem Again' },
   52:                      { name => 'NextProblemAnalysis',
   53:                        text => 'Next Problem' },
   54:                      { name => 'SelectAnother',
   55:                        text => 'Choose a different Problem' },
   56:                      );
   57: 
   58: sub BuildSubmissionTimePage {
   59:     my ($r,$c)=@_;
   60:     #
   61:     my %Saveable_Parameters = ('Status' => 'scalar',
   62:                                'Section' => 'array');
   63:     &Apache::loncommon::store_course_settings('submissiontime_analysis',
   64:                                               \%Saveable_Parameters);
   65:     &Apache::loncommon::restore_course_settings('submissiontime_analysis',
   66:                                                 \%Saveable_Parameters);
   67:     #
   68:     &Apache::lonstatistics::PrepareClasslist();    
   69:     #
   70:     $r->print(&Apache::lonhtmlcommon::breadcrumbs('Submission Time Plots'));
   71:     &Apache::lonquickgrades::startGradeScreen($r,'statistics');
   72:     $r->print(&CreateInterface());
   73:     #
   74:     my @Students = @Apache::lonstatistics::Students;
   75:     #
   76:     if (@Students < 1) {
   77:         $r->print('<div class="LC_warning">'
   78:                  .&mt('There are no students in the sections selected.')
   79:                  .'</div>'
   80:         );
   81:     }
   82:     #
   83:     my @CacheButtonHTML = 
   84:         &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
   85:     $r->rflush();
   86:     #
   87:     if (! exists($env{'form.problemchoice'}) ||
   88:         exists($env{'form.SelectAnother'})) {
   89:         my $submit_button = '<input type="submit" name="" value="'.
   90: #           &mt('Graph Problem Submission Times').'" />';
   91:             &mt('Generate Graph').'" />';
   92:         $r->print($submit_button.'&nbsp;'x5);
   93:         $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
   94:         $r->print(&Apache::lonstathelpers::problem_selector('.',
   95:                                                             $submit_button));
   96:     } else {
   97:         foreach my $button (@SubmitButtons) {
   98:             $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
   99:                       'value="'.&mt($button->{'text'}).'" />');
  100:             $r->print('&nbsp;'x5);
  101:         }
  102:         foreach my $html (@CacheButtonHTML) {
  103:             $r->print($html.('&nbsp;'x5));
  104:         }
  105:         $r->rflush();
  106:         #
  107:         # Determine which problem we are to analyze
  108:         my ($navmap,$current_problem) = &get_current_problem(); # need to retrieve $navmap
  109:                                                                 # to support $resource->* calls
  110:                                                                 # for src and compTitle (below)
  111:         #
  112:         # Store the current problem choice and send it out in the form
  113:         $env{'form.problemchoice'} = 
  114:             &Apache::lonstathelpers::make_target_id($current_problem);
  115:         $r->print('<input type="hidden" name="problemchoice" value="'.
  116:                   $env{'form.problemchoice'}.'" />');
  117:         #
  118:         $r->print('<hr />');
  119:         $r->rflush();
  120:         #
  121:         my $resource = $current_problem->{'resource'};
  122:         if (! defined($resource)) {
  123:             $r->print('<div class="LC_warning">'
  124:                      .&mt('Resource is undefined.')
  125:                      .'</div>'
  126:             );
  127:         } else {
  128:             $r->print('<h1>'.$resource->compTitle.'</h1>');
  129:             $r->print('<h3>'.$resource->src.'</h3>');
  130:             $r->print('<p>'.
  131:                  &Apache::lonstatistics::section_and_enrollment_description().
  132:                       '</p>');
  133:             $r->rflush();
  134:             $r->print(&Apache::lonstathelpers::render_resource($resource));
  135:             $r->print('<br />');
  136:             $r->rflush();
  137: 	    if (@Students) {	    
  138: 		$r->print(&analyze_times($r,$resource->symb,\@Students,
  139: 					 $current_problem->{'part'}));
  140: 	    }
  141:         }
  142:         $r->print('<hr />');
  143:     }
  144: }
  145: 
  146: sub get_current_problem {
  147:     my $current_problem = &Apache::lonstathelpers::get_target_from_id
  148:         ($env{'form.problemchoice'});
  149:     my ($navmap,$prev,$curr,$next) =
  150:         &Apache::lonstathelpers::get_prev_curr_next($current_problem,
  151:                                                     '.',
  152:                                                     'part');
  153:     if (exists($env{'form.PrevProblemAnalysis'}) && defined($prev)) {
  154:         $current_problem = $prev;
  155:     } elsif (exists($env{'form.NextProblemAnalysis'}) && defined($next)) {
  156:         $current_problem = $next;
  157:     } else {
  158:         $current_problem = $curr;
  159:     }
  160:     return ($navmap,$current_problem);
  161: }
  162: 
  163: #########################################################
  164: #########################################################
  165: ##
  166: ##                  Time Analysis
  167: ##
  168: #########################################################
  169: #########################################################
  170: sub get_week_start {
  171:     my ($randomtime) = @_;
  172:     my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = 
  173:         localtime($randomtime);
  174:     $randomtime -= $wday * 86400;
  175:     ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = 
  176:         localtime($randomtime);
  177:     my $week_start = &Time::Local::timelocal(0,0,0,$mday,$month,$year);
  178:     return $week_start;
  179: }
  180: 
  181: sub analyze_times {
  182:     my ($r,$symb,$students,$part) = @_;
  183:     my $htmltable;
  184:     #
  185:     # Convenience arrays
  186:     my @FullWeekDay = (qw/Sunday Monday Tuesday Wednesday Thursday Friday
  187:                        Saturday/);
  188:     my @WeekDay = (qw/SUN MON TUE WED THU FRI SAT SUN/);
  189:     my @Month = (qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/);
  190:     #
  191:     my $html; # holds results of analysis
  192:     # Get the data
  193:     my $SubData = &Apache::loncoursedata::get_response_time_data
  194:         ([&Apache::lonstatistics::get_selected_sections()],
  195:          [&Apache::lonstatistics::get_selected_groups()],
  196:          $Apache::lonstatistics::enrollment_status,
  197:          $symb,$part);
  198:     if (! defined($SubData) || ! ref($SubData)) {
  199:         $html.= '<div class="LC_warning">'
  200:                .&mt('There is no submission data for this problem at all.')
  201:                .'</div>';
  202:         return $html;
  203:     }
  204:     my $NumSub = scalar(@{$SubData});
  205:     if (! @{$SubData}) {
  206:         $html.= '<div class="LC_warning">'
  207:                .&mt('There is no submission data for this problem.')
  208:                .'</div>';
  209:         return $html;
  210:     }
  211:     # Process the data
  212:     #
  213:     my (undef,undef,undef,$mday,$month,$year,$wday,$yday,$isdst) = 
  214:         localtime(&get_time_from_row($SubData->[0]));
  215:     my $day_start = &Time::Local::timelocal(0,0,0,$mday,$month,$year);
  216:     #
  217:     # Configure the bins used to store the data.
  218:     my $binsize = 3600; # seconds
  219:     my $bins_per_day = 86400/$binsize;
  220:     my $bincount = 0;
  221:     my $endtime = $day_start;
  222:     #
  223:     # Initialize loop variables
  224:     my $max;            # The sum of @Ydata
  225:     my @Ydata=(0);      # number of submissions
  226:     my @AnsData=(0);    # number of correct submissions
  227:     my @Xlabel=($WeekDay[$wday]); # Labels of itmes
  228:     my @BinEnd;                   # The end time of each bin
  229:     my $cumulative_answers = 0;   # The sum of @AnsData
  230:     my %students;       # which students have attempted the problem?
  231:     #
  232:     foreach my $row (@$SubData) {
  233:         my $subtime = &get_time_from_row($row);
  234:         while ($subtime > $endtime && $endtime < time) {
  235:             # Create a new bin
  236:             $bincount++;
  237:             $Ydata[$bincount]   = 0;
  238:             $AnsData[$bincount] = 0;
  239:             $endtime += $binsize;
  240:             push(@BinEnd,$endtime);
  241:             if ($bincount % (86400/$binsize) == 0) {
  242:                 $wday++;
  243:                 $wday %= 7;
  244:                 $Xlabel[$bincount] = $WeekDay[$wday];
  245:             } else {
  246:                 $Xlabel[$bincount] = '';
  247:             }
  248:         }
  249:         $Ydata[$bincount]++;
  250:         $max = $Ydata[$bincount] if ($max < $Ydata[$bincount]);
  251:         $AnsData[$bincount] += &successful_submission($row);
  252:         $cumulative_answers += &successful_submission($row);
  253:         $students{$row->[&Apache::loncoursedata::RT_student_id()]}++;
  254:     }
  255:     #
  256:     # Pad the data to a full day
  257:     while ($bincount % $bins_per_day != 0) {
  258:         $bincount++;
  259:         $Ydata[$bincount]=0;
  260:         $AnsData[$bincount]=0;
  261:         $endtime += $binsize;
  262:         push(@BinEnd,$endtime);
  263:         if ($bincount % (86400/$binsize) == 0) {
  264:             $wday ++;
  265:             $wday %= 7;
  266:             $Xlabel[$bincount] = $WeekDay[$wday];
  267:         } else {
  268:             $Xlabel[$bincount] = '';
  269:         }
  270:     }
  271:     my $numstudents = scalar(keys(%students));
  272:     #
  273:     # Determine a nice maximum value to use
  274:     foreach my $maximum (10,15,20,25,30,40,50,60,70,80,90,100,
  275:                           120,150,200,250,300,350,400,450,500,
  276:                           600,700,800,900,1000,1100,1200,1500,2000,
  277:                           2500,3000,4000,5000) {
  278:         if ($max < $maximum) {
  279:             $max = $maximum;
  280:             last;
  281:         }
  282:     }
  283:     #
  284:     # Build the data table
  285:     $htmltable = '<br><h3>'.&mt('Student submission data').'</h3><p>'.
  286:         &Apache::loncommon::start_data_table().
  287:         &Apache::loncommon::start_data_table_header_row().
  288:         &Apache::loncommon::start_data_table_row().
  289:         '<th valign="bottom">'.&mt('Begin').'</th>'.
  290:         '<th valign="bottom">'.&mt('End').'</th>'.
  291:         '<th valign="bottom">'.&mt('Submissions (plotted)').'</th>'.
  292:         '<th valign="bottom">'.&mt('Correct Submissions (not plotted)').'</th>'.
  293:         '<th valign="bottom">'.&mt('Cumulative Correct of those attempting the problem (not plotted)').'</th>'.
  294:         '<th valign="bottom">'.&mt('Cumulative Percent Correct of those attempting the problem (not plotted)').'</th>'.
  295:         '<th valign="bottom">'.&mt('Cumulative Percent Correct of selected students (plotted)').'</th>'.
  296:         &Apache::loncommon::end_data_table_row().
  297:         &Apache::loncommon::end_data_table_header_row().
  298:         '<tbody>';
  299:     my @CumulativeCorrect=(0);
  300:     my @corr_as_percent_of_selected;
  301:     my @corr_as_percent_of_answering;
  302:     for (my $i=0;$i<=$#Ydata;$i++) {
  303:         $CumulativeCorrect[$i]=$CumulativeCorrect[-1]+$AnsData[$i];
  304:         $corr_as_percent_of_answering[$i] = 
  305:             sprintf('%3.1f',100*$CumulativeCorrect[$i]/$numstudents);
  306:         $corr_as_percent_of_selected[$i] = 
  307:             sprintf('%3.1f',100*$CumulativeCorrect[$i]/scalar(@$students));
  308:         if ($Ydata[$i] != 0) {
  309:             next if (! defined($BinEnd[$i]) || $BinEnd[$i] == 0);
  310:             $htmltable .=
  311:                &Apache::loncommon::start_data_table_row().
  312:                 '<td align="right"><span class="LC_nobreak">'.
  313:                 &Apache::lonlocal::locallocaltime($BinEnd[$i]-$binsize).
  314:                 '</span></td>'.
  315:                 '<td align="right"><span class="LC_nobreak">'.
  316:                     &Apache::lonlocal::locallocaltime($BinEnd[$i]).'</td>'.
  317:                 '</span></td>'.
  318:                 '<td align="right">'.$Ydata[$i].('&nbsp;'x3).'</td>'.
  319:                 '<td align="right">'.$AnsData[$i].('&nbsp;'x3).'</td>'.
  320:                 '<td align="right">'.$CumulativeCorrect[$i].'</td>'.
  321:                 '<td align="right">'.$corr_as_percent_of_answering[$i].'</td>'.
  322:                 '<td align="right">'.$corr_as_percent_of_selected[$i].'</td>'.
  323:                &Apache::loncommon::end_data_table_row().$/;
  324:         }
  325:     }
  326:     $htmltable .= '</tbody>'.&Apache::loncommon::end_data_table().'</p>';
  327:     #
  328:     # Build the plot
  329:     my $title = '';#'Number of Submissions and Number Correct';
  330:     my $xlabel;
  331:     (undef,undef,undef,$mday,$month,$year,$wday) = localtime($day_start);
  332:     $xlabel .= $FullWeekDay[$wday].' '.
  333:         join(' ',($Month[$month],$mday,1900+$year)).' - ';
  334:     (undef,undef,undef,$mday,$month,$year,$wday) = localtime($endtime);
  335:     $xlabel .= $FullWeekDay[$wday].' '.
  336:         join(' ',($Month[$month],$mday,1900+$year));
  337:     my $width = 50+2*$bincount;
  338:     if ($width < 250) {
  339:         $width = 250;
  340:     }
  341:     #
  342:     $html .= &Apache::loncommon::DrawXYYGraph($title,
  343:                                               $xlabel,
  344:                                               'Submissions vs Time',
  345:                                               $plotcolors,
  346:                                               \@Xlabel,
  347:                                               \@Ydata,0,$max,
  348:                                               \@corr_as_percent_of_selected,0,100,
  349:                                               (xskip => $bins_per_day,
  350:                                                x_ticks => $bins_per_day,
  351:                                                x_tick_offset => $bins_per_day,
  352:                                                width => $width,
  353:                       y1_label=>'Number of Submissions per hour',
  354:                       y2_label=>'Percent of Students answering Correctly',
  355:                      'data.1.label'=>'Submissions per hour',
  356:                      'data.2.label'=>'Percent correct',
  357:                                                )
  358:                                               );
  359:     $html .= '<br />'.$htmltable;
  360:     return $html;
  361: }
  362: 
  363: sub successful_submission {
  364:     my ($row) = @_;
  365:     if (ref($row) eq 'ARRAY') {
  366:         return $row->[&Apache::loncoursedata::RT_awarded()];
  367:     }
  368:     return undef;
  369: }
  370: 
  371: sub get_time_from_row {
  372:     my ($row) = @_;
  373:     if (ref($row) eq 'ARRAY') {
  374:         return $row->[&Apache::loncoursedata::RT_timestamp()];
  375:     } 
  376:     return undef;
  377: }
  378: 
  379: sub get_tries_from_row {
  380:     my ($row) = @_;
  381:     if (ref($row) eq 'ARRAY') {
  382:         return $row->[&Apache::loncoursedata::RT_tries()];
  383:     }
  384:     return undef;
  385: }
  386: 
  387: sub Process_Row {
  388:     my ($row) = @_;
  389:     my %RowData;
  390:     my ($student_id,$award,$tries,$time) = @$row;
  391:     return %RowData;
  392: }
  393: 
  394: #########################################################
  395: #########################################################
  396: ##
  397: ##   Generic Interface Routines
  398: ##
  399: #########################################################
  400: #########################################################
  401: sub CreateInterface {
  402:     ##
  403:     ## Environment variable initialization
  404:     if (! exists$env{'form.AnalyzeOver'}) {
  405:         $env{'form.AnalyzeOver'} = 'Tries';
  406:     }
  407:     ##
  408:     ## Build the menu
  409:     my $Str = '';
  410:     $Str .= '<p>';
  411:     $Str .= &Apache::loncommon::start_data_table();
  412:     $Str .= &Apache::loncommon::start_data_table_header_row();
  413:     $Str .= '<th>'.&mt('Sections').'</th>';
  414:     $Str .= '<th>'.&mt('Groups').'</th>';
  415:     $Str .= '<th>'.&mt('Access Status').'</th>';
  416:     $Str .= &Apache::loncommon::end_data_table_header_row();
  417:     ##
  418:     ## 
  419:     $Str .= &Apache::loncommon::start_data_table_row();
  420:     $Str .= '<td align="center">'."\n";
  421:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',4);
  422:     $Str .= '</td>';
  423:     #
  424:     $Str .= '<td align="center">'."\n";
  425:     $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',4);
  426:     $Str .= '</td>';
  427:     #
  428:     $Str .= '<td align="center">';
  429:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,4);
  430:     $Str .= '</td>';
  431:     #
  432:     $Str .= &Apache::loncommon::end_data_table_row();
  433:     $Str .= &Apache::loncommon::end_data_table();
  434:     #
  435:     $Str .= '</p>';
  436:     ##
  437:     return $Str;
  438: }
  439: 
  440: 1;
  441: 
  442: __END__

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