[LON-CAPA-cvs] cvs: loncom /interface loncoursedata.pm /interface/statistics lonproblemstatistics.pm

matthew lon-capa-cvs@mail.lon-capa.org
Tue, 23 Mar 2004 16:35:16 -0000


This is a MIME encoded message

--matthew1080059716
Content-Type: text/plain

matthew		Tue Mar 23 11:35:16 2004 EDT

  Modified files:              
    /loncom/interface	loncoursedata.pm 
    /loncom/interface/statistics	lonproblemstatistics.pm 
  Log:
  First stab at degree of discrimination computation.
  loncoursedata:
      Added $weight_table, &populate_weight_table, 
      &rank_students_by_scores_on_resources, &get_sum_of_scores, and
      &limit_by_section_and_status
  lonproblemstatistics:
      Added degree of discrimination to @Fields,
      &compute_discrimination_factor, and modified &get_statistics to call
      the latter.
  
  
--matthew1080059716
Content-Type: text/plain
Content-Disposition: attachment; filename="matthew-20040323113516.txt"

Index: loncom/interface/loncoursedata.pm
diff -u loncom/interface/loncoursedata.pm:1.126 loncom/interface/loncoursedata.pm:1.127
--- loncom/interface/loncoursedata.pm:1.126	Mon Mar 22 12:25:26 2004
+++ loncom/interface/loncoursedata.pm	Tue Mar 23 11:35:15 2004
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: loncoursedata.pm,v 1.126 2004/03/22 17:25:26 matthew Exp $
+# $Id: loncoursedata.pm,v 1.127 2004/03/23 16:35:15 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -475,6 +475,13 @@
 'transaction', and 'timestamp'.  
 The primary key is based on the first 3 columns.
 
+=item $weight_table
+
+The weight table holds the weight for the problems used in the class.
+Whereas the weight of a problem can vary by section and student the data
+here is applied to the class as a whole.
+Columns: 'symb_id','part_id','response_id','weight'.
+
 =back
 
 =back
@@ -518,6 +525,7 @@
 my $fulldump_response_table;
 my $fulldump_part_table;
 my $fulldump_timestamp_table;
+my $weight_table;
 
 my @Tables;
 ################################################
@@ -628,7 +636,7 @@
                     { name => 'tries',
                       type => 'SMALLINT UNSIGNED' },
                     { name => 'awarded',
-                      type => 'TINYTEXT' },
+                      type => 'REAL' },
                     { name => 'award',
                       type => 'TINYTEXT' },
                     { name => 'awarddetail',
@@ -663,7 +671,7 @@
                     { name => 'award',
                       type => 'TINYTEXT' },
                     { name => 'awarded',
-                      type => 'TINYTEXT' },
+                      type => 'REAL' },
                     { name => 'previous',
                       type => 'SMALLINT UNSIGNED' },
 #                    { name => 'regrader',
@@ -740,7 +748,6 @@
                   { columns=>['transaction'] },
                   ],
     };
-
     #
     my $parameters_table_def = {
         id => $parameters_table,
@@ -760,6 +767,22 @@
         'PRIMARY KEY' => ['symb_id','student_id','parameter (255)'],
     };
     #
+    my $weight_table_def = {
+        id => $weight_table,
+        permanent => 'no',
+        columns => [{ name => 'symb_id',
+                      type => 'MEDIUMINT UNSIGNED',
+                      restrictions => 'NOT NULL'  },
+                    { name => 'part_id',
+                      type => 'MEDIUMINT UNSIGNED',
+                      restrictions => 'NOT NULL'  },
+                    { name => 'weight',
+                      type => 'REAL',
+                      restrictions => 'NOT NULL'  },
+                    ],
+        'PRIMARY KEY' => ['symb_id','part_id'],
+    };
+    #
     # Create the tables
     my $tableid;
     $tableid = &Apache::lonmysql::create_table($symb_table_def);
@@ -816,6 +839,12 @@
                                  &Apache::lonmysql::get_error());
         return 9;
     }
+    $tableid = &Apache::lonmysql::create_table($weight_table_def);
+    if (! defined($tableid)) {
+        &Apache::lonnet::logthis("error creating weight_table: ".
+                                 &Apache::lonmysql::get_error());
+        return 10;
+    }
     return 0;
 }
 
@@ -1077,7 +1106,6 @@
     return;
 }
 
-
 ################################################
 ################################################
 
@@ -1548,7 +1576,8 @@
     my @CurrentTable = &Apache::lonmysql::tables_in_db();
     my ($found_symb,$found_student,$found_part,
         $found_performance,$found_parameters,$found_fulldump_part,
-        $found_fulldump_response,$found_fulldump_timestamp);
+        $found_fulldump_response,$found_fulldump_timestamp,
+        $found_weight);
     foreach (@CurrentTable) {
         $found_symb        = 1 if ($_ eq $symb_table);
         $found_student     = 1 if ($_ eq $student_table);
@@ -1558,12 +1587,13 @@
         $found_fulldump_part      = 1 if ($_ eq $fulldump_part_table);
         $found_fulldump_response  = 1 if ($_ eq $fulldump_response_table);
         $found_fulldump_timestamp = 1 if ($_ eq $fulldump_timestamp_table);
+        $found_weight      = 1 if ($_ eq $weight_table);
     }
-    if (!$found_symb        || 
-        !$found_student     || !$found_part   ||
-        !$found_performance || !$found_parameters ||
+    if (!$found_symb          || 
+        !$found_student       || !$found_part              ||
+        !$found_performance   || !$found_parameters        ||
         !$found_fulldump_part || !$found_fulldump_response ||
-        !$found_fulldump_timestamp ) {
+        !$found_fulldump_timestamp || !$found_weight ) {
         if (&init_dbs($courseid)) {
             return 'error';
         }
@@ -2035,6 +2065,8 @@
              deg_of_diff  => $DegOfDiff };
 }
 
+##
+## This is a helper for get_statistics
 sub execute_SQL_request {
     my ($dbh,$request)=@_;
 #    &Apache::lonnet::logthis($request);
@@ -2047,7 +2079,218 @@
     return ();
 }
 
+######################################################
+######################################################
+
+=pod
+
+=item &populate_weight_table
+
+=cut
+
+######################################################
+######################################################
+sub populate_weight_table {
+    my ($courseid) = @_;
+    if (! defined($courseid)) {
+        $courseid = $ENV{'request.course.id'};
+    }
+    #
+    &setup_table_names($courseid);
+    my ($top,$sequences,$assessments) = get_sequence_assessment_data();
+    if (! defined($top) || ! ref($top)) {
+        # There has been an error, better report it
+        &Apache::lonnet::logthis('top is undefined');
+        return;
+    }
+    #       Since we use lonnet::EXT to retrieve problem weights,
+    #       to ensure current data we must clear the caches out.
+    &Apache::lonnet::clear_EXT_cache_status();
+    my $dbh = &Apache::lonmysql::get_dbh();
+    my $request = 'INSERT IGNORE INTO '.$weight_table.
+        "(symb_id,part_id,weight) VALUES ";
+    my $weight;
+    foreach my $res (@$assessments) {
+        my $symb_id = &get_symb_id($res->{'symb'});
+        foreach my $part (@{$res->{'parts'}}) {
+            my $part_id = &get_part_id($part);
+            $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
+                                           $res->{'symb'},
+                                           undef,undef,undef);
+            if (!defined($weight) || ($weight eq '')) { 
+                $weight=1;
+            }
+            $request .= "('".$symb_id."','".$part_id."','".$weight."'),";
+        }
+    }
+    $request =~ s/(,)$//;
+#    &Apache::lonnet::logthis('request = '.$/.$request);
+    $dbh->do($request);
+    if ($dbh->err()) {
+        &Apache::lonnet::logthis("error ".$dbh->errstr().
+                                 " occured executing \n".
+                                 $request);
+    }
+    return;
+}
+
+##########################################################
+##########################################################
 
+=pod
+
+=item &limit_by_section_and_status
+
+Build SQL WHERE condition which limits the data collected by section and
+student status.
+
+Inputs: $Sections (array ref)
+    $enrollment (string: 'any', 'expired', 'active')
+    $tablename The name of the table that holds the student data
+
+Returns: $student_requirements,$enrollment_requirements
+
+=cut
+
+##########################################################
+##########################################################
+sub limit_by_section_and_status {
+    my ($Sections,$enrollment,$tablename) = @_;
+    my $student_requirements = undef;
+    if ( (defined($Sections) && $Sections->[0] ne 'all')) {
+        $student_requirements = '('.
+            join(' OR ', map { $tablename.".section='".$_."'" } @$Sections
+                 ).')';
+    }
+    #
+    my $enrollment_requirements=undef;
+    if (defined($enrollment) && $enrollment ne 'Any') {
+        $enrollment_requirements = $tablename.".status='".$enrollment."'";
+    }
+    return ($student_requirements,$enrollment_requirements);
+}
+
+######################################################
+######################################################
+
+=pod
+
+=item rank_students_by_scores_on_resources
+
+Inputs: 
+    $resources: array ref of hash ref.  Each hash ref needs key 'symb'.
+    $Sections: array ref of sections to include,
+    $enrollment: string,
+    $courseid (may be omitted)
+
+Returns; An array of arrays.  The sub arrays contain a student name and
+their score on the resources.
+
+=cut
+
+######################################################
+######################################################
+sub RNK_student { return 0; };
+sub RNK_score   { return 1; };
+
+sub rank_students_by_scores_on_resources {
+    my ($resources,$Sections,$enrollment,$courseid) = @_;
+    return if (! defined($resources) || ! ref($resources) eq 'ARRAY');
+    if (! defined($courseid)) {
+        $courseid = $ENV{'request.course.id'};
+    }
+    #
+    &setup_table_names($courseid);
+    my $dbh = &Apache::lonmysql::get_dbh();
+    my ($section_limits,$enrollment_limits)=
+        &limit_by_section_and_status($Sections,$enrollment,'b');
+    my $symb_limits = '('.join(' OR ',map {'a.symb_id='.&get_symb_id($_);
+                                       } @$resources
+                               ).')';
+    my $request = 'SELECT b.student,SUM(a.awarded*w.weight) AS score FROM '.
+        $performance_table.' AS a '.
+        'NATURAL LEFT JOIN '.$weight_table.' AS w '.
+        'LEFT JOIN '.$student_table.' AS b ON a.student_id=b.student_id '.
+        'WHERE ';
+    if (defined($section_limits)) {
+        $request .= $section_limits.' AND ';
+    }
+    if (defined($enrollment_limits)) {
+        $request .= $enrollment_limits.' AND ';
+    }
+    if ($symb_limits ne '()') {
+        $request .= $symb_limits.' AND ';
+    }
+    $request =~ s/( AND )$//;   # Remove extra conjunction
+    $request =~ s/( WHERE )$//; # In case there were no limits placed on it
+    $request .= ' GROUP BY a.student_id ORDER BY score';
+    #&Apache::lonnet::logthis('request = '.$/.$request);
+    my $sth = $dbh->prepare($request);
+    $sth->execute();
+    my $rows = $sth->fetchall_arrayref();
+    return ($rows);
+}
+
+########################################################
+########################################################
+
+=pod
+
+=item &get_sum_of_scores
+
+Inputs: $resource (hash ref, needs {'symb'} key),
+$part, (the part id),
+$students (array ref, contents of array are scalars holding 'sname:sdom'),
+$courseid
+
+Returns: the sum of the score on the problem part over the students and the
+   maximum possible value for the sum (taken from the weight table).
+
+=cut
+
+########################################################
+########################################################
+sub get_sum_of_scores {
+    my ($resource,$part,$students,$courseid) = @_;
+    if (! defined($courseid)) {
+        $courseid = $ENV{'request.course.id'};
+    }
+    #
+    &setup_table_names($courseid);
+    my $dbh = &Apache::lonmysql::get_dbh();
+    my $request = 'SELECT SUM(a.awarded*w.weight),SUM(w.weight) FROM '.
+        $performance_table.' AS a '.
+        'NATURAL LEFT JOIN '.$weight_table.' AS w ';
+    $request .= 'WHERE a.symb_id='.&get_symb_id($resource->{'symb'}).
+        ' AND a.part_id='.&get_part_id($part);
+    if (defined($students)) {
+        $request .= ' AND ('.
+            join(' OR ',map {'a.student_id='.&get_student_id(split(':',$_));
+                         } @$students).
+                             ')';
+    }
+    my $sth = $dbh->prepare($request);
+    $sth->execute();
+    my $rows = $sth->fetchrow_arrayref();
+    if ($dbh->err) {
+        &Apache::lonnet::logthis('error = '.$dbh->errstr());
+        return (undef,undef);
+    }
+    return ($rows->[0],$rows->[1]);
+}
+
+
+######################################################
+######################################################
+
+=pod
+
+=item get_student_data
+
+=cut
+
+######################################################
+######################################################
 sub get_student_data {
     my ($students,$courseid) = @_;
     $courseid = $ENV{'request.course.id'} if (! defined($courseid));
@@ -2099,17 +2342,8 @@
     my $dbh = &Apache::lonmysql::get_dbh();
     return undef if (! defined($dbh));
     #
-    my $student_requirements;
-    if ( (defined($Sections) && $Sections->[0] ne 'all')) {
-        $student_requirements = '('.
-            join(' OR ', map { "d.section='".$_."'" } @$Sections
-                 ).')';
-    }
-    #
-    my $enrollment_requirements=undef;
-    if (defined($enrollment) && $enrollment ne 'Any') {
-        $enrollment_requirements = "d.status='".$enrollment."'";
-    }
+    my ($student_requirements,$enrollment_requirements) = 
+        &limit_by_section_and_status($Sections,$enrollment,'d');
     my $request = 'SELECT '.
         'a.student_id, a.awarddetail, a.response_specific_value, '.
         'a.submission, b.timestamp, c.tries, d.student '.
@@ -2400,6 +2634,7 @@
     $fulldump_part_table      = $base_id.'_'.'partdata';
     $fulldump_response_table  = $base_id.'_'.'responsedata';
     $fulldump_timestamp_table = $base_id.'_'.'timestampdata';
+    $weight_table             = $base_id.'_'.'weight';
     #
     @Tables = (
                $symb_table,
@@ -2410,6 +2645,7 @@
                $fulldump_part_table,
                $fulldump_response_table,
                $fulldump_timestamp_table,
+               $weight_table,
                );
     return;
 }
Index: loncom/interface/statistics/lonproblemstatistics.pm
diff -u loncom/interface/statistics/lonproblemstatistics.pm:1.70 loncom/interface/statistics/lonproblemstatistics.pm:1.71
--- loncom/interface/statistics/lonproblemstatistics.pm:1.70	Sun Mar  7 16:42:19 2004
+++ loncom/interface/statistics/lonproblemstatistics.pm	Tue Mar 23 11:35:15 2004
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: lonproblemstatistics.pm,v 1.70 2004/03/07 21:42:19 matthew Exp $
+# $Id: lonproblemstatistics.pm,v 1.71 2004/03/23 16:35:15 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -58,7 +58,7 @@
 use Apache::lonlocal;
 use Spreadsheet::WriteExcel;
 use Apache::lonstathelpers();
-
+use Time::HiRes;
 ##
 ## Localization notes:
 ##
@@ -168,6 +168,15 @@
              sortable  => 'yes',
              graphable => 'yes',
              long_title => 'Percent of students whose final answer is wrong' },
+           { name   => 'deg_of_disc',
+             title  => 'Deg of Disc',
+             align  => 'right',
+             color  => '#FFFFE6',
+             format => '%4.2f',
+             sortable  => 'yes',
+             graphable => 'yes',
+             long_title => 'Degree of Discrimination' },
+
 );
 
 ###############################################
@@ -283,6 +292,8 @@
     #
     &Apache::lonstatistics::PrepareClasslist();
     #
+    &Apache::loncoursedata::populate_weight_table();
+    #
     my ($interface,$output_mode,$show) = &CreateInterface();
     $r->print($interface);
     $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
@@ -849,7 +860,64 @@
     $data->{'title.link'}  = $resource->{'src'}.'?symb='.
         &Apache::lonnet::escape($resource->{'symb'});
     #
+    $data->{'deg_of_disc'} = &compute_discrimination_factor($resource,$part,$sequence);
     return $data;
+}
+
+
+###############################################
+###############################################
+
+=pod
+
+=item &compute_discrimination_factor()
+
+Inputs: $Resource, $Sequence
+
+Returns: integer between -1 and 1
+
+=cut
+
+###############################################
+###############################################
+sub compute_discrimination_factor {
+    my ($resource,$part,$sequence) = @_;
+    &Apache::lonnet::logthis($sequence->{'title'}.' '.$resource->{'title'});
+    my @Resources;
+    foreach my $res (@{$sequence->{'contents'}}) {
+        next if ($res->{'symb'} eq $resource->{'symb'});
+        push (@Resources,$res->{'symb'});
+    }
+    #
+    # rank
+    my $ranking = 
+        &Apache::loncoursedata::rank_students_by_scores_on_resources
+        (\@Resources,
+         \@Apache::lonstatistics::SelectedSections,
+         $Apache::lonstatistics::enrollment_status,undef);
+    #
+    # 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}[($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 $deg_of_disc;
+    if ($top_max == 0 || $bottom_max==0) {
+        $deg_of_disc = 'nan';
+    } else {
+        $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;
 }
 
 ###############################################

--matthew1080059716--