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