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