--- loncom/interface/statistics/lonproblemstatistics.pm 2003/03/27 19:26:33 1.47 +++ loncom/interface/statistics/lonproblemstatistics.pm 2003/06/13 20:27:17 1.53 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemstatistics.pm,v 1.47 2003/03/27 19:26:33 matthew Exp $ +# $Id: lonproblemstatistics.pm,v 1.53 2003/06/13 20:27:17 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -56,6 +56,108 @@ use Apache::loncoursedata; use Apache::lonstatistics; use Spreadsheet::WriteExcel; +my @Fields = ( + { name => 'problem_num', + title => 'P#', + align => 'right', + color => '#FFFFE6' }, + { name => 'container', + title => 'Sequence or Folder', + align => 'left', + color => '#FFFFE6', + sortable => 'yes' }, + { name => 'title', + title => 'Title', + align => 'left', + color => '#FFFFE6', + special => 'link', + sortable => 'yes', }, + { name => 'part', + title => 'Part', + align => 'left', + color => '#FFFFE6' }, + { name => 'num_students', + title => '#Stdnts', + align => 'right', + color => '#EEFFCC', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students Attempting Problem' }, + { name => 'tries', + title => 'Tries', + align => 'right', + color => '#EEFFCC', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Total Number of Tries' }, + { name => 'max_tries', + title => 'Max Tries', + align => 'right', + color => '#DDFFFF', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Maximum Number of Tries' }, + { name => 'mean_tries', + title => 'Mean Tries', + align => 'right', + color => '#DDFFFF', + format => '%5.2f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Average Number of Tries' }, + { 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' }, + { name => 'skew_tries', + title => 'Skew Tries', + align => 'right', + color => '#DDFFFF', + 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' }, + { name => 'num_solved', + title => '#YES', + align => 'right', + color => '#FFDDDD', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students able to Solve' }, + { name => 'num_override', + title => '#yes', + align => 'right', + color => '#FFDDDD', + format => '%d', + sortable => 'yes', + graphable => 'yes', + long_title => 'Number of Students given Override' }, + { name => 'per_wrong', + title => '%Wrng', + align => 'right', + color => '#FFFFE6', + format => '%4.1f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Percent Wrong' }, +); + ############################################### ############################################### @@ -75,6 +177,7 @@ sub CreateInterface { $Str .= ''."\n"; $Str .= ''; $Str .= ''; + $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''."\n"; @@ -82,6 +185,8 @@ sub CreateInterface { $Str .= ''."\n"; $Str .= '
SectionsEnrollment StatusSequences and FoldersOutput
'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= ''; + $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); + $Str .= ''; # my $only_seq_with_assessments = sub { my $s=shift; @@ -97,6 +202,7 @@ sub CreateInterface { $Str .= &CreateAndParseOutputSelector(); $Str .= '
'."\n"; + $Str .= ''; return $Str; } @@ -112,7 +218,7 @@ The current output selected is indicated variables $output_mode and $show. @OutputOptions holds the descriptions of the output options and the values for $output_mode and $show. - Based on code from lonstudentassessment.pm. +Based on code from lonstudentassessment.pm. =cut @@ -142,20 +248,6 @@ my @OutputOptions = 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 { @@ -196,6 +288,10 @@ sub CreateAndParseOutputSelector { # Build the form element $Str = qq/'); $r->print(''); + $r->print(''); if (! exists($ENV{'form.statsfirstcall'})) { - $r->print(< - -Please make your selections in the boxes above and hit -the button marked "Update Display". - -

-ENDMSG return; } # @@ -294,12 +383,6 @@ ENDMSG } elsif ($output_mode eq 'excel') { $r->print("

Preparing Excel Spreadsheet

"); &output_excel($r); - } elsif ($output_mode eq 'plot') { - if ($show eq 'deg of diff') { - &plot_statistics($r,'DoDiff'); - } elsif ($show eq 'per wrong') { - &plot_statistics($r,'%Wrng'); - } } else { $r->print("

Not implemented

"); } @@ -324,37 +407,23 @@ sub output_html_grouped_by_sequence { my ($r) = @_; my $problem_num = 0; #$r->print(&ProblemStatisticsLegend()); - my @Header = ("Title","Part","#Stdnts","Tries","Mod", - "Mean","#YES","#yes","%Wrng","DoDiff", - "S.D.","Skew.");#,"D.F.1st","D.F.2nd"); - # #FFFFE6 #EEFFCC #DDFFFF FFDDDD #DDFFDD #FFDDFF foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) { next if ($sequence->{'num_assess'}<1); $r->print("

".$sequence->{'title'}."

"); $r->print('
'."\n"); $r->print(''."\n"); - $r->print('\n"); + $r->print(''); + my $Str = &statistics_table_header('no container no plots'); + $r->print(''.$Str."\n"); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { $problem_num++; - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - # - $part = ' ' if ($part == 0); - # - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); my $option = ''; - $r->print(''.&statistics_html_table_data - ($resource,$part,$num,$tries,$mod,$mean,$Solved, - $solved,$wrongpercent,$DegOfDiff,$STD,$SKEW, - $option). + $r->print(''.&statistics_html_table_data($data, + 'no container'). "\n"); } } @@ -383,39 +452,29 @@ different columns. sub output_html_ungrouped { my ($r,$option) = @_; # + if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '') { + &plot_statistics($r,$ENV{'form.plot'}); + } + # my $problem_num = 0; my $show_container = 0; my $show_part = 0; #$r->print(&ProblemStatisticsLegend()); - my @Header = ("Title","Part","#Stdnts","Tries","Mod", - "Mean","#YES","#yes","%Wrng","DoDiff", - "S.D.","Skew");#,"D.F.1st","D.F.2nd"); - # my $sortby = undef; - foreach (@Header) { - if ($ENV{'form.sortby'} eq $_) { - $sortby = $_; + foreach my $field (@Fields) { + if ($ENV{'form.sortby'} eq $field->{'name'}) { + $sortby = $field->{'name'}; } } - if (! defined($sortby) || $sortby eq '') { - $sortby = 'Container'; + if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') { + $sortby = 'container'; } # If there is more than one sequence, list their titles my @Sequences = &Apache::lonstatistics::Sequences_with_Assess(); - if (@Sequences > 1) { - unshift(@Header,"Container"); - $show_container = 1; - } - # - # If the option for showing the problem number is needed, push that - # on the list too - if (defined($option) && $option =~ /show probnum/) { - unshift(@Header,"P#"); + if (@Sequences < 1) { + $option .= ' no container'; } # - $r->print('
'. - join("",@Header)."
'; - $row .= '' if ($options !~ /no part/); - foreach ($num,$tries) { - $row .= ''; - } - foreach ($mod) { - $row .= ''; - } - foreach ($mean) { - $row .= ''; - } - foreach ($Solved,$solved) { - $row .= ''; - } - foreach ($wrongpercent) { - $row .= ''; - } - foreach ($DegOfDiff,$STD,$SKEW) { - $row .= ''; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $row .= ''; } return $row; } +sub statistics_table_header { + my ($options) = @_; + my $header_row; + foreach my $field (@Fields) { + next if ($options =~ /no $field->{'name'}/); + $header_row .= ''; + } + return $header_row; +} + ############################################### ############################################### @@ -727,60 +767,43 @@ sub plot_statistics { my ($r,$datafield) = @_; my @Data; # - my %Fields = ('#Stdnts'=> 0, - 'Tries' => 1, - 'Mod' => 2, - 'Mean' => 3, - '#YES' => 4, - '#yes' => 5, - '%Wrng' => 9, - 'DoDiff' => 6, - 'S.D.' => 7, - 'Skew' => 8,); - # - my $field = '%Wrng'; - foreach (keys(%Fields)) { - $field = $_ if ($datafield eq $_); + # + my $sortfield = undef; + my $title = undef; + foreach my $field (@Fields) { + if ($datafield eq $field->{'name'} && + exists($field->{'graphable'}) && $field->{'graphable'} eq 'yes') { + $sortfield = $field->{'name'}; + $title = $field->{'long_title'}; + } } - my $fieldindex = $Fields{$field}; + return if (! defined($sortfield) || $sortfield eq ''); # my $Max = 0; + my $problem_num = 0; foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) { next if ($sequence->{'num_assess'}<1); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { - my @Results = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - my ($num,$Solved,$solved) = @Results[0,4,5]; - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - push (@Results,$wrongpercent); - my $data = $Results[$fieldindex]; - $data = 0 if ($data eq 'nan'); - $Max = $data if ($Max<$data); - push (@Data,$data); + my $problem_number++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); + my $value = $data->{$sortfield}; + $Max = $value if ($Max < $value); + push (@Data,$value); } } } # # Print out plot request - my $title = 'Percent Wrong'; - if ($field eq 'DoDiff') { - $title = 'Degree of Difficulty'; - } - my $yaxis = 'Percent'; - if ($field eq 'DoDiff') { - $yaxis = ''; - } elsif ($field ne '%Wrng') { - $yaxis = ''; + my $yaxis = ''; + if ($sortfield eq 'per_wrong') { + $yaxis = 'Percent'; } # # Determine appropriate value for $Max - if ($field eq 'DoDiff') { + if ($sortfield eq 'deg_of_diff') { if ($Max > 0.5) { $Max = 1; } elsif ($Max > 0.2) { @@ -788,7 +811,7 @@ sub plot_statistics { } elsif ($Max > 0.1) { $Max = 0.2; } - } elsif ($field eq '%Wrng') { + } elsif ($sortfield eq 'per_wrong') { if ($Max > 50) { $Max = 100; } elsif ($Max > 25) { @@ -809,7 +832,7 @@ sub plot_statistics { # # Print out the data $ENV{'form.sortby'} = 'Contents'; - &output_html_ungrouped($r,'show probnum'); +# &output_html_ungrouped($r); return; } @@ -826,27 +849,44 @@ sub plot_statistics { ############################################### sub DrawGraph { my ($values,$title,$xaxis,$yaxis,$Max)=@_; - $title = '' if (! defined($title)); + $title = '' if (! defined($title)); $xaxis = '' if (! defined($xaxis)); $yaxis = '' if (! defined($yaxis)); # my $sendValues = join(',', @$values); my $sendCount = scalar(@$values); - if ( $Max > 1 ) { - if ($Max % 10) { - if ( int($Max) < $Max ) { - $Max++; - $Max = int($Max); - } - } - } else { - $Max = 1; + $Max =1 if ($Max < 1); + if ( int($Max) < $Max ) { + $Max++; + $Max = int($Max); } my @GData = ($title,$xaxis,$yaxis,$Max,$sendCount,$sendValues); return ''; } +sub get_statistics { + my ($sequence,$resource,$part,$problem_num) = @_; + # + my $symb = $resource->{'symb'}; + my $courseid = $ENV{'request.course.id'}; + # + my $students = \@Apache::lonstatistics::Students; + if ($Apache::lonstatistics::SelectedSections[0] eq 'all') { + $students = undef; + } + my $data = &Apache::loncoursedata::get_problem_statistics + ($students,$symb,$part,$courseid); + $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'}); + # + return $data; +} + ############################################### ############################################### @@ -868,7 +908,7 @@ sub ProblemStatisticsLegend { $Ptr .= 'Tries'; $Ptr .= ''; + $Ptr .= 'Max Tries'; $Ptr .= '';
'."\n"); - $r->rflush(); - # # Compile the data my @Statsarray; foreach my $sequence (@Sequences) { @@ -424,59 +483,18 @@ sub output_html_ungrouped { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { $problem_num++; - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); - # + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); $show_part = 1 if ($part ne '0'); - $part = ' ' if ($part == 0); # - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - push (@Statsarray, - { 'sequence' => $sequence, - 'resource' => $resource, - 'Title' => $resource->{'title'}, - 'Part' => $part, - '#Stdnts' => $num, - 'Tries' => $tries, - 'Mod' => $mod, - 'Mean' => $mean, - '#YES' => $Solved, - '#yes' => $solved, - '%Wrng' => $wrongpercent, - 'DoDiff' => $DegOfDiff, - 'S.D.' => $STD, - 'Skew' => $SKEW, - 'problem_num' => $problem_num, - }); + push (@Statsarray,$data); } } } # - # Table Headers - $r->print(''."\n"); - my $Str = ''; - foreach (@Header) { - next if ($_ eq 'Part' && !$show_part); - # Do not allow sorting on some fields - if ($_ eq $sortby || /^(Part|P\#)$/) { - $Str .= ''; - } else { - $Str .= ''; - } - } - $r->print(''.$Str."\n"); - # # Sort the data my @OutputOrder; - if ($sortby eq 'Container') { + if ($sortby eq 'container') { @OutputOrder = @Statsarray; } else { # $sortby is already defined, so we can charge ahead @@ -484,8 +502,8 @@ sub output_html_ungrouped { # 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'}); + lc($a->{'title'}) cmp lc($b->{'title'}) || + lc($a->{'part'}) cmp lc($b->{'part'}); } @Statsarray; } else { # Numerical comparison @@ -505,30 +523,35 @@ sub output_html_ungrouped { } if ($retvalue eq '0') { $retvalue = $b->{$sortby} <=> $a->{$sortby} || - lc($a->{'Title'}) <=> lc($b->{'Title'}) || - lc($a->{'Part'}) <=> lc($b->{'Part'}); + lc($a->{'title'}) <=> lc($b->{'title'}) || + lc($a->{'part'}) <=> lc($b->{'part'}); } $retvalue; } @Statsarray; } } - $option .= ',no part' if (! $show_part); - foreach my $row (@OutputOrder) { - $r->print(''); - if (defined($option) && $option =~ /show probnum/) { - $r->print(''); - } - if ($show_container) { - $r->print(''); - } - $r->print(&statistics_html_table_data - ($row->{'resource'},$row->{'Part'},$row->{'#Stdnts'}, - $row->{'Tries'},$row->{'Mod'},$row->{'Mean'}, - $row->{'#YES'},$row->{'#yes'},$row->{"\%Wrng"}, - $row->{'DoDiff'},$row->{'S.D.'},$row->{'Skew'}, - $option)); - $r->print("\n"); + $option .= 'no part' if (! $show_part); + my $num_output = 0; + # + # output the headers + $r->print('
'.$_.''. - ''. - $_.'
'.$row->{'problem_num'}.'' - .$row->{'sequence'}->{'title'}.'
'."\n"); + $r->print(''."\n"); + my $Str = &statistics_table_header($option.' sortable'); + $r->print(''.$Str."\n"); + # + foreach my $rowdata (@OutputOrder) { + $num_output++; + if ($num_output % 25 == 0) { + $r->print("
\n
\n"); + # + $r->print('
'."\n"); + $r->print(''."\n"); + my $Str = &statistics_table_header($option.' sortable'); + $r->print(''.$Str."\n"); + $r->rflush(); + } + $r->print(''.&statistics_html_table_data($rowdata,$option). + "\n"); } $r->print("
\n"); $r->print("
\n"); @@ -620,37 +643,31 @@ sub output_excel { $cols_output=0; # # Add the headers - my @Header = ("Container","Title","Part","#Stdnts","Tries","Mod", - "Mean","#YES","#yes","%Wrng","DoDiff", - "S.D.","Skew.");#,"D.F.1st","D.F.2nd"); - foreach (@Header) { - $excel_sheet->write($rows_output,$cols_output++,$_); + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + $excel_sheet->write($rows_output,$cols_output++,$field->{'title'}); } $rows_output++; # # Write the data + my $problem_num=0; foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) { next if ($sequence->{'num_assess'}<1); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { $cols_output=0; - my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, - $SKEW) = &Apache::loncoursedata::get_problem_statistics - (undef,$resource->{'symb'},$part, - $ENV{'request.course.id'}); + $problem_num++; + my $data = &get_statistics($sequence,$resource,$part, + $problem_num); # if (!defined($part) || $part eq '') { $part = ' '; } - my $wrongpercent = 0; - if (defined($num) && $num > 0) { - $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; - } - foreach ($sequence->{'title'},$resource->{'title'},$part, - $num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent, - $DegOfDiff,$STD,$SKEW) { - $excel_sheet->write($rows_output,$cols_output++,$_); + foreach my $field (@Fields) { + next if ($field->{'name'} eq 'problem_num'); + $excel_sheet->write($rows_output,$cols_output++, + $data->{$field->{'name'}}); } $rows_output++; } @@ -680,38 +697,61 @@ Help function used to format the rows fo ############################################### ############################################### sub statistics_html_table_data { - my ($resource,$part,$num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent, - $DegOfDiff,$STD,$SKEW,$options) = @_; + my ($data,$options) = @_; my $row = ''; - $row .= '
'. - ''. - $resource->{'title'}.''. - ''.$part.''.$_.''.$_.''. - sprintf("%5.2f",$_).''.$_.''. - sprintf("%5.1f",$_).''. - sprintf("%5.2f",$_).'{'align'})) { + $row .= ' align="'.$field->{'align'}.'"'; + } + $row .= '>'; + if (exists($field->{'special'}) && $field->{'special'} eq 'link') { + $row .= ''; + } + if (exists($field->{'format'})) { + $row .= sprintf($field->{'format'},$data->{$field->{'name'}}); + } else { + $row .= $data->{$field->{'name'}}; + } + if (exists($field->{'special'}) && $field->{'special'} eq 'link') { + $row.= ''; + } + $row .= ''; + if ($options =~ /sortable/ && + exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') { + $header_row .= '{'name'}."'". + ';document.Statistics.submit();">'; + } + $header_row .= $field->{'title'}; + if ($options =~ /sortable/) { + $header_row.= ''; + } + if ($options !~ /no plots/ && + exists($field->{'graphable'}) && + $field->{'graphable'} eq 'yes') { + $header_row.=' ('; + $header_row .= ''; + $header_row .= 'plot)'; + } + $header_row .= 'Total number of tries for solving the problem.'; $Ptr .= '
'; - $Ptr .= 'ModLargest number of tries for solving the problem by a student.'; $Ptr .= '
'; $Ptr .= 'Mean