[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--