'."\n";
- return $Str;
-}
+lonproblemstatistics
-#######################################################
-#######################################################
+=head1 SYNOPSIS
-=pod
+Routines to present problem statistics to instructors via tables,
+Excel files, and plots.
-=item &CreateAndParseOutputSelector()
+=over 4
=cut
-#######################################################
-#######################################################
-my $output_mode;
-my $show;
-
-my @OutputOptions =
- (
- { name => 'problem statistics grouped by sequence',
- value => 'HTML problem statistics grouped',
- description => 'Output statistics for the problem parts.',
- mode => 'html',
- show => 'grouped',
- },
- { name => 'problem statistics ungrouped',
- value => 'HTML problem statistics ungrouped',
- description => 'Output statistics for the problem parts.',
- mode => 'html',
- show => 'ungrouped',
- },
- { name => 'problem statistics, Excel',
- value => 'Excel problem statistics',
- description => 'Output statistics for the problem parts '.
- 'in an Excel workbook',
- mode => 'excel',
- show => 'all',
- },
- { name => 'Degree of Difficulty Plot',
- value => 'plot deg diff',
- description => 'Generate a plot of the degree of difficulty of each '.
- 'problem part.',
- mode => 'plot',
- show => 'deg of diff',
- },
- { name => 'Percent Wrong Plot',
- value => 'plot per wrong',
- description => 'Generate a plot showing the percent of students who '.
- 'were unable to complete each problem part',
- mode => 'plot',
- show => 'per wrong',
- },
- );
+###############################################
+###############################################
-sub OutputDescriptions {
- my $Str = '';
- $Str .= "
Output Modes
\n";
- $Str .= "
\n";
- foreach my $outputmode (@OutputOptions) {
- $Str .="
".$outputmode->{'name'}."
\n";
- $Str .="
".$outputmode->{'description'}."
\n";
- }
- $Str .= "
\n";
- return $Str;
-}
+package Apache::lonproblemstatistics;
-sub CreateAndParseOutputSelector {
- my $Str = '';
- my $elementname = 'outputmode';
+use strict;
+use Apache::lonnet;
+use Apache::loncommon();
+use Apache::lonquickgrades();
+use Apache::lonhtmlcommon;
+use Apache::loncoursedata;
+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'});
+ }
+ }
+ }
#
- # Format for output options is 'mode, restrictions';
- my $selected = 'html, with links';
- if (exists($ENV{'form.'.$elementname})) {
- if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
- $selected = $ENV{'form.'.$elementname}->[0];
+ # 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 {
- $selected = $ENV{'form.'.$elementname};
+ $NeededFields{$env{'form.plot'}}++;
}
}
#
- # Set package variables describing output mode
- $output_mode = 'html';
- $show = 'all';
- foreach my $option (@OutputOptions) {
- next if ($option->{'value'} ne $selected);
- $output_mode = $option->{'mode'};
- $show = $option->{'show'};
+ # 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'}}++;
+ }
+ }
}
#
- # Build the form element
- $Str = qq/";
- return $Str;
+ return;
+}
+
+sub field_selection_input {
+ my $Str = '\n";
}
###############################################
###############################################
+=pod
+
+=item &CreateInterface()
+
+Create the main intereface for the statistics page. Allows the user to
+select sections, maps, and output.
+
+=cut
+
###############################################
###############################################
-sub Gather_Student_Data {
+sub CreateInterface {
my ($r) = @_;
- my $c = $r->connection();
#
- my @Sequences = &Apache::lonstatistics::Sequences_with_Assess();
+ &parse_field_selection();
#
- my @Students = @Apache::lonstatistics::Students;
+ my $Str = '';
+ $Str .= '
'.
+ &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;
+ }
+ $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 ($output_mode eq 'html') {
- $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;
+}
+
+
###############################################
###############################################
-
+##
+## Misc HTML output routines
+##
###############################################
###############################################
sub statistics_html_table_data {
- my ($resource,$part,$num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent,
- $DegOfDiff,$STD,$SKEW) = @_;
+ my ($data,$options) = @_;
my $row = '';
- $row .= '
';
}
return $row;
}
+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 .= '