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