'."\n";
+ $Str .= &Apache::loncommon::end_data_table_row();
+ $Str .= &Apache::loncommon::end_data_table();
+ #
+ $Str .= '';
+ $Str .= '';
+ $Str .= (' 'x10);
+ #
+ return $Str;
}
-sub BuildProblemStatisticsPage {
- my ($cacheDB, $students, $courseID, $c, $r)=@_;
+###############################################
+###############################################
+
+=pod
+
+=item &BuildProblemStatisticsPage()
- my @Header = ("Homework Sets Order","#Stdnts","Tries","Mod",
- "Mean","#YES","#yes","%Wrng","DoDiff",
- "S.D.","Skew.","D.F.1st","D.F.2nd");
- my $color=&setbgcolor(0);
- my %cache;
+Main interface to problem statistics.
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.6');
+=cut
+
+###############################################
+###############################################
+my $navmap;
+my @sequences;
+
+sub clean_up {
+ undef($navmap);
+ undef(@sequences);
+}
+
+sub BuildProblemStatisticsPage {
+ my ($r,$c)=@_;
+ undef($navmap);
+ undef(@sequences);
+ #
+ my %Saveable_Parameters = ('Status' => 'scalar',
+ 'statsoutputmode' => 'scalar',
+ 'Section' => 'array',
+ 'Groups' => 'array',
+ 'StudentData' => 'array',
+ 'Maps' => 'array',
+ 'fieldselections'=> 'array');
+ &Apache::loncommon::store_course_settings('statistics',
+ \%Saveable_Parameters);
+ &Apache::loncommon::restore_course_settings('statistics',
+ \%Saveable_Parameters);
+ #
+ &Apache::lonstatistics::PrepareClasslist();
+ #
+ # Clear the package variables
+ undef(@StatsArray);
+ undef(%SeqStat);
+ #
+ # Finally let the user know we are here
+ $r->print(&Apache::lonhtmlcommon::breadcrumbs('Overall Problem Statistics',
+ 'Statistics_Overall_Key'));
+
+ my $interface = &CreateInterface($r);
+ $r->print($interface);
+ $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.').
+ '
'.
+ '
'.
+ &mt('It may take some time to update the student data '.
+ 'for the first analysis. Future analysis this session '.
+ 'will not have this delay.').
+ '
';
- if($cache{'DisplayLegend'} eq 'Show Legend') {
- $Ptr .= &ProblemStatisticsLegend();
- }
- $r->print($Ptr);
$r->rflush();
- untie(%cache);
-
- my ($result, $orderedProblems) =
- &InitializeProblemStatistics($cacheDB, $students, $courseID, $c, $r);
- if($result ne 'OK') {
+ #
+ # 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();
+ #
+ ($navmap,@sequences) =
+ &Apache::lonstatistics::selected_sequences_with_assessments();
+ if (! ref($navmap)) {
+ $r->print('
'.&mt('A course-wide error occurred.').'
'.
+ '
'.$navmap.'
');
+ &clean_up();
return;
}
-
- unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
- $r->print('Unable to tie database.6');
- return;
+ if (exists($env{'form.Excel'})) {
+ $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;
+}
- my @values = ();
- 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 ne -1) {
- #$r->print(' finish a graph ');
- }
- if($sortProblems eq 'Sort Within Sequence') {
- $r->print(''.$cache{$sequence.':title'}.'');
+###############################################
+###############################################
+##
+## Misc HTML output routines
+##
+###############################################
+###############################################
+sub statistics_html_table_data {
+ my ($data,$options) = @_;
+ my $row = '';
+ foreach my $field (@Fields) {
+ next if ($options =~ /no $field->{'name'}/);
+ next if ($field->{'selected'} ne 'yes');
+ $row .= '
'."\n");
+##
+## &write_headers
+##
+sub write_headers {
+ my ($excel_sheet,$format,$rows_output,$cols_output,$Fields) = @_;
+ ##
+ ## First the long titles
+ foreach my $field (@{$Fields}) {
+ next if ($field->{'name'} eq 'problem_num');
+ next if ($field->{'selected'} ne 'yes');
+ if (exists($field->{'long_title'})) {
+ $excel_sheet->write($$rows_output,${$cols_output},
+ $field->{'long_title'},
+ $format->{'bold'});
+ } else {
+ $excel_sheet->write($$rows_output,${$cols_output},'');
+ }
+ ${$cols_output}+= 1;
}
-
+ ${$cols_output} =0;
+ ${$rows_output}+=1;
+ ##
+ ## Then the short titles
+ foreach my $field (@{$Fields}) {
+ next if ($field->{'selected'} ne 'yes');
+ next if ($field->{'name'} eq 'problem_num');
+ # Use english for excel as I am not sure how well excel handles
+ # other character sets....
+ $excel_sheet->write($$rows_output,$$cols_output,
+ $field->{'title'},
+ $format->{'bold'});
+ $$cols_output+=1;
+ }
+ ${$cols_output} =0;
+ ${$rows_output}+=1;
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 .= 'parts}) {
+ next if (($res->is_survey($part)) || ($res->is_anonsurvey($part))) ;
+ #
+ # This is where all the work happens
+ my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
+ push (@Data,$data);
+ push (@StatsArray,$data);
+ }
}
- $Ptr .= '
';
- $Ptr .= ' 0) {
+ # Assume we have already computed the statistics
+ return;
}
- $Ptr .= '
';
- $Ptr .= 'connection;
+ foreach my $seq (@sequences) {
+ last if ($c->aborted);
+ &compute_sequence_statistics($seq);
+ &compute_statistics_on_sequence($seq);
}
- $Ptr .= '
Total number of students attempted the problem.';
- $Ptr .= '
';
- $Ptr .= 'Tries
';
- $Ptr .= '
Total number of tries for solving the problem.';
- $Ptr .= '
';
- $Ptr .= 'Mod
';
- $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 .= '
';
- $Ptr .= '
Disc.
';
- $Ptr .= '
Number of Students had at least one discussion.';
- $Ptr .= '
';
-
- return $Ptr;
-}
-
-sub ExtractStudentData {
- my ($cache, $students)=@_;
-
- my @problemList=();
- my %problemData;
- foreach my $sequence (split(':', $cache->{'orderedSequences'})) {
- foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {
- foreach my $part (split(/\:/,$cache->{$sequence.':'.
- $problemID.
- ':parts'})) {
- my $id = $sequence.':'.$problemID.':'.$part;
- push(@problemList, $id);
- my $totalTries = 0;
- my $totalAwarded = 0;
- my $correct = 0;
- my $correctByOverride = 0;
- my $studentCount = 0;
- my $maxTries = 0;
- my $totalFirst = 0;
- my @studentTries=();
- foreach(@$students) {
- my $code = $cache->{"$_:$problemID:$part:code"};
-
- if(defined($cache->{$_.':error'}) || $code eq ' ' ||
- $cache->{"$_:$problemID:NoVersion"} eq 'true') {
- next;
- }
-
- $studentCount++;
- my $tries = $cache->{"$_:$problemID:$part:tries"};
- if($maxTries < $tries) {
- $maxTries = $tries;
+sub sort_data {
+ my ($sortkey) = @_;
+ return if (! @StatsArray);
+ #
+ # Sort the data
+ my $sortby = undef;
+ foreach my $field (@Fields) {
+ if ($sortkey eq $field->{'name'}) {
+ $sortby = $field->{'name'};
+ }
+ }
+ if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
+ $sortby = 'container';
+ }
+ if ($sortby ne 'container') {
+ # $sortby is already defined, so we can charge ahead
+ if ($sortby =~ /^(title|part)$/i) {
+ # Alpha comparison
+ @StatsArray = 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
+ @StatsArray = sort {
+ my $retvalue = 0;
+ if ($b->{$sortby} eq 'nan') {
+ if ($a->{$sortby} ne 'nan') {
+ $retvalue = -1;
+ } else {
+ $retvalue = 0;
}
- $totalTries += $tries;
- push(@studentTries, $tries);
-
- my $awarded = $cache->{"$_:$problemID:$part:awarded"};
- $totalAwarded += $awarded;
-
- if($code eq '*') {
- $correct++;
- if($tries == 1) {
- $totalFirst++;
- }
- } elsif($code eq '+') {
- $correctByOverride++;
+ }
+ if ($a->{$sortby} eq 'nan') {
+ if ($b->{$sortby} ne 'nan') {
+ $retvalue = 1;
}
}
-
- my $studentTriesJoined = join(':::', @studentTries);
- $problemData{$id.':sequenceTitle'} =
- $cache->{$sequence.':title'};
- $problemData{$id.':studentCount'} = $studentCount;
- $problemData{$id.':totalTries'} = $totalTries;
- $problemData{$id.':studentTries'} = $studentTriesJoined;
- $problemData{$id.':totalAwarded'} = $totalAwarded;
- $problemData{$id.':correct'} = $correct;
- $problemData{$id.':correctByOverride'} = $correctByOverride;
- $problemData{$id.':wrong'} = $studentCount -
- ($correct + $correctByOverride);
- $problemData{$id.':maxTries'} = $maxTries;
- $problemData{$id.':totalFirst'} = $totalFirst;
- }
+ if ($retvalue eq '0') {
+ $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
+ lc($a->{'title'}) <=> lc($b->{'title'}) ||
+ lc($a->{'part'}) <=> lc($b->{'part'});
+ }
+ $retvalue;
+ } @StatsArray;
}
}
+ #
+ # Renumber the data set
+ my $count;
+ foreach my $data (@StatsArray) {
+ $data->{'problem_num'} = ++$count;
+ }
+ return;
+}
- my @upperStudents1=();
- my @lowerStudents1=();
- my @upperStudents2=();
- my @lowerStudents2=();
- my $upperCount = int(0.27*scalar(@$students));
- # Discriminant Factor criterion 1
- my $sortedStudents = &SortDivideByTries($students,$cache,':totalAwarded');
-
- for(my $i=0; $i<$upperCount; $i++) {
- push(@lowerStudents1, $sortedStudents->[$i]);
- push(@upperStudents1, $sortedStudents->[(scalar(@$students)-$i-1)]);
- }
-
- $problemData{'studentsUpperListCriterion1'}=join(':::', @upperStudents1);
- $problemData{'studentsLowerListCriterion1'}=join(':::', @lowerStudents1);
-
- # Discriminant Factor criterion 2
- $sortedStudents = &SortDivideByTries($students, $cache, ':totalSolved');
-
- for(my $i=0; $i<$upperCount; $i++) {
- push(@lowerStudents2, $sortedStudents->[$i]);
- push(@upperStudents2, $sortedStudents->[(scalar(@$students)-$i-1)]);
- }
- $problemData{'studentsUpperListCriterion2'}=join(':::', @upperStudents2);
- $problemData{'studentsLowerListCriterion2'}=join(':::', @lowerStudents2);
-
- $problemData{'problemList'} = join(':::', @problemList);
-
- return \%problemData;
-}
-
-sub SortDivideByTries {
- my ($toSort, $data, $sortOn)=@_;
- my @orderedData = sort { ($data->{$a.':totalTries'}) ?
- ($data->{$a.$sortOn}/$data->{$a.':totalTries'}):0
- <=>
- ($data->{$b.':totalTries'}) ?
- ($data->{$b.$sortOn}/$data->{$b.':totalTries'}):0
- } @$toSort;
-
- return \@orderedData;
-}
-
-sub SortProblems {
- my ($problemData,$sortBy,$sortProblems,$ascend)=@_;
-
- my @problems = split(':::', $problemData->{'problemList'});
- if($sortBy eq "Homework Sets Order") {
- return \@problems;
- }
-
- my $data;
-
- if ($sortBy eq "#Stdnts") { $data = ':studentCount'; }
- elsif($sortBy eq "Tries") { $data = ':totalTries'; }
- elsif($sortBy eq "Mod") { $data = ':maxTries'; }
- elsif($sortBy eq "Mean") { $data = ':mean'; }
- elsif($sortBy eq "#YES") { $data = ':correct'; }
- elsif($sortBy eq "#yes") { $data = ':correctByOverride'; }
- elsif($sortBy eq "%Wrng") { $data = ':percentWrong'; }
- elsif($sortBy eq "DoDiff") { $data = ':degreeOfDifficulty'; }
- elsif($sortBy eq "S.D.") { $data = ':standardDeviation'; }
- elsif($sortBy eq "Skew.") { $data = ':skewness'; }
- elsif($sortBy eq "D.F.1st") { $data = ':discriminationFactor1'; }
- elsif($sortBy eq "D.F.2nd") { $data = ':discriminationFactor2'; }
- else { return \@problems; }
-
- my %temp;
- my @sequenceList=();
- foreach(@problems) {
- my ($sequence) = split(':', $_);
-
- my @array=();
- my $tempArray;
- if(defined($temp{$sequence})) {
- $tempArray = $temp{$sequence};
- } else {
- push(@sequenceList, $sequence);
- $tempArray = \@array;
- $temp{$sequence} = $tempArray;
- }
-
- push(@$tempArray, $_);
- }
-
- my @orderedProblems;
- if($sortProblems eq "Sort Within Sequence") {
- foreach(keys(%temp)) {
- my $tempArray = $temp{$_};
- my @tempOrder =
- sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
- @$tempArray;
- $temp{$_} = \@tempOrder;
- }
- foreach(@sequenceList) {
- my $tempArray = $temp{$_};
- @orderedProblems = (@orderedProblems, @$tempArray);
- }
+########################################################
+########################################################
+
+=pod
+
+=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
+and &compute_discrimination_factor
+
+Inputs: $sequence, $resource, $part, $problem_num
+
+Returns: Hash reference with statistics data from
+loncoursedata::get_problem_statistics.
+
+=cut
+
+########################################################
+########################################################
+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 $data = &Apache::loncoursedata::get_problem_statistics
+ ([&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->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];
+ 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;
+}
+
+###############################################
+###############################################
+
+=pod
+
+=item &compute_discrimination_factor()
+
+Inputs: $Resource, $Sequence
+
+Returns: integer between -1 and 1
+
+=cut
+
+###############################################
+###############################################
+sub compute_discrimination_factor {
+ my ($resource,$part,$seq) = @_;
+ my $symb = $resource->symb;
+ my @Resources;
+ foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)){
+ next if ($res->symb eq $symb);
+ push (@Resources,$res->symb);
+ }
+ #
+ # rank
+ my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
+ my $ranking =
+ &Apache::loncoursedata::rank_students_by_scores_on_resources
+ (\@Resources,
+ [&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()];
+ } @{$ranking}[0..$number_to_grab];
+ 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';
} else {
- @orderedProblems =
- sort { $problemData->{$a.$data} <=> $problemData->{$b.$data} }
- @problems;
+ $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
}
+ #&Apache::lonnet::logthis(' '.$top_sum.'/'.$top_max.
+ # ' - '.$bottom_sum.'/'.$bottom_max);
+ return $deg_of_disc;
+}
- if($ascend eq 'Descending') {
- @orderedProblems = reverse(@orderedProblems);
+###############################################
+###############################################
+##
+## 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';
}
-
- return \@orderedProblems;
+ $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;
}
-sub CalculateStatistics {
- my ($data, $cache, $courseID)=@_;
- my @problems = split(':::', $data->{'problemList'});
- foreach(@problems) {
- # Mean
- my $mean = ($data->{$_.':studentCount'}) ?
- ($data->{$_.':totalTries'} / $data->{$_.':studentCount'}) : 0;
- $data->{$_.':mean'} = sprintf("%.2f", $mean);
- # %Wrong
- my $pw = ($data->{$_.':studentCount'}) ?
- (($data->{$_.':wrong'} / $data->{$_.':studentCount'}) * 100.0) :
- 100.0;
- $data->{$_.':percentWrong'} = sprintf("%.1f", $pw);
+=pod
- # Degree of Difficulty
- my $dod = ($data->{$_.':totalTries'}) ?
- (1 - (($data->{$_.':correct'} + $data->{$_.':correctByOverride'}) /
- $data->{$_.':totalTries'})) : 0;
+=item ProblemStatisticsLegend
- $data->{$_.':degreeOfDifficulty'} = sprintf("%.2f", $dod);
+=over 4
- # Factor in mean
- my @studentTries = split(':::', $data->{$_.':studentTries'});
- foreach(my $index=0; $index < scalar(@studentTries); $index++) {
- $studentTries[$index] -= $mean;
- }
- my $sumSquared = 0;
- my $sumCubed = 0;
- foreach(@studentTries) {
- my $squared = ($_ * $_);
- my $cubed = ($squared * $_);
- $sumSquared += $squared;
- $sumCubed += $cubed;
- }
+=item #Stdnts
+Total number of students attempted the problem.
- # Standard deviation
- my $standardDeviation;
- if($data->{$_.':studentCount'} - 1 > 0) {
- $standardDeviation = (sqrt($sumSquared)) /
- ($data->{$_.':studentCount'} - 1);
- } else {
- $standardDeviation = 0.0;
- }
- $data->{$_.':standardDeviation'} = sprintf("%.1f", $standardDeviation);
+=item Tries
+Total number of tries for solving the problem.
- # Skewness
- my $skew;
- if($standardDeviation > 0.0999 && $data->{$_.':studentCount'} > 0) {
- $skew = (((sqrt($sumSquared)) / $data->{$_.':studentCount'}) /
- ($standardDeviation *
- $standardDeviation *
- $standardDeviation));
- } else {
- $skew = 0.0;
- }
+=item Max Tries
+Largest number of tries for solving the problem by a student.
- $data->{$_.':skewness'} = sprintf("%.1f", $skew);
+=item Mean
+Average number of tries. [ Tries / #Stdnts ]
- # Discrimination Factor 1
- my ($sequence, $problem, $part) = split(':', $_);
+=item #YES
+Number of students solved the problem correctly.
- my @upper1 = split(':::', $data->{'studentsUpperListCriterion1'});
- my @lower1 = split(':::', $data->{'studentsLowerListCriterion1'});
+=item #yes
+Number of students solved the problem by override.
- my $upper1Sum=0;
- foreach my $name (@upper1) {
- $upper1Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $upper1Sum = (scalar(@upper1)) ? ($upper1Sum/(scalar(@upper1))) : 0;
+=item %Wrong
+Percentage of students who tried to solve the problem
+but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
- my $lower1Sum=0;
- foreach my $name (@lower1) {
- $lower1Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $lower1Sum = (scalar(@lower1)) ? ($lower1Sum/(scalar(@lower1))) : 0;
+=item DoDiff
+Degree of Difficulty of the problem.
+[ 1 - ((#YES+#yes) / Tries) ]
- my $df1 = $upper1Sum - $lower1Sum;
- $data->{$_.':discriminationFactor1'} = sprintf("%.2f", $df1);
+=item S.D.
+Standard Deviation of the tries.
+[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
+where Xi denotes every student\'s tries ]
- # Discrimination Factor 2
- my @upper2 = split(':::', $data->{'studentsUpperListCriterion2'});
- my @lower2 = split(':::', $data->{'studentsLowerListCriterion2'});
+=item Skew.
+Skewness of the students tries.
+[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
- my $upper2Sum=0;
- foreach my $name (@upper2) {
- $upper2Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $upper2Sum = (scalar(@upper2)) ? ($upper2Sum/(scalar(@upper2))) : 0;
+=item Dis.F.
+Discrimination Factor: A Standard for evaluating the
+problem according to a Criterion
- my $lower2Sum=0;
- foreach my $name (@lower2) {
- $lower2Sum += $cache->{"$name:$problem:$part:awarded"};
- }
- $lower2Sum = (scalar(@lower2)) ? ($lower2Sum/(scalar(@lower2))) : 0;
+=item [Criterion to group students into %27 Upper Students -
+and %27 Lower Students]
+1st Criterion for Sorting the Students:
+Sum of Partial Credit Awarded / Total Number of Tries
+2nd Criterion for Sorting the Students:
+Total number of Correct Answers / Total Number of Tries
- my $df2 = $upper2Sum - $lower2Sum;
- $data->{$_.':discriminationFactor2'} = sprintf("%.2f", $df2);
+=item Disc.
+Number of Students had at least one discussion.
- my %storestats;
- my $Average = ($data->{$_.':studentCount'}) ?
- $data->{$_.':totalTries'}/$data->{$_.':studentCount'} : 0;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___timestamp'}=time;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___stdno'}=$data->{$_.':studentCount'};
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___avetries'}=$Average;
- $storestats{$courseID.'___'.$cache->{$sequence.':source'}.
- '___difficulty'}=$data->{$_.':degreeOfDifficulty'};
- $cache->{$sequence.':source'} =~ /^(\w+)\/(\w+)/;
- if($data->{$_.':studentCount'}) {
- &Apache::lonnet::put('nohist_resevaldata',\%storestats,$1,$2);
- }
- }
+=back
- return;
-}
+=cut
-#---- END Problem Statistics Web Page ----------------------------------------
+############################################################
+############################################################
1;
__END__
500 Internal Server Error
Internal Server Error
The server encountered an internal error or
misconfiguration and was unable to complete
your request.
Please contact the server administrator at
root@localhost to inform them of the time this error occurred,
and the actions you performed just before this error.
More information about this error may be available
in the server error log.