--- loncom/interface/statistics/lonproblemstatistics.pm 2004/03/23 16:35:15 1.71 +++ loncom/interface/statistics/lonproblemstatistics.pm 2011/01/17 00:19:41 1.119 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemstatistics.pm,v 1.71 2004/03/23 16:35:15 matthew Exp $ +# $Id: lonproblemstatistics.pm,v 1.119 2011/01/17 00:19:41 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,15 +50,23 @@ Excel files, and plots. package Apache::lonproblemstatistics; use strict; -use Apache::lonnet(); +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: ## @@ -66,27 +74,67 @@ use Time::HiRes; ## 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' }, + color => '#FFFFE6', + selectable => 'no', + defaultselected => 'yes', + }, { name => 'container', title => 'Sequence or Folder', align => 'left', color => '#FFFFE6', - sortable => 'yes' }, + sortable => 'yes', + selectable => 'no', + defaultselected => 'yes', + }, { name => 'title', title => 'Title', align => 'left', color => '#FFFFE6', special => 'link', - sortable => 'yes', }, - { name => 'part', + 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', @@ -94,7 +142,10 @@ my @Fields = ( format => '%d', sortable => 'yes', graphable => 'yes', - long_title => 'Number of Students Attempting Problem' }, + long_title => 'Number of Students Attempting Problem', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'tries', title => 'Tries', align => 'right', @@ -102,7 +153,10 @@ my @Fields = ( format => '%d', sortable => 'yes', graphable => 'yes', - long_title => 'Total Number of Tries' }, + long_title => 'Total Number of Tries', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'max_tries', title => 'Max Tries', align => 'right', @@ -110,7 +164,21 @@ my @Fields = ( format => '%d', sortable => 'yes', graphable => 'yes', - long_title => 'Maximum Number of Tries' }, + 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', @@ -118,7 +186,10 @@ my @Fields = ( format => '%5.2f', sortable => 'yes', graphable => 'yes', - long_title => 'Average Number of Tries' }, + long_title => 'Average Number of Tries', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'std_tries', title => 'S.D. tries', align => 'right', @@ -126,7 +197,10 @@ my @Fields = ( format => '%5.2f', sortable => 'yes', graphable => 'yes', - long_title => 'Standard Deviation of Number of Tries' }, + long_title => 'Standard Deviation of Number of Tries', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'skew_tries', title => 'Skew Tries', align => 'right', @@ -134,16 +208,10 @@ my @Fields = ( format => '%5.2f', sortable => 'yes', graphable => 'yes', - long_title => 'Skew of Number of Tries' }, - { name => 'deg_of_diff', - title => 'DoDiff', - align => 'right', - color => '#DDFFFF', - format => '%5.2f', - sortable => 'yes', - graphable => 'yes', - long_title => 'Degree of Difficulty'. - '[ 1 - ((#YES+#yes) / Tries) ]'}, + long_title => 'Skew of Number of Tries', + selectable => 'yes', + defaultselected => 'no', + }, { name => 'num_solved', title => '#YES', align => 'right', @@ -151,7 +219,10 @@ my @Fields = ( format => '%4.1f',# format => '%d', sortable => 'yes', graphable => 'yes', - long_title => 'Number of Students able to Solve' }, + long_title => 'Number of Students able to Solve', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'num_override', title => '#yes', align => 'right', @@ -159,26 +230,367 @@ my @Fields = ( format => '%4.1f',# format => '%d', sortable => 'yes', graphable => 'yes', - long_title => 'Number of Students given Override' }, + 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 => '#FFFFE6', + color => '#FFDDDD', format => '%4.1f', sortable => 'yes', graphable => 'yes', - long_title => 'Percent of students whose final answer is wrong' }, + 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 => 'Deg of Disc', + title => 'DoDisc', align => 'right', color => '#FFFFE6', format => '%4.2f', sortable => 'yes', graphable => 'yes', - long_title => 'Degree of Discrimination' }, + 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'}); + } + } + } + # + # 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'}]; + } + # + # 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'}}++; + } + } + } + # + # Always show all the sequence statistics (for now) + foreach my $field (@SeqFields) { + $field->{'selected'} = 'yes'; + } + return; +} + +sub field_selection_input { + my $Str = '\n"; +} + ############################################### ############################################### @@ -193,75 +605,57 @@ select sections, maps, and output. ############################################### ############################################### -my @OutputOptions = - ( - { name => 'grouped by sequence', - value => 'HTML problem statistics grouped', - description => 'Output statistics for the problem parts.', - mode => 'html', - show => 'grouped', - }, - { name => 'ungrouped', - value => 'HTML problem statistics ungrouped', - description => 'Output statistics for the problem parts.', - mode => 'html', - show => 'ungrouped', - }, - { name => 'Excel', - value => 'Excel problem statistics', - description => 'Output statistics for the problem parts '. - 'in an Excel workbook', - mode => 'excel', - show => 'all', - }, - ); - sub CreateInterface { + my ($r) = @_; + # + &parse_field_selection(); + # my $Str = ''; - $Str .= &Apache::lonhtmlcommon::breadcrumbs - (undef,'Overall Problem Statistics','Statistics_Overall_Key'); - $Str .= '
'.&mt('Sections').' | '; - $Str .= ''.&mt('Enrollment Status').' | '; - $Str .= ''.&mt('Sequences and Folders').' | '; - $Str .= ''.&mt('Output').' | '; - $Str .= ''. - &Apache::lonstathelpers::limit_by_time_form().' | '; - $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 .= '
---|---|---|---|---|---|---|---|---|---|---|
'."\n"; + $Str .= &Apache::loncommon::start_data_table_row(); + $Str .= ' | '."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); - $Str .= ' | '; + $Str .= ' | '; + $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5); + $Str .= ' | '; $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); - $Str .= ' | '; + $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 .= ''."\n"; - my ($html,$outputmode,$show) = - &Apache::lonstatistics::CreateAndParseOutputSelector( - 'statsoutputmode', - 'HTML problem statistics grouped', - @OutputOptions); - $Str .= $html; - $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(); # - &Apache::lonstatistics::Gather_Student_Data($r); - # + # 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".
+ ' |
'."\n".
+ ' |
'."\n".
+ ' |
'."\n");
- $r->print('
|
".&Apache::loncommon::DrawBarGraph($title, + 'Problem Number', + $yaxis, + $max, + undef, # colors + \@Labels, + \@Data)."
\n"); + return; +} + +sub degrees_plot { + my ($r)=@_; + my $count = scalar(@StatsArray); + my $width = 50 + 10*$count; + $width = 300 if ($width < 300); + my $height = 300; + my $plot = ''; + my $ymax = 0; + my $ymin = 0; + my @Disc; my @Diff; my @Labels; + foreach my $data (@StatsArray) { + push(@Labels,$data->{'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; + } + } + if ($ymax > 0) { + if (abs($ymax) < 0.05) { + $ymax = 0; + } else { + $ymax = 1; } } # - # Sort the data - my @OutputOrder; - if ($sortby eq 'container') { - @OutputOrder = @Statsarray; + my $xmax = $Labels[-1]; + if ($xmax > 50) { + if ($xmax % 10 != 0) { + $xmax = 10 * (int($xmax/10)+1); + } } else { - # $sortby is already defined, so we can charge ahead - if ($sortby =~ /^(title|part)$/i) { - # Alpha comparison - @OutputOrder = 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 - @OutputOrder = 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; + if ($xmax % 5 != 0) { + $xmax = 5 * (int($xmax/5)+1); } } - $option .= 'no part' if (! $show_part); - my $num_output = 0; # - # output the headers - $r->print(''."\n");
- $r->print(' |
'."\n");
- $r->print(' |
'.&Apache::lonxml::xmlparse($r,'web',$plot).'
'.$/; + $r->print($plotresult); return; } +sub plot_dropdown { + my $current = ''; + # + 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".''."\n"; + return $Str; +} + ############################################### ############################################### - -=pod - -=item &output_excel() - -Presents the statistical data in an Excel 95 compatable spreadsheet file. - -=cut - +## +## Excel output routines +## ############################################### ############################################### -sub output_excel { +sub Excel_output { my ($r) = @_; - my $filename = '/prtspool/'. - $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. - time.'_'.rand(1000000000).'.xls'; + $r->print('".&Apache::loncommon::DrawBarGraph($title, - 'Problem Number', - $yaxis, - $Max, - undef, # colors - undef, # labels - \@Data)."
\n"); # - # Print out the data - $ENV{'form.sortby'} = 'Contents'; -# &output_html_ungrouped($r); + # Renumber the data set + my $count; + foreach my $data (@StatsArray) { + $data->{'problem_num'} = ++$count; + } return; } @@ -830,12 +1549,13 @@ sub plot_statistics { =item &get_statistics() -Wrapper routine from the call to loncoursedata::get_problem_statistics. -Calls lonstathelpers::get_time_limits() to limit the data set by time. +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 +Returns: Hash reference with statistics data from loncoursedata::get_problem_statistics. =cut @@ -846,25 +1566,60 @@ 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 $symb = $resource->symb; + my $courseid = $env{'request.course.id'}; # my $data = &Apache::loncoursedata::get_problem_statistics - (\@Apache::lonstatistics::SelectedSections, + ([&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->{'title'}; - $data->{'title'} = $resource->{'title'}; - $data->{'title.link'} = $resource->{'src'}.'?symb='. - &Apache::lonnet::escape($resource->{'symb'}); - # - $data->{'deg_of_disc'} = &compute_discrimination_factor($resource,$part,$sequence); + $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]; + $data->{'urlres'}=$urlres; + 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; } - ############################################### ############################################### @@ -881,34 +1636,43 @@ Returns: integer between -1 and 1 ############################################### ############################################### sub compute_discrimination_factor { - my ($resource,$part,$sequence) = @_; - &Apache::lonnet::logthis($sequence->{'title'}.' '.$resource->{'title'}); + my ($resource,$part,$seq) = @_; + my $symb = $resource->symb; my @Resources; - foreach my $res (@{$sequence->{'contents'}}) { - next if ($res->{'symb'} eq $resource->{'symb'}); - push (@Resources,$res->{'symb'}); + foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)){ + next if ($res->symb eq $symb); + push (@Resources,$res->symb); } # # rank - my $ranking = + my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits(); + my $ranking = &Apache::loncoursedata::rank_students_by_scores_on_resources (\@Resources, - \@Apache::lonstatistics::SelectedSections, - $Apache::lonstatistics::enrollment_status,undef); + [&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()]; + my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()]; } @{$ranking}[0..$number_to_grab]; - my @TopSet = - map { - $_->[&Apache::loncoursedata::RNK_student()]; - } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)]; - my ($bottom_sum,$bottom_max) = - &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet); - my ($top_sum,$top_max) = - &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet); + 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'; @@ -922,73 +1686,135 @@ sub compute_discrimination_factor { ############################################### ############################################### +## +## 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'; + } + $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; +} + + =pod -=item &ProblemStatisticsLegend() +=item ProblemStatisticsLegend -HELP This needs to be localized, or at least generated automatically. +=over 4 -=cut +=item #Stdnts +Total number of students attempted the problem. -############################################### -############################################### -sub ProblemStatisticsLegend { - my $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 .= 'Max Tries | '; - $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 .= ' |
Disc. | '; - $Ptr .= 'Number of Students had at least one discussion.'; - $Ptr .= ' |