--- loncom/interface/statistics/lonproblemstatistics.pm 2002/09/02 20:21:43 1.32 +++ loncom/interface/statistics/lonproblemstatistics.pm 2011/01/17 00:19:41 1.119 @@ -1,7 +1,6 @@ # The LearningOnline Network with CAPA -# (Publication Handler # -# $Id: lonproblemstatistics.pm,v 1.32 2002/09/02 20:21:43 www Exp $ +# $Id: lonproblemstatistics.pm,v 1.119 2011/01/17 00:19:41 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,799 +25,1796 @@ # 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. +## +# +# +## +## 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. NOT translated +## 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"; +} - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $r->print('Unable to tie database.5'); - return ('ERROR', undef); - } +############################################### +############################################### + +=pod - my $orderedProblems = &SortProblems(\%cache, - $cache{'ProblemStatisticsSort'}, - $cache{'SortProblems'}, - $cache{'ProblemStatisticsAscend'}); - untie(%cache); +=item &CreateInterface() - return ('OK', $orderedProblems); +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 .= '
'; + $Str .= &Apache::loncommon::start_data_table(); + $Str .= &Apache::loncommon::start_data_table_header_row(); + $Str .= '
' + .&mt('Status: [_1]', + '') + .'
'; + # + $Str .= ''; + $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'); - return; - } - my $Ptr = ''; - $Ptr .= 'Select Map | '."\n"; - $Ptr .= ''; - $Ptr .= &Apache::lonhtmlcommon::MapOptions(\%cache, 'Statistics', - 'Statistics'); - $Ptr .= ' |
Sorting Type: | '."\n"; - $Ptr .= ''."\n"; - $Ptr .= &Apache::lonhtmlcommon::AscendOrderOptions( - $cache{'ProblemStatisticsAscend'}, - 'ProblemStatistics', - 'Statistics'); - $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 .= ' |
'. + &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; } - - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - return 'Unable to tie database.7'; + $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(); + # + ($navmap,@sequences) = + &Apache::lonstatistics::selected_sequences_with_assessments(); + if (! ref($navmap)) { + $r->print(''."\n".
+ ' |