'."\n";
$Str .= '';
$Str .= ' 'x5;
+ $Str .= 'Plot '.&plot_dropdown().(' 'x10);
$Str .= '';
$Str .= ' 'x5;
- return ($Str,$outputmode,$show);
+ $Str .= '';
+ $Str .= ' 'x5;
+ $Str .= '';
+ $Str .= ' 'x5;
+ return $Str;
}
###############################################
@@ -265,237 +266,529 @@ Main interface to problem statistics.
sub BuildProblemStatisticsPage {
my ($r,$c)=@_;
#
- my ($interface,$output_mode,$show) = &CreateInterface();
+ my %Saveable_Parameters = ('Status' => 'scalar',
+ 'statsoutputmode' => 'scalar',
+ 'Section' => 'array',
+ 'StudentData' => 'array',
+ 'Maps' => 'array');
+ &Apache::loncommon::store_course_settings('statistics',
+ \%Saveable_Parameters);
+ &Apache::loncommon::restore_course_settings('statistics',
+ \%Saveable_Parameters);
+ #
+ &Apache::lonstatistics::PrepareClasslist();
+ #
+ # Clear the package variables
+ undef(@StatsArray);
+ #
+ # Finally let the user know we are here
+ my $interface = &CreateInterface();
$r->print($interface);
- $r->print('');
$r->print('');
- $r->print('');
+ #
if (! exists($ENV{'form.statsfirstcall'})) {
+ $r->print('');
+ $r->print('
'.
+ &mt('Press "Generate Statistics" when you are ready.').
+ '
'.
+ &mt('It may take some time to update the student data '.
+ 'for the first analysis. Future analysis this session '.
+ ' will not have this delay.').
+ '
');
return;
+ } elsif ($ENV{'form.statsfirstcall'} eq 'yes' ||
+ exists($ENV{'form.UpdateCache'}) ||
+ exists($ENV{'form.ClearCache'}) ) {
+ $r->print('');
+ &Apache::lonstatistics::Gather_Student_Data($r);
+ } else {
+ $r->print('');
}
+ $r->rflush();
#
- &Apache::lonstatistics::Gather_Student_Data($r);
- #
+ # This probably does not need to be done each time we are called, but
+ # it does not slow things down noticably.
+ &Apache::loncoursedata::populate_weight_table();
#
- if ($output_mode eq 'html') {
- $r->print("
\n");
+ return;
+}
-Presents the statistics data as an html table organized by the order
-the assessments appear in the course.
+sub html_preamble {
+ my $Str='';
+ $Str .= "
\n";
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
+ if (defined($starttime) || defined($endtime)) {
+ # Inform the user what the time limits on the data are.
+ $Str .= '
'.&mt('Statistics on submissions from [_1] to [_2]',
+ &Apache::lonlocal::locallocaltime($starttime),
+ &Apache::lonlocal::locallocaltime($endtime)
+ ).'
';
+ }
+ $Str .= "
".&mt('Compiled on [_1]',
+ &Apache::lonlocal::locallocaltime(time))."
";
+ return $Str;
+}
-=cut
###############################################
###############################################
-sub output_html_grouped_by_sequence {
- my ($r) = @_;
- my $problem_num = 0;
- #$r->print(&ProblemStatisticsLegend());
- foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
- next if ($sequence->{'num_assess'}<1);
- $r->print("
".$sequence->{'title'}."
");
- $r->print('
'."\n");
- $r->print('
'."\n");
- $r->print('
');
- my $Str = &statistics_table_header('no container no plots');
- $r->print('
'.$Str."
\n");
- foreach my $resource (@{$sequence->{'contents'}}) {
- next if ($resource->{'type'} ne 'assessment');
- foreach my $part (@{$resource->{'parts'}}) {
- $problem_num++;
- my $data = &get_statistics($sequence,$resource,$part,
- $problem_num);
- my $option = '';
- $r->print('
\n");
#
- # Print out the data
- $ENV{'form.sortby'} = 'Contents';
-# &output_html_ungrouped($r);
+ # Renumber the data set
+ my $count;
+ foreach my $data (@StatsArray) {
+ $data->{'problem_num'} = ++$count;
+ }
return;
}
-###############################################
-###############################################
+########################################################
+########################################################
-=pod
+=pod
-=item &DrawGraph()
+=item &get_statistics()
-=cut
+Wrapper routine from the call to loncoursedata::get_problem_statistics.
+Calls lonstathelpers::get_time_limits() to limit the data set by time
+and &compute_discrimination_factor
-###############################################
-###############################################
-sub DrawGraph {
- my ($values,$title,$xaxis,$yaxis,$Max)=@_;
- $title = '' if (! defined($title));
- $xaxis = '' if (! defined($xaxis));
- $yaxis = '' if (! defined($yaxis));
- $title = &Apache::lonnet::escape($title);
- $xaxis = &Apache::lonnet::escape($xaxis);
- $yaxis = &Apache::lonnet::escape($yaxis);
- #
- my $sendValues = join(',', @$values);
- my $sendCount = scalar(@$values);
- $Max =1 if ($Max < 1);
- if ( int($Max) < $Max ) {
- $Max++;
- $Max = int($Max);
- }
- my @GData = ($title,$xaxis,$yaxis,$Max,$sendCount,$sendValues);
- return '';
-}
+Inputs: $sequence, $resource, $part, $problem_num
+
+Returns: Hash reference with statistics data from
+loncoursedata::get_problem_statistics.
+=cut
+
+########################################################
+########################################################
sub get_statistics {
my ($sequence,$resource,$part,$problem_num) = @_;
#
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
my $symb = $resource->{'symb'};
my $courseid = $ENV{'request.course.id'};
#
- my $students = \@Apache::lonstatistics::Students;
- if ($Apache::lonstatistics::SelectedSections[0] eq 'all') {
- $students = undef;
- }
my $data = &Apache::loncoursedata::get_problem_statistics
- ($students,$symb,$part,$courseid);
+ (\@Apache::lonstatistics::SelectedSections,
+ $Apache::lonstatistics::enrollment_status,
+ $symb,$part,$courseid,$starttime,$endtime);
$data->{'part'} = $part;
$data->{'problem_num'} = $problem_num;
$data->{'container'} = $sequence->{'title'};
@@ -811,78 +1036,234 @@ sub get_statistics {
$data->{'title.link'} = $resource->{'src'}.'?symb='.
&Apache::lonnet::escape($resource->{'symb'});
#
+ $data->{'deg_of_disc'} = &compute_discrimination_factor($resource,$part,$sequence);
return $data;
}
+
###############################################
###############################################
-=pod
+=pod
-=item &ProblemStatisticsLegend()
+=item &compute_discrimination_factor()
-HELP This needs to be localized, or at least generated automatically.
+Inputs: $Resource, $Sequence
+
+Returns: integer between -1 and 1
=cut
###############################################
###############################################
-sub ProblemStatisticsLegend {
- my $Ptr = '';
- $Ptr = '
';
- $Ptr .= '
';
- $Ptr .= '#Stdnts
';
- $Ptr .= '
Total number of students attempted the problem.';
- $Ptr .= '
';
- $Ptr .= 'Tries
';
- $Ptr .= '
Total number of tries for solving the problem.';
- $Ptr .= '
';
- $Ptr .= 'Max Tries
';
- $Ptr .= '
Largest number of tries for solving the problem by a student.';
- $Ptr .= '
';
- $Ptr .= 'Mean
';
- $Ptr .= '
Average number of tries. [ Tries / #Stdnts ]';
- $Ptr .= '
';
- $Ptr .= '#YES
';
- $Ptr .= '
Number of students solved the problem correctly.';
- $Ptr .= '
';
- $Ptr .= '#yes
';
- $Ptr .= '
Number of students solved the problem by override.';
- $Ptr .= '
';
- $Ptr .= '%Wrong
';
- $Ptr .= '
Percentage of students who tried to solve the problem ';
- $Ptr .= 'but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]';
- $Ptr .= '
';
- $Ptr .= 'DoDiff
';
- $Ptr .= '
Degree of Difficulty of the problem. ';
- $Ptr .= '[ 1 - ((#YES+#yes) / Tries) ]';
- $Ptr .= '
';
- $Ptr .= 'S.D.
';
- $Ptr .= '
Standard Deviation of the tries. ';
- $Ptr .= '[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1) ';
- $Ptr .= 'where Xi denotes every student\'s tries ]';
- $Ptr .= '
';
- $Ptr .= 'Skew.
';
- $Ptr .= '
Skewness of the students tries.';
- $Ptr .= '[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]';
- $Ptr .= '
';
- $Ptr .= 'Dis.F.
';
- $Ptr .= '
Discrimination Factor: A Standard for evaluating the ';
- $Ptr .= 'problem according to a Criterion ';
- $Ptr .= '[Criterion to group students into %27 Upper Students - ';
- $Ptr .= 'and %27 Lower Students] ';
- $Ptr .= '1st Criterion for Sorting the Students: ';
- $Ptr .= 'Sum of Partial Credit Awarded / Total Number of Tries ';
- $Ptr .= '2nd Criterion for Sorting the Students: ';
- $Ptr .= 'Total number of Correct Answers / Total Number of Tries';
- $Ptr .= '
';
- $Ptr .= '
Disc.
';
- $Ptr .= '
Number of Students had at least one discussion.';
- $Ptr .= '
';
- return $Ptr;
+sub compute_discrimination_factor {
+ my ($resource,$part,$sequence) = @_;
+ my @Resources;
+ foreach my $res (@{$sequence->{'contents'}}) {
+ next if ($res->{'symb'} eq $resource->{'symb'});
+ push (@Resources,$res->{'symb'});
+ }
+ #
+ # rank
+ my $ranking =
+ &Apache::loncoursedata::rank_students_by_scores_on_resources
+ (\@Resources,
+ \@Apache::lonstatistics::SelectedSections,
+ $Apache::lonstatistics::enrollment_status,undef);
+ #
+ # compute their percent scores on the problems in the sequence,
+ my $number_to_grab = int(scalar(@{$ranking})/4);
+ my $num_students = scalar(@{$ranking});
+ my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()];
+ } @{$ranking}[0..$number_to_grab];
+ my @TopSet =
+ map {
+ $_->[&Apache::loncoursedata::RNK_student()];
+ } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)];
+ my ($bottom_sum,$bottom_max) =
+ &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet);
+ my ($top_sum,$top_max) =
+ &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet);
+ my $deg_of_disc;
+ if ($top_max == 0 || $bottom_max==0) {
+ $deg_of_disc = 'nan';
+ } else {
+ $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
+ }
+ #&Apache::lonnet::logthis(' '.$top_sum.'/'.$top_max.
+ # ' - '.$bottom_sum.'/'.$bottom_max);
+ return $deg_of_disc;
+}
+
+###############################################
+###############################################
+
+=pod
+
+=item ProblemStatisticsLegend
+
+=over 4
+
+=item #Stdnts
+Total number of students attempted the problem.
+
+=item Tries
+Total number of tries for solving the problem.
+
+=item Max Tries
+Largest number of tries for solving the problem by a student.
+
+=item Mean
+Average number of tries. [ Tries / #Stdnts ]
+
+=item #YES
+Number of students solved the problem correctly.
+
+=item #yes
+Number of students solved the problem by override.
+
+=item %Wrong
+Percentage of students who tried to solve the problem
+but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
+
+=item DoDiff
+Degree of Difficulty of the problem.
+[ 1 - ((#YES+#yes) / Tries) ]
+
+=item S.D.
+Standard Deviation of the tries.
+[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
+where Xi denotes every student\'s tries ]
+
+=item Skew.
+Skewness of the students tries.
+[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
+
+=item Dis.F.
+Discrimination Factor: A Standard for evaluating the
+problem according to a Criterion
+
+=item [Criterion to group students into %27 Upper Students -
+and %27 Lower Students]
+1st Criterion for Sorting the Students:
+Sum of Partial Credit Awarded / Total Number of Tries
+2nd Criterion for Sorting the Students:
+Total number of Correct Answers / Total Number of Tries
+
+=item Disc.
+Number of Students had at least one discussion.
+
+=back
+
+=cut
+
+
+############################################################
+############################################################
+##
+## How this all works:
+## Statistics are computed by calling &get_statistics with the sequence,
+## resource, and part id to run statistics on. At various places within
+## the loops which compute the statistics, as well as before and after
+## the entire process, subroutines can be called. The subroutines are
+## registered to the following hooks:
+##
+## hook subroutine inputs
+## ----------------------------------------------------------
+## pre $r,$count
+## pre_seq $r,$count,$seq
+## pre_res $r,$count,$seq,$res
+## calc $r,$count,$seq,$res,$data
+## post_res $r,$count,$seq,$res
+## post_seq $r,$count,$seq
+## post $r,$count
+##
+## abort $r
+##
+## subroutines will be called in the order in which they are registered.
+##
+############################################################
+############################################################
+{
+
+my %hooks;
+my $aborted = 0;
+
+sub abort_computation {
+ $aborted = 1;
+}
+
+sub clear_hooks {
+ $aborted = 0;
+ undef(%hooks);
}
-#---- END Problem Statistics Web Page ----------------------------------------
+sub register_hook {
+ my ($hookname,$subref)=@_;
+ if ($hookname !~ /^(pre|pre_seq|pre_res|post|post_seq|post_res|calc)$/){
+ return;
+ }
+ if (ref($subref) ne 'CODE') {
+ &Apache::lonnet::logthis('attempt to register hook to non-code: '.
+ $hookname,' = '.$subref);
+ } else {
+ if (exists($hooks{$hookname})) {
+ push(@{$hooks{$hookname}},$subref);
+ } else {
+ $hooks{$hookname} = [$subref];
+ }
+ }
+ return;
+}
+
+sub run_hooks {
+ my $context = shift();
+ foreach my $hook (@{$hooks{$context}}) {
+ if ($aborted && $context ne 'abort') {
+ last;
+ }
+ my $retvalue = $hook->(@_);
+ if (defined($retvalue) && $retvalue eq '0') {
+ $aborted = 1 if (! $aborted);
+ }
+ }
+}
+
+sub run_statistics {
+ my ($r) = @_;
+ my $count = 0;
+ &run_hooks('pre',$r,$count);
+ foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+ last if ($aborted);
+ next if ($seq->{'num_assess'}<1);
+ &run_hooks('pre_seq',$r,$count,$seq);
+ foreach my $res (@{$seq->{'contents'}}) {
+ last if ($aborted);
+ next if ($res->{'type'} ne 'assessment');
+ &run_hooks('pre_res',$r,$count,$seq,$res);
+ foreach my $part (@{$res->{'parts'}}) {
+ last if ($aborted);
+ #
+ # This is where all the work happens
+ my $data = &get_statistics($seq,$res,$part,++$count);
+ &run_hooks('calc',$r,$count,$seq,$res,$part,$data);
+ }
+ &run_hooks('post_res',$r,$count,$seq,$res);
+ }
+ &run_hooks('post_seq',$r,$count,$seq);
+ }
+ if ($aborted) {
+ &run_hooks('abort',$r);
+ } else {
+ &run_hooks('post',$r,$count);
+ }
+ return;
+}
+
+} # End of %hooks scope
+
+############################################################
+############################################################
1;
__END__