'."\n";
+ $Str .= '';
+ $Str .= ' 'x5;
+ $Str .= 'Plot '.&plot_dropdown().(' 'x10);
+ $Str .= '';
+ $Str .= ' 'x5;
+ $Str .= '';
+ $Str .= ' 'x5;
+ $Str .= '';
+ $Str .= ' 'x5;
+ return $Str;
+}
-# $r->print(' Access Number = '.$#Act.' Submit Time='.$Submit.' First Access='.$Act[0].' Last Access='.$Act[$#Act].' Submit - First = '.$Dif.'');
+###############################################
+###############################################
+=pod
-#time spent for solving the problem
-# $r->print(' Def'.($Act[$#Act-1]-$Act[0]));
+=item &BuildProblemStatisticsPage()
- return $Dif;
-}
+Main interface to problem statistics.
-sub LoadActivityLog {
-# my $CacheDB = "/home/minaeibi/act183.log.cache";
- my $CacheDB = "/home/httpd/perl/tmp/act183.log.cache";
+=cut
- my %Activity;
- if (-e "$CacheDB") {
- if (tie(%Activity,'GDBM_File',"$CacheDB",&GDBM_READER,0640)) {
- return;
+###############################################
+###############################################
+sub BuildProblemStatisticsPage {
+ my ($r,$c)=@_;
+ #
+ 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('');
+ #
+ 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();
+ #
+ # 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 (exists($ENV{'form.Excel'})) {
+ &Excel_output($r);
+ } else {
+ my $count = 0;
+ foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+ $count += $seq->{'num_assess'};
+ }
+ if ($count > 10) {
+ $r->print('
'.
+ &mt('Compiling statistics for [_1] problems',$count).
+ '
');
+ if ($count > 30) {
+ $r->print('
'.&mt('This will take some time.').'
');
+ }
+ $r->rflush();
}
- else {
-# $r->print("Unable to tie log Cache hash to db file");
+ #
+ my $sortby = $ENV{'form.sortby'};
+ $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/);
+ my $plot = $ENV{'form.plot'};
+ if ($plot eq '' || $plot eq 'none') {
+ undef($plot);
+ }
+ if ($sortby eq 'container' && ! defined($plot)) {
+ &output_html_by_sequence($r);
+ } else {
+ if (defined($plot)) {
+ &make_plot($r,$plot);
+ }
+ &output_html_stats($r);
}
}
- else {
- if (tie(%Activity,'GDBM_File',$CacheDB,&GDBM_WRCREAT,0640)) {
- foreach (keys %Activity) {delete $Activity{$_};}
- &Build_log(\%Activity);
- }
- else {
-# $r->print("Unable to tie log Build hash to db file");
+ return;
+}
+
+##########################################################
+##########################################################
+##
+## HTML output routines
+##
+##########################################################
+##########################################################
+sub output_html_by_sequence {
+ my ($r) = @_;
+ my $c = $r->connection();
+ $r->print(&html_preamble());
+ #
+ foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+ last if ($c->aborted);
+ next if ($seq->{'num_assess'} < 1);
+ $r->print("
".$seq->{'title'}."
".
+ '
'."\n".
+ '
'."\n".
+ '
'.
+ &statistics_table_header('no container')."
\n");
+ my @Data = &compute_statistics_on_sequence($seq);
+ foreach my $data (@Data) {
+ $r->print('
\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;
}
-sub CreateProblemStatisticsTableHeading {
- my ($displayFormat,$sequenceSource,$sequenceTitle,$headings,$r)=@_;
- if($displayFormat eq 'Display CSV Format') {
- $r->print(' "'.$sequenceTitle.'","');
- $r->print($sequenceSource.'"');
- return;
+###############################################
+###############################################
+##
+## Misc HTML output routines
+##
+###############################################
+###############################################
+sub statistics_html_table_data {
+ my ($data,$options) = @_;
+ my $row = '';
+ foreach my $field (@Fields) {
+ next if ($options =~ /no $field->{'name'}/);
+ $row .= '
'."\n");
- $r->rflush();
+sub make_single_stat_plot {
+ my ($r,$datafield) = @_;
+ #
+ my $title; my $yaxis;
+ foreach my $field (@Fields) {
+ next if ($field->{'name'} ne $datafield);
+ $title = $field->{'long_title'};
+ $yaxis = $field->{'title'};
+ last;
+ }
+ if ($title eq '' || $yaxis eq '') {
+ # datafield is something we do not know enough about to plot
+ $r->print('
'.
+ &mt('Unable to plot the requested statistic.').
+ '
');
+ return;
+ }
+ #
+ # Build up the data sets to plot
+ my @Labels;
+ my @Data;
+ my $max = 1;
+ foreach my $data (@StatsArray) {
+ push(@Labels,$data->{'problem_num'});
+ push(@Data,$data->{$datafield});
+ if ($data->{$datafield}>$max) {
+ $max = $data->{$datafield};
+ }
+ }
+ foreach (1,2,3,4,5,10,15,20,25,40,50,75,100,150,200,250,300,500,600,750,
+ 1000,1500,2000,2500,3000,3500,4000,5000,7500,10000,15000,20000) {
+ if ($max <= $_) {
+ $max = $_;
+ last;
+ }
+ }
+ if ($max > 20000) {
+ $max = 10000*(int($max/10000)+1);
+ }
+ #
+ $r->print("
'."\n";
-
- return $Ptr;
-}
+=over 4
-sub AscendOrderOptions {
- my ($order)=@_;
+=item #Stdnts
+Total number of students attempted the problem.
- my $OpSel1 = '';
- my $OpSel2 = '';
+=item Tries
+Total number of tries for solving the problem.
- if($order eq 'Ascending') {
- $OpSel1 = ' selected';
- } else {
- $OpSel2 = ' selected';
- }
+=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 ]
- my $Ptr = '';
- $Ptr .= '
Sorting Type:
'."\n";
- $Ptr .= '
'."\n";
-
- return $Ptr;
-}
-
-sub ProblemStatisticsButtons {
- my ($displayFormat)=@_;
-
- my $Ptr = '
';
- $Ptr .= '
+
+=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);
+}
+
+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 {
- $Ptr .= 'value="Display Table Format" />'."\n";
+ if (exists($hooks{$hookname})) {
+ push(@{$hooks{$hookname}},$subref);
+ } else {
+ $hooks{$hookname} = [$subref];
+ }
}
- $Ptr .= '
';
+ return;
+}
- return $Ptr;
+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 ProblemStatisticsLegend {
- my $Ptr = '';
- $Ptr = '
';
- $Ptr .= '
';
- $Ptr .= '#Stdnts:
';
- $Ptr .= '
Total Number of Students opened the problem.';
- $Ptr .= '
';
- $Ptr .= 'Tries:
';
- $Ptr .= '
Total Number of Tries for solving the problem.';
- $Ptr .= '
';
- $Ptr .= 'Mod:
';
- $Ptr .= '
Maximunm Number of Tries for solving the problem.';
- $Ptr .= '
';
- $Ptr .= 'Mean:
';
- $Ptr .= '
Average Number of the 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 .= '%Wrng:
';
- $Ptr .= '
Percentage of students tried to solve the problem ';
- $Ptr .= 'but 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 .= '[Applied Criterion in %27 Upper Students - ';
- $Ptr .= 'Applied the same Criterion in %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;
-}
-
-#---- END Problem Statistics Web Page ----------------------------------------
-
-#---- Problem Statistics Graph Web Page --------------------------------------
-
-# ------------------------------------------- Prepare data for Graphical chart
-
-sub GetGraphData {
- my ($ylab,$r,%GraphDat)=@_;
- my $Col;
- my $data='';
+sub run_statistics {
+ my ($r) = @_;
my $count = 0;
- my $Max = 0;
- my $cid=$ENV{'request.course.id'};
- my $GraphDB = "/home/httpd/perl/tmp/$ENV{'user.name'}".
- "_$ENV{'user.domain'}_$cid\_graph.db";
- foreach (keys %GraphDat) {delete $GraphDat{$_};}
- if (-e "$GraphDB") {
- if (tie(%GraphDat,'GDBM_File',"$GraphDB",&GDBM_READER,0640)) {
- if ( $ylab eq 'DoDiff Graph' ) {
- $ylab = 'Degree-of-Difficulty';
- $Col = 0;
- }
- else {
- $ylab = 'Wrong-Percentage';
- $Col = 1;
- }
- foreach (sort NumericSort keys %GraphDat) {
- my @Temp=split(/\:/,$GraphDat{$_});
- my $inf = $Temp[$Col];
- if ( $Max < $inf ) {$Max = $inf;}
- $data .= $inf.',';
- $count++;
- }
- if ( $Max > 1 ) {
- $Max += (10 - $Max % 10);
- $Max = int($Max);
- }
- else { $Max = 1; }
- untie(%GraphDat);
- my $Course = $ENV{'course.'.$cid.'.description'};
- $Course =~ s/\ /"_"/eg;
- my $GData=$Course.'&'.'Problems'.'&'.$ylab.'&'.$Max.'&'.$count.'&'.$data;
- }
- else {
- $r->print("Unable to tie hash to db file");
- }
+ &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__