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