');
- $r->print(&OptionResponseProblemSelector());
+ $r->print(&Apache::lonstathelpers::ProblemSelector
+ ($problem_types));
}
}
@@ -136,275 +181,1215 @@ sub BuildProblemAnalysisPage {
#########################################################
#########################################################
##
-## Misc interface routines use by analysis code
+## Numerical Response Routines
##
#########################################################
#########################################################
-sub build_foil_index {
- my ($ORdata) = @_;
- my @Foils = sort(keys(%{$ORdata->{'Foiltext'}}));
- my %Row_Label;
- foreach my $foilid (@Foils) {
- my $value = $ORdata->{'Foiltext'}->{$foilid};
- $Row_Label{$foilid} = $ORdata->{'Foiltext'}->{$foilid};
+sub NumericalResponseAnalysis {
+ my ($r,$problem,$ProblemData,$Students) = @_;
+ 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
+ ($Students,$resource->{'symb'},$respid);
+ if (! defined($PerformanceData) ||
+ ref($PerformanceData) ne 'ARRAY' ) {
+ $analysis_html = '
'.
+ &mt('There is no submission data for this resource').
+ '
';
+ $r->print($analysis_html);
+ return;
}
#
- # Build up the table of row labels.
- my $table = '
'."\n";
- $table .= '
'.&mt('Foil Number').'
'.
- '
'.&mt('Foil Text')."
\n";
- my $index = 1;
- foreach my $foilid (@Foils) {
- $table .= '
'.$index.'
'.
- '
'.$Row_Label{$foilid}."
\n";
- } continue {
- $index++;
+ # This next call causes all the waiting around that people complain about
+ my ($max,$min) = &GetStudentAnswers($r,$problem,$Students);
+ return if ($c->aborted());
+ #
+ # Collate the data
+ my %Data;
+ foreach my $student (@$Students) {
+ my $answer = $student->{'answer'};
+ $Data{$answer}++;
}
- $table .= "
\n";
- return ($table,@Foils);
+ my @Labels = sort {$a <=> $b } keys(%Data);
+ my @PlotData = @Data{@Labels};
+ #
+ 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->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
+ ($resource,$sname,$sdom,$partid,$respid);
+ &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+ &mt('last student'));
+ $student->{'answer'} = $answer;
+ }
+ &Apache::lonstathelpers::write_answer_cache();
+ return if ($c->aborted());
+ $r->rflush();
+ # close progress window
+ &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+ return;
+}
+
+sub build_student_data_worksheet {
+ my ($workbook,$format) = @_;
+ my $rows_output = 3;
+ my $cols_output = 0;
+ my $worksheet = $workbook->addworksheet('Student Data');
+ $worksheet->write($rows_output++,0,'Student Data',$format->{'h3'});
+ my @Headers = ('full name','username','domain','section',
+ "student\nnumber",'identifier');
+ $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
+ my @Students = @Apache::lonstatistics::Students;
+ my $studentrows = &Apache::loncoursedata::get_student_data(\@Students);
+ my %ids;
+ foreach my $row (@$studentrows) {
+ my ($mysqlid,$student) = @$row;
+ $ids{$student}=$mysqlid;
+ }
+ foreach my $student (@Students) {
+ my $name_domain = $student->{'username'}.':'.$student->{'domain'};
+ $worksheet->write_row($rows_output++,0,
+ [$student->{'fullname'},
+ $student->{'username'},$student->{'domain'},
+ $student->{'section'},$student->{'id'},
+ $ids{$name_domain}]);
+ }
+ return $worksheet;
}
#########################################################
#########################################################
##
-## Tries Analysis
+## Radio Response Routines
##
#########################################################
#########################################################
-sub tries_analysis {
- my ($PerformanceData,$ORdata) = @_;
+sub RadioResponseAnalysis {
+ my ($r,$problem,$ProblemData,$Students) = @_;
+ my ($resource,$respid) = ($problem->{'resource'},
+ $problem->{'respid'});
+ my $analysis_html;
+ my $PerformanceData =
+ &Apache::loncoursedata::get_response_data
+ ($Students,$resource->{'symb'},$respid);
+ if (! defined($PerformanceData) ||
+ ref($PerformanceData) ne 'ARRAY' ) {
+ $analysis_html = '
'.
+ &mt('There is no submission data for this resource').
+ '
'.
+ &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 %ResponseData = &analyze_option_data_by_tries($PerformanceData,
- $mintries,$maxtries);
- my ($table,@Foils) = &build_foil_index($ORdata);
+ my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
+ if (! defined($Concepts) ||
+ ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils'))) {
+ $table = '
'.
+ &mt('Not enough data for concept analysis. '.
+ 'Performing Foil Analysis').
+ '
'.$table;
+ $ENV{'form.AnalyzeAs'} = 'Foils';
+ }
+ my %ResponseData = &OR_analyze_by_tries($r,$PerformanceData,
+ $mintries,$maxtries);
+ my $analysis = '';
+ if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
+ $analysis = &OR_Tries_Foil_Analysis($mintries,$maxtries,$Concepts,
+ \%ResponseData,$ORdata);
+ } else {
+ $analysis = &OR_Tries_Concept_Analysis($mintries,$maxtries,
+ $Concepts,\%ResponseData,$ORdata);
+ }
+ $table .= $analysis;
+ return $table;
+}
+
+sub OR_Tries_Foil_Analysis {
+ my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
+ my %ResponseData = %$respdat;
+ #
+ # Compute the data neccessary to make the plots
+ my @PlotData;
+ 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 {
+ 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 {
+ 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 $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[$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,\@Labels,$Datasets[0]);
+ $analysis_html.= '
\n";
+ return $analysis_html;
+}
+
+sub OR_Tries_Concept_Analysis {
+ my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
+ my %ResponseData = %$respdat;
+ my $analysis_html = "
\n";
#
# Compute the data neccessary to make the plots
my @PlotData;
- my @CumulativePlotData;
- foreach my $foilid (@Foils) {
+ # Concept analysis
+ #
+ # Note: we do not bother with characterizing the students incorrect
+ # answers at the concept level because an incorrect answer for one foil
+ # may be a correct answer for another foil.
+ my %ConceptData;
+ foreach my $concept (@{$Concepts}) {
for (my $i=$mintries;$i<=$maxtries;$i++) {
#
# Gather the per-attempt data
- push (@{$PlotData[$i]->{'good'}},
- $ResponseData{$foilid}->[$i]->{'percent_corr'});
- push (@{$PlotData[$i]->{'bad'}},
- 100-$ResponseData{$foilid}->[$i]->{'percent_corr'});
- #
- # Someday we may need the cumulative data and I think
- # this is a neat way of computing it as we go along.
- push (@{$CumulativePlotData[$i]->{'good'}},
- $CumulativePlotData[-1]->{'good'}+
- $ResponseData{$foilid}->[$i]->{'correct'});
- push (@{$CumulativePlotData[$i]->{'bad'}},
- $CumulativePlotData[-1]->{'bad'}+
- $ResponseData{$foilid}->[$i]->{'incorrect'});
+ my $cdata = $ConceptData{$concept}->[$i];
+ foreach my $foilid (@{$concept->{'foils'}}) {
+ $cdata->{'_correct'} +=
+ $ResponseData{$foilid}->[$i]->{'_correct'};
+ $cdata->{'_total'} +=
+ $ResponseData{$foilid}->[$i]->{'_total'};
+ }
+ push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
+ if ($cdata->{'_total'} == 0) {
+ push (@{$PlotData[$i]->{'_correct'}},0);
+ } else {
+ push (@{$PlotData[$i]->{'_correct'}},
+ 100*$cdata->{'_correct'}/$cdata->{'_total'});
+ }
}
}
- #
# Build a table for the plots
- $table .= "
\n";
- my @Plots;
for (my $i=$mintries;$i<=$maxtries;$i++) {
- my $minstu = $ResponseData{$Foils[0]}->[$i]->{'total'};
- my $maxstu = $ResponseData{$Foils[0]}->[$i]->{'total'};
- foreach my $foilid (@Foils) {
- if ($minstu > $ResponseData{$foilid}->[$i]->{'total'}) {
- $minstu = $ResponseData{$foilid}->[$i]->{'total'};
- }
- if ($maxstu < $ResponseData{$foilid}->[$i]->{'total'}) {
- $maxstu = $ResponseData{$foilid}->[$i]->{'total'};
- }
- }
- $maxstu = 0 if (! $maxstu);
- $minstu = 0 if (! $minstu);
- my $graphlink;
- if ($maxstu == $minstu) {
- $graphlink = &Apache::loncommon::DrawGraph
- ('Attempt '.$i.', '.$maxstu.' students',
- 'Foil Number',
- 'Percent Correct',
- 100,
- $PlotData[$i]->{'good'},
- $PlotData[$i]->{'bad'});
- } else {
- $graphlink = &Apache::loncommon::DrawGraph
- ('Attempt '.$i.', '.$minstu.'-'.$maxstu.
- ' students',
- 'Foil Number',
- 'Percent Correct',
- 100,
- $PlotData[$i]->{'good'},
- $PlotData[$i]->{'bad'});
- }
- push(@Plots,$graphlink);
- }
- #
- # Should this be something the user can set? Too many dialogs!
- my $plots_per_row = 2;
- while (my $plotlink = shift(@Plots)) {
- $table .= '
'.$plotlink.'
';
- for (my $i=1;$i<$plots_per_row;$i++) {
- if ($plotlink = shift(@Plots)) {
- $table .= '
\n";
- }
- }
+ { # These braces are here to organize the code, not scope it.
+ {
+ $Str .= ''.&mt('Analyze Over ');
+ $Str .= &Apache::loncommon::help_open_topic
+ ('Analysis_Analyze_Over');
+ $Str .='';
+ $Str .= ' ';
}
- if ($seq_str ne '') {
- $Str .= '