# The LearningOnline Network with CAPA # # $Id: lonproblemstatistics.pm,v 1.42 2003/03/26 15:19:16 matthew Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # (Navigate problems for statistical reports # ### package Apache::lonproblemstatistics; use strict; use Apache::lonnet(); use Apache::lonhtmlcommon; use Apache::loncoursedata; use Apache::lonstatistics; ####################################################### ####################################################### sub CreateInterface { my $Str = ''; $Str .= ''."\n"; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''."\n"; # $Str .= ''."\n"; $Str .= '
SectionsSequences and FoldersOutput
'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',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 .= ''."\n"; $Str .= &CreateAndParseOutputSelector(); $Str .= '
'."\n"; return $Str; } ####################################################### ####################################################### =pod =item &CreateAndParseOutputSelector() =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; } sub CreateAndParseOutputSelector { my $Str = ''; my $elementname = 'outputmode'; # # 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]; } else { $selected = $ENV{'form.'.$elementname}; } } # # 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'}; } # # Build the form element $Str = qq/"; return $Str; } ############################################### ############################################### ############################################### ############################################### sub Gather_Student_Data { my ($r) = @_; my $c = $r->connection(); # my @Sequences = &Apache::lonstatistics::Sequences_with_Assess(); # my @Students = @Apache::lonstatistics::Students; # # Open the progress window my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin ($r,'Statistics Compilation Status', 'Statistics Compilation Progress', scalar(@Students)); # while (my $student = shift @Students) { return if ($c->aborted()); my ($status,undef) = &Apache::loncoursedata::ensure_current_data ($student->{'username'},$student->{'domain'}, $ENV{'request.course.id'}); &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, 'last student'); } &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); $r->rflush(); } ############################################### ############################################### ############################################### ############################################### sub BuildProblemStatisticsPage { my ($r,$c)=@_; # $output_mode = 'html'; $show = 'grouped'; # $r->print(&CreateInterface()); $r->print(''); $r->print(''); if (! exists($ENV{'form.statsfirstcall'})) { return; } # &Gather_Student_Data($r); # # if ($output_mode eq 'html') { $r->print("

". $ENV{'course.'.$ENV{'request.course.id'}.'.description'}. "

\n"); $r->print("

".localtime(time)."

"); $r->rflush(); if ($show eq 'grouped') { &output_html_grouped_by_sequence($r); } elsif ($show eq 'ungrouped') { &output_html_ungrouped($r); } } else { $r->print("

Not implemented

"); } return; } sub output_html_grouped_by_sequence { my ($r) = @_; #$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"); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { if ($part == 0) { $part = ' '; } my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, $SKEW) = &Apache::loncoursedata::get_problem_statistics (undef,$resource->{'symb'},$part, $ENV{'request.course.id'}); my $wrongpercent = 0; if (defined($num) && $num > 0) { $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; } $r->print(''.&statistics_html_table_data ($resource,$part,$num,$tries,$mod,$mean,$Solved, $solved,$wrongpercent,$DegOfDiff,$STD,$SKEW). "\n"); } } $r->print("
'. join("",@Header)."
\n"); $r->print("
\n"); $r->rflush(); } # return; } ############################################### ############################################### ############################################### ############################################### sub output_html_ungrouped { my ($r) = @_; # my $show_container = 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 = $_; } } if (! defined($sortby) || $sortby eq '') { $sortby = 'Container'; } # #FFFFE6 #EEFFCC #DDFFFF FFDDDD #DDFFDD #FFDDFF my @Sequences = &Apache::lonstatistics::Sequences_with_Assess(); if (@Sequences > 1) { unshift(@Header,"Container"); $show_container = 1; } # $r->print('
'."\n"); $r->print(''."\n"); my $Str = ''; foreach (@Header) { if (/^(Part)$/) { # Do not allow sorting on this field $Str .= ''; } else { $Str .= ''; } } $r->print(''.$Str."\n"); $r->rflush(); # # Compile the data my %Statshash; my @Statsarray; foreach my $sequence (@Sequences) { next if ($sequence->{'num_assess'}<1); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { my ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD, $SKEW) = &Apache::loncoursedata::get_problem_statistics (undef,$resource->{'symb'},$part, $ENV{'request.course.id'}); my $wrongpercent = 0; if (defined($num) && $num > 0) { $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; } my $key = $resource->{'symb'}.':'.$part; $Statshash{$key} = { '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, }; push (@Statsarray,$Statshash{$key}); } } } # # Sort the data if ($sortby eq 'Container') { foreach my $sequence (@Sequences) { next if ($sequence->{'num_assess'}<1); foreach my $resource (@{$sequence->{'contents'}}) { next if ($resource->{'type'} ne 'assessment'); foreach my $part (@{$resource->{'parts'}}) { my $key = $resource->{'symb'}.':'.$part; $r->print(''); if ($show_container) { $r->print(''); } $r->print(&stats_row_from_hash($Statshash{$key})); $r->print("\n"); } } } } else { # $sortby is already defined, so we can charge ahead my @OutputOrder; if ($sortby =~ /^(title|part)$/i) { # Alpha comparison @OutputOrder = sort { $b->{$sortby} cmp $a->{$sortby} || $b->{'Title'} cmp $a->{'Title'} || $b->{'Part'} cmp $a->{'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} || $b->{'Title'} <=> $a->{'Title'} || $b->{'Part'} <=> $a->{'Part'}; } $retvalue; } @Statsarray; } foreach my $row (@OutputOrder) { $r->print(''); if ($show_container) { $r->print(''); } $r->print(&stats_row_from_hash($row)); $r->print("\n"); } } $r->print("
'.$_.''. ''. $_.'
' .$sequence->{'title'}.'
' .$row->{'sequence'}->{'title'}.'
\n"); $r->print("
\n"); $r->rflush(); # return; } sub stats_row_from_hash { my ($data) = @_; if (ref($data) ne 'HASH') { my %Tmp = @_; $data = \%Tmp; } return &statistics_html_table_data($data->{'resource'},$data->{'Part'}, $data->{'#Stdnts'}, $data->{'Tries'}, $data->{'Mod'}, $data->{'Mean'}, $data->{'#YES'}, $data->{'#yes'}, $data->{"\%Wrng"}, $data->{'DoDiff'}, $data->{'S.D.'}, $data->{'Skew'}); } ############################################### ############################################### ############################################### ############################################### sub statistics_html_table_data { my ($resource,$part,$num,$tries,$mod,$mean,$Solved,$solved,$wrongpercent, $DegOfDiff,$STD,$SKEW) = @_; my $row = ''; $row .= ''. ''. $resource->{'title'}.''. ''; $row .= ''.$part.'' if (defined($part)); foreach ($num,$tries) { $row .= ''.$_.''; } foreach ($mod,$mean) { $row .= ''. sprintf("%5.2f",$_).''; } foreach ($Solved,$solved) { $row .= ''.$_.''; } foreach ($wrongpercent) { $row .= ''. sprintf("%5.1f",$_).''; } foreach ($DegOfDiff,$STD,$SKEW) { $row .= ''. sprintf("%5.2f",$_).''; } return $row; } ############################################### ############################################### sub BuildGraphicChart { my ($graph,$cacheDB,$courseDescription,$students,$courseID,$r,$c)=@_; my %cache; my $max; my $title = ''; if($graph eq 'DoDiffGraph') { $title = 'Degree-of-Difficulty'; } else { $title = 'Wrong-Percentage'; } my $currentSequence = -1; my $sortProblems = 'Sort Within Sequence'; my ($result, $orderedProblems) = &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r); if($result ne 'OK') { return; } my @values = (); unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { return 'Unable to tie database.7'; } foreach(@$orderedProblems) { my ($sequence,$problem,$part)=split(':', $_); if($cache{'StatisticsMaps'} ne 'All Maps' && $cache{'StatisticsMaps'} ne $cache{$sequence.':title'}) { next; } if( $currentSequence == -1 || ($sortProblems eq 'Sort Within Sequence' && $currentSequence != $sequence)) { if($currentSequence != -1) { &DrawGraph(\@values,$courseDescription,$title,$max,$r); } if($sortProblems eq 'Sort Within Sequence') { $r->print('
'.$cache{$sequence.':title'}.''."\n"); } $currentSequence = $sequence; @values = (); $max=0; } my $data = 0; if($graph eq 'DoDiffGraph') { $data = sprintf("%.2f", $cache{$_.':degreeOfDifficulty'}), } else { $data = sprintf("%.1f", $cache{$_.':percentWrong'}), } if($max < $data) { $max = $data; } push(@values, $data); } untie(%cache); &DrawGraph(\@values,$courseDescription,$title,$max,$r); return; } sub DrawGraph { my ($values,$courseDescription,$title,$Max,$r)=@_; my $sendValues = join(',', @$values); my $sendCount = scalar(@$values); $r->print("
The Maximum Value is: $Max"); if ( $Max > 1 ) { if ($Max % 10) { if ( int($Max) < $Max ) { $Max++; $Max = int($Max); } } #(10 - $Max % 10); } else { $Max = 1; } my @GData = ('','Problem_number',$title,$Max,$sendCount,$sendValues); # $r->print(''."\n"); $r->print('
'."\n"); $r->print(''); # $r->print('
'."\n"); $r->print('
'."\n"); } #---- Problem Statistics Web Page --------------------------------------- sub CreateProblemStatisticsTableHeading { my ($headings,$r)=@_; my $Str=''; $Str .= ''."\n"; $Str .= 'P#'."\n"; foreach(@$headings) { $Str .= ''; $Str .= ''.$_.' '."\n"; } $Str .= "\n".''."\n"; return $Str; } sub BuildStatisticsTable { my ($cache,$displayFormat,$sortProblems,$orderedProblems,$headings, $r,$color)=@_; my $count = 1; my $currentSequence = -1; foreach(@$orderedProblems) { my ($sequence,$problem,$part)=split(':', $_); if($cache->{'StatisticsMaps'} ne 'All Maps' && $cache->{'StatisticsMaps'} ne $cache->{$sequence.':title'}) { next; } if($currentSequence == -1 || ($sortProblems eq 'Sort Within Sequence' && $currentSequence != $sequence)) { if($displayFormat ne 'Display CSV Format') { if($currentSequence ne -1) { $r->print(''); $r->print('
'); } if($sortProblems eq 'Sort Within Sequence') { $r->print(''.$cache->{$sequence.':title'}.''); } $r->print('
'."\n"); $r->print(''."\n"); $r->print(&CreateProblemStatisticsTableHeading($headings, $r)); } else { if($sortProblems eq 'Sort Within Sequence') { $r->print('"'.$cache->{$sequence.':title'}.'"'); } $r->print('
'); } $currentSequence = $sequence; } my $ref = ''.$cache->{$problem.':title'}.''; my $title = $cache->{$problem.':title'}; if($part != 0) { $title .= ' Part '.$part; } my $source = $cache->{$problem.':source'}; my $tableData = join('&', $ref, $title, $source, $cache->{$_.':studentCount'}, $cache->{$_.':totalTries'}, $cache->{$_.':maxTries'}, $cache->{$_.':mean'}, $cache->{$_.':correct'}, $cache->{$_.':correctByOverride'}, $cache->{$_.':percentWrong'}, $cache->{$_.':degreeOfDifficulty'}, $cache->{$_.':standardDeviation'}, $cache->{$_.':skewness'}, $cache->{$_.':discriminationFactor1'}, $cache->{$_.':discriminationFactor2'}); &TableRow($displayFormat,$tableData,$count,$r,$color); $count++; } if($displayFormat ne 'Display CSV Format') { $r->print('
'."\n"); $r->print('
'); } else { $r->print('
'); } return; } sub TableRow { my ($displayFormat,$Str,$RealIdx,$r,$color)=@_; my($ref,$title,$source,$StdNo,$TotalTries,$MxTries,$Avg,$YES,$Override, $Wrng,$DoD,$SD,$Sk,$_D1,$_D2)=split(/\&/,$Str); my $Ptr; if($displayFormat eq 'Display CSV Format') { $Ptr='"'.$RealIdx.'",'."\n". '"'.$title.'",'."\n". '"'.$source.'",'."\n". '"'.$StdNo.'",'."\n". '"'.$TotalTries.'",'."\n". '"'.$MxTries.'",'."\n". '"'.$Avg.'",'."\n". '"'.$YES.'",'."\n". '"'.$Override.'",'."\n". '"'.$Wrng.'",'."\n". '"'.$DoD.'",'."\n". '"'.$SD.'",'."\n". '"'.$Sk.'",'."\n". '"'.$_D1.'",'."\n". '"'.$_D2.'"'."\n". "
\n"; $r->print("\n".$Ptr); } else { $Ptr=''."\n". ''.$RealIdx.''."\n". ''.$ref.''."\n". ' '.$StdNo.''."\n". ''.$TotalTries.''."\n". ''.$MxTries.''."\n". ''.$Avg.''."\n". ' '.$YES.''."\n". ' '.$Override.''."\n". ' '.$Wrng.''."\n". ' '.$DoD.''."\n". ' '.$SD.''."\n". ' '.$Sk.''."\n". ' '.$_D1.''."\n". ' '.$_D2.''."\n"; $r->print($Ptr.''."\n"); } return; } # For loading the colored table for display or un-colored for print sub setbgcolor { my $PrintTable=shift; my %color; if ($PrintTable){ $color{"gb"}="#FFFFFF"; $color{"red"}="#FFFFFF"; $color{"yellow"}="#FFFFFF"; $color{"green"}="#FFFFFF"; $color{"purple"}="#FFFFFF"; } else { $color{"gb"}="#DDFFFF"; $color{"red"}="#FFDDDD"; $color{"yellow"}="#EEFFCC"; $color{"green"}="#DDFFDD"; $color{"purple"}="#FFDDFF"; } return \%color; } sub ProblemStatisticsButtons { my ($displayFormat, $displayLegend, $sortProblems)=@_; my $Ptr = ''; $Ptr .= '