--- loncom/interface/statistics/lonproblemanalysis.pm 2003/09/29 21:13:23 1.24 +++ loncom/interface/statistics/lonproblemanalysis.pm 2014/02/26 17:46:19 1.146 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemanalysis.pm,v 1.24 2003/09/29 21:13:23 matthew Exp $ +# $Id: lonproblemanalysis.pm,v 1.146 2014/02/26 17:46:19 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -24,202 +24,2051 @@ # # http://www.lon-capa.org/ # -# (Navigate problems for statistical reports -# YEAR=2002 -# 5/12,7/26,9/7,11/22 Behrouz Minaei -# -### - package Apache::lonproblemanalysis; use strict; -use Apache::lonnet(); +use Apache::lonnet; +use Apache::loncommon(); use Apache::lonhtmlcommon(); use Apache::loncoursedata(); +use Apache::lonquickgrades(); use Apache::lonstatistics; use Apache::lonlocal; +use Apache::lonstathelpers(); +use Apache::lonstudentsubmissions(); +use HTML::Entities(); +use Time::Local(); +use capa; +use lib '/home/httpd/lib/perl/'; +use LONCAPA; + + +my $plotcolors = ['#33ff00', + '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933', + '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66', + ]; + +my @SubmitButtons = ({ name => 'PrevProblemAnalysis', + text => 'Previous Problem' }, + { name => 'ProblemAnalysis', + text => 'Analyze Problem Again' }, + { name => 'NextProblemAnalysis', + text => 'Next Problem' }, + { name => 'break'}, + { name => 'SelectAnother', + text => 'Choose a different Problem' }); sub BuildProblemAnalysisPage { my ($r,$c)=@_; - $r->print('
'. - # Oh this is dumb! Need to rewrite relative links - # otherwise images (for example) will not show. - &Apache::lonnet::ssi_body($resource->{'src'}). - ' |
'.&Apache::lonstatistics::section_and_enrollment_description().'
'); + if ($env{'form.show_prob'} eq 'true') { + $r->print('\nProblem choice = $symb $id\n\n"); $r->print('
'.
+ &mt($no_data_message,$plot_num,@extra_data).
+ ' | |
'. + &mt($header_message,$plot_num,@extra_data). + ' | |
'. + &mt($stats_message, + $stats->{'submission_count'}, + $stats->{'correct_count'}, + $stats->{'incorrect_count'}, + $stats->{'students'}, + @extra_data). + ' | |
'. + &numerical_plot_percent($r,$responses,$stats).' | '. + ''. + &numerical_plot_differences($r,$responses,$stats).' | '. + '
'.$post_message.' |
'.&mt('Sections').' | '; - $Str .= ''.&mt('Enrollment Status').' | '; - $Str .= ''.&mt('Sequences and Folders').' | '; - $Str .= '
'."\n"; - $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); - $Str .= ' | '; - $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); - $Str .= ' | '; - my $only_seq_with_assessments = sub { - my $s=shift; - if ($s->{'num_assess'} < 1) { - return 0; - } else { - return 1; + my $total = $stats->{'submission_count'}; + return '' if ($total == 0); + my $max_bins = 21; + my $min_bin_size = $stats->{'min_abs'}; + my $low_bin = $stats->{'lowest_ans'}-$stats->{'max_bin_size'}; + my $high_bin = $stats->{'highest_ans'}+$stats->{'max_bin_size'}; + if ($high_bin > 0 && $low_bin > -$high_bin) { + $low_bin = -$high_bin; + } elsif ($low_bin < 0 && $high_bin < -$low_bin) { + $high_bin = -$low_bin; + } + if ($high_bin == $low_bin) { + $high_bin+=1; + $low_bin-=1; + } + if (!$min_bin_size || + ($high_bin -$low_bin)/$min_bin_size * 2 > $max_bins) { + $min_bin_size = abs($high_bin - $low_bin) / $max_bins * 2; + } + my @bins; + for (my $num = $low_bin;$num <= $high_bin;$num+=($min_bin_size/2)) { + push(@bins,$num); + } + # + my @correct; + my @incorrect; + my @count; + while (my ($ans,$submissions) = each(%$responses)) { + while (my ($submission,$counts) = each(%$submissions)) { + my ($correct_count,$incorrect_count) = @$counts; + my $scaled_value = $submission-$ans; + if ($scaled_value < $bins[0]) { + $bins[0]=$scaled_value-1; + } + my $bin=0; + for ($bin=0;$bin<$#bins;$bin++) { + last if ($bins[$bin]>$scaled_value); + } + $correct[$bin-1]+=$correct_count; + $incorrect[$bin-1]+=$incorrect_count; + $count[$bin-1]+=$correct_count+$incorrect_count; } - }; - $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, - $only_seq_with_assessments); - $Str .= ' |
'. - ''. - ' | '.
- ''.$res->{'title'}.' ';
- if ($partdata->{'option'} > 1) {
- $seq_str .= &mt('response').' '.$respid;
+ }
+ my @plot_correct = @correct;
+ my @plot_incorrect = @incorrect;
+ my $max;
+ for (my $i=0;$i<=$#bins;$i++) {
+ $plot_correct[$i] *= 100/$total;
+ $plot_incorrect[$i] *= 100/$total;
+ if (! defined($max) ||
+ $max < $plot_correct[$i]+$plot_incorrect[$i] ) {
+ $max = $plot_correct[$i]+$plot_incorrect[$i];
+ }
+ }
+ foreach (qw/1 5 10 15 20 25 30 40 50 75 100/) {
+ if ($max <$_) { $max = $_; last; }
+ }
+ #
+ my %lt = &Apache::lonlocal::texthash(
+ 'title' => 'Difference between submission and correct',
+ 'xlabel' => 'Difference from Correct',
+ 'ylabel' => 'Percent of Answers');
+ my @labels = (1..scalar(@bins)-1);
+ my $graph = &Apache::loncommon::DrawBarGraph
+ ($lt{'title'},$lt{'xlabel'},$lt{'ylabel'},
+ $max,['#33FF00','#FF3300'],\@labels,\@plot_correct,\@plot_incorrect,
+ {xskip=>1});
+ #
+ my $table = $graph.$/.
+ &numerical_bin_table(\@bins,\@labels,\@incorrect,\@correct,\@count).$/;
+ return $table;
+}
+
+sub numerical_classify_responses {
+ my ($full_row_data,$correct,$function) = @_;
+ my %submission_data;
+ my %students;
+ my %stats;
+ my $max=0;
+ foreach my $row (@$full_row_data) {
+ my %subm = &hashify_attempt($row);
+ if (ref($correct) eq 'HASH') {
+ my $s_correct = $correct->{$subm{'student'}};
+ $subm{'correct'} = $s_correct->{'answer'};
+ foreach my $item ('unit','ans_low','ans_high') {
+ $subm{$item} = $s_correct->{$item};
+ }
+ } else { # This probably never happens....
+ $subm{'correct'} = $correct->{'answer'};
+ $subm{'unit'} = $correct->{'unit'};
+ }
+ #
+ my $abs_low =abs($subm{'correct'}-$subm{'ans_low'});
+ my $abs_high=abs($subm{'correct'}-$subm{'ans_high'});
+ if (! defined($stats{'min_abs'}) ||
+ $stats{'min_abs'} > $abs_low) {
+ $stats{'min_abs'} = $abs_low;
+ }
+ if ($stats{'min_abs'} > $abs_high) {
+ $stats{'min_abs'} = $abs_high;
+ }
+ if (! defined($stats{'max_abs'}) ||
+ $stats{'max_abs'} < $abs_low) {
+ $stats{'max_abs'} = $abs_low;
+ }
+ if ($stats{'max_abs'} < $abs_high) {
+ $stats{'max_abs'} = $abs_high;
+ }
+ my $low_percent;
+ my $high_percent;
+ if (defined($subm{'correct'}) && $subm{'correct'} != 0) {
+ $low_percent = 100 * abs($abs_low / $subm{'correct'});
+ $high_percent = 100 * abs($abs_high / $subm{'correct'});
+ }
+ if (! defined($stats{'min_percent'}) ||
+ $stats{'min_percent'} > $low_percent) {
+ $stats{'min_percent'} = $low_percent;
+ }
+ if ($stats{'min_percent'} > $high_percent) {
+ $stats{'min_percent'} = $high_percent;
+ }
+ if (! defined($stats{'max_percent'}) ||
+ $stats{'max_percent'} < $low_percent) {
+ $stats{'max_percent'} = $low_percent;
+ }
+ if ($stats{'max_percent'} < $high_percent) {
+ $stats{'max_percent'} = $high_percent;
+ }
+ if (! defined($stats{'lowest_ans'}) ||
+ $stats{'lowest_ans'} > $subm{'correct'}) {
+ $stats{'lowest_ans'} = $subm{'correct'};
+ }
+ if (! defined($stats{'highest_ans'}) ||
+ $stats{'highest_ans'} < $subm{'correct'}) {
+ $stats{'highest_ans'} = $subm{'correct'};
+ }
+ #
+ $subm{'submission'} =~ s/=\d+\s*$//;
+ if (&$function(\%subm)) {
+ my $scaled = '1';
+ my ($sname,$sdom) = split(':',$subm{'student'});
+ my ($myunit,$mysub) = ($subm{'unit'},$subm{'submission'});
+ my $result =
+ &capa::caparesponse_get_real_response($myunit,
+ $mysub,
+ \$scaled);
+# &Apache::lonnet::logthis('scaled = '.$scaled.' result ='.$result);
+ next if (! defined($scaled));
+# next if ($result ne '6');
+ my $submission = $scaled;
+ $students{$subm{'student'}}++;
+ $stats{'submission_count'}++;
+ if (&numerical_submission_is_correct($subm{'award'})) {
+ $stats{'correct_count'}++;
+ $submission_data{$subm{'correct'}}->{$submission}->[0]++;
+ } elsif (&numerical_submission_is_incorrect($subm{'award'})) {
+ $stats{'incorrect_count'}++;
+ $submission_data{$subm{'correct'}}->{$submission}->[1]++;
+ }
+ }
+ }
+ $stats{'correct_count'} |= 0;
+ $stats{'incorrect_count'} |= 0;
+ $stats{'students'}=scalar(keys(%students));
+ return (\%submission_data,\%stats);
+}
+
+sub numerical_submission_is_correct {
+ my ($award) = @_;
+ if ($award =~ /^(APPROX_ANS|EXACT_ANS)$/) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub numerical_submission_is_incorrect {
+ my ($award) = @_;
+ if ($award =~ /^(INCORRECT)$/) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub numerical_bin_table {
+ my ($bins,$labels,$incorrect,$correct,$count)=@_;
+ my $table =
+ '
'
+ .&mt('There is no submission data for this resource.')
+ .' ';
+ $r->print($analysis_html);
+ return;
+ }
+ #
+ $analysis_html.='
'
+ .&mt('There is no student data for this problem.')
+ .' '
+ );
+ } else {
+ $r->rflush();
+ if ($env{'form.AnalyzeOver'} eq 'tries') {
+ my $analysis_html = &OR_tries_analysis($r,
+ $PerformanceData,
+ $problem_data);
+ $r->print($analysis_html);
+ $r->rflush();
+ } elsif ($env{'form.AnalyzeOver'} eq 'time') {
+ my $analysis_html = &OR_time_analysis($PerformanceData,
+ $problem_data);
+ $r->print($analysis_html);
+ $r->rflush();
+ } else {
+ $r->print(''
+ .&mt('The analysis you have selected is not supported at this time.')
+ .' '
+ );
+ }
+ }
+}
+
+#########################################################
+#
+# Option Response: tries Analysis
+#
+#########################################################
+sub OR_tries_analysis {
+ my ($r,$PerformanceData,$ORdata) = @_;
+ my $mintries = 1;
+ my $maxtries = $env{'form.NumPlots'};
+ my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
+ if (! defined($Concepts)) {
+ $Concepts = [];
+ }
+ my %response_data = &OR_analyze_by_tries($r,$PerformanceData,
+ $mintries,$maxtries);
+ my $analysis = '';
+ #
+ # Compute the data necessary to make the plots
+ my @foil_plot;
+ my @concept_data;
+ for (my $j=0;$j<=scalar(@$Concepts);$j++) {
+ my $concept = $Concepts->[$j];
+ foreach my $foilid (@{$concept->{'foils'}}) {
+ for (my $try=$mintries;$try<=$maxtries;$try++) {
+ # concept analysis data
+ $concept_data[$j]->[$try]->{'_correct'} +=
+ $response_data{$foilid}->[$try]->{'_correct'};
+ $concept_data[$j]->[$try]->{'_total'} +=
+ $response_data{$foilid}->[$try]->{'_total'};
+ #
+ # foil analysis data
+ if ($response_data{$foilid}->[$try]->{'_total'} == 0) {
+ push(@{$foil_plot[$try]->{'_correct'}},0);
+ } else {
+ push(@{$foil_plot[$try]->{'_correct'}},
+ 100*$response_data{$foilid}->[$try]->{'_correct'}/
+ $response_data{$foilid}->[$try]->{'_total'});
+ }
+ foreach my $option (@{$ORdata->{'_Options'}}) {
+ push(@{$foil_plot[$try]->{'_total'}},
+ $response_data{$foilid}->[$try]->{'_total'});
+ if ($response_data{$foilid}->[$try]->{'_total'} == 0) {
+ push (@{$foil_plot[$try]->{$option}},0);
+ } else {
+ if ($response_data{$foilid}->[$try]->{'_total'} ==
+ $response_data{$foilid}->[$try]->{'_correct'}) {
+ push(@{$foil_plot[$try]->{$option}},0);
+ } else {
+ push (@{$foil_plot[$try]->{$option}},
+ 100 *
+ $response_data{$foilid}->[$try]->{$option} /
+ ($response_data{$foilid}->[$try]->{'_total'}
+ -
+ $response_data{$foilid}->[$try]->{'_correct'}
+ ));
}
- $seq_str .= " |
'
+ .' '
+ .&mt('None of the selected students attempted the problem more than [quant,_1,time].'
+ ,$try-1)
+ .' '
+ .' | ||||
'
+ .' '
+ .&mt('None of the selected students have attempted the problem.')
+ .' '
+ .' | ||||
  | '.$seq->{'title'}.' | '. - "|||
'. + ''. + &mt('Attempt [_1], [_2] submissions, [_3] correct, [_4] incorrect', + $try,$data_count,$correct,$data_count-$correct). + ''.' | ||||
'.$concept_graph.' | '. + ''.$correct_graph.' | '. + ''.$incorrect_graph.' | '. + ''.$optionkey.' | '. + ' |
'. + &mt('Not enough data for concept analysis.'. + ' Performing Foil Analysis instead.'). + '
'. + $table; + } + # + my $num_plots = $env{'form.NumPlots'}; + my $num_data = scalar(@$performance_data)-1; + # + my $current_index; + $table .= "'. + &mt('Data from [_1] to [_2]', + &Apache::lonlocal::locallocaltime($starttime), + &Apache::lonlocal::locallocaltime($endtime)). + ' | |||
'. + &mt('[quant,_1,submission,submissions,No submissions] from [quant,_2,student], [_3] correct, [_4] incorrect', + $data_count,$student_count,$correct,$data_count-$correct). + ' | |||
'.$concept_correct_plot.' | '. + ''.$foil_correct_plot.' | '. + ''.$foil_incorrect_plot.' | '. + ''.$foilkey.' |
'.
+ &mt('Start time: [_1]',$startdateform).' '. + &mt('End time: [_1]',$enddateform).' | |||
'. + ''.('*'x3).''.' | '. + ''.&HTML::Entities::encode($option,'<>&"').' | '. + "
'. + ''.('*'x4).' | '. + ''.&HTML::Entities::encode($foil,'<>&"'). + (' 'x2).$extra_data->{$foil}.' | '. + "
'; + $Str .= &Apache::loncommon::start_data_table(); + $Str .= &Apache::loncommon::start_data_table_header_row(); + $Str .= '