--- loncom/interface/statistics/lonproblemstatistics.pm 2004/08/04 15:07:42 1.93 +++ loncom/interface/statistics/lonproblemstatistics.pm 2005/08/26 21:53:23 1.105 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonproblemstatistics.pm,v 1.93 2004/08/04 15:07:42 matthew Exp $ +# $Id: lonproblemstatistics.pm,v 1.105 2005/08/26 21:53:23 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,7 +50,7 @@ Excel files, and plots. package Apache::lonproblemstatistics; use strict; -use Apache::lonnet(); +use Apache::lonnet; use Apache::loncommon(); use Apache::lonhtmlcommon; use Apache::loncoursedata; @@ -231,6 +231,17 @@ my @Fields = ( selectable => 'yes', defaultselected => 'yes', }, + { name => 'tries_per_correct', + title => 'tries/correct', + align => 'right', + color => '#FFDDDD', + format => '%4.1f', + sortable => 'yes', + graphable => 'yes', + long_title => 'Tries per Correct Answer', + selectable => 'yes', + defaultselected => 'yes', + }, { name => 'num_wrong', title => '#Wrng', align => 'right', @@ -320,6 +331,40 @@ my @Fields = ( # selectable => 'no', # defaultselected => 'yes', # }, +## maxtries included for research purposes. Commented out most of the time. +# { name => 'maxtries', +# title => 'Maxtries', +# align => 'left', +# color => '#FFFFFF', +# sortable => 'no', +# graphable => 'no', +# long_title => 'Maximum number of tries', +# selectable => 'no', +# defaultselected => 'yes', +# }, +## hinttries included for research purposes. Commented out most of the time. +# { name => 'hinttries', +# title => 'hinttries', +# align => 'left', +# color => '#FFFFFF', +# sortable => 'no', +# graphable => 'no', +# long_title => 'Number of tries before a hint appears', +# selectable => 'no', +# defaultselected => 'yes', +# }, +# +## problem weight for instructor + { name => 'weight', + title => 'weight', + align => 'right', + color => '#FFFFFF', + sortable => 'no', + graphable => 'no', + long_title => 'Problem weight (for instructor)', + selectable => 'yes', + defaultselected => 'yes', + }, ); my @SeqFields = ( @@ -471,35 +516,35 @@ my %SelectedFields; sub parse_field_selection { # # Pull out the defaults - if (! defined($ENV{'form.fieldselections'})) { - $ENV{'form.fieldselections'} = []; + if (! defined($env{'form.fieldselections'})) { + $env{'form.fieldselections'} = []; foreach my $field (@Fields) { next if ($field->{'selectable'} ne 'yes'); if ($field->{'defaultselected'} eq 'yes') { - push(@{$ENV{'form.fieldselections'}},$field->{'name'}); + push(@{$env{'form.fieldselections'}},$field->{'name'}); } } } # # Make sure the data we are plotting is there my %NeededFields; - if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '' && - $ENV{'form.plot'} ne 'none') { - if ($ENV{'form.plot'} eq 'degrees') { + if (exists($env{'form.plot'}) && $env{'form.plot'} ne '' && + $env{'form.plot'} ne 'none') { + if ($env{'form.plot'} eq 'degrees') { $NeededFields{'deg_of_diff'}++; $NeededFields{'deg_of_disc'}++; - } elsif ($ENV{'form.plot'} eq 'tries statistics') { + } elsif ($env{'form.plot'} eq 'tries statistics') { $NeededFields{'mean_tries'}++; $NeededFields{'std_tries'}++; $NeededFields{'problem_num'}++; } else { - $NeededFields{$ENV{'form.plot'}}++; + $NeededFields{$env{'form.plot'}}++; } } # # This should not happen, but in case it does... - if (ref($ENV{'form.fieldselections'}) ne 'ARRAY') { - $ENV{'form.fieldselections'} = [$ENV{'form.fieldselections'}]; + if (ref($env{'form.fieldselections'}) ne 'ARRAY') { + $env{'form.fieldselections'} = [$env{'form.fieldselections'}]; } # # Set the field data and the selected fields (for easier checking) @@ -514,7 +559,7 @@ sub parse_field_selection { $field->{'selected'} = 'yes'; $SelectedFields{$field->{'name'}}++; } - foreach my $selection (@{$ENV{'form.fieldselections'}}) { + foreach my $selection (@{$env{'form.fieldselections'}}) { if ($selection eq $field->{'name'} || $selection eq 'all') { $field->{'selected'} = 'yes'; $SelectedFields{$field->{'name'}}++; @@ -581,16 +626,7 @@ sub CreateInterface { $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,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 .= &Apache::lonstatistics::map_select('Maps','multiple,all',5); $Str .= ''.&field_selection_input(); $Str .= ''."\n"; $Str .= ''."\n"; @@ -622,8 +658,18 @@ Main interface to problem statistics. ############################################### ############################################### +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', @@ -645,7 +691,7 @@ sub BuildProblemStatisticsPage { # Finally let the user know we are here my $interface = &CreateInterface($r); $r->print($interface); - $r->print(''); # my @CacheButtonHTML = @@ -656,7 +702,7 @@ sub BuildProblemStatisticsPage { } # $r->print($Str); - if (! exists($ENV{'form.firstrun'})) { + if (! exists($env{'form.firstrun'})) { $r->print('

'. &mt('Press "Generate Statistics" when you are ready.'). '

'. @@ -664,6 +710,7 @@ sub BuildProblemStatisticsPage { 'for the first analysis. Future analysis this session '. ' will not have this delay.'). '

'); + &clean_up(); return; } $r->rflush(); @@ -672,15 +719,31 @@ sub BuildProblemStatisticsPage { # it does not slow things down noticably. &Apache::loncoursedata::populate_weight_table(); # - if (exists($ENV{'form.Excel'})) { + ($navmap,@sequences) = + &Apache::lonstatistics::selected_sequences_with_assessments(); + if (! ref($navmap)) { + $r->print('

'.&mt('A course-wide error occured.').'

'. + '

'.$navmap.'

'); + &clean_up(); + return; + } + if (exists($env{'form.Excel'})) { + $r->print('

'. + &Apache::lonstatistics::section_and_enrollment_description(). + '

'); &Excel_output($r); } else { $r->print(''.' 'x5); $r->rflush(); + $r->print('

'. + &Apache::lonstatistics::section_and_enrollment_description(). + '

'); my $count = 0; - foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { - $count += $seq->{'num_assess_parts'}; + foreach my $seq (@sequences) { + my @resources = + &Apache::lonstathelpers::get_resources($navmap,$seq); + $count += scalar(@resources); } if ($count > 10) { $r->print('

'. @@ -692,9 +755,9 @@ sub BuildProblemStatisticsPage { $r->rflush(); } # - my $sortby = $ENV{'form.sortby'}; + my $sortby = $env{'form.sortby'}; $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/); - my $plot = $ENV{'form.plot'}; + my $plot = $env{'form.plot'}; if ($plot eq '' || $plot eq 'none') { undef($plot); } @@ -709,6 +772,7 @@ sub BuildProblemStatisticsPage { &output_sequence_statistics($r); } } + &clean_up(); return; } @@ -722,9 +786,8 @@ sub output_sequence_statistics { ''."\n". ''); $r->print(&sequence_html_header()); - foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + foreach my $seq (@sequences) { last if ($c->aborted); - next if ($seq->{'num_assess'} < 1); &compute_sequence_statistics($seq); $r->print(&sequence_html_output($seq)); } @@ -747,10 +810,9 @@ sub output_html_by_sequence { my $c = $r->connection(); $r->print(&html_preamble()); # - foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + foreach my $seq (@sequences) { last if ($c->aborted); - next if ($seq->{'num_assess'} < 1); - $r->print("

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

". + $r->print("

".$seq->compTitle."

". '
'."\n". ''."\n". ''. @@ -771,7 +833,7 @@ sub output_html_stats { my ($r)=@_; &compute_all_statistics($r); $r->print(&html_preamble()); - &sort_data($ENV{'form.sortby'}); + &sort_data($env{'form.sortby'}); # my $count=0; foreach my $data (@StatsArray) { @@ -793,7 +855,7 @@ sub output_html_stats { sub html_preamble { my $Str=''; $Str .= "

". - $ENV{'course.'.$ENV{'request.course.id'}.'.description'}. + $env{'course.'.$env{'request.course.id'}.'.description'}. "

\n"; my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits(); if (defined($starttime) || defined($endtime)) { @@ -887,7 +949,7 @@ sub sequence_html_header { sub sequence_html_output { my ($seq) = @_; - my $data = $SeqStat{$seq->{'symb'}}; + my $data = $SeqStat{$seq->symb}; my $row = ''; foreach my $field (@SeqFields) { next if ($field->{'selected'} ne 'yes'); @@ -917,7 +979,7 @@ sub sequence_html_output { sub make_plot { my ($r,$plot) = @_; &compute_all_statistics($r); - &sort_data($ENV{'form.sortby'}); + &sort_data($env{'form.sortby'}); if ($plot eq 'degrees') { °rees_plot($r); } elsif ($plot eq 'tries statistics') { @@ -1171,8 +1233,8 @@ END sub plot_dropdown { my $current = ''; # - if (defined($ENV{'form.plot'})) { - $current = $ENV{'form.plot'}; + if (defined($env{'form.plot'})) { + $current = $env{'form.plot'}; } # my @Additional_Plots = ( @@ -1216,40 +1278,21 @@ sub Excel_output { &compute_all_statistics($r); my $c = $r->connection; return if ($c->aborted()); + # + my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits(); ## ## Create the excel workbook - my $filename = '/prtspool/'. - $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. - time.'_'.rand(1000000000).'.xls'; - my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits(); - # - # Create sheet - my $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); - # - # Check for errors - if (! defined($excel_workbook)) { - $r->log_error("Error creating excel spreadsheet $filename: $!"); - $r->print(&mt("Problems creating new Excel file. ". - "This error has been logged. ". - "Please alert your LON-CAPA administrator.")); - return 0; - } - # - # The excel spreadsheet stores temporary data in files, then put them - # together. If needed we should be able to disable this (memory only). - # The temporary directory must be specified before calling 'addworksheet'. - # File::Temp is used to determine the temporary directory. - $excel_workbook->set_tempdir($Apache::lonnet::tmpdir); + my ($excel_workbook,$filename,$format) = + &Apache::loncommon::create_workbook($r); + return if (! defined($excel_workbook)); # # Add a worksheet - my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'}; if (length($sheetname) > 31) { $sheetname = substr($sheetname,0,31); } my $excel_sheet = $excel_workbook->addworksheet( &Apache::loncommon::clean_excel_name($sheetname)); - # - my $format = &Apache::loncommon::define_excel_formats($excel_workbook); ## ## Begin creating excel sheet ## @@ -1257,17 +1300,16 @@ sub Excel_output { # # Put the course description in the header $excel_sheet->write($rows_output,$cols_output++, - $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, + $env{'course.'.$env{'request.course.id'}.'.description'}, $format->{'h1'}); $cols_output += 3; # # Put a description of the sections listed my $sectionstring = ''; $excel_sheet->write($rows_output,$cols_output++, - &Apache::lonstathelpers::sections_description - (@Apache::lonstatistics::SelectedSections), + &Apache::lonstatistics::section_and_enrollment_description('plaintext'), $format->{'h3'}); - $cols_output += scalar(@Apache::lonstatistics::SelectedSections); + $cols_output += scalar(&Apache::lonstatistics::get_selected_sections()); # # Time restrictions my $time_string; @@ -1299,9 +1341,8 @@ sub Excel_output { ## &write_headers($excel_sheet,$format,\$rows_output,\$cols_output, \@SeqFields); - foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { - next if ($seq->{'num_assess'} < 1); - my $data = $SeqStat{$seq->{'symb'}}; + foreach my $seq (@sequences) { + my $data = $SeqStat{$seq->symb}; $cols_output=0; foreach my $field (@SeqFields) { next if ($field->{'selected'} ne 'yes'); @@ -1397,10 +1438,9 @@ sub write_headers { sub compute_statistics_on_sequence { my ($seq) = @_; my @Data; - foreach my $res (@{$seq->{'contents'}}) { - next if ($res->{'type'} ne 'assessment'); - foreach my $part (@{$res->{'parts'}}) { - next if ($res->{'partdata'}->{$part}->{'Survey'}); + foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)) { + foreach my $part (@{$res->parts}) { + next if ($res->is_survey($part)); # # This is where all the work happens my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1); @@ -1418,9 +1458,8 @@ sub compute_all_statistics { return; } my $c = $r->connection; - foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + foreach my $seq (@sequences) { last if ($c->aborted); - next if ($seq->{'num_assess'} < 1); &compute_sequence_statistics($seq); &compute_statistics_on_sequence($seq); } @@ -1507,20 +1546,20 @@ 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 $symb = $resource->symb; + my $courseid = $env{'request.course.id'}; # my $data = &Apache::loncoursedata::get_problem_statistics - (\@Apache::lonstatistics::SelectedSections, + ([&Apache::lonstatistics::get_selected_sections()], $Apache::lonstatistics::enrollment_status, $symb,$part,$courseid,$starttime,$endtime); $data->{'symb'} = $symb; $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'}); + $data->{'container'} = $sequence->compTitle; + $data->{'title'} = $resource->compTitle; + $data->{'title.link'} = $resource->src.'?symb='. + &Apache::lonnet::escape($resource->symb); # if ($SelectedFields{'deg_of_disc'}) { $data->{'deg_of_disc'} = @@ -1529,12 +1568,12 @@ sub get_statistics { # # Store in metadata if computations were done for all students if ($data->{'num_students'} > 1) { - my @Sections = @Apache::lonstatistics::SelectedSections; + 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]; + $data->{'course'} = $env{'request.course.id'}; + my $urlres=(&Apache::lonnet::decode_symb($resource->symb))[2]; $data->{'urlres'}=$urlres; my %storestats = &LONCAPA::lonmetadata::dynamic_metadata_storage($data); @@ -1542,11 +1581,20 @@ sub get_statistics { &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) # $data->{'duedate'} = # &Apache::lonnet::EXT('resource.'.$part.'.duedate',$symb); # $data->{'opendate'} = # &Apache::lonnet::EXT('resource.'.$part.'.opendate',$symb); +# $data->{'maxtries'} = +# &Apache::lonnet::EXT('resource.'.$part.'.maxtries',$symb); +# $data->{'hinttries'} = +# &Apache::lonnet::EXT('resource.'.$part.'.hinttries',$symb); + $data->{'weight'} = + &Apache::lonnet::EXT('resource.'.$part.'.weight',$symb); # $data->{'resptypes'} = join(',',@{$resource->{'partdata'}->{$part}->{'ResponseTypes'}}); return $data; } @@ -1567,11 +1615,12 @@ Returns: integer between -1 and 1 ############################################### ############################################### sub compute_discrimination_factor { - my ($resource,$part,$sequence) = @_; + my ($resource,$part,$seq) = @_; + my $symb = $resource->symb; my @Resources; - foreach my $res (@{$sequence->{'contents'}}) { - next if ($res->{'symb'} eq $resource->{'symb'}); - push (@Resources,$res->{'symb'}); + foreach my $res (&Apache::lonstathelpers::get_resources($navmap,$seq)){ + next if ($res->symb eq $symb); + push (@Resources,$res->symb); } # # rank @@ -1579,7 +1628,7 @@ sub compute_discrimination_factor { my $ranking = &Apache::loncoursedata::rank_students_by_scores_on_resources (\@Resources, - \@Apache::lonstatistics::SelectedSections, + [&Apache::lonstatistics::get_selected_sections()], $Apache::lonstatistics::enrollment_status,undef, $starttime,$endtime); # @@ -1597,10 +1646,10 @@ sub compute_discrimination_factor { return 'nan'; } my ($bottom_sum,$bottom_max) = - &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet, + &Apache::loncoursedata::get_sum_of_scores($symb,$part,\@BottomSet, undef,$starttime,$endtime); my ($top_sum,$top_max) = - &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet, + &Apache::loncoursedata::get_sum_of_scores($symb,$part,\@TopSet, undef,$starttime,$endtime); my $deg_of_disc; if ($top_max == 0 || $bottom_max==0) { @@ -1632,21 +1681,22 @@ sub compute_discrimination_factor { ############################################### sub compute_sequence_statistics { my ($seq) = @_; - my $symb = $seq->{'symb'}; + my $symb = $seq->symb; my @Resources; - foreach my $res (@{$seq->{'contents'}}) { - next if ($res->{'type'} ne 'assessment'); - push (@Resources,$res->{'symb'}); + 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::SelectedSections, + ([&Apache::lonstatistics::get_selected_sections()], $Apache::lonstatistics::enrollment_status, \@Resources,$starttime,$endtime,undef); - $SeqStat{$symb}->{'title'} = $seq->{'title'}; + $SeqStat{$symb}->{'title'} = $seq->compTitle; $SeqStat{$symb}->{'scoremax'} = $smax; $SeqStat{$symb}->{'scoremin'} = $smin; $SeqStat{$symb}->{'scoremean'} = $sMean; @@ -1658,10 +1708,10 @@ sub compute_sequence_statistics { # 'correct' is taken to mean my ($cmin,$cmax,$cMean,$cSTD,$ccount)= &Apache::loncoursedata::count_stats - (\@Apache::lonstatistics::SelectedSections, + ([&Apache::lonstatistics::get_selected_sections()], $Apache::lonstatistics::enrollment_status, \@Resources,$starttime,$endtime,undef); - my $K = $seq->{'num_assess_parts'}; + my $K = $part_count; my $kr_21; if ($K > 1 && $cSTD > 0) { $kr_21 = ($K/($K-1)) * (1 - $cMean*($K-$cMean)/($K*$cSTD**2));