--- loncom/interface/statistics/lonproblemanalysis.pm 2004/02/20 16:38:49 1.73 +++ loncom/interface/statistics/lonproblemanalysis.pm 2004/03/16 16:30:31 1.80 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemanalysis.pm,v 1.73 2004/02/20 16:38:49 matthew Exp $ +# $Id: lonproblemanalysis.pm,v 1.80 2004/03/16 16:30:31 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -160,13 +160,8 @@ sub BuildProblemAnalysisPage { $ProblemData, \@Students); } elsif ($current_problem->{'resptype'} eq 'numerical') { -# if (exists($ENV{'form.ExcelOutput'})) { - &Apache::lonstudentsubmissions::prepare_excel_output - ($r,$current_problem,$ProblemData,\@Students); -# } else { -# &NumericalResponseAnalysis($r,$current_problem, -# $ProblemData,\@Students); -# } + &NumericalResponseAnalysis($r,$current_problem, + $ProblemData,\@Students); } else { $r->print('

This analysis is not supported

'); } @@ -192,9 +187,10 @@ sub BuildProblemAnalysisPage { ######################################################### sub NumericalResponseAnalysis { my ($r,$problem,$ProblemData,$Students) = @_; - $r->print('

This analysis is not yet supported

'); + my $c = $r->connection(); my ($resource,$respid) = ($problem->{'resource'}, $problem->{'respid'}); + $r->print('Response '.$respid.'
'); my $analysis_html; my $PerformanceData = &Apache::loncoursedata::get_response_data @@ -207,53 +203,127 @@ sub NumericalResponseAnalysis { $r->print($analysis_html); return; } + # + # This next call causes all the waiting around that people complain about my ($max,$min) = &GetStudentAnswers($r,$problem,$Students); - $r->print('Maximum = '.$max.' Minimum = '.$min); - my $max_students = 0; + return if ($c->aborted()); + # + # Collate the data my %Data; foreach my $student (@$Students) { my $answer = $student->{'answer'}; $Data{$answer}++; - if ($max_students < $Data{$answer}) { - $max_students = $Data{$answer}; - } - } - foreach (5,10,20,25,50,75,100,150,200,250,500,1000,1500,2000,2500,5000) { - if ($max_students < $_) { - $max_students = $_; - last; - } } my @Labels = sort {$a <=> $b } keys(%Data); - $r->print('number of labels = '.scalar(@Labels)); my @PlotData = @Data{@Labels}; - $r->print('number of PlotData = '.scalar(@PlotData)); - my $graph = - &Apache::loncommon::DrawXYGraph('Correct Answer Distribution', - 'Correct Answer', - 'Number of students', - $max_students, - undef, - \@Labels, - [\@PlotData], - (xskip=>10)); - $r->print($graph); + # + my $width = 500; + my $height = 100; + my $plot = &one_dimensional_plot($r,500,100,scalar(@$Students), + \@Labels,\@PlotData); + $r->print($plot); return; } +sub one_dimensional_plot { + my ($r,$width,$height,$N,$Labels,$Data)=@_; + # + # Compute data -> image scaling factors + my $min = $Labels->[0]; + my $max = $Labels->[-1]; + my $h_scale = ($width-10)/($max-$min); + # + my $max_y = 0; + foreach (@$Data) { + $max_y = $_ if ($max_y < $_); + } + my $ticscale = 5; + if ($max_y * $ticscale > $height/2) { + $ticscale = int($height/2/$max_y); + $ticscale = 1 if ($ticscale < 1); + } + # + # Create the plot + my $plot = + qq{}; + for (my $idx=0;$idx[$idx] - $min); + my $top = $height/2-$Data->[$idx]*$ticscale; + my $bottom = $height/2+$Data->[$idx]*$ticscale; + $plot .= + &line($xloc,$top,$xloc,$bottom,'888888',1); + } + # + # Put the scale on last to ensure it is on top of the data. + if ($min < 0 && $max > 0) { + my $circle_x = 5+$h_scale*abs($min); # '0' in data coordinates + my $r = 4; + $plot .= &line(5,$height/2,$circle_x-$r,$height/2,'000000',1); + $plot .= &circle($circle_x,$height/2,$r+1,'000000'); + $plot .= &line($circle_x+$r,$height/2,$width-5,$height/2,'000000',1); + } else { + $plot .= &line(5,$height/2,$width-5,$height/2,'000000',1); + } + $plot .= ''; + my $plotresult = &Apache::lonxml::xmlparse($r,'web',$plot); + + my $title = 'Distribution of correct answers'; + my $result = ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
'. + ''.$title.' (N='.$N.')'. + ''. + '
'.$min.''.$plotresult.''.$max.'
'. + 'Maximum Number of Coinciding Values: '.$max_y. + '
'; + return $result; +} + +## +## Helper subroutines for . +## These should probably go somewhere more suitable soon. +sub line { + my ($x1,$y1,$x2,$y2,$color,$thickness) = @_; + return qq{$/}; +} + +sub rectangle { + my ($x1,$y1,$x2,$y2,$color,$thickness,$filled) = @_; + return qq{}; +} + +sub arc { + my ($x,$y,$width,$height,$start,$end,$color,$thickness,$filled)=@_; + return qq{}; +} + +sub circle { + my ($x,$y,$radius,$color,$thickness,$filled)=@_; + return &arc($x,$y,$radius,$radius,0,360,$color,$thickness,$filled); +} + sub GetStudentAnswers { my ($r,$problem,$Students) = @_; + my $c = $r->connection(); my %Answers; my ($resource,$partid,$respid) = ($problem->{'resource'}, $problem->{'part'}, $problem->{'respid'}); + # Read in the cache (if it exists) before we start timing things. + &Apache::lonstathelpers::ensure_proper_cache($resource->{'symb'}); # Open progress window my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin ($r,'Student Answer Compilation Status', 'Student Answer Compilation Progress', scalar(@$Students)); - $r->print("\n"); $r->rflush(); foreach my $student (@$Students) { + last if ($c->aborted()); my $sname = $student->{'username'}; my $sdom = $student->{'domain'}; my $answer = &Apache::lonstathelpers::analyze_problem_as_student @@ -262,7 +332,8 @@ sub GetStudentAnswers { &mt('last student')); $student->{'answer'} = $answer; } - $r->print("
\n"); + &Apache::lonstathelpers::write_answer_cache(); + return if ($c->aborted()); $r->rflush(); # close progress window &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); @@ -541,7 +612,8 @@ sub OR_tries_analysis { my $mintries = 1; my $maxtries = $ENV{'form.NumPlots'}; my ($table,$Foils,$Concepts) = &build_foil_index($ORdata); - if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) { + if (! defined($Concepts) || + ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils'))) { $table = '

'. &mt('Not enough data for concept analysis. '. 'Performing Foil Analysis'). @@ -552,7 +624,7 @@ sub OR_tries_analysis { $mintries,$maxtries); my $analysis = ''; if ($ENV{'form.AnalyzeAs'} eq 'Foils') { - $analysis = &OR_Tries_Foil_Analysis($mintries,$maxtries,$Foils, + $analysis = &OR_Tries_Foil_Analysis($mintries,$maxtries,$Concepts, \%ResponseData,$ORdata); } else { $analysis = &OR_Tries_Concept_Analysis($mintries,$maxtries, @@ -563,82 +635,100 @@ sub OR_tries_analysis { } sub OR_Tries_Foil_Analysis { - my ($mintries,$maxtries,$Foils,$respdat,$ORdata) = @_; + my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_; my %ResponseData = %$respdat; # # Compute the data neccessary to make the plots my @PlotData; - foreach my $foilid (@$Foils) { - for (my $i=$mintries;$i<=$maxtries;$i++) { - if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) { - push(@{$PlotData[$i]->{'_correct'}},0); - } else { - push(@{$PlotData[$i]->{'_correct'}}, - 100*$ResponseData{$foilid}->[$i]->{'_correct'}/ - $ResponseData{$foilid}->[$i]->{'_total'}); - } - foreach my $option (@{$ORdata->{'_Options'}}) { - push(@{$PlotData[$i]->{'_total'}}, - $ResponseData{$foilid}->[$i]->{'_total'}); - if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) { - push (@{$PlotData[$i]->{$option}},0); + foreach my $concept (@$Concepts) { + foreach my $foilid (@{$concept->{'foils'}}) { + for (my $try=$mintries;$try<=$maxtries;$try++) { + if ($ResponseData{$foilid}->[$try]->{'_total'} == 0) { + push(@{$PlotData[$try]->{'_correct'}},0); } else { - if ($ResponseData{$foilid}->[$i]->{'_total'} == - $ResponseData{$foilid}->[$i]->{'_correct'}) { - push(@{$PlotData[$i]->{$option}},0); + push(@{$PlotData[$try]->{'_correct'}}, + 100*$ResponseData{$foilid}->[$try]->{'_correct'}/ + $ResponseData{$foilid}->[$try]->{'_total'}); + } + foreach my $option (@{$ORdata->{'_Options'}}) { + push(@{$PlotData[$try]->{'_total'}}, + $ResponseData{$foilid}->[$try]->{'_total'}); + if ($ResponseData{$foilid}->[$try]->{'_total'} == 0) { + push (@{$PlotData[$try]->{$option}},0); } else { - push (@{$PlotData[$i]->{$option}}, - 100 * $ResponseData{$foilid}->[$i]->{$option} / - ($ResponseData{$foilid}->[$i]->{'_total'} - - $ResponseData{$foilid}->[$i]->{'_correct'})); + if ($ResponseData{$foilid}->[$try]->{'_total'} == + $ResponseData{$foilid}->[$try]->{'_correct'}) { + push(@{$PlotData[$try]->{$option}},0); + } else { + push (@{$PlotData[$try]->{$option}}, + 100 * + $ResponseData{$foilid}->[$try]->{$option} / + ($ResponseData{$foilid}->[$try]->{'_total'} + - + $ResponseData{$foilid}->[$try]->{'_correct'} + )); + } } - } + } # End of foreach my $option } - } - } + } # End of foreach my $foilid + } # End of foreach my $concept # # Build a table for the plots my $analysis_html = "\n"; - my $foilkey = &build_option_index($ORdata); - for (my $i=$mintries;$i<=$maxtries;$i++) { - my $count = $ResponseData{'_total'}->[$i]; - if ($count == 0) { - $count = 'no submissions'; - } elsif ($count == 1) { - $count = '1 submission'; - } else { - $count = $count.' submissions'; - } - my $title = 'Attempt '.$i.', '.$count; + my $optionkey = &build_option_index($ORdata); + for (my $try=$mintries;$try<=$maxtries;$try++) { + my $count = $ResponseData{'_total'}->[$try]; + my $title = 'Submission '.$try.' (N='.$count.')'; my @Datasets; foreach my $option ('_correct',@{$ORdata->{'_Options'}}) { - next if (! exists($PlotData[$i]->{$option})); - push(@Datasets,$PlotData[$i]->{$option}); + next if (! exists($PlotData[$try]->{$option})); + push(@Datasets,$PlotData[$try]->{$option}); + } + # + # Put a blank in the data set between concepts + for (my $set =0;$set<=$#Datasets;$set++) { + my @Data = @{$Datasets[$set]}; + my $idx = 0; + foreach my $concept (@{$Concepts}) { + foreach my $foilid (@{$concept->{'foils'}}) { + $Datasets[$set]->[$idx++]=shift(@Data); + } + if ($concept->{'name'} ne $Concepts->[-1]->{'name'}) { + $Datasets[$set]->[$idx++] = 0; + } + } } + # + # Set up the labels needed for the bar graph + my @Labels; + my $idx = 1; + foreach my $concept (@{$Concepts}) { + foreach my $foilid (@{$concept->{'foils'}}) { + push(@Labels,$idx++); + } + push(@Labels,''); + } + # my $correctgraph = &Apache::loncommon::DrawBarGraph ($title,'Foil Number','Percent Correct', - 100,$plotcolors,undef,$Datasets[0]); + 100,$plotcolors,\@Labels,$Datasets[0]); $analysis_html.= ''; - ## - ## + + # + # next if (! defined($Datasets[0])); for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) { $Datasets[0]->[$i]=0; } - $count = $ResponseData{'_total'}->[$i]-$ResponseData{'_correct'}->[$i]; - if ($count == 0) { - $count = 'no submissions'; - } elsif ($count == 1) { - $count = '1 submission'; - } else { - $count = $count.' submissions'; - } - $title = 'Attempt '.$i.', '.$count; + $count = $ResponseData{'_total'}->[$try] - + $ResponseData{'_correct'}->[$try]; + $title = 'Submission '.$try.' (N='.$count.')'; my $incorrectgraph = &Apache::loncommon::DrawBarGraph ($title,'Foil Number','% Option Chosen Incorrectly', - 100,$plotcolors,undef,@Datasets); + 100,$plotcolors,\@Labels,@Datasets); $analysis_html.= ''; - $analysis_html.= '\n"; + $analysis_html.= '\n"; } $analysis_html .= "
'.$correctgraph.''.$incorrectgraph.''.$foilkey."
'.$optionkey."
\n"; return $analysis_html; @@ -1299,7 +1389,7 @@ sub CreateInterface { ## Build the menu my $Str = ''; $Str .= &Apache::lonhtmlcommon::breadcrumbs - (undef,&mt('Detailed Problem Analysis')); + (undef,'Detailed Problem Analysis'); $Str .= ''."\n"; $Str .= ''; $Str .= '';
'.&mt('Sections').'