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