Annotation of loncom/interface/statistics/lonproblemstatistics.pm, revision 1.70
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.70 ! matthew 3: # $Id: lonproblemstatistics.pm,v 1.69 2004/03/01 16:39:19 matthew Exp $
1.1 stredwic 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: # (Navigate problems for statistical reports
28: #
1.47 matthew 29: ###############################################
30: ###############################################
31:
32: =pod
33:
34: =head1 NAME
35:
36: lonproblemstatistics
37:
38: =head1 SYNOPSIS
39:
40: Routines to present problem statistics to instructors via tables,
41: Excel files, and plots.
42:
43: =over 4
44:
45: =cut
46:
47: ###############################################
48: ###############################################
1.1 stredwic 49:
1.36 minaeibi 50: package Apache::lonproblemstatistics;
1.1 stredwic 51:
52: use strict;
53: use Apache::lonnet();
1.62 matthew 54: use Apache::loncommon();
1.1 stredwic 55: use Apache::lonhtmlcommon;
56: use Apache::loncoursedata;
1.41 matthew 57: use Apache::lonstatistics;
1.59 matthew 58: use Apache::lonlocal;
1.44 matthew 59: use Spreadsheet::WriteExcel;
1.70 ! matthew 60: use Apache::lonstathelpers();
1.1 stredwic 61:
1.59 matthew 62: ##
63: ## Localization notes:
64: ##
65: ## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
66: ## header for plots created with Graph.pm, both of which more than likely do
67: ## not support localization.
68: ##
1.49 matthew 69: my @Fields = (
70: { name => 'problem_num',
71: title => 'P#',
72: align => 'right',
73: color => '#FFFFE6' },
74: { name => 'container',
1.51 matthew 75: title => 'Sequence or Folder',
1.49 matthew 76: align => 'left',
77: color => '#FFFFE6',
78: sortable => 'yes' },
79: { name => 'title',
80: title => 'Title',
81: align => 'left',
82: color => '#FFFFE6',
83: special => 'link',
84: sortable => 'yes', },
85: { name => 'part',
86: title => 'Part',
87: align => 'left',
1.55 matthew 88: color => '#FFFFE6',
89: },
1.49 matthew 90: { name => 'num_students',
91: title => '#Stdnts',
92: align => 'right',
93: color => '#EEFFCC',
94: format => '%d',
95: sortable => 'yes',
96: graphable => 'yes',
97: long_title => 'Number of Students Attempting Problem' },
98: { name => 'tries',
99: title => 'Tries',
100: align => 'right',
101: color => '#EEFFCC',
102: format => '%d',
103: sortable => 'yes',
104: graphable => 'yes',
105: long_title => 'Total Number of Tries' },
106: { name => 'max_tries',
107: title => 'Max Tries',
108: align => 'right',
109: color => '#DDFFFF',
110: format => '%d',
111: sortable => 'yes',
112: graphable => 'yes',
113: long_title => 'Maximum Number of Tries' },
114: { name => 'mean_tries',
115: title => 'Mean Tries',
116: align => 'right',
117: color => '#DDFFFF',
118: format => '%5.2f',
119: sortable => 'yes',
120: graphable => 'yes',
121: long_title => 'Average Number of Tries' },
122: { name => 'std_tries',
123: title => 'S.D. tries',
124: align => 'right',
125: color => '#DDFFFF',
126: format => '%5.2f',
127: sortable => 'yes',
128: graphable => 'yes',
129: long_title => 'Standard Deviation of Number of Tries' },
130: { name => 'skew_tries',
131: title => 'Skew Tries',
132: align => 'right',
133: color => '#DDFFFF',
134: format => '%5.2f',
135: sortable => 'yes',
136: graphable => 'yes',
137: long_title => 'Skew of Number of Tries' },
138: { name => 'deg_of_diff',
139: title => 'DoDiff',
140: align => 'right',
141: color => '#DDFFFF',
142: format => '%5.2f',
143: sortable => 'yes',
144: graphable => 'yes',
1.55 matthew 145: long_title => 'Degree of Difficulty'.
146: '[ 1 - ((#YES+#yes) / Tries) ]'},
1.49 matthew 147: { name => 'num_solved',
148: title => '#YES',
149: align => 'right',
150: color => '#FFDDDD',
1.63 matthew 151: format => '%4.1f',# format => '%d',
1.49 matthew 152: sortable => 'yes',
153: graphable => 'yes',
154: long_title => 'Number of Students able to Solve' },
155: { name => 'num_override',
156: title => '#yes',
157: align => 'right',
158: color => '#FFDDDD',
1.63 matthew 159: format => '%4.1f',# format => '%d',
1.49 matthew 160: sortable => 'yes',
161: graphable => 'yes',
162: long_title => 'Number of Students given Override' },
163: { name => 'per_wrong',
164: title => '%Wrng',
165: align => 'right',
166: color => '#FFFFE6',
167: format => '%4.1f',
168: sortable => 'yes',
169: graphable => 'yes',
1.55 matthew 170: long_title => 'Percent of students whose final answer is wrong' },
1.49 matthew 171: );
172:
1.47 matthew 173: ###############################################
174: ###############################################
175:
176: =pod
177:
178: =item &CreateInterface()
179:
180: Create the main intereface for the statistics page. Allows the user to
181: select sections, maps, and output.
182:
183: =cut
1.1 stredwic 184:
1.47 matthew 185: ###############################################
186: ###############################################
1.57 matthew 187: my @OutputOptions =
188: (
1.70 ! matthew 189: { name => 'grouped by sequence',
1.57 matthew 190: value => 'HTML problem statistics grouped',
191: description => 'Output statistics for the problem parts.',
192: mode => 'html',
193: show => 'grouped',
194: },
1.70 ! matthew 195: { name => 'ungrouped',
1.57 matthew 196: value => 'HTML problem statistics ungrouped',
197: description => 'Output statistics for the problem parts.',
198: mode => 'html',
199: show => 'ungrouped',
200: },
1.70 ! matthew 201: { name => 'Excel',
1.57 matthew 202: value => 'Excel problem statistics',
203: description => 'Output statistics for the problem parts '.
204: 'in an Excel workbook',
205: mode => 'excel',
206: show => 'all',
207: },
208: );
209:
1.41 matthew 210: sub CreateInterface {
211: my $Str = '';
1.67 matthew 212: $Str .= &Apache::lonhtmlcommon::breadcrumbs
1.69 matthew 213: (undef,'Overall Problem Statistics','Statistics_Overall_Key');
1.41 matthew 214: $Str .= '<table cellspacing="5">'."\n";
215: $Str .= '<tr>';
1.59 matthew 216: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
217: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
218: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
219: $Str .= '<td align="center"><b>'.&mt('Output').'</b></td>';
1.70 ! matthew 220: $Str .= '<td rowspan="2">'.
! 221: &Apache::lonstathelpers::limit_by_time_form().'</td>';
1.41 matthew 222: $Str .= '</tr>'."\n";
223: #
224: $Str .= '<tr><td align="center">'."\n";
225: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.50 matthew 226: $Str .= '</td><td align="center">';
227: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.41 matthew 228: $Str .= '</td><td align="center">';
229: #
230: my $only_seq_with_assessments = sub {
231: my $s=shift;
232: if ($s->{'num_assess'} < 1) {
233: return 0;
234: } else {
235: return 1;
236: }
237: };
238: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
239: $only_seq_with_assessments);
240: $Str .= '</td><td>'."\n";
1.57 matthew 241: my ($html,$outputmode,$show) =
242: &Apache::lonstatistics::CreateAndParseOutputSelector(
243: 'statsoutputmode',
244: 'HTML problem statistics grouped',
245: @OutputOptions);
246: $Str .= $html;
1.41 matthew 247: $Str .= '</td></tr>'."\n";
248: $Str .= '</table>'."\n";
1.59 matthew 249: $Str .= '<input type="submit" name="GenerateStatistics" value="'.
250: &mt('Generate Statistics').'" />';
1.54 matthew 251: $Str .= ' 'x5;
1.59 matthew 252: $Str .= '<input type="submit" name="ClearCache" value="'.
253: &mt('Clear Caches').'" />';
1.54 matthew 254: $Str .= ' 'x5;
1.57 matthew 255: return ($Str,$outputmode,$show);
1.41 matthew 256: }
1.25 stredwic 257:
1.41 matthew 258: ###############################################
259: ###############################################
1.28 stredwic 260:
1.47 matthew 261: =pod
262:
263: =item &BuildProblemStatisticsPage()
264:
265: Main interface to problem statistics.
266:
267: =cut
268:
1.41 matthew 269: ###############################################
270: ###############################################
271: sub BuildProblemStatisticsPage {
272: my ($r,$c)=@_;
1.61 matthew 273: #
274: my %Saveable_Parameters = ('Status' => 'scalar',
275: 'statsoutputmode' => 'scalar',
276: 'Section' => 'array',
277: 'StudentData' => 'array',
278: 'Maps' => 'array');
279: &Apache::loncommon::store_course_settings('statistics',
280: \%Saveable_Parameters);
281: &Apache::loncommon::restore_course_settings('statistics',
282: \%Saveable_Parameters);
283: #
284: &Apache::lonstatistics::PrepareClasslist();
1.41 matthew 285: #
1.57 matthew 286: my ($interface,$output_mode,$show) = &CreateInterface();
287: $r->print($interface);
1.41 matthew 288: $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
289: $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
290: '" />');
1.49 matthew 291: $r->print('<input type="hidden" name="plot" value="" />');
1.41 matthew 292: if (! exists($ENV{'form.statsfirstcall'})) {
293: return;
1.28 stredwic 294: }
1.41 matthew 295: #
1.56 matthew 296: &Apache::lonstatistics::Gather_Student_Data($r);
1.41 matthew 297: #
298: #
299: if ($output_mode eq 'html') {
300: $r->print("<h2>".
301: $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
302: "</h2>\n");
1.70 ! matthew 303: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
! 304: if (defined($starttime) || defined($endtime)) {
! 305: # Inform the user what the time limits on the data are.
! 306: $r->print('<h3>'.&mt('Statistics on submissions from [_1] to [_2]',
! 307: &Apache::lonlocal::locallocaltime($starttime),
! 308: &Apache::lonlocal::locallocaltime($endtime)).
! 309: '</h3>');
! 310: }
! 311: $r->print("<h3>".&mt('Compiled on [_1]',
! 312: &Apache::lonlocal::locallocaltime(time))."</h3>");
1.41 matthew 313: $r->rflush();
314: if ($show eq 'grouped') {
315: &output_html_grouped_by_sequence($r);
316: } elsif ($show eq 'ungrouped') {
317: &output_html_ungrouped($r);
318: }
1.44 matthew 319: } elsif ($output_mode eq 'excel') {
1.59 matthew 320: $r->print('<h2>'.&mt('Preparing Excel Spreadsheet').'</h2>');
1.44 matthew 321: &output_excel($r);
1.41 matthew 322: } else {
1.59 matthew 323: $r->print('<h1>'.&mt('Not implemented').'</h1>');
1.28 stredwic 324: }
1.41 matthew 325: return;
326: }
1.21 stredwic 327:
1.44 matthew 328: ###############################################
329: ###############################################
330:
1.47 matthew 331: =pod
332:
333: =item &output_html_grouped_by_sequence()
334:
335: Presents the statistics data as an html table organized by the order
336: the assessments appear in the course.
337:
338: =cut
339:
1.44 matthew 340: ###############################################
341: ###############################################
1.41 matthew 342: sub output_html_grouped_by_sequence {
343: my ($r) = @_;
1.45 matthew 344: my $problem_num = 0;
1.41 matthew 345: #$r->print(&ProblemStatisticsLegend());
346: foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
347: next if ($sequence->{'num_assess'}<1);
348: $r->print("<h3>".$sequence->{'title'}."</h3>");
349: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
350: $r->print('<table border="0" cellpadding="3">'."\n");
1.49 matthew 351: $r->print('<tr bgcolor="#FFFFE6">');
352: my $Str = &statistics_table_header('no container no plots');
353: $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
1.41 matthew 354: foreach my $resource (@{$sequence->{'contents'}}) {
355: next if ($resource->{'type'} ne 'assessment');
356: foreach my $part (@{$resource->{'parts'}}) {
1.45 matthew 357: $problem_num++;
1.49 matthew 358: my $data = &get_statistics($sequence,$resource,$part,
359: $problem_num);
1.45 matthew 360: my $option = '';
1.49 matthew 361: $r->print('<tr>'.&statistics_html_table_data($data,
362: 'no container').
1.41 matthew 363: "</tr>\n");
364: }
365: }
366: $r->print("</table>\n");
367: $r->print("</td></tr></table>\n");
368: $r->rflush();
1.21 stredwic 369: }
1.41 matthew 370: #
371: return;
372: }
1.25 stredwic 373:
1.41 matthew 374: ###############################################
375: ###############################################
1.26 stredwic 376:
1.47 matthew 377: =pod
378:
379: =item &output_html_ungrouped()
380:
381: Presents the statistics data in a single html table which can be sorted by
382: different columns.
383:
384: =cut
385:
1.41 matthew 386: ###############################################
387: ###############################################
388: sub output_html_ungrouped {
1.45 matthew 389: my ($r,$option) = @_;
1.41 matthew 390: #
1.49 matthew 391: if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '') {
392: &plot_statistics($r,$ENV{'form.plot'});
393: }
394: #
1.45 matthew 395: my $problem_num = 0;
1.41 matthew 396: my $show_container = 0;
1.43 matthew 397: my $show_part = 0;
1.41 matthew 398: #$r->print(&ProblemStatisticsLegend());
1.42 matthew 399: my $sortby = undef;
1.49 matthew 400: foreach my $field (@Fields) {
401: if ($ENV{'form.sortby'} eq $field->{'name'}) {
402: $sortby = $field->{'name'};
1.42 matthew 403: }
404: }
1.49 matthew 405: if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
406: $sortby = 'container';
1.42 matthew 407: }
1.45 matthew 408: # If there is more than one sequence, list their titles
1.41 matthew 409: my @Sequences = &Apache::lonstatistics::Sequences_with_Assess();
1.49 matthew 410: if (@Sequences < 1) {
411: $option .= ' no container';
1.45 matthew 412: }
1.41 matthew 413: #
1.42 matthew 414: # Compile the data
415: my @Statsarray;
1.41 matthew 416: foreach my $sequence (@Sequences) {
417: next if ($sequence->{'num_assess'}<1);
418: foreach my $resource (@{$sequence->{'contents'}}) {
419: next if ($resource->{'type'} ne 'assessment');
420: foreach my $part (@{$resource->{'parts'}}) {
1.45 matthew 421: $problem_num++;
1.49 matthew 422: my $data = &get_statistics($sequence,$resource,$part,
423: $problem_num);
1.43 matthew 424: $show_part = 1 if ($part ne '0');
425: #
1.49 matthew 426: push (@Statsarray,$data);
1.42 matthew 427: }
428: }
429: }
430: #
431: # Sort the data
1.43 matthew 432: my @OutputOrder;
1.49 matthew 433: if ($sortby eq 'container') {
1.43 matthew 434: @OutputOrder = @Statsarray;
1.42 matthew 435: } else {
436: # $sortby is already defined, so we can charge ahead
437: if ($sortby =~ /^(title|part)$/i) {
438: # Alpha comparison
439: @OutputOrder = sort {
1.43 matthew 440: lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
1.49 matthew 441: lc($a->{'title'}) cmp lc($b->{'title'}) ||
442: lc($a->{'part'}) cmp lc($b->{'part'});
1.42 matthew 443: } @Statsarray;
444: } else {
445: # Numerical comparison
446: @OutputOrder = sort {
447: my $retvalue = 0;
448: if ($b->{$sortby} eq 'nan') {
449: if ($a->{$sortby} ne 'nan') {
450: $retvalue = -1;
451: } else {
452: $retvalue = 0;
453: }
454: }
455: if ($a->{$sortby} eq 'nan') {
456: if ($b->{$sortby} ne 'nan') {
457: $retvalue = 1;
458: }
459: }
460: if ($retvalue eq '0') {
461: $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
1.49 matthew 462: lc($a->{'title'}) <=> lc($b->{'title'}) ||
463: lc($a->{'part'}) <=> lc($b->{'part'});
1.41 matthew 464: }
1.42 matthew 465: $retvalue;
466: } @Statsarray;
467: }
1.43 matthew 468: }
1.49 matthew 469: $option .= 'no part' if (! $show_part);
470: my $num_output = 0;
471: #
472: # output the headers
473: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
474: $r->print('<table border="0" cellpadding="3">'."\n");
475: my $Str = &statistics_table_header($option.' sortable');
476: $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
477: #
478: foreach my $rowdata (@OutputOrder) {
479: $num_output++;
480: if ($num_output % 25 == 0) {
481: $r->print("</table>\n</td></tr></table>\n");
482: #
483: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n");
484: $r->print('<table border="0" cellpadding="3">'."\n");
485: my $Str = &statistics_table_header($option.' sortable');
486: $r->print('<tr bgcolor="#FFFFE6">'.$Str."</tr>\n");
487: $r->rflush();
488: }
489: $r->print('<tr>'.&statistics_html_table_data($rowdata,$option).
490: "</tr>\n");
1.26 stredwic 491: }
1.41 matthew 492: $r->print("</table>\n");
493: $r->print("</td></tr></table>\n");
1.26 stredwic 494: $r->rflush();
1.41 matthew 495: #
496: return;
1.42 matthew 497: }
498:
1.41 matthew 499: ###############################################
500: ###############################################
1.26 stredwic 501:
1.47 matthew 502: =pod
503:
504: =item &output_excel()
505:
506: Presents the statistical data in an Excel 95 compatable spreadsheet file.
507:
508: =cut
509:
1.41 matthew 510: ###############################################
511: ###############################################
1.44 matthew 512: sub output_excel {
513: my ($r) = @_;
514: my $filename = '/prtspool/'.
515: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
516: time.'_'.rand(1000000000).'.xls';
517: #
1.70 ! matthew 518: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
! 519: #
1.44 matthew 520: my $excel_workbook = undef;
521: my $excel_sheet = undef;
522: #
523: my $rows_output = 0;
524: my $cols_output = 0;
525: #
526: # Create sheet
527: $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
528: #
529: # Check for errors
530: if (! defined($excel_workbook)) {
531: $r->log_error("Error creating excel spreadsheet $filename: $!");
1.59 matthew 532: $r->print(&mt("Problems creating new Excel file. ".
1.44 matthew 533: "This error has been logged. ".
1.59 matthew 534: "Please alert your LON-CAPA administrator."));
1.44 matthew 535: return ;
536: }
537: #
538: # The excel spreadsheet stores temporary data in files, then put them
539: # together. If needed we should be able to disable this (memory only).
540: # The temporary directory must be specified before calling 'addworksheet'.
541: # File::Temp is used to determine the temporary directory.
542: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
543: #
544: # Add a worksheet
545: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
546: if (length($sheetname) > 31) {
547: $sheetname = substr($sheetname,0,31);
548: }
1.62 matthew 549: $excel_sheet = $excel_workbook->addworksheet(
550: &Apache::loncommon::clean_excel_name($sheetname)
551: );
1.44 matthew 552: #
553: # Put the course description in the header
554: $excel_sheet->write($rows_output,$cols_output++,
555: $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
556: $cols_output += 3;
557: #
558: # Put a description of the sections listed
559: my $sectionstring = '';
560: my @Sections = @Apache::lonstatistics::SelectedSections;
561: if (scalar(@Sections) > 1) {
562: if (scalar(@Sections) > 2) {
563: my $last = pop(@Sections);
564: $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
565: } else {
566: $sectionstring = "Sections ".join(' and ',@Sections);
567: }
568: } else {
569: if ($Sections[0] eq 'all') {
570: $sectionstring = "All sections";
571: } else {
572: $sectionstring = "Section ".$Sections[0];
573: }
574: }
575: $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
576: $cols_output += scalar(@Sections);
577: #
1.70 ! matthew 578: # Time restrictions
! 579: my $time_string;
! 580: if (defined($starttime)) {
! 581: # call localtime but not lonlocal:locallocaltime because excel probably
! 582: # cannot handle localized text. Probably.
! 583: $time_string .= 'Data collected from '.localtime($time_string);
! 584: if (defined($endtime)) {
! 585: $time_string .= ' to '.localtime($endtime);
! 586: }
! 587: $time_string .= '.';
! 588: } elsif (defined($endtime)) {
! 589: # See note above about lonlocal:locallocaltime
! 590: $time_string .= 'Data collected before '.localtime($endtime).'.';
! 591: }
! 592:
! 593: #
1.44 matthew 594: # Put the date in there too
595: $excel_sheet->write($rows_output,$cols_output++,
596: 'Compiled on '.localtime(time));
597: #
598: $rows_output++;
599: $cols_output=0;
600: #
1.55 matthew 601: # Long Headersheaders
602: foreach my $field (@Fields) {
603: next if ($field->{'name'} eq 'problem_num');
604: if (exists($field->{'long_title'})) {
605: $excel_sheet->write($rows_output,$cols_output++,
606: $field->{'long_title'});
607: } else {
608: $excel_sheet->write($rows_output,$cols_output++,'');
609: }
610: }
611: $rows_output++;
612: $cols_output=0;
613: # Brief headers
1.49 matthew 614: foreach my $field (@Fields) {
615: next if ($field->{'name'} eq 'problem_num');
1.59 matthew 616: # Use english for excel as I am not sure how well excel handles
617: # other character sets....
1.49 matthew 618: $excel_sheet->write($rows_output,$cols_output++,$field->{'title'});
1.44 matthew 619: }
620: $rows_output++;
621: #
622: # Write the data
1.49 matthew 623: my $problem_num=0;
1.44 matthew 624: foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
625: next if ($sequence->{'num_assess'}<1);
626: foreach my $resource (@{$sequence->{'contents'}}) {
627: next if ($resource->{'type'} ne 'assessment');
628: foreach my $part (@{$resource->{'parts'}}) {
629: $cols_output=0;
1.49 matthew 630: $problem_num++;
631: my $data = &get_statistics($sequence,$resource,$part,
632: $problem_num);
1.44 matthew 633: #
634: if (!defined($part) || $part eq '') {
635: $part = ' ';
636: }
1.49 matthew 637: foreach my $field (@Fields) {
638: next if ($field->{'name'} eq 'problem_num');
639: $excel_sheet->write($rows_output,$cols_output++,
640: $data->{$field->{'name'}});
1.44 matthew 641: }
642: $rows_output++;
643: }
644: }
645: }
646: #
647: # Write the excel file
648: $excel_workbook->close();
649: # Tell the user where to get their excel file
650: $r->print('<br />'.
1.59 matthew 651: '<a href="'.$filename.'">'.
652: &mt('Your Excel Spreadsheet').'</a>'."\n");
1.44 matthew 653: $r->rflush();
654: return;
655: }
656:
1.45 matthew 657: ###############################################
658: ###############################################
1.44 matthew 659:
1.47 matthew 660: =pod
661:
662: =item &statistics_html_table_data()
663:
664: Help function used to format the rows for HTML table output.
665:
666: =cut
667:
1.45 matthew 668: ###############################################
669: ###############################################
1.41 matthew 670: sub statistics_html_table_data {
1.49 matthew 671: my ($data,$options) = @_;
1.41 matthew 672: my $row = '';
1.49 matthew 673: foreach my $field (@Fields) {
674: next if ($options =~ /no $field->{'name'}/);
675: $row .= '<td bgcolor="'.$field->{'color'}.'"';
676: if (exists($field->{'align'})) {
677: $row .= ' align="'.$field->{'align'}.'"';
678: }
679: $row .= '>';
680: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
1.53 matthew 681: $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.'">';
1.49 matthew 682: }
683: if (exists($field->{'format'})) {
684: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
685: } else {
686: $row .= $data->{$field->{'name'}};
687: }
688: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
689: $row.= '</a>';
690: }
691: $row .= '</td>';
1.26 stredwic 692: }
1.41 matthew 693: return $row;
694: }
1.26 stredwic 695:
1.49 matthew 696: sub statistics_table_header {
697: my ($options) = @_;
698: my $header_row;
699: foreach my $field (@Fields) {
700: next if ($options =~ /no $field->{'name'}/);
701: $header_row .= '<th>';
702: if ($options =~ /sortable/ &&
703: exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
704: $header_row .= '<a href="javascript:'.
705: 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
706: ';document.Statistics.submit();">';
707: }
1.59 matthew 708: $header_row .= &mt($field->{'title'});
1.49 matthew 709: if ($options =~ /sortable/) {
710: $header_row.= '</a>';
711: }
712: if ($options !~ /no plots/ &&
713: exists($field->{'graphable'}) &&
714: $field->{'graphable'} eq 'yes') {
715: $header_row.=' (';
716: $header_row .= '<a href="javascript:'.
717: "document.Statistics.plot.value='$field->{'name'}'".
718: ';document.Statistics.submit();">';
1.59 matthew 719: $header_row .= &mt('plot').'</a>)';
1.49 matthew 720: }
721: $header_row .= '</th>';
722: }
723: return $header_row;
724: }
725:
1.41 matthew 726: ###############################################
727: ###############################################
1.47 matthew 728:
729: =pod
730:
731: =item &plot_statistics()
732:
733: =cut
734:
735: ###############################################
736: ###############################################
1.45 matthew 737: sub plot_statistics {
738: my ($r,$datafield) = @_;
739: my @Data;
740: #
1.49 matthew 741: #
742: my $sortfield = undef;
743: my $title = undef;
744: foreach my $field (@Fields) {
745: if ($datafield eq $field->{'name'} &&
746: exists($field->{'graphable'}) && $field->{'graphable'} eq 'yes') {
747: $sortfield = $field->{'name'};
748: $title = $field->{'long_title'};
749: }
1.34 minaeibi 750: }
1.49 matthew 751: return if (! defined($sortfield) || $sortfield eq '');
1.45 matthew 752: #
753: my $Max = 0;
1.49 matthew 754: my $problem_num = 0;
1.45 matthew 755: foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()) {
756: next if ($sequence->{'num_assess'}<1);
757: foreach my $resource (@{$sequence->{'contents'}}) {
758: next if ($resource->{'type'} ne 'assessment');
759: foreach my $part (@{$resource->{'parts'}}) {
1.49 matthew 760: my $problem_number++;
761: my $data = &get_statistics($sequence,$resource,$part,
762: $problem_num);
763: my $value = $data->{$sortfield};
764: $Max = $value if ($Max < $value);
765: push (@Data,$value);
1.45 matthew 766: }
767: }
1.26 stredwic 768: }
1.45 matthew 769: #
770: # Print out plot request
1.49 matthew 771: my $yaxis = '';
772: if ($sortfield eq 'per_wrong') {
773: $yaxis = 'Percent';
1.45 matthew 774: }
775: #
776: # Determine appropriate value for $Max
1.49 matthew 777: if ($sortfield eq 'deg_of_diff') {
1.45 matthew 778: if ($Max > 0.5) {
779: $Max = 1;
780: } elsif ($Max > 0.2) {
781: $Max = 0.5;
782: } elsif ($Max > 0.1) {
783: $Max = 0.2;
784: }
1.49 matthew 785: } elsif ($sortfield eq 'per_wrong') {
1.45 matthew 786: if ($Max > 50) {
787: $Max = 100;
788: } elsif ($Max > 25) {
789: $Max = 50;
790: } elsif ($Max > 20) {
791: $Max = 25;
792: } elsif ($Max > 10) {
793: $Max = 20;
794: } elsif ($Max > 5) {
795: $Max = 10;
1.24 stredwic 796: } else {
1.45 matthew 797: $Max = 5;
1.24 stredwic 798: }
799: }
1.45 matthew 800:
1.60 matthew 801: $r->print("<p>".&Apache::loncommon::DrawBarGraph($title,
802: 'Problem Number',
803: $yaxis,
804: $Max,
1.65 matthew 805: undef, # colors
806: undef, # labels
1.60 matthew 807: \@Data)."</p>\n");
1.45 matthew 808: #
809: # Print out the data
810: $ENV{'form.sortby'} = 'Contents';
1.49 matthew 811: # &output_html_ungrouped($r);
1.24 stredwic 812: return;
1.48 matthew 813: }
814:
1.70 ! matthew 815: ########################################################
! 816: ########################################################
! 817:
! 818: =pod
! 819:
! 820: =item &get_statistics()
! 821:
! 822: Wrapper routine from the call to loncoursedata::get_problem_statistics.
! 823: Calls lonstathelpers::get_time_limits() to limit the data set by time.
! 824:
! 825: Inputs: $sequence, $resource, $part, $problem_num
! 826:
! 827: Returns: Hash reference with statistics data from
! 828: loncoursedata::get_problem_statistics.
! 829:
! 830: =cut
! 831:
! 832: ########################################################
! 833: ########################################################
1.48 matthew 834: sub get_statistics {
1.49 matthew 835: my ($sequence,$resource,$part,$problem_num) = @_;
1.48 matthew 836: #
1.70 ! matthew 837: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.49 matthew 838: my $symb = $resource->{'symb'};
1.48 matthew 839: my $courseid = $ENV{'request.course.id'};
840: #
1.49 matthew 841: my $data = &Apache::loncoursedata::get_problem_statistics
1.66 matthew 842: (\@Apache::lonstatistics::SelectedSections,
843: $Apache::lonstatistics::enrollment_status,
1.70 ! matthew 844: $symb,$part,$courseid,$starttime,$endtime);
1.49 matthew 845: $data->{'part'} = $part;
846: $data->{'problem_num'} = $problem_num;
847: $data->{'container'} = $sequence->{'title'};
848: $data->{'title'} = $resource->{'title'};
1.53 matthew 849: $data->{'title.link'} = $resource->{'src'}.'?symb='.
850: &Apache::lonnet::escape($resource->{'symb'});
1.49 matthew 851: #
852: return $data;
1.1 stredwic 853: }
1.12 minaeibi 854:
1.45 matthew 855: ###############################################
856: ###############################################
1.47 matthew 857:
858: =pod
859:
860: =item &ProblemStatisticsLegend()
1.59 matthew 861:
862: HELP This needs to be localized, or at least generated automatically.
1.47 matthew 863:
864: =cut
1.1 stredwic 865:
1.45 matthew 866: ###############################################
867: ###############################################
1.1 stredwic 868: sub ProblemStatisticsLegend {
869: my $Ptr = '';
870: $Ptr = '<table border="0">';
871: $Ptr .= '<tr><td>';
1.6 minaeibi 872: $Ptr .= '<b>#Stdnts</b></td>';
1.19 stredwic 873: $Ptr .= '<td>Total number of students attempted the problem.';
1.1 stredwic 874: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 875: $Ptr .= '<b>Tries</b></td>';
1.19 stredwic 876: $Ptr .= '<td>Total number of tries for solving the problem.';
1.1 stredwic 877: $Ptr .= '</td></tr><tr><td>';
1.49 matthew 878: $Ptr .= '<b>Max Tries</b></td>';
1.19 stredwic 879: $Ptr .= '<td>Largest number of tries for solving the problem by a student.';
1.1 stredwic 880: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 881: $Ptr .= '<b>Mean</b></td>';
1.19 stredwic 882: $Ptr .= '<td>Average number of tries. [ Tries / #Stdnts ]';
1.1 stredwic 883: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 884: $Ptr .= '<b>#YES</b></td>';
1.1 stredwic 885: $Ptr .= '<td>Number of students solved the problem correctly.';
886: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 887: $Ptr .= '<b>#yes</b></td>';
1.1 stredwic 888: $Ptr .= '<td>Number of students solved the problem by override.';
889: $Ptr .= '</td></tr><tr><td>';
1.19 stredwic 890: $Ptr .= '<b>%Wrong</b></td>';
891: $Ptr .= '<td>Percentage of students who tried to solve the problem ';
892: $Ptr .= 'but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]';
1.1 stredwic 893: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 894: $Ptr .= '<b>DoDiff</b></td>';
1.1 stredwic 895: $Ptr .= '<td>Degree of Difficulty of the problem. ';
896: $Ptr .= '[ 1 - ((#YES+#yes) / Tries) ]';
897: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 898: $Ptr .= '<b>S.D.</b></td>';
1.1 stredwic 899: $Ptr .= '<td>Standard Deviation of the tries. ';
900: $Ptr .= '[ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1) ';
901: $Ptr .= 'where Xi denotes every student\'s tries ]';
902: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 903: $Ptr .= '<b>Skew.</b></td>';
1.1 stredwic 904: $Ptr .= '<td>Skewness of the students tries.';
905: $Ptr .= '[(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]';
906: $Ptr .= '</td></tr><tr><td>';
1.6 minaeibi 907: $Ptr .= '<b>Dis.F.</b></td>';
1.1 stredwic 908: $Ptr .= '<td>Discrimination Factor: A Standard for evaluating the ';
909: $Ptr .= 'problem according to a Criterion<br>';
1.31 stredwic 910: $Ptr .= '<b>[Criterion to group students into %27 Upper Students - ';
911: $Ptr .= 'and %27 Lower Students]</b><br>';
1.1 stredwic 912: $Ptr .= '<b>1st Criterion</b> for Sorting the Students: ';
913: $Ptr .= '<b>Sum of Partial Credit Awarded / Total Number of Tries</b><br>';
914: $Ptr .= '<b>2nd Criterion</b> for Sorting the Students: ';
915: $Ptr .= '<b>Total number of Correct Answers / Total Number of Tries</b>';
916: $Ptr .= '</td></tr>';
917: $Ptr .= '<tr><td><b>Disc.</b></td>';
918: $Ptr .= '<td>Number of Students had at least one discussion.';
919: $Ptr .= '</td></tr></table>';
920: return $Ptr;
921: }
1.24 stredwic 922:
923: #---- END Problem Statistics Web Page ----------------------------------------
1.4 minaeibi 924:
1.1 stredwic 925: 1;
926: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>