--- loncom/interface/statistics/lonproblemstatistics.pm 2004/03/29 19:45:55 1.77
+++ loncom/interface/statistics/lonproblemstatistics.pm 2005/03/03 18:56:26 1.98
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonproblemstatistics.pm,v 1.77 2004/03/29 19:45:55 matthew Exp $
+# $Id: lonproblemstatistics.pm,v 1.98 2005/03/03 18:56:26 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -55,12 +55,14 @@ use Apache::loncommon();
use Apache::lonhtmlcommon;
use Apache::loncoursedata;
use Apache::lonstatistics;
+use LONCAPA::lonmetadata();
use Apache::lonlocal;
use Spreadsheet::WriteExcel;
use Apache::lonstathelpers();
use Time::HiRes;
my @StatsArray;
+my %SeqStat; # keys are symbs, values are hash refs
##
## Localization notes:
@@ -69,13 +71,42 @@ my @StatsArray;
## 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',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'container',
title => 'Sequence or Folder',
@@ -83,7 +114,7 @@ my @Fields = (
color => '#FFFFE6',
sortable => 'yes',
selectable => 'no',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'title',
title => 'Title',
@@ -92,14 +123,14 @@ my @Fields = (
special => 'link',
sortable => 'yes',
selectable => 'no',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'part',
title => 'Part',
align => 'left',
color => '#FFFFE6',
selectable => 'no',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'num_students',
title => '#Stdnts',
@@ -110,7 +141,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Number of Students Attempting Problem',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'tries',
title => 'Tries',
@@ -121,7 +152,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Total Number of Tries',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'max_tries',
title => 'Max Tries',
@@ -132,7 +163,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Maximum Number of Tries',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'min_tries',
title => 'Min Tries',
@@ -143,7 +174,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Minumum Number of Tries',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'mean_tries',
title => 'Mean Tries',
@@ -154,7 +185,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Average Number of Tries',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'std_tries',
title => 'S.D. tries',
@@ -165,7 +196,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Standard Deviation of Number of Tries',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'skew_tries',
title => 'Skew Tries',
@@ -176,7 +207,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Skew of Number of Tries',
selectable => 'yes',
- selected => 'no',
+ defaultselected => 'no',
},
{ name => 'num_solved',
title => '#YES',
@@ -187,7 +218,7 @@ my @Fields = (
graphable => 'yes',
long_title => 'Number of Students able to Solve',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'num_override',
title => '#yes',
@@ -198,7 +229,18 @@ my @Fields = (
graphable => 'yes',
long_title => 'Number of Students given Override',
selectable => 'yes',
- selected => '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',
@@ -207,9 +249,20 @@ my @Fields = (
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',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'deg_of_diff',
title => 'DoDiff',
@@ -221,7 +274,7 @@ my @Fields = (
long_title => 'Degree of Difficulty'.
'[ 1 - ((#YES+#yes) / Tries) ]',
selectable => 'yes',
- selected => 'yes',
+ defaultselected => 'yes',
},
{ name => 'deg_of_disc',
title => 'DoDisc',
@@ -232,8 +285,230 @@ my @Fields = (
graphable => 'yes',
long_title => 'Degree of Discrimination',
selectable => 'yes',
- selected => 'no',
+ 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 => 'no',
+ 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;
@@ -245,12 +520,28 @@ sub parse_field_selection {
$ENV{'form.fieldselections'} = [];
foreach my $field (@Fields) {
next if ($field->{'selectable'} ne 'yes');
- if ($field->{'selected'} eq 'yes') {
+ if ($field->{'defaultselected'} eq 'yes') {
push(@{$ENV{'form.fieldselections'}},$field->{'name'});
}
}
}
#
+ # 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'}}++;
+ }
+ }
+ #
# This should not happen, but in case it does...
if (ref($ENV{'form.fieldselections'}) ne 'ARRAY') {
$ENV{'form.fieldselections'} = [$ENV{'form.fieldselections'}];
@@ -259,8 +550,15 @@ sub parse_field_selection {
# Set the field data and the selected fields (for easier checking)
undef(%SelectedFields);
foreach my $field (@Fields) {
- next if ($field->{'selectable'} ne 'yes');
- $field->{'selected'} = 'no';
+ 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';
@@ -268,6 +566,11 @@ sub parse_field_selection {
}
}
}
+ #
+ # Always show all the sequence statistics (for now)
+ foreach my $field (@SeqFields) {
+ $field->{'selected'} = 'yes';
+ }
return;
}
@@ -300,7 +603,10 @@ select sections, maps, and output.
###############################################
###############################################
sub CreateInterface {
+ my ($r) = @_;
+ #
&parse_field_selection();
+ #
my $Str = '';
$Str .= &Apache::lonhtmlcommon::breadcrumbs
(undef,'Overall Problem Statistics','Statistics_Overall_Key');
@@ -320,32 +626,22 @@ sub CreateInterface {
$Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
$Str .= '
';
#
- my $only_seq_with_assessments = sub {
- my $s=shift;
- if ($s->{'num_assess'} < 1) {
- return 0;
- } else {
- return 1;
- }
- };
- $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
- $only_seq_with_assessments);
+ $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5);
$Str .= ' | '.&field_selection_input();
$Str .= ' | '."\n";
$Str .= ''."\n";
+ #
+ $Str .= ''.&mt('Status: [_1]',
+ ''
+ ).
+ '
';
+ #
$Str .= '';
$Str .= ' 'x5;
$Str .= 'Plot '.&plot_dropdown().(' 'x10);
- $Str .= '';
- $Str .= ' 'x5;
- $Str .= '';
- $Str .= ' 'x5;
- $Str .= '';
- $Str .= ' 'x5;
+ #
return $Str;
}
@@ -362,8 +658,13 @@ Main interface to problem statistics.
###############################################
###############################################
+my $navmap;
+my @sequences;
+
sub BuildProblemStatisticsPage {
my ($r,$c)=@_;
+ undef($navmap);
+ undef(@sequences);
#
my %Saveable_Parameters = ('Status' => 'scalar',
'statsoutputmode' => 'scalar',
@@ -380,15 +681,23 @@ sub BuildProblemStatisticsPage {
#
# Clear the package variables
undef(@StatsArray);
+ undef(%SeqStat);
#
# Finally let the user know we are here
- my $interface = &CreateInterface();
+ my $interface = &CreateInterface($r);
$r->print($interface);
$r->print('');
#
- if (! exists($ENV{'form.statsfirstcall'})) {
- $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.').
'
'.
@@ -397,13 +706,6 @@ sub BuildProblemStatisticsPage {
' will not have this delay.').
'
');
return;
- } elsif ($ENV{'form.statsfirstcall'} eq 'yes' ||
- exists($ENV{'form.UpdateCache'}) ||
- exists($ENV{'form.ClearCache'}) ) {
- $r->print('');
- &Apache::lonstatistics::Gather_Student_Data($r);
- } else {
- $r->print('');
}
$r->rflush();
#
@@ -413,10 +715,22 @@ sub BuildProblemStatisticsPage {
#
if (exists($ENV{'form.Excel'})) {
&Excel_output($r);
- } else {
+ } else {
+ $r->print(''.' 'x5);
+ $r->rflush();
my $count = 0;
- foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
- $count += $seq->{'num_assess'};
+ ($navmap,@sequences) =
+ &Apache::lonstatistics::selected_sequences_with_assessments();
+ if (! ref($navmap)) {
+ $r->print(''.&mt('A course-wide error occured.').'
'.
+ ''.$navmap.'
');
+ return;
+ }
+ foreach my $seq (@sequences) {
+ my @resources =
+ &Apache::lonstathelpers::get_resources($navmap,$seq);
+ $count += scalar(@resources);
}
if ($count > 10) {
$r->print(''.
@@ -435,17 +749,41 @@ sub BuildProblemStatisticsPage {
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);
}
}
return;
}
+sub output_sequence_statistics {
+ my ($r) = @_;
+ my $c=$r->connection();
+ $r->print(''.&mt('Sequence Statistics').
+ &Apache::loncommon::help_open_topic('Statistics_Sequence').
+ '
');
+ $r->print('
'."\n".
+ ''."\n".
+ '');
+ $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(' ');
+ $r->print(' |
');
+ $r->rflush();
+ return;
+}
+
+
##########################################################
##########################################################
##
@@ -458,10 +796,9 @@ sub output_html_by_sequence {
my $c = $r->connection();
$r->print(&html_preamble());
#
- foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+ foreach my $seq (@sequences) {
last if ($c->aborted);
- next if ($seq->{'num_assess'} < 1);
- $r->print("".$seq->{'title'}."
".
+ $r->print("".$seq->compTitle."
".
''."\n".
''."\n".
''.
@@ -541,7 +878,7 @@ sub statistics_html_table_data {
if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
$row .= '';
}
- if (exists($field->{'format'})) {
+ if (exists($field->{'format'}) && $data->{$field->{'name'}} !~ /[A-Z]/i) {
$row .= sprintf($field->{'format'},$data->{$field->{'name'}});
} else {
$row .= $data->{$field->{'name'}};
@@ -584,6 +921,40 @@ sub statistics_table_header {
return $header_row;
}
+sub sequence_html_header {
+ my $Str .= '';
+ foreach my $field (@SeqFields) {
+# next if ($field->{'selected'} ne 'yes');
+ $Str .= '{'title'}.' | ';
+ }
+ $Str .= ' ';
+ return $Str;
+}
+
+
+sub sequence_html_output {
+ my ($seq) = @_;
+ my $data = $SeqStat{$seq->symb};
+ my $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 .= ' '."\n";
+ return $row;
+}
+
####################################################
####################################################
##
@@ -711,6 +1082,11 @@ sub degrees_plot {
my $diffdata .= ''.join(',',@Labels).''.$/.
''.join(',',@Diff).''.$/;
#
+ my $title = 'Degree of Discrimination\nand Degree of Difficulty';
+ if ($xmax > 50) {
+ $title = 'Degree of Discrimination and Degree of Difficulty';
+ }
+ #
$plot=<<"END";
- Degree of Discrmination and Degree of Difficulty
+ $title
Problem Number
'.join(',',@Mean).''.$/.
''.join(',',@STD).''.$/;
#
+ my $title = 'Mean and S.D. of Tries';
+ if ($xmax > 25) {
+ $title = 'Mean and Standard Deviation of Tries';
+ }
+ #
$plot=<<"END";
- Mean and S.D. of Tries
+ $title
Problem Number
+ Number of Tries
'yes',
name => 'degrees',
- title => 'DoDisc and DoDiff' },
+ title => 'Difficulty Indexes' },
{ graphable=>'yes',
name => 'tries statistics',
- title => 'Mean and S.D. of Tries' });
+ title => 'Tries Statistics' });
#
my $Str= "\n".' |