--- loncom/interface/statistics/lonproblemstatistics.pm 2002/10/30 18:37:00 1.34
+++ loncom/interface/statistics/lonproblemstatistics.pm 2014/02/03 18:52:30 1.125
@@ -1,7 +1,6 @@
# The LearningOnline Network with CAPA
-# (Publication Handler
#
-# $Id: lonproblemstatistics.pm,v 1.34 2002/10/30 18:37:00 minaeibi Exp $
+# $Id: lonproblemstatistics.pm,v 1.125 2014/02/03 18:52:30 bisitz Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -26,843 +25,1807 @@
# http://www.lon-capa.org/
#
# (Navigate problems for statistical reports
-# YEAR=2001
-# 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei
-# 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei
-# YEAR=2002
-# 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei
-# 5/12,5/14,5/15,5/19,5/26,7/16,7/25,7/29,8/5 Behrouz Minaei
#
-###
+###############################################
+###############################################
-package Apache::lonproblemstatistics;
+=pod
+
+=head1 NAME
+
+lonproblemstatistics
+
+=head1 SYNOPSIS
+
+Routines to present problem statistics to instructors via tables,
+Excel files, and plots.
+
+=over 4
+
+=cut
+
+###############################################
+###############################################
+
+package Apache::lonproblemstatistics;
use strict;
-use Apache::lonnet();
+use Apache::lonnet;
+use Apache::loncommon();
+use Apache::lonquickgrades();
use Apache::lonhtmlcommon;
use Apache::loncoursedata;
-use GDBM_File;
-
-my $jr;
-
-sub InitializeProblemStatistics {
- my ($cacheDB, $students, $courseID, $c, $r)=@_;
- my %cache;
-
- $jr = $r;
-
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database1.');
- return ('ERROR', undef);
- }
-
- # Remove students who don't have the proper section.
- my @sectionsSelected = split(':',$cache{'sectionsSelected'});
- for(my $studentIndex=((scalar @$students)-1); $studentIndex>=0;
- $studentIndex--) {
- my $value = $cache{$students->[$studentIndex].':section'};
- my $found = 0;
- foreach (@sectionsSelected) {
- if($_ eq 'none') {
- if($value eq '' || !defined($value) || $value eq ' ') {
- $found = 1;
- last;
- }
- } else {
- if($value eq $_) {
- $found = 1;
- last;
- }
+use Apache::lonstatistics;
+use LONCAPA::lonmetadata();
+use Apache::lonlocal;
+use Spreadsheet::WriteExcel;
+use Apache::lonstathelpers();
+use Time::HiRes;
+use LONCAPA;
+
+
+my @StatsArray;
+my %SeqStat; # keys are symbs, values are hash refs
+
+##
+## Localization notes:
+##
+## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
+## header for plots created with Graph.pm, both of which more than likely do
+## not support localization.
+##
+## Additional Notes:
+## Localization can be done and is done before passing the phrases
+## to the output.
+## This might conflict with special characters, e.g. German Umlaute or
+## chinese characters. Do not use such characters in this case.
+## If this failed, consider that the sequence and folder names
+## are also passed to the output and would fail the same way.
+##
+#
+#
+##
+## Description of Field attributes
+##
+## Attribute Required Value Meaning or Use
+##
+## name yes any scalar Used to uniquely identify field
+## title yes any scalar This is what the user sees to identify
+## the field. Passed through &mt().
+## long_title yes any scalar Used as graph heading and in excel
+## output. Passed through &mt().
+## align no (left|right|center) HTML cell contents alignment
+## color yes html color HTML cell background color
+## used to visually group statistics
+## special no (link) Indicates a link, target is name.link
+## Currently set in &get_statistics()
+## graphable no (yes|no) Can a bar graph of the field be
+## produced?
+## sortable no (yes|no) Should a sort link be put in the
+## column header?
+## selectable yes (yes|no) Can the column be removed from the
+## statistics display?
+## selected yes (yes|no) Is the column selected by default?
+##
+## format no sprintf format string
+##
+## excel_format no excel format type
+## (see &Apache::loncommon::define_excel_formats
+my @Fields = (
+ { name => 'problem_num',
+ title => 'P#',
+ align => 'right',
+ color => '#FFFFE6',
+ selectable => 'no',
+ defaultselected => 'yes',
+ },
+ { name => 'container',
+ title => 'Sequence or Folder',
+ align => 'left',
+ color => '#FFFFE6',
+ sortable => 'yes',
+ selectable => 'no',
+ defaultselected => 'yes',
+ },
+ { name => 'title',
+ title => 'Title',
+ align => 'left',
+ color => '#FFFFE6',
+ special => 'link',
+ sortable => 'yes',
+ selectable => 'no',
+ defaultselected => 'yes',
+ },
+ { name => 'part',
+ title => 'Part',
+ align => 'left',
+ color => '#FFFFE6',
+ selectable => 'no',
+ defaultselected => 'yes',
+ },
+ { name => 'num_students',
+ title => '#Stdnts',
+ align => 'right',
+ color => '#EEFFCC',
+ format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Number of Students Attempting Problem',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'tries',
+ title => 'Tries',
+ align => 'right',
+ color => '#EEFFCC',
+ format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Total Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'max_tries',
+ title => 'Max Tries',
+ align => 'right',
+ color => '#DDFFFF',
+ format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Maximum Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'min_tries',
+ title => 'Min Tries',
+ align => 'right',
+ color => '#DDFFFF',
+ format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Minumum Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'mean_tries',
+ title => 'Mean Tries',
+ align => 'right',
+ color => '#DDFFFF',
+ format => '%5.2f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Average Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'std_tries',
+ title => 'S.D. tries',
+ align => 'right',
+ color => '#DDFFFF',
+ format => '%5.2f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Standard Deviation of Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'skew_tries',
+ title => 'Skew Tries',
+ align => 'right',
+ color => '#DDFFFF',
+ format => '%5.2f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Skew of Number of Tries',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'num_solved',
+ title => '#YES',
+ align => 'right',
+ color => '#FFDDDD',
+ format => '%4.1f',# format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Number of Students able to Solve',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'num_override',
+ title => '#yes',
+ align => 'right',
+ color => '#FFDDDD',
+ format => '%4.1f',# format => '%d',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Number of Students given Override',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'tries_per_correct',
+ title => 'tries/correct',
+ align => 'right',
+ color => '#FFDDDD',
+ format => '%4.1f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Tries per Correct Answer',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'num_wrong',
+ title => '#Wrng',
+ align => 'right',
+ color => '#FFDDDD',
+ format => '%4.1f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Number of students whose final answer is wrong',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'per_wrong',
+ title => '%Wrng',
+ align => 'right',
+ color => '#FFDDDD',
+ format => '%4.1f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Percent of students whose final answer is wrong',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'deg_of_diff',
+ title => 'DoDiff',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%5.2f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Degree of Difficulty'.
+ ' ~[ 1 - ((#YES+#yes) / Tries) ~]',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+ { name => 'deg_of_disc',
+ title => 'DoDisc',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4.2f',
+ sortable => 'yes',
+ graphable => 'yes',
+ long_title => 'Degree of Discrimination',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+## duedate included for research purposes. Commented out most of the time.
+# { name => 'duedate',
+# title => 'Due Date',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'yes',
+# graphable => 'no',
+# long_title => 'Due date of resource for instructor',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+## opendate included for research purposes. Commented out most of the time.
+# { name => 'opendate',
+# title => 'Open Date',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'yes',
+# graphable => 'no',
+# long_title => 'date resource became answerable',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+## symb included for research purposes. Commented out most of the time.
+# { name => 'symb',
+# title => 'Symb',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'yes',
+# graphable => 'no',
+# long_title => 'Unique LON-CAPA identifier for problem',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+## resptypes included for research purposes. Commented out most of the time.
+# { name => 'resptypes',
+# title => 'Response Types',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'no',
+# graphable => 'no',
+# long_title => 'Response Types used in this problem',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+## maxtries included for research purposes. Commented out most of the time.
+# { name => 'maxtries',
+# title => 'Maxtries',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'no',
+# graphable => 'no',
+# long_title => 'Maximum number of tries',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+## hinttries included for research purposes. Commented out most of the time.
+# { name => 'hinttries',
+# title => 'hinttries',
+# align => 'left',
+# color => '#FFFFFF',
+# sortable => 'no',
+# graphable => 'no',
+# long_title => 'Number of tries before a hint appears',
+# selectable => 'no',
+# defaultselected => 'yes',
+# },
+#
+## problem weight for instructor
+ { name => 'weight',
+ title => 'weight',
+ align => 'right',
+ color => '#FFFFFF',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Problem weight (for instructor)',
+ selectable => 'yes',
+ defaultselected => 'yes',
+ },
+);
+
+my @SeqFields = (
+ { name => 'title',
+ title => 'Sequence',
+ align => 'left',
+ color => '#FFFFE6',
+ special => 'no',
+ sortable => 'no',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'items',
+ title => '#Items',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4d',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Number of Items in Sequence',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'scoremean',
+ title => 'Score Mean',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Mean Sequence Score',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'scorestd',
+ title => 'Score STD',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Standard Deviation of Sequence Scores',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'scoremax',
+ title => 'Score Max',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Maximum Sequence Score',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'scoremin',
+ title => 'Score Min',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Minumum Sequence Score',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'scorecount',
+ title => 'Score N',
+ align => 'right',
+ color => '#FFFFE6',
+ format => '%4d',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Number of Students in score computations',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'countmean',
+ title => 'Count Mean',
+ align => 'right',
+ color => '#FFFFFF',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Mean Sequence Score',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'countstd',
+ title => 'Count STD',
+ align => 'right',
+ color => '#FFFFFF',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Standard Deviation of Sequence Scores',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'countmax',
+ title => 'Count Max',
+ align => 'right',
+ color => '#FFFFFF',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Maximum Number of Correct Problems',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'countmin',
+ title => 'Count Min',
+ align => 'right',
+ color => '#FFFFFF',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Minumum Number of Correct Problems',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'count',
+ title => 'Count N',
+ align => 'right',
+ color => '#FFFFFF',
+ format => '%4d',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'Number of Students in score computations',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+ { name => 'KR-21',
+ title => 'KR-21',
+ align => 'right',
+ color => '#FFAAAA',
+ format => '%4.2f',
+ sortable => 'no',
+ graphable => 'no',
+ long_title => 'KR-21 reliability statistic',
+ selectable => 'yes',
+ defaultselected => 'no',
+ },
+);
+
+my %SelectedFields;
+
+sub parse_field_selection {
+ #
+ # Pull out the defaults
+ if (! defined($env{'form.fieldselections'})) {
+ $env{'form.fieldselections'} = [];
+ foreach my $field (@Fields) {
+ next if ($field->{'selectable'} ne 'yes');
+ if ($field->{'defaultselected'} eq 'yes') {
+ push(@{$env{'form.fieldselections'}},$field->{'name'});
}
}
- if($found == 0) {
- splice(@$students, $studentIndex, 1);
- }
- }
-
- my $isNotCached = 0;
- my $lastStatus = (defined($cache{'StatisticsLastStatus'})) ?
- $cache{'StatisticsLastStatus'} : 'Nothing';
- my $whichStudents = join(':::',sort(@$students));
- if(!defined($cache{'StatisticsCached'}) ||
- $lastStatus ne $cache{'Status'} ||
- $whichStudents ne $cache{'StatisticsWhichStudents'}) {
- $isNotCached = 1;
}
-
- untie(%cache);
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
- $r->print('Unable to tie database.2');
- return ('ERROR', undef);
- }
- if($isNotCached && defined($cache{'StatisticsCached'})) {
- my @statkeys = split(':::', $cache{'StatisticsKeys'});
- delete $cache{'StatisticsKeys'};
- delete $cache{'StatisticsCached'};
- foreach(@statkeys) {
- delete $cache{$_};
+ #
+ # Make sure the data we are plotting is there
+ my %NeededFields;
+ if (exists($env{'form.plot'}) && $env{'form.plot'} ne '' &&
+ $env{'form.plot'} ne 'none') {
+ if ($env{'form.plot'} eq 'degrees') {
+ $NeededFields{'deg_of_diff'}++;
+ $NeededFields{'deg_of_disc'}++;
+ } elsif ($env{'form.plot'} eq 'tries statistics') {
+ $NeededFields{'mean_tries'}++;
+ $NeededFields{'std_tries'}++;
+ $NeededFields{'problem_num'}++;
+ } else {
+ $NeededFields{$env{'form.plot'}}++;
}
}
-
- untie(%cache);
- if($isNotCached) {
- &Apache::loncoursedata::DownloadStudentCourseDataSeparate($students,
- 'true',
- $cacheDB,
- 'true',
- 'true',
- $courseID,
- $r, $c);
- }
- if($c->aborted()) { return ('ERROR', undef); }
-
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.3');
- return ('ERROR', undef);
+ #
+ # This should not happen, but in case it does...
+ if (ref($env{'form.fieldselections'}) ne 'ARRAY') {
+ $env{'form.fieldselections'} = [$env{'form.fieldselections'}];
+ }
+ #
+ # Set the field data and the selected fields (for easier checking)
+ undef(%SelectedFields);
+ foreach my $field (@Fields) {
+ if ($field->{'selectable'} ne 'yes') {
+ $field->{'selected'} = 'yes';
+ } else {
+ $field->{'selected'} = 'no';
+ }
+ if (exists($NeededFields{$field->{'name'}})) {
+ $field->{'selected'} = 'yes';
+ $SelectedFields{$field->{'name'}}++;
+ }
+ foreach my $selection (@{$env{'form.fieldselections'}}) {
+ if ($selection eq $field->{'name'} || $selection eq 'all') {
+ $field->{'selected'} = 'yes';
+ $SelectedFields{$field->{'name'}}++;
+ }
+ }
}
- my $problemData;
- if($isNotCached) {
- ($problemData) = &ExtractStudentData(\%cache, $students);
- &CalculateStatistics($problemData, \%cache, $courseID);
+ #
+ # Always show all the sequence statistics (for now)
+ foreach my $field (@SeqFields) {
+ $field->{'selected'} = 'yes';
}
- untie(%cache);
+ return;
+}
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
- $r->print('Unable to tie database.4');
- return ('ERROR', undef);
- }
- if($isNotCached) {
- foreach(keys(%$problemData)) {
- $cache{$_} = $problemData->{$_};
+sub field_selection_input {
+ my $Str = ''."\n";
+ $Str .= ''.&mt('all').' '."\n";
+ foreach my $field (@Fields) {
+ next if ($field->{'selectable'} ne 'yes');
+ $Str .= ' {'selected'} eq 'yes') {
+ $Str .= 'selected="selected" ';
}
- $cache{'StatisticsKeys'} = join(':::', keys(%$problemData));
- $cache{'StatisticsCached'} = 'true';
- $cache{'StatisticsLastStatus'} = $cache{'Status'};
- $cache{'StatisticsWhichStudents'} = $whichStudents;
+ $Str .= '>'.&mt($field->{'title'}).' '."\n";
}
- untie(%cache);
+ $Str .= " \n";
+}
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.5');
- return ('ERROR', undef);
- }
+###############################################
+###############################################
- my $orderedProblems = &SortProblems(\%cache,
- $cache{'ProblemStatisticsSort'},
- $cache{'SortProblems'},
- $cache{'ProblemStatisticsAscend'});
- untie(%cache);
+=pod
- return ('OK', $orderedProblems);
+=item &CreateInterface()
+
+Create the main intereface for the statistics page. Allows the user to
+select sections, maps, and output.
+
+=cut
+
+###############################################
+###############################################
+sub CreateInterface {
+ my ($r) = @_;
+ #
+ &parse_field_selection();
+ #
+ my $Str = '';
+ $Str .= &Apache::loncommon::start_data_table();
+ $Str .= &Apache::loncommon::start_data_table_header_row();
+ $Str .= '
'.&mt('Sections').' ';
+ $Str .= ''.&mt('Groups').' ';
+ $Str .= ''.&mt('Access Status').' ';
+ $Str .= ''.&mt('Sequences and Folders').' ';
+ $Str .= ''.&mt('Statistics').' ';
+ $Str .= ''.&mt('Plot Graph').' ';
+ $Str .= ''.&mt('Time Period').' ';
+ $Str .= &Apache::loncommon::end_data_table_header_row();
+ #
+ $Str .= &Apache::loncommon::start_data_table_row();
+ $Str .= ''."\n";
+ $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
+ $Str .= ' ';
+ $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5);
+ $Str .= ' ';
+ $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
+ $Str .= ' ';
+ #
+ $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5);
+ $Str .= ' ';
+ $Str .= &field_selection_input();
+ $Str .= ' ';
+ $Str .= &plot_dropdown();
+ $Str .= "\n";
+ $Str .= ' ';
+ $Str .= &Apache::lonstathelpers::limit_by_time_form();
+ $Str .= ' '."\n";
+ $Str .= &Apache::loncommon::end_data_table_row();
+ $Str .= &Apache::loncommon::end_data_table();
+ #
+ $Str .= ' ';
+ $Str .= (' 'x10);
+ #
+ return $Str;
}
-sub BuildProblemStatisticsPage {
- my ($cacheDB, $students, $courseID, $c, $r)=@_;
+###############################################
+###############################################
- my @Header = ("Homework Sets Order","#Stdnts","Tries","Mod",
- "Mean","#YES","#yes","%Wrng","DoDiff",
- "S.D.","Skew.","D.F.1st","D.F.2nd");
- my $color=&setbgcolor(0);
- my %cache;
+=pod
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.6');
+=item &BuildProblemStatisticsPage()
+
+Main interface to problem statistics.
+
+=cut
+
+###############################################
+###############################################
+my $navmap;
+my @sequences;
+
+sub clean_up {
+ undef($navmap);
+ undef(@sequences);
+}
+
+sub BuildProblemStatisticsPage {
+ my ($r,$c)=@_;
+ undef($navmap);
+ undef(@sequences);
+ #
+ my %Saveable_Parameters = ('Status' => 'scalar',
+ 'statsoutputmode' => 'scalar',
+ 'Section' => 'array',
+ 'Groups' => 'array',
+ 'StudentData' => 'array',
+ 'Maps' => 'array',
+ 'fieldselections'=> '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);
+ undef(%SeqStat);
+ #
+ # Finally let the user know we are here
+ $r->print(&Apache::lonhtmlcommon::breadcrumbs('Overall Problem Statistics',
+ 'Statistics_Overall_Key'));
+ &Apache::lonquickgrades::startGradeScreen($r,'statistics');
+
+ my $interface = &CreateInterface($r);
+ $r->print($interface);
+ $r->print(' ');
+ #
+ my @CacheButtonHTML =
+ &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
+ my $Str;
+ foreach my $html (@CacheButtonHTML) {
+ $Str.=$html.(' 'x5);
+ }
+ #
+ $r->print($Str);
+ if (! exists($env{'form.firstrun'})) {
+ $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.').
+ '
');
+ &clean_up();
return;
}
- my $Ptr = '';
- $Ptr .= '';
- $Ptr .= 'Select Map '."\n";
- $Ptr .= '';
- $Ptr .= &Apache::lonhtmlcommon::MapOptions(\%cache, 'Statistics',
- 'Statistics');
- $Ptr .= ' '."\n";
- $Ptr .= 'Sorting Type: '."\n";
- $Ptr .= ''."\n";
- $Ptr .= &Apache::lonhtmlcommon::AscendOrderOptions(
- $cache{'ProblemStatisticsAscend'},
- 'ProblemStatistics',
- 'Statistics');
- $Ptr .= ' '."\n";
- $Ptr .= 'Select Sections ';
- $Ptr .= ' '."\n";
- $Ptr .= ''."\n";
- my @sections = split(':',$cache{'sectionList'});
- my @sectionsSelected = split(':',$cache{'sectionsSelected'});
- $Ptr .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections,
- \@sectionsSelected,
- 'Statistics');
- $Ptr .= ' '."\n";
- $Ptr .= &ProblemStatisticsButtons($cache{'DisplayFormat'},
- $cache{'DisplayLegend'},
- $cache{'SortProblems'});
- $Ptr .= '
';
- if($cache{'DisplayLegend'} eq 'Show Legend') {
- $Ptr .= &ProblemStatisticsLegend();
- }
- $r->print($Ptr);
$r->rflush();
- untie(%cache);
-
- my ($result, $orderedProblems) =
- &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r);
- if($result ne 'OK') {
+ #
+ # 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();
+ #
+ ($navmap,@sequences) =
+ &Apache::lonstatistics::selected_sequences_with_assessments();
+ if (! ref($navmap)) {
+ $r->print(''.&mt('A course-wide error occurred.').'
'.
+ ''.$navmap.' ');
+ &clean_up();
return;
}
-
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.6');
- return;
+ if (exists($env{'form.Excel'})) {
+ $r->print(''.
+ &Apache::lonstatistics::section_and_enrollment_description().
+ '
');
+ &Excel_output($r);
+ } else {
+ $r->print(' '.' 'x5);
+ $r->rflush();
+ $r->print(''.
+ &Apache::lonstatistics::section_and_enrollment_description().
+ '
');
+ my $count = 0;
+ foreach my $seq (@sequences) {
+ my @resources =
+ &Apache::lonstathelpers::get_resources($navmap,$seq);
+ $count += scalar(@resources);
+ }
+ if ($count > 10) {
+ $r->print(''.
+ &mt('Compiling statistics for [quant,_1,problem]',$count).
+ '
');
+ if ($count > 30) {
+ $r->print(''.&mt('This will take some time.').'
');
+ }
+ $r->rflush();
+ }
+ #
+ 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_sequence_statistics($r);
+ &output_html_by_sequence($r);
+ } else {
+ if (defined($plot)) {
+ &make_plot($r,$plot);
+ }
+ &output_html_stats($r);
+ &output_sequence_statistics($r);
+ }
}
- &BuildStatisticsTable(\%cache, $cache{'DisplayFormat'},
- $cache{'SortProblems'}, $orderedProblems,
- \@Header, $r, $color);
- untie(%cache);
-
+ &clean_up();
return;
}
-sub BuildGraphicChart {
- my ($graph,$cacheDB,$courseDescription,$students,$courseID,$r,$c)=@_;
- my %cache;
- my $max = 0;
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- return 'Unable to tie database.7';
+sub output_sequence_statistics {
+ my ($r) = @_;
+ my $c=$r->connection();
+ $r->print(''.&mt('Sequence Statistics').
+ &Apache::loncommon::help_open_topic('Statistics_Sequence').
+ ' ');
+ $r->print(&Apache::loncommon::start_data_table());
+ $r->print(&sequence_html_header());
+ foreach my $seq (@sequences) {
+ last if ($c->aborted);
+ &compute_sequence_statistics($seq);
+ $r->print(&sequence_html_output($seq));
}
+ $r->print(&Apache::loncommon::end_data_table());
+ $r->rflush();
+ return;
+}
-# my @problems = split(':::', $cache{'problemList'});
- my $title = '';
- if($graph eq 'DoDiffGraph') {
- $title = 'Degree-of-Difficulty';
- } else {
- $title = 'Wrong-Percentage';
+##########################################################
+##########################################################
+##
+## HTML output routines
+##
+##########################################################
+##########################################################
+sub output_html_by_sequence {
+ my ($r) = @_;
+ my $c = $r->connection();
+ $r->print(' '.&html_preamble());
+ #
+ foreach my $seq (@sequences) {
+ last if ($c->aborted);
+ $r->print("".$seq->compTitle." ".
+ &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ &statistics_table_header('no container').
+ &Apache::loncommon::end_data_table_header_row()."\n");
+ my @Data = &compute_statistics_on_sequence($seq);
+ foreach my $data (@Data) {
+ $r->print(&Apache::loncommon::start_data_table_row().
+ &statistics_html_table_data($data,'no container').
+ &Apache::loncommon::end_data_table_row()."\n");
+ }
+ $r->print(&Apache::loncommon::end_data_table()."\n");
+ $r->rflush();
}
+ return;
+}
-
- # foreach (@problems) {
- # my $data = 0;
- # if($graph eq 'DoDiffGraph') {
- # $data = sprintf("%.2f", $cache{$_.':degreeOfDifficulty'}),
- # } else {
- # $data = sprintf("%.1f", $cache{$_.':percentWrong'}),
- # }
- # if($max < $data) {
- # $max = $data;
- # }
- # push(@values, $data);
- # }
-
-
- my $count = 1;
- my $currentSequence = -1;
- my $sortProblems = 'Sort Within Sequence';
-
- my ($result, $orderedProblems) =
- &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r);
- if($result ne 'OK') {
- return;
+sub output_html_stats {
+ my ($r)=@_;
+ &compute_all_statistics($r);
+ $r->print(&html_preamble());
+ &sort_data($env{'form.sortby'});
+ #
+ my $count=0;
+ foreach my $data (@StatsArray) {
+ if ($count++ % 50 == 0) {
+ $r->print(&Apache::loncommon::end_data_table());
+ $r->print(&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_row().
+ &statistics_table_header().
+ &Apache::loncommon::end_data_table_row());
+ }
+ $r->print(&Apache::loncommon::start_data_table_row().
+ &statistics_html_table_data($data).
+ &Apache::loncommon::end_data_table_row());
}
+ $r->print(&Apache::loncommon::end_data_table_row());
+ return;
+}
- my @values = ();
+sub html_preamble {
+ my $Str='';
+ $Str .= "".
+ $env{'course.'.$env{'request.course.id'}.'.description'}.
+ " \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;
+}
- foreach(@$orderedProblems) {
- my ($sequence,$problem,$part)=split(':', $_);
- if($cache{'StatisticsMaps'} ne 'All Maps' &&
- $cache{'StatisticsMaps'} ne $cache{$sequence.':title'}) {
- next;
- }
- if($currentSequence == -1 ||
- ($sortProblems eq 'Sort Within Sequence' &&
- $currentSequence != $sequence)) {
- if($currentSequence ne -1) {
- #$r->print(' finish a graph ');
- }
- if($sortProblems eq 'Sort Within Sequence') {
- $r->print(''.$cache{$sequence.':title'}.' ');
+###############################################
+###############################################
+##
+## Misc HTML output routines
+##
+###############################################
+###############################################
+sub statistics_html_table_data {
+ my ($data,$options) = @_;
+ my $row = '';
+ foreach my $field (@Fields) {
+ next if ($options =~ /no $field->{'name'}/);
+ next if ($field->{'selected'} ne 'yes');
+ $row .= '{'align'})) {
+ $row .= ' align="'.$field->{'align'}.'"';
}
-
- my $sendValues = join(',', @values);
- my $sendCount = scalar(@values);
-
- my @GData = ($courseDescription, 'Problems',
- $title, $max, $sendCount, $sendValues);
- $r->print(''."\n");
- $r->print(' ');
- $r->print(' ';
}
-
-#$r->print(' ');
-
- untie(%cache);
-
- return;
+ return $row;
}
-
-#---- Problem Statistics Web Page ---------------------------------------
-
-sub CreateProblemStatisticsTableHeading {
- my ($headings,$r)=@_;
-
- my $Str='';
- $Str .= ''."\n";
- $Str .= 'P# '."\n";
- foreach(@$headings) {
- $Str .= '';
- $Str .= ''.$_.'   '."\n";
+sub statistics_table_header {
+ my ($options) = @_;
+ my $header_row;
+ foreach my $field (@Fields) {
+ next if ($options =~ /no $field->{'name'}/);
+ next if ($field->{'selected'} ne 'yes');
+ $header_row .= '';
+ my $header_row_text = &mt($field->{'title'});
+ if (exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
+ $header_row .=
+ '{'name'}."'".
+ ';document.Statistics.submit();">'.
+ $header_row_text.
+ ' ';
+ } else {
+ $header_row .= $header_row_text;
+ }
+ if ($options !~ /no plots/ &&
+ exists($field->{'graphable'}) &&
+ $field->{'graphable'} eq 'yes') {
+ $header_row.=' (';
+ $header_row .= '';
+ $header_row .= &mt('plot').' )';
+ }
+ $header_row .= ' ';
}
- $Str .= "\n".' '."\n";
+ return $header_row;
+}
+sub sequence_html_header {
+ my $Str .= &Apache::loncommon::start_data_table_header_row();
+ foreach my $field (@SeqFields) {
+# next if ($field->{'selected'} ne 'yes');
+ $Str .= '{'title'}).' ';
+ }
+ $Str .= &Apache::loncommon::end_data_table_header_row();
return $Str;
}
-sub BuildStatisticsTable {
- my ($cache,$displayFormat,$sortProblems,$orderedProblems,$headings,
- $r,$color)=@_;
-
- my $count = 1;
- my $currentSequence = -1;
- foreach(@$orderedProblems) {
- my ($sequence,$problem,$part)=split(':', $_);
- if($cache->{'StatisticsMaps'} ne 'All Maps' &&
- $cache->{'StatisticsMaps'} ne $cache->{$sequence.':title'}) {
- next;
- }
- if($currentSequence == -1 ||
- ($sortProblems eq 'Sort Within Sequence' &&
- $currentSequence != $sequence)) {
- if($displayFormat ne 'Display CSV Format') {
- if($currentSequence ne -1) {
- $r->print('');
- $r->print(' ');
- }
- if($sortProblems eq 'Sort Within Sequence') {
- $r->print(''.$cache->{$sequence.':title'}.' ');
- }
- $r->print(''."\n");
- $r->print(''."\n");
- $r->print(&CreateProblemStatisticsTableHeading($headings, $r));
- } else {
- if($sortProblems eq 'Sort Within Sequence') {
- $r->print('"'.$cache->{$sequence.':title'}.'"');
- }
- $r->print(' ');
- }
- $currentSequence = $sequence;
+sub sequence_html_output {
+ my ($seq) = @_;
+ my $data = $SeqStat{$seq->symb};
+ my $row = &Apache::loncommon::start_data_table_row();
+ foreach my $field (@SeqFields) {
+ next if ($field->{'selected'} ne 'yes');
+ $row .= '{'align'})) {
+ $row .= ' align="'.$field->{'align'}.'"';
+ }
+ $row .= '>';
+ if (exists($field->{'format'})) {
+ $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
+ } else {
+ $row .= $data->{$field->{'name'}};
}
+ $row .= ' ';
+ }
+ $row .= &Apache::loncommon::end_data_table_row()."\n";
+ return $row;
+}
- my $ref = ''.$cache->{$problem.':title'}.' ';
- my $title = $cache->{$problem.':title'};
- if($part != 0) {
- $title .= ' Part '.$part;
- }
- my $source = $cache->{$problem.':source'};
- my $tableData = join('&', $ref, $title, $source,
- $cache->{$_.':studentCount'},
- $cache->{$_.':totalTries'},
- $cache->{$_.':maxTries'},
- $cache->{$_.':mean'},
- $cache->{$_.':correct'},
- $cache->{$_.':correctByOverride'},
- $cache->{$_.':percentWrong'},
- $cache->{$_.':degreeOfDifficulty'},
- $cache->{$_.':standardDeviation'},
- $cache->{$_.':skewness'},
- $cache->{$_.':discriminationFactor1'},
- $cache->{$_.':discriminationFactor2'});
-
- &TableRow($displayFormat,$tableData,$count,$r,$color);
-
- $count++;
- }
- if($displayFormat ne 'Display CSV Format') {
- $r->print('
'."\n");
- $r->print('
');
+####################################################
+####################################################
+##
+## Plotting Routines
+##
+####################################################
+####################################################
+sub make_plot {
+ my ($r,$plot) = @_;
+ &compute_all_statistics($r);
+ &sort_data($env{'form.sortby'});
+ if ($plot eq 'degrees') {
+ °rees_plot($r);
+ } elsif ($plot eq 'tries statistics') {
+ &tries_data_plot($r);
} else {
- $r->print(' ');
+ &make_single_stat_plot($r,$plot);
}
-
return;
}
-sub TableRow {
- my ($displayFormat,$Str,$RealIdx,$r,$color)=@_;
- my($ref,$title,$source,$StdNo,$TotalTries,$MxTries,$Avg,$YES,$Override,
- $Wrng,$DoD,$SD,$Sk,$_D1,$_D2)=split(/\&/,$Str);
- my $Ptr;
- if($displayFormat eq 'Display CSV Format') {
- $Ptr='"'.$RealIdx.'",'."\n".
- '"'.$title.'",'."\n".
- '"'.$source.'",'."\n".
- '"'.$StdNo.'",'."\n".
- '"'.$TotalTries.'",'."\n".
- '"'.$MxTries.'",'."\n".
- '"'.$Avg.'",'."\n".
- '"'.$YES.'",'."\n".
- '"'.$Override.'",'."\n".
- '"'.$Wrng.'",'."\n".
- '"'.$DoD.'",'."\n".
- '"'.$SD.'",'."\n".
- '"'.$Sk.'",'."\n".
- '"'.$_D1.'",'."\n".
- '"'.$_D2.'"'."\n".
- " \n";
-
- $r->print("\n".$Ptr);
- } else {
- $Ptr=''."\n".
- ''.$RealIdx.' '."\n".
- ''.$ref.' '."\n".
- ' '.$StdNo.' '."\n".
- ''.$TotalTries.' '."\n".
- ''.$MxTries.' '."\n".
- ''.$Avg.' '."\n".
- ' '.$YES.' '."\n".
- ' '.$Override.' '."\n".
- ' '.$Wrng.' '."\n".
- ' '.$DoD.' '."\n".
- ' '.$SD.' '."\n".
- ' '.$Sk.' '."\n".
- ' '.$_D1.' '."\n".
- ' '.$_D2.' '."\n";
- $r->print($Ptr.' '."\n");
+sub make_single_stat_plot {
+ my ($r,$datafield) = @_;
+ #
+ my $title; my $yaxis;
+ foreach my $field (@Fields) {
+ next if ($field->{'name'} ne $datafield);
+ $title = &mt($field->{'long_title'});
+ $yaxis = &mt($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("".&Apache::loncommon::DrawBarGraph($title,
+ &mt('Problem Number'),
+ $yaxis,
+ $max,
+ undef, # colors
+ \@Labels,
+ \@Data)."
\n");
return;
}
-# For loading the colored table for display or un-colored for print
-sub setbgcolor {
- my $PrintTable=shift;
- my %color;
- if ($PrintTable){
- $color{"gb"}="#FFFFFF";
- $color{"red"}="#FFFFFF";
- $color{"yellow"}="#FFFFFF";
- $color{"green"}="#FFFFFF";
- $color{"purple"}="#FFFFFF";
- } else {
- $color{"gb"}="#DDFFFF";
- $color{"red"}="#FFDDDD";
- $color{"yellow"}="#EEFFCC";
- $color{"green"}="#DDFFDD";
- $color{"purple"}="#FFDDFF";
- }
-
- return \%color;
-}
-
-sub ProblemStatisticsButtons {
- my ($displayFormat, $displayLegend, $sortProblems)=@_;
-
- my $Ptr = '';
- $Ptr .= ' {'problem_num'});
+ my $disc = $data->{'deg_of_disc'};
+ my $diff = $data->{'deg_of_diff'};
+ push(@Disc,$disc);
+ push(@Diff,$diff);
+ #
+ $ymin = $disc if ($ymin > $disc);
+ $ymin = $diff if ($ymin > $diff);
+ $ymax = $disc if ($ymax < $disc);
+ $ymax = $diff if ($ymax < $diff);
+ }
+ #
+ # Make sure we show relevant information.
+ if ($ymin < 0) {
+ if (abs($ymin) < 0.05) {
+ $ymin = 0;
+ } else {
+ $ymin = -1;
+ }
}
- $Ptr .= ' ';
- $Ptr .= ' 0) {
+ if (abs($ymax) < 0.05) {
+ $ymax = 0;
+ } else {
+ $ymax = 1;
+ }
}
- $Ptr .= ' ';
- $Ptr .= ' 50) {
+ if ($xmax % 10 != 0) {
+ $xmax = 10 * (int($xmax/10)+1);
+ }
} else {
- $Ptr .= 'value="Display CSV Format" />'."\n";
+ if ($xmax % 5 != 0) {
+ $xmax = 5 * (int($xmax/5)+1);
+ }
}
- $Ptr .= ' ';
-
- return $Ptr;
+ #
+ my $discdata .= ''.join(',',@Labels).' '.$/.
+ ''.join(',',@Disc).' '.$/;
+ #
+ my $diffdata .= ''.join(',',@Labels).' '.$/.
+ ''.join(',',@Diff).' '.$/;
+ #
+ my $title = &mt('Degree of Discrimination[_1]and Degree of Difficulty','\n');
+ if ($xmax > 50) {
+ $title = &mt('Degree of Discrimination and Degree of Difficulty');
+ }
+ my %lt = &Apache::lonlocal::texthash(
+ 'alttag' => 'Degree of Discrimination and Degree of Difficulty Plot',
+ 'xlabel' => 'Problem Number',
+ );
+ #
+ $plot=<<"END";
+
+
+ $title
+
+ $lt{'xlabel'}
+
+ $discdata
+
+
+ $diffdata
+
+
+END
+ my $plotresult =
+ ''.&Apache::lonxml::xmlparse($r,'web',$plot).'
'.$/;
+ $r->print($plotresult);
+ return;
}
-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 .= 'Mod ';
- $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 ExtractStudentData {
- my ($cache, $students)=@_;
-
- my @problemList=();
- my %problemData;
- foreach my $sequence (split(':', $cache->{'orderedSequences'})) {
- foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {
- foreach my $part (split(/\:/,$cache->{$sequence.':'.
- $problemID.
- ':parts'})) {
- my $id = $sequence.':'.$problemID.':'.$part;
- push(@problemList, $id);
- my $totalTries = 0;
- my $totalAwarded = 0;
- my $correct = 0;
- my $correctByOverride = 0;
- my $studentCount = 0;
- my $maxTries = 0;
- my $totalFirst = 0;
- my @studentTries=();
- foreach(@$students) {
- my $code = $cache->{"$_:$problemID:$part:code"};
-
- if(defined($cache->{$_.':error'}) || $code eq ' ' ||
- $cache->{"$_:$problemID:NoVersion"} eq 'true') {
- next;
- }
-
- $studentCount++;
- my $tries = $cache->{"$_:$problemID:$part:tries"};
- if($maxTries < $tries) {
- $maxTries = $tries;
- }
- $totalTries += $tries;
- push(@studentTries, $tries);
-
- my $awarded = $cache->{"$_:$problemID:$part:awarded"};
- $totalAwarded += $awarded;
+sub tries_data_plot {
+ my ($r)=@_;
+ my $count = scalar(@StatsArray);
+ my $width = 50 + 10*$count;
+ $width = 300 if ($width < 300);
+ my $height = 300;
+ my $plot = '';
+ my @STD; my @Mean; my @Max; my @Min;
+ my @Labels;
+ my $ymax = 5;
+ foreach my $data (@StatsArray) {
+ my $max = $data->{'mean_tries'} + $data->{'std_tries'};
+ $ymax = $max if ($ymax < $max);
+ $ymax = $max if ($ymax < $max);
+ push(@Labels,$data->{'problem_num'});
+ push(@STD,$data->{'std_tries'});
+ push(@Mean,$data->{'mean_tries'});
+ }
+ #
+ # Make sure we show relevant information.
+ my $xmax = $Labels[-1];
+ if ($xmax > 50) {
+ if ($xmax % 10 != 0) {
+ $xmax = 10 * (int($xmax/10)+1);
+ }
+ } else {
+ if ($xmax % 5 != 0) {
+ $xmax = 5 * (int($xmax/5)+1);
+ }
+ }
+ $ymax = int($ymax)+1+2;
+ #
+ my $std_data .= ''.join(',',@Labels).' '.$/.
+ ''.join(',',@Mean).' '.$/;
+ #
+ my $std_error_data .= ''.join(',',@Labels).' '.$/.
+ ''.join(',',@Mean).' '.$/.
+ ''.join(',',@STD).' '.$/;
+ #
+ my $title = &mt('Mean and S.D. of Tries');
+ if ($xmax > 30) {
+ $title = &mt('Mean and Standard Deviation of Tries');
+ }
+ #
+ my %lt = &Apache::lonlocal::texthash(
+ 'alttag' => 'Mean and S.D of Tries Plot',
+ 'xlabel' => 'Problem Number',
+ 'ylabel' => 'Number of Tries',
+ );
+ $plot=<<"END";
+
+ $title
+
+ $lt{'xlabel'}
+ $lt{'ylabel'}
+
+ $std_error_data
+
+
+ $std_data
+
+
+END
+ my $plotresult =
+ ''.&Apache::lonxml::xmlparse($r,'web',$plot).'
'.$/;
+ $r->print($plotresult);
+ return;
+}
- if($code eq '*') {
- $correct++;
- if($tries == 1) {
- $totalFirst++;
- }
- } elsif($code eq '+') {
- $correctByOverride++;
- }
- }
+sub plot_dropdown {
+ my $current = '';
+ my $title;
+ #
+ if (defined($env{'form.plot'})) {
+ $current = $env{'form.plot'};
+ }
+ #
+ my @Additional_Plots = (
+ { graphable=>'yes',
+ name => 'degrees',
+ title => 'Difficulty Indexes' },
+ { graphable=>'yes',
+ name => 'tries statistics',
+ title => 'Tries Statistics' });
+ #
+ my $Str= "\n".'';
+ $Str .= ' '."\n";
+ $Str .= ''.&mt('none').' '."\n";
+ foreach my $field (@Additional_Plots,@Fields) {
+ if (! exists($field->{'graphable'}) ||
+ $field->{'graphable'} ne 'yes') {
+ next;
+ }
+ $Str .= '{'name'} eq $current) {
+ $Str .= ' selected="selected"';
+ }
+ $title = &mt($field->{'long_title'});
+ $title = &mt($field->{'title'}) if (!$title);
+ $Str.= '>'.$title.' '."\n";
+ }
+ $Str .= ' '."\n";
+ return $Str;
+}
- my $studentTriesJoined = join(':::', @studentTries);
- $problemData{$id.':sequenceTitle'} =
- $cache->{$sequence.':title'};
- $problemData{$id.':studentCount'} = $studentCount;
- $problemData{$id.':totalTries'} = $totalTries;
- $problemData{$id.':studentTries'} = $studentTriesJoined;
- $problemData{$id.':totalAwarded'} = $totalAwarded;
- $problemData{$id.':correct'} = $correct;
- $problemData{$id.':correctByOverride'} = $correctByOverride;
- $problemData{$id.':wrong'} = $studentCount -
- ($correct + $correctByOverride);
- $problemData{$id.':maxTries'} = $maxTries;
- $problemData{$id.':totalFirst'} = $totalFirst;
+###############################################
+###############################################
+##
+## Excel output routines
+##
+###############################################
+###############################################
+sub Excel_output {
+ my ($r) = @_;
+ $r->print(''.&mt('Preparing Excel Spreadsheet').' ');
+ ##
+ ## Compute the statistics
+ &compute_all_statistics($r);
+ my $c = $r->connection;
+ return if ($c->aborted());
+ #
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
+ ##
+ ## Create the excel workbook
+ my ($excel_workbook,$filename,$format) =
+ &Apache::loncommon::create_workbook($r);
+ return if (! defined($excel_workbook));
+ #
+ # Add a worksheet
+ my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'};
+ if (length($sheetname) > 31) {
+ $sheetname = substr($sheetname,0,31);
+ }
+ my $excel_sheet = $excel_workbook->addworksheet(
+ &Apache::loncommon::clean_excel_name($sheetname));
+ ##
+ ## Begin creating excel sheet
+ ##
+ my ($rows_output,$cols_output) = (0,0);
+ #
+ # Put the course description in the header
+ $excel_sheet->write($rows_output,$cols_output++,
+ $env{'course.'.$env{'request.course.id'}.'.description'},
+ $format->{'h1'});
+ $cols_output += 3;
+ #
+ # Put a description of the sections listed
+ my $sectionstring = '';
+ $excel_sheet->write($rows_output,$cols_output++,
+ &Apache::lonstatistics::section_and_enrollment_description('plaintext'),
+ $format->{'h3'});
+ $cols_output += scalar(&Apache::lonstatistics::get_selected_sections());
+ $cols_output += scalar(&Apache::lonstatistics::get_selected_groups());
+ #
+ # Time restrictions
+ my $time_string;
+ if (defined($starttime)) {
+ if (defined($endtime)) {
+ $time_string .= &mt('Data collected from [_1] to [_2]',
+ &Apache::lonlocal::locallocaltime($starttime),
+ &Apache::lonlocal::locallocaltime($endtime));
+ } else {
+ $time_string .= &mt('Data collected from [_1]',
+ &Apache::lonlocal::locallocaltime($starttime));
+ }
+ } elsif (defined($endtime)) {
+ $time_string .= &mt('Data collected before [_1]',
+ &Apache::lonlocal::locallocaltime($endtime));
+ }
+ if (defined($time_string)) {
+ $excel_sheet->write($rows_output,$cols_output++,$time_string);
+ $cols_output+= 5;
+ }
+ #
+ # Put the date in there too
+ $excel_sheet->write($rows_output,$cols_output++,
+ &mt('Compiled on [_1]',&Apache::lonlocal::locallocaltime(time)));
+ #
+ $rows_output++;
+ $cols_output=0;
+ ##
+ ## Sequence Statistics
+ ##
+ &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
+ \@SeqFields);
+ foreach my $seq (@sequences) {
+ my $data = $SeqStat{$seq->symb};
+ $cols_output=0;
+ foreach my $field (@SeqFields) {
+ next if ($field->{'selected'} ne 'yes');
+ my $fieldformat = undef;
+ if (exists($field->{'excel_format'})) {
+ $fieldformat = $format->{$field->{'excel_format'}};
}
+ $excel_sheet->write($rows_output,$cols_output++,
+ $data->{$field->{'name'}},$fieldformat);
}
+ $rows_output++;
+ $cols_output=0;
}
-
- my @upperStudents1=();
- my @lowerStudents1=();
- my @upperStudents2=();
- my @lowerStudents2=();
- my $upperCount = int(0.27*scalar(@$students));
- # Discriminant Factor criterion 1
- my $sortedStudents = &SortDivideByTries($students,$cache,':totalAwarded');
-
- for(my $i=0; $i<$upperCount; $i++) {
- push(@lowerStudents1, $sortedStudents->[$i]);
- push(@upperStudents1, $sortedStudents->[(scalar(@$students)-$i-1)]);
+ ##
+ ## Resource Statistics
+ ##
+ $rows_output++;
+ $cols_output=0;
+ &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
+ \@Fields);
+ #
+ foreach my $data (@StatsArray) {
+ $cols_output=0;
+ foreach my $field (@Fields) {
+ next if ($field->{'selected'} ne 'yes');
+ next if ($field->{'name'} eq 'problem_num');
+ my $fieldformat = undef;
+ if (exists($field->{'excel_format'})) {
+ $fieldformat = $format->{$field->{'excel_format'}};
+ }
+ $excel_sheet->write($rows_output,$cols_output++,
+ $data->{$field->{'name'}},$fieldformat);
+ }
+ $rows_output++;
+ $cols_output=0;
}
+ #
+ $excel_workbook->close();
+ #
+ # Tell the user where to get their excel file
+ $r->print(' '.
+ ''.
+ &mt('Your Excel Spreadsheet').' '."\n");
+ $r->rflush();
+ return;
+}
- $problemData{'studentsUpperListCriterion1'}=join(':::', @upperStudents1);
- $problemData{'studentsLowerListCriterion1'}=join(':::', @lowerStudents1);
-
- # Discriminant Factor criterion 2
- $sortedStudents = &SortDivideByTries($students, $cache, ':totalSolved');
-
- for(my $i=0; $i<$upperCount; $i++) {
- push(@lowerStudents2, $sortedStudents->[$i]);
- push(@upperStudents2, $sortedStudents->[(scalar(@$students)-$i-1)]);
+##
+## &write_headers
+##
+sub write_headers {
+ my ($excel_sheet,$format,$rows_output,$cols_output,$Fields) = @_;
+ ##
+ ## First the long titles
+ foreach my $field (@{$Fields}) {
+ next if ($field->{'name'} eq 'problem_num');
+ next if ($field->{'selected'} ne 'yes');
+ if (exists($field->{'long_title'})) {
+ $excel_sheet->write($$rows_output,${$cols_output},
+ $field->{'long_title'},
+ $format->{'bold'});
+ } else {
+ $excel_sheet->write($$rows_output,${$cols_output},'');
+ }
+ ${$cols_output}+= 1;
}
- $problemData{'studentsUpperListCriterion2'}=join(':::', @upperStudents2);
- $problemData{'studentsLowerListCriterion2'}=join(':::', @lowerStudents2);
-
- $problemData{'problemList'} = join(':::', @problemList);
+ ${$cols_output} =0;
+ ${$rows_output}+=1;
+ ##
+ ## Then the short titles
+ foreach my $field (@{$Fields}) {
+ next if ($field->{'selected'} ne 'yes');
+ next if ($field->{'name'} eq 'problem_num');
+ # Use english for excel as I am not sure how well excel handles
+ # other character sets....
+ $excel_sheet->write($$rows_output,$$cols_output,
+ $field->{'title'},
+ $format->{'bold'});
+ $$cols_output+=1;
+ }
+ ${$cols_output} =0;
+ ${$rows_output}+=1;
+ return;
+}
- return \%problemData;
+##################################################
+##################################################
+##
+## Statistics Gathering and Manipulation Routines
+##
+##################################################
+##################################################
+sub compute_statistics_on_sequence {
+ my ($seq) = @_;
+ my @Data;
+ foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)) {
+ foreach my $part (@{$res->parts}) {
+ next if (($res->is_survey($part)) || ($res->is_anonsurvey($part))) ;
+ #
+ # This is where all the work happens
+ my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
+ push (@Data,$data);
+ push (@StatsArray,$data);
+ }
+ }
+ return @Data;
}
-sub SortDivideByTries {
- my ($toSort, $data, $sortOn)=@_;
- my @orderedData = sort { ($data->{$a.':totalTries'}) ?
- ($data->{$a.$sortOn}/$data->{$a.':totalTries'}):0
- <=>
- ($data->{$b.':totalTries'}) ?
- ($data->{$b.$sortOn}/$data->{$b.':totalTries'}):0
- } @$toSort;
+sub compute_all_statistics {
+ my ($r) = @_;
+ if (@StatsArray > 0) {
+ # Assume we have already computed the statistics
+ return;
+ }
+ my $c = $r->connection;
+ foreach my $seq (@sequences) {
+ last if ($c->aborted);
+ &compute_sequence_statistics($seq);
+ &compute_statistics_on_sequence($seq);
+ }
+}
- return \@orderedData;
+sub sort_data {
+ my ($sortkey) = @_;
+ return if (! @StatsArray);
+ #
+ # Sort the data
+ my $sortby = undef;
+ foreach my $field (@Fields) {
+ if ($sortkey eq $field->{'name'}) {
+ $sortby = $field->{'name'};
+ }
+ }
+ if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
+ $sortby = 'container';
+ }
+ if ($sortby ne 'container') {
+ # $sortby is already defined, so we can charge ahead
+ if ($sortby =~ /^(title|part)$/i) {
+ # Alpha comparison
+ @StatsArray = sort {
+ lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
+ lc($a->{'title'}) cmp lc($b->{'title'}) ||
+ lc($a->{'part'}) cmp lc($b->{'part'});
+ } @StatsArray;
+ } else {
+ # Numerical comparison
+ @StatsArray = sort {
+ my $retvalue = 0;
+ if ($b->{$sortby} eq 'nan') {
+ if ($a->{$sortby} ne 'nan') {
+ $retvalue = -1;
+ } else {
+ $retvalue = 0;
+ }
+ }
+ if ($a->{$sortby} eq 'nan') {
+ if ($b->{$sortby} ne 'nan') {
+ $retvalue = 1;
+ }
+ }
+ if ($retvalue eq '0') {
+ $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
+ lc($a->{'title'}) <=> lc($b->{'title'}) ||
+ lc($a->{'part'}) <=> lc($b->{'part'});
+ }
+ $retvalue;
+ } @StatsArray;
+ }
+ }
+ #
+ # Renumber the data set
+ my $count;
+ foreach my $data (@StatsArray) {
+ $data->{'problem_num'} = ++$count;
+ }
+ return;
}
-sub SortProblems {
- my ($problemData,$sortBy,$sortProblems,$ascend)=@_;
+########################################################
+########################################################
- my @problems = split(':::', $problemData->{'problemList'});
- if($sortBy eq "Homework Sets Order") {
- return \@problems;
- }
+=pod
- my $data;
+=item &get_statistics()
- if ($sortBy eq "#Stdnts") { $data = ':studentCount'; }
- elsif($sortBy eq "Tries") { $data = ':totalTries'; }
- elsif($sortBy eq "Mod") { $data = ':maxTries'; }
- elsif($sortBy eq "Mean") { $data = ':mean'; }
- elsif($sortBy eq "#YES") { $data = ':correct'; }
- elsif($sortBy eq "#yes") { $data = ':correctByOverride'; }
- elsif($sortBy eq "%Wrng") { $data = ':percentWrong'; }
- elsif($sortBy eq "DoDiff") { $data = ':degreeOfDifficulty'; }
- elsif($sortBy eq "S.D.") { $data = ':standardDeviation'; }
- elsif($sortBy eq "Skew.") { $data = ':skewness'; }
- elsif($sortBy eq "D.F.1st") { $data = ':discriminationFactor1'; }
- elsif($sortBy eq "D.F.2nd") { $data = ':discriminationFactor2'; }
- else { return \@problems; }
+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
+
+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 $data = &Apache::loncoursedata::get_problem_statistics
+ ([&Apache::lonstatistics::get_selected_sections()],
+ [&Apache::lonstatistics::get_selected_groups()],
+ $Apache::lonstatistics::enrollment_status,
+ $symb,$part,$courseid,$starttime,$endtime);
+ $data->{'symb'} = $symb;
+ $data->{'part'} = $part;
+ $data->{'problem_num'} = $problem_num;
+ $data->{'container'} = $sequence->compTitle;
+ $data->{'title'} = $resource->compTitle;
+ $data->{'title.link'} = $resource->src.'?symb='.
+ &escape($resource->symb);
+ #
+ if ($SelectedFields{'deg_of_disc'}) {
+ $data->{'deg_of_disc'} =
+ &compute_discrimination_factor($resource,$part,$sequence);
+ }
+ #
+ # Store in metadata if computations were done for all students
+ if ($data->{'num_students'} > 1) {
+ my @Sections = &Apache::lonstatistics::get_selected_sections();
+ my $sections = '"'.join(' ',@Sections).'"';
+ $sections =~ s/&+/_/g; # Ensure no special characters
+ $data->{'sections'}=$sections;
+ $data->{'course'} = $env{'request.course.id'};
+ my $urlres=(&Apache::lonnet::decode_symb($resource->symb))[2];
+ my %storestats =
+ &LONCAPA::lonmetadata::dynamic_metadata_storage($data);
+ my ($dom,$user) = ($urlres=~m{^($LONCAPA::domain_re)/($LONCAPA::username_re)});
+ &Apache::lonnet::put('nohist_resevaldata',\%storestats,$dom,$user);
+ }
+ #
+ $data->{'tries_per_correct'} = $data->{'tries'} /
+ ($data->{'num_solved'}+0.1);
+ #
+ # Get the due date for research purposes (commented out most of the time)
+# my $duedate = &Apache::lonnet::EXT('resource.'.$part.'.duedate',$symb);;
+# my $opendate = &Apache::lonnet::EXT('resource.'.$part.'.opendate',$symb);
+# my $maxtries = &Apache::lonnet::EXT('resource.'.$part.'.maxtries',$symb);
+# my $hinttries = &Apache::lonnet::EXT('resource.'.$part.'.hinttries',$symb);
+ my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',$symb);
+ $data->{'weight'} = $weight;
+# $data->{'duedate'} = $duedate;
+# $data->{'opendate'} = $opendate;
+# $data->{'maxtries'} = $maxtries;
+# $data->{'hinttries'} = $hinttries;
+# $data->{'resptypes'} = join(',',@{$resource->{'partdata'}->{$part}->{'ResponseTypes'}});
+ return $data;
+}
- my %temp;
- my @sequenceList=();
- foreach(@problems) {
- my ($sequence) = split(':', $_);
+###############################################
+###############################################
- my @array=();
- my $tempArray;
- if(defined($temp{$sequence})) {
- $tempArray = $temp{$sequence};
- } else {
- push(@sequenceList, $sequence);
- $tempArray = \@array;
- $temp{$sequence} = $tempArray;
- }
+=pod
- push(@$tempArray, $_);
- }
+=item &compute_discrimination_factor()
- my @orderedProblems;
- if($sortProblems eq "Sort Within Sequence") {
- foreach(keys(%temp)) {
- my $tempArray = $temp{$_};
- my @tempOrder =
- sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
- @$tempArray;
- $temp{$_} = \@tempOrder;
- }
- foreach(@sequenceList) {
- my $tempArray = $temp{$_};
- @orderedProblems = (@orderedProblems, @$tempArray);
- }
+Inputs: $Resource, $Sequence
+
+Returns: integer between -1 and 1
+
+=cut
+
+###############################################
+###############################################
+sub compute_discrimination_factor {
+ my ($resource,$part,$seq) = @_;
+ my $symb = $resource->symb;
+ my @Resources;
+ foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)){
+ next if ($res->symb eq $symb);
+ push (@Resources,$res->symb);
+ }
+ #
+ # rank
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
+ my $ranking =
+ &Apache::loncoursedata::rank_students_by_scores_on_resources
+ (\@Resources,
+ [&Apache::lonstatistics::get_selected_sections()],
+ [&Apache::lonstatistics::get_selected_groups()],
+ $Apache::lonstatistics::enrollment_status,undef,
+ $starttime,$endtime, $symb);
+ #
+ # 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}[-$number_to_grab..0];
+ if (! @BottomSet || (@BottomSet == 1 && $BottomSet[0] eq '') ||
+ ! @TopSet || (@TopSet == 1 && $TopSet[0] eq '')) {
+ return 'nan';
+ }
+ my ($bottom_sum,$bottom_max) =
+ &Apache::loncoursedata::get_sum_of_scores($symb,$part,\@BottomSet,
+ undef,$starttime,$endtime);
+ my ($top_sum,$top_max) =
+ &Apache::loncoursedata::get_sum_of_scores($symb,$part,\@TopSet,
+ undef,$starttime,$endtime);
+ my $deg_of_disc;
+ if ($top_max == 0 || $bottom_max==0) {
+ $deg_of_disc = 'nan';
} else {
- @orderedProblems =
- sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
- @problems;
+ $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;
+}
- if($ascend eq 'Descending') {
- @orderedProblems = reverse(@orderedProblems);
+###############################################
+###############################################
+##
+## Compute KR-21
+##
+## To compute KR-21, you need the following information:
+##
+## K=the number of items in your test
+## M=the mean score on the test
+## s=the standard deviation of the scores on your test
+##
+## then:
+##
+## KR-21 rk= [K/(K-1)] * [1- (M*(K-M))/(K*s^2))]
+##
+###############################################
+###############################################
+sub compute_sequence_statistics {
+ my ($seq) = @_;
+ my $symb = $seq->symb;
+ my @Resources;
+ my $part_count;
+ foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)) {
+ push (@Resources,$res->symb);
+ $part_count += scalar(@{$res->parts});
+ }
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
+ #
+ # First compute statistics based on student scores
+ my ($smin,$smax,$sMean,$sSTD,$scount,$sMAX) =
+ &Apache::loncoursedata::score_stats
+ ([&Apache::lonstatistics::get_selected_sections()],
+ [&Apache::lonstatistics::get_selected_groups()],
+ $Apache::lonstatistics::enrollment_status,
+ \@Resources,$starttime,$endtime,undef);
+ $SeqStat{$symb}->{'title'} = $seq->compTitle;
+ $SeqStat{$symb}->{'scoremax'} = $smax;
+ $SeqStat{$symb}->{'scoremin'} = $smin;
+ $SeqStat{$symb}->{'scoremean'} = $sMean;
+ $SeqStat{$symb}->{'scorestd'} = $sSTD;
+ $SeqStat{$symb}->{'scorecount'} = $scount;
+ $SeqStat{$symb}->{'max_possible'} = $sMAX;
+ #
+ # Compute statistics based on the number of correct problems
+ # 'correct' is taken to mean
+ my ($cmin,$cmax,$cMean,$cSTD,$ccount)=
+ &Apache::loncoursedata::count_stats
+ ([&Apache::lonstatistics::get_selected_sections()],
+ [&Apache::lonstatistics::get_selected_groups()],
+ $Apache::lonstatistics::enrollment_status,
+ \@Resources,$starttime,$endtime,undef);
+ my $K = $part_count;
+ my $kr_21;
+ if ($K > 1 && $cSTD > 0) {
+ $kr_21 = ($K/($K-1)) * (1 - $cMean*($K-$cMean)/($K*$cSTD**2));
+ } else {
+ $kr_21 = 'nan';
}
-
- return \@orderedProblems;
+ $SeqStat{$symb}->{'countmax'} = $cmax;
+ $SeqStat{$symb}->{'countmin'} = $cmin;
+ $SeqStat{$symb}->{'countstd'} = $cSTD;
+ $SeqStat{$symb}->{'countmean'} = $cMean;
+ $SeqStat{$symb}->{'count'} = $ccount;
+ $SeqStat{$symb}->{'items'} = $K;
+ $SeqStat{$symb}->{'KR-21'}=$kr_21;
+ return;
}
-sub CalculateStatistics {
- my ($data, $cache, $courseID)=@_;
- my @problems = split(':::', $data->{'problemList'});
- foreach(@problems) {
- # Mean
- my $mean = ($data->{$_.':studentCount'}) ?
- ($data->{$_.':totalTries'} / $data->{$_.':studentCount'}) : 0;
- $data->{$_.':mean'} = sprintf("%.2f", $mean);
- # %Wrong
- my $pw = ($data->{$_.':studentCount'}) ?
- (($data->{$_.':wrong'} / $data->{$_.':studentCount'}) * 100.0) :
- 100.0;
- $data->{$_.':percentWrong'} = sprintf("%.1f", $pw);
+=pod
- # Degree of Difficulty
- my $dod = ($data->{$_.':totalTries'}) ?
- (1 - (($data->{$_.':correct'} + $data->{$_.':correctByOverride'}) /
- $data->{$_.':totalTries'})) : 0;
+=item ProblemStatisticsLegend
- $data->{$_.':degreeOfDifficulty'} = sprintf("%.2f", $dod);
+=over 4
- # Factor in mean
- my @studentTries = split(':::', $data->{$_.':studentTries'});
- foreach(my $index=0; $index < scalar(@studentTries); $index++) {
- $studentTries[$index] -= $mean;
- }
- my $sumSquared = 0;
- my $sumCubed = 0;
- foreach(@studentTries) {
- my $squared = ($_ * $_);
- my $cubed = ($squared * $_);
- $sumSquared += $squared;
- $sumCubed += $cubed;
- }
+=item #Stdnts
+Total number of students attempted the problem.
- # Standard deviation
- my $standardDeviation;
- if($data->{$_.':studentCount'} - 1 > 0) {
- $standardDeviation = (sqrt($sumSquared)) /
- ($data->{$_.':studentCount'} - 1);
- } else {
- $standardDeviation = 0.0;
- }
- $data->{$_.':standardDeviation'} = sprintf("%.1f", $standardDeviation);
+=item Tries
+Total number of tries for solving the problem.
- # Skewness
- my $skew;
- if($standardDeviation > 0.0999 && $data->{$_.':studentCount'} > 0) {
- $skew = (((sqrt($sumSquared)) / $data->{$_.':studentCount'}) /
- ($standardDeviation *
- $standardDeviation *
- $standardDeviation));
- } else {
- $skew = 0.0;
- }
+=item Max Tries
+Largest number of tries for solving the problem by a student.
- $data->{$_.':skewness'} = sprintf("%.1f", $skew);
+=item Mean
+Average number of tries. [ Tries / #Stdnts ]
- # Discrimination Factor 1
- my ($sequence, $problem, $part) = split(':', $_);
+=item #YES
+Number of students solved the problem correctly.
- my @upper1 = split(':::', $data->{'studentsUpperListCriterion1'});
- my @lower1 = split(':::', $data->{'studentsLowerListCriterion1'});
+=item #yes
+Number of students solved the problem by override.
- my $upper1Sum=0;
- foreach my $name (@upper1) {
- $upper1Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $upper1Sum = (scalar(@upper1)) ? ($upper1Sum/(scalar(@upper1))) : 0;
+=item %Wrong
+Percentage of students who tried to solve the problem
+but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
- my $lower1Sum=0;
- foreach my $name (@lower1) {
- $lower1Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $lower1Sum = (scalar(@lower1)) ? ($lower1Sum/(scalar(@lower1))) : 0;
+=item DoDiff
+Degree of Difficulty of the problem.
+[ 1 - ((#YES+#yes) / Tries) ]
- my $df1 = $upper1Sum - $lower1Sum;
- $data->{$_.':discriminationFactor1'} = sprintf("%.2f", $df1);
+=item S.D.
+Standard Deviation of the tries.
+[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
+where Xi denotes every student\'s tries ]
- # Discrimination Factor 2
- my @upper2 = split(':::', $data->{'studentsUpperListCriterion2'});
- my @lower2 = split(':::', $data->{'studentsLowerListCriterion2'});
+=item Skew.
+Skewness of the students tries.
+[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
- my $upper2Sum=0;
- foreach my $name (@upper2) {
- $upper2Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $upper2Sum = (scalar(@upper2)) ? ($upper2Sum/(scalar(@upper2))) : 0;
+=item Dis.F.
+Discrimination Factor: A Standard for evaluating the
+problem according to a Criterion
- my $lower2Sum=0;
- foreach my $name (@lower2) {
- $lower2Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $lower2Sum = (scalar(@lower2)) ? ($lower2Sum/(scalar(@lower2))) : 0;
+=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
- my $df2 = $upper2Sum - $lower2Sum;
- $data->{$_.':discriminationFactor2'} = sprintf("%.2f", $df2);
+=item Disc.
+Number of Students had at least one discussion.
- my %storestats;
- my $Average = ($data->{$_.':studentCount'}) ?
- $data->{$_.':totalTries'}/$data->{$_.':studentCount'} : 0;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___timestamp'}=time;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___stdno'}=$data->{$_.':studentCount'};
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___avetries'}=$Average;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___difficulty'}=$data->{$_.':degreeOfDifficulty'};
- $cache->{$sequence.':source'} =~ /^(\w+)\/(\w+)/;
- if($data->{$_.':studentCount'}) {
- &Apache::lonnet::put('nohist_resevaldata',\%storestats,$1,$2);
- }
- }
+=back
- return;
-}
+=cut
-#---- END Problem Statistics Web Page ----------------------------------------
+############################################################
+############################################################
1;
__END__