--- loncom/interface/statistics/lonproblemanalysis.pm 2005/02/22 05:28:21 1.113 +++ loncom/interface/statistics/lonproblemanalysis.pm 2012/05/03 11:21:33 1.142 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemanalysis.pm,v 1.113 2005/02/22 05:28:21 matthew Exp $ +# $Id: lonproblemanalysis.pm,v 1.142 2012/05/03 11:21:33 goltermann Exp $ # # Copyright Michigan State University Board of Trustees # @@ -27,10 +27,11 @@ 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(); @@ -38,6 +39,9 @@ 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', @@ -59,6 +63,7 @@ sub BuildProblemAnalysisPage { # my %Saveable_Parameters = ('Status' => 'scalar', 'Section' => 'array', + 'Groups' => 'array', 'NumPlots' => 'scalar', 'AnalyzeOver' => 'scalar', ); @@ -69,12 +74,18 @@ sub BuildProblemAnalysisPage { # &Apache::lonstatistics::PrepareClasslist(); # + $r->print(&Apache::lonhtmlcommon::breadcrumbs('Detailed Problem Analysis')); + &Apache::lonquickgrades::startGradeScreen($r,'statistics'); + $r->print(&CreateInterface()); # my @Students = @Apache::lonstatistics::Students; # - if (@Students < 1 && exists($ENV{'form.firstrun'})) { - $r->print('
'.&Apache::lonstatistics::section_and_enrollment_description().'
'); + if ($env{'form.show_prob'} eq 'true') { + $r->print(''.
+ ' '.
&mt($no_data_message,$plot_num,@extra_data).
- ' | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'. @@ -303,21 +334,34 @@ sub numerical_plot_percent { # my $total = $stats->{'submission_count'}; return '' if ($total == 0); - my $min_bin_size = $stats->{'min_percent'}; - 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; + my $max_bins = 50; + my $lowest_percent = $stats->{'min_percent'}; + my $highest_percent = $stats->{'max_percent'}; + my $percent_spread = $highest_percent - $lowest_percent; + foreach (qw/20 30 40 50 100 200/) { + if ($percent_spread < $_) { + $highest_percent =$_/2; + last; + } } - if (($high_bin -$low_bin)/$min_bin_size > 1000) { - $min_bin_size = abs($high_bin - $low_bin) / 1000; + $percent_spread = $highest_percent - $lowest_percent; + my $bin_size = 1; + foreach (qw/0.01 0.05 0.1 0.5 1 2 5 10 20 25 50 100/) { + if ($lowest_percent/2 < $_){ + $bin_size = $_; + if ( ($percent_spread/$bin_size) < $max_bins ) { + last; + } + } } my @bins; - for (my $num = $low_bin;$num <= $high_bin;$num+=($min_bin_size/2)) { - push(@bins,$num); + for (my $bin = -$highest_percent;$bin<0;$bin+=$bin_size) { + push (@bins,$bin); } + for (my $bin = 0; $bin<$highest_percent;$bin+=$bin_size) { + push (@bins,$bin); + } + push(@bins,$highest_percent); # my @correct; my @incorrect; @@ -325,46 +369,42 @@ sub numerical_plot_percent { while (my ($ans,$submissions) = each(%$responses)) { while (my ($submission,$counts) = each(%$submissions)) { my ($correct_count,$incorrect_count) = @$counts; - my $scaled_value = ($submission-$ans)/$ans; - my $bin=0; - for ($bin=0;$bin<$#bins;$bin++) { # not <= for a reason + my $scaled_value = + ($ans) ? 100*($submission-$ans)/abs($ans) + : 0; + if ($scaled_value < $bins[0]) { + $bins[0]=$scaled_value -1; + } + my $bin; + for ($bin=0;$bin<$#bins;$bin++) { last if ($bins[$bin]>$scaled_value); } - $correct[$bin]+=$correct_count; - $incorrect[$bin]+=$incorrect_count; - $count[$bin]+=$correct_count+$incorrect_count; + $correct[$bin-1]+=$correct_count; + $incorrect[$bin-1]+=$incorrect_count; + $count[$bin-1]+=$correct_count+$incorrect_count; } } # - # Skip empty bins - my (@plot_correct,@plot_incorrect,@new_bins,@new_count); - my $min_skip = 2; - for (my $i=0;$i<=$#bins;$i++) { - my $sum=0; - for (my $j=-$min_skip;$j<=$min_skip && $i+$j<=$#bins;$j++) { - $sum += $correct[$i+$j] + $incorrect[$i+$j]; - } - if ($sum) { - push(@new_bins,$bins[$i]); - push(@plot_correct,$correct[$i]); - push(@plot_incorrect,$incorrect[$i]); - push(@new_count,$correct[$i]+$incorrect[$i]); - } - } - @correct = @plot_correct; - @incorrect = @plot_incorrect; - @count = @new_count; - @bins = @new_bins; - for (my $i=0;$i<=$#bins;$i++) { + 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 $title = &mt('Percent Difference'); - my @labels = (1..scalar(@bins)); + my @labels = (1..scalar(@bins)-1); my $graph = &Apache::loncommon::DrawBarGraph - ($title,'Percent difference from correct','Number of answers', - 100,['#33FF00','#FF3300'],\@labels,\@plot_correct,\@plot_incorrect, + ($title,'Percent Difference from Correct','Percent of Answers', + $max,['#33FF00','#FF3300'],\@labels,\@plot_correct,\@plot_incorrect, {xskip=>1}); # my $table = $graph.$/. @@ -377,7 +417,7 @@ sub numerical_plot_differences { # my $total = $stats->{'submission_count'}; return '' if ($total == 0); - my $max_bins = 50; + 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'}; @@ -386,7 +426,12 @@ sub numerical_plot_differences { } elsif ($low_bin < 0 && $high_bin < -$low_bin) { $high_bin = -$low_bin; } - if (($high_bin -$low_bin)/$min_bin_size * 2 > $max_bins) { + 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; @@ -401,27 +446,38 @@ sub numerical_plot_differences { 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++) { # not <= for a reason + for ($bin=0;$bin<$#bins;$bin++) { last if ($bins[$bin]>$scaled_value); } - $correct[$bin]+=$correct_count; - $incorrect[$bin]+=$incorrect_count; - $count[$bin]+=$correct_count+$incorrect_count; + $correct[$bin-1]+=$correct_count; + $incorrect[$bin-1]+=$incorrect_count; + $count[$bin-1]+=$correct_count+$incorrect_count; } } 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 $title = &mt('Difference between submission and correct'); - my @labels = (1..scalar(@bins)); + my @labels = (1..scalar(@bins)-1); my $graph = &Apache::loncommon::DrawBarGraph - ($title,'Difference from Correct','Number of answers', - 100,['#33FF00','#FF3300'],\@labels,\@plot_correct,\@plot_incorrect, + ($title,'Difference from Correct','Percent of Answers', + $max,['#33FF00','#FF3300'],\@labels,\@plot_correct,\@plot_incorrect, {xskip=>1}); # my $table = $graph.$/. @@ -464,8 +520,12 @@ sub numerical_classify_responses { if ($stats{'max_abs'} < $abs_high) { $stats{'max_abs'} = $abs_high; } - my $low_percent = 100 * abs($abs_low / $subm{'correct'}); - my $high_percent = 100 * abs($abs_high / $subm{'correct'}); + 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; @@ -498,7 +558,7 @@ sub numerical_classify_responses { &capa::caparesponse_get_real_response($myunit, $mysub, \$scaled); - &Apache::lonnet::logthis('scaled = '.$scaled.' result ='.$result); +# &Apache::lonnet::logthis('scaled = '.$scaled.' result ='.$result); next if (! defined($scaled)); # next if ($result ne '6'); my $submission = $scaled; @@ -513,6 +573,8 @@ sub numerical_classify_responses { } } } + $stats{'correct_count'} |= 0; + $stats{'incorrect_count'} |= 0; $stats{'students'}=scalar(keys(%students)); return (\%submission_data,\%stats); } @@ -546,7 +608,9 @@ sub numerical_bin_table { ' | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'.$labels->[$i].' | '. @@ -566,10 +630,7 @@ sub numerical_determine_answers { my ($r,$resource,$partid,$respid,$students)=@_; my $c = $r->connection(); # - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin - ($r,'Student Answer Compilation Status', - 'Student Answer Compilation Progress', scalar(@$students), - 'inline',undef,'Statistics','stats_status'); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,scalar(@$students)); # # Read in the cache (if it exists) before we start timing things. &Apache::lonstathelpers::ensure_proper_cache($resource->{'symb'}); @@ -587,13 +648,23 @@ sub numerical_determine_answers { $sdom); # make the key my $key = $partid.'.'.$respid; + # pick one of the possible answers + my $which = 'INTERNAL'; + if (!exists($analysis->{$key}{$which})) { + $which = (sort(keys(%{ $analysis->{$key} })))[0]; + } foreach my $item ('answer','unit','ans_high','ans_low') { - $correct->{$sname.':'.$sdom}->{$item} = - $analysis->{$key.'.'.$item}->[0]; + if (ref($analysis->{$key.'.'.$item}) eq 'ARRAY') { + $correct->{$sname.':'.$sdom}->{$item} = + $analysis->{$key.'.'.$item}[0]; + } else { + $correct->{$sname.':'.$sdom}->{$item} = + $analysis->{$key.'.'.$item}{$which}[0][0]; + } } - $answers{$analysis->{$key.'.answer'}->[0]}++; + $answers{$correct->{$sname.':'.$sdom}{'answer'}}++; &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - &mt('last student')); + 'last student'); } &Apache::lonstathelpers::write_analysis_cache(); &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); @@ -717,7 +788,7 @@ sub circle { sub radio_response_analysis { my ($r,$problem,$problem_analysis,$students) = @_; # - if ($ENV{'form.AnalyzeOver'} !~ /^(tries|time)$/) { + if ($env{'form.AnalyzeOver'} !~ /^(tries|time)$/) { $r->print('Bad request'); } # @@ -746,9 +817,10 @@ sub radio_response_analysis { $analysis_html .= $table; # Gather student data my $response_data = &Apache::loncoursedata::get_response_data - (\@Apache::lonstatistics::SelectedSections, + ([&Apache::lonstatistics::get_selected_sections()], + [&Apache::lonstatistics::get_selected_groups()], $Apache::lonstatistics::enrollment_status, - $resource->{'symb'},$respid); + $resource->symb,$respid); my $correct; # either a hash reference or a scalar if ($problem_analysis->{'answercomputed'} || scalar(@$concepts) > 1) { # This takes a while for large classes... @@ -759,7 +831,7 @@ sub radio_response_analysis { my ($idx,@remainder) = split('&',$student->{'answer'}); my ($answer) = ($remainder[$idx]=~/^(.*)=([^=]*)$/); $correct->{$student->{'username'}.':'.$student->{'domain'}}= - &Apache::lonnet::unescape($answer); + &unescape($answer); } } else { foreach my $foil (keys(%$foildata)) { @@ -770,21 +842,21 @@ sub radio_response_analysis { } # if (! defined($response_data) || ref($response_data) ne 'ARRAY' ) { - $analysis_html = '
'. ''. &mt($pre_graph_text, $plot_num,$foil_choice_data->{'_count'}, $correct, - $foil_choice_data->{'_count'}-$correct, + $incorrect, $foil_choice_data->{'_students'}, @extra_data). ' | |||||||||||||||
'.
+ ' '.
&mt($no_data_text,
$plot_num,$foil_choice_data->{'_count'},
$correct,
$foil_choice_data->{'_count'}-$correct,
- @extra_data);
+ @extra_data).
+ ' ';
if (defined($post_graph_text)) {
$analysis_html.=''.$post_graph_text; } @@ -916,7 +990,7 @@ sub ensure_start_end_times { (localtime($last - $sec_in_day*($plot_num-1)))[3..5]; $start = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear); $end = $start + $sec_in_day; - if ($plot_num == $ENV{'form.NumPlots'}) { + if ($plot_num == $env{'form.NumPlots'}) { $start = $first; } } @@ -1153,32 +1227,34 @@ sub OptionResponseAnalysis { $problem->{'respid'}); # Note: part data is not needed. my $PerformanceData = &Apache::loncoursedata::get_response_data - (\@Apache::lonstatistics::SelectedSections, + ([&Apache::lonstatistics::get_selected_sections()], + [&Apache::lonstatistics::get_selected_groups()], $Apache::lonstatistics::enrollment_status, - $resource->{'symb'},$respid); + $resource->symb,$respid); if (! defined($PerformanceData) || ref($PerformanceData) ne 'ARRAY' ) { - $r->print(' '. - &mt('There is no student data for this problem.'). - ''); + $r->print(''
+ .&mt('There is no student data for this problem.')
+ .' '
+ );
} else {
$r->rflush();
- if ($ENV{'form.AnalyzeOver'} eq 'tries') {
+ 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') {
+ } 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'). - ''); + $r->print('div class="LC_warning"' + .&mt('The analysis you have selected is not supported at this time.') + .'' + ); } } } @@ -1191,7 +1267,7 @@ sub OptionResponseAnalysis { sub OR_tries_analysis { my ($r,$PerformanceData,$ORdata) = @_; my $mintries = 1; - my $maxtries = $ENV{'form.NumPlots'}; + my $maxtries = $env{'form.NumPlots'}; my ($table,$Foils,$Concepts) = &build_foil_index($ORdata); if (! defined($Concepts)) { $Concepts = []; @@ -1255,12 +1331,18 @@ sub OR_tries_analysis { if (! defined($response_data{'_total'}->[$try]) || $response_data{'_total'}->[$try] == 0) { if ($try > 1) { - $analysis_html.= ' | |||||||||||||||
'. - &mt('None of the selected students attempted the problem more than [_1] times.',$try-1). - ' | |||||||||||||||
'
+ .' '
+ .&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').' | |||||||||||||||
'
+ .' '
+ .&mt('None of the selected students have attempted the problem.')
+ .' '
+ .' | |||||||||||||||
'.
&mt('[_1] submissions from [_2] students, [_3] correct, [_4] incorrect',
@@ -1475,7 +1559,7 @@ sub OR_time_analysis {
$table .= ''.
&mt('Start time: [_1]',$startdateform).' | '. &mt('End time: [_1]',$enddateform).'   | | |
'.&mt('Concept Number').' | '. ''.&mt('Concept').' | '. ''.&mt('Foil Number').' | '. ''.&mt('Foil Name').' | '. ''.&mt('Foil Text').' | '. ''.&mt('Correct Value').' | '. - "
---|---|---|---|---|---|
'.&mt('Foil Number').' | '. ''.&mt('Foil Name').' | '. ''.&mt('Foil Text').' | '. ''.&mt('Correct Value').' | '. - "||
'.$conceptindex.' | '. ''.&HTML::Entities::encode($concept->{'name'},'<>&"').' | '. ''.$foilindex++.' | '. ''.&HTML::Entities::encode($Foildata{$firstfoil}->{'name'},'<>&"').' | '. ''.$Foildata{$firstfoil}->{'text'}.' | '. ''.&HTML::Entities::encode($Foildata{$firstfoil}->{'value'},'<>&"').' | '. - "
'.$foilindex++.' | '. ''.&HTML::Entities::encode($Foildata{$firstfoil}->{'name'},'<>&"').' | '. ''.$Foildata{$firstfoil}->{'text'}.' | '. ''.&HTML::Entities::encode($Foildata{$firstfoil}->{'value'},'<>&"').' | '. - "||
'. ' | '. ' | '.$foilindex.' | '. ''.&HTML::Entities::encode($Foildata{$foilid}->{'name'},'<>&"').' | '. ''.$Foildata{$foilid}->{'text'}.' | '. ''.&HTML::Entities::encode($Foildata{$foilid}->{'value'},'<>&"').' | '. - "
'.$foilindex.' | '. ''.&HTML::Entities::encode($Foildata{$foilid}->{'name'},'<>&"').' | '. ''.$Foildata{$foilid}->{'text'}.' | '. ''.&HTML::Entities::encode($Foildata{$foilid}->{'value'},'<>&"').' | '. - "
'.&mt('Sections').' | '; - $Str .= ''.&mt('Enrollment Status').' | '; -# $Str .= ''.&mt('Sequences and Folders').' | '; - $Str .= ''; - $Str .= ' | '.&mt('Sections').' | '; + $Str .= ''.&mt('Groups').' | '; + $Str .= ''.&mt('Access Status').' | '; + $Str .= ''.&mt('Options').' | '; + $Str .= &Apache::loncommon::end_data_table_header_row(); ## ## - $Str .= '
---|---|---|---|---|---|---|
'."\n"; + $Str .= &Apache::loncommon::start_data_table_row(); + $Str .= ' | '."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= ' | '; # + $Str .= ''."\n"; + $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5); + $Str .= ' | '; + # $Str .= ''; $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); $Str .= ' | '; # -# $Str .= ''; - my $only_seq_with_assessments = sub { - my $s=shift; - if ($s->{'num_assess'} < 1) { - return 0; - } else { - return 1; - } - }; - &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, - $only_seq_with_assessments); ## ## - $Str .= ' | '; + $Str .= ' | ';
##
my $showprob_checkbox =
- '';
- $Str.= ' '; + $Str.= ' '; ## my $analyze_selector = ''; - $Str .= ' '.$/; + ' '.$/; ## my $numplots_selector = ' '; - $Str .= ' '; ## - $Str .= ' | ';
##
##
- $Str .= '