Annotation of loncom/interface/statistics/lonproblemstatistics.pm, revision 1.95
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.95 ! matthew 3: # $Id: lonproblemstatistics.pm,v 1.94 2004/10/06 15:37:59 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.84 matthew 58: use LONCAPA::lonmetadata();
1.59 matthew 59: use Apache::lonlocal;
1.44 matthew 60: use Spreadsheet::WriteExcel;
1.70 matthew 61: use Apache::lonstathelpers();
1.71 matthew 62: use Time::HiRes;
1.73 matthew 63:
64: my @StatsArray;
1.79 matthew 65: my %SeqStat; # keys are symbs, values are hash refs
1.73 matthew 66:
1.59 matthew 67: ##
68: ## Localization notes:
69: ##
70: ## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
71: ## header for plots created with Graph.pm, both of which more than likely do
72: ## not support localization.
73: ##
1.79 matthew 74: #
75: #
76: ##
77: ## Description of Field attributes
78: ##
79: ## Attribute Required Value Meaning or Use
80: ##
81: ## name yes any scalar Used to uniquely identify field
82: ## title yes any scalar This is what the user sees to identify
83: ## the field. Passed through &mt().
84: ## long_title yes any scalar Used as graph heading and in excel
85: ## output. NOT translated
86: ## align no (left|right|center) HTML cell contents alignment
87: ## color yes html color HTML cell background color
88: ## used to visually group statistics
89: ## special no (link) Indicates a link, target is name.link
90: ## Currently set in &get_statistics()
91: ## graphable no (yes|no) Can a bar graph of the field be
92: ## produced?
93: ## sortable no (yes|no) Should a sort link be put in the
94: ## column header?
95: ## selectable yes (yes|no) Can the column be removed from the
96: ## statistics display?
97: ## selected yes (yes|no) Is the column selected by default?
98: ##
1.85 matthew 99: ## format no sprintf format string
100: ##
101: ## excel_format no excel format type
102: ## (see &Apache::loncommon::define_excel_formats
1.49 matthew 103: my @Fields = (
104: { name => 'problem_num',
105: title => 'P#',
106: align => 'right',
1.76 matthew 107: color => '#FFFFE6',
108: selectable => 'no',
1.80 matthew 109: defaultselected => 'yes',
1.76 matthew 110: },
1.49 matthew 111: { name => 'container',
1.51 matthew 112: title => 'Sequence or Folder',
1.49 matthew 113: align => 'left',
114: color => '#FFFFE6',
1.76 matthew 115: sortable => 'yes',
116: selectable => 'no',
1.80 matthew 117: defaultselected => 'yes',
1.76 matthew 118: },
1.49 matthew 119: { name => 'title',
120: title => 'Title',
121: align => 'left',
122: color => '#FFFFE6',
123: special => 'link',
1.76 matthew 124: sortable => 'yes',
125: selectable => 'no',
1.80 matthew 126: defaultselected => 'yes',
1.76 matthew 127: },
1.49 matthew 128: { name => 'part',
129: title => 'Part',
130: align => 'left',
1.55 matthew 131: color => '#FFFFE6',
1.76 matthew 132: selectable => 'no',
1.80 matthew 133: defaultselected => 'yes',
1.76 matthew 134: },
1.49 matthew 135: { name => 'num_students',
136: title => '#Stdnts',
137: align => 'right',
138: color => '#EEFFCC',
139: format => '%d',
140: sortable => 'yes',
141: graphable => 'yes',
1.76 matthew 142: long_title => 'Number of Students Attempting Problem',
143: selectable => 'yes',
1.80 matthew 144: defaultselected => 'yes',
1.76 matthew 145: },
1.49 matthew 146: { name => 'tries',
147: title => 'Tries',
148: align => 'right',
149: color => '#EEFFCC',
150: format => '%d',
151: sortable => 'yes',
152: graphable => 'yes',
1.76 matthew 153: long_title => 'Total Number of Tries',
154: selectable => 'yes',
1.80 matthew 155: defaultselected => 'yes',
1.76 matthew 156: },
1.49 matthew 157: { name => 'max_tries',
158: title => 'Max Tries',
159: align => 'right',
160: color => '#DDFFFF',
161: format => '%d',
162: sortable => 'yes',
163: graphable => 'yes',
1.76 matthew 164: long_title => 'Maximum Number of Tries',
165: selectable => 'yes',
1.80 matthew 166: defaultselected => 'yes',
1.76 matthew 167: },
1.73 matthew 168: { name => 'min_tries',
169: title => 'Min Tries',
170: align => 'right',
171: color => '#DDFFFF',
172: format => '%d',
173: sortable => 'yes',
174: graphable => 'yes',
1.76 matthew 175: long_title => 'Minumum Number of Tries',
176: selectable => 'yes',
1.80 matthew 177: defaultselected => 'yes',
1.76 matthew 178: },
1.49 matthew 179: { name => 'mean_tries',
180: title => 'Mean Tries',
181: align => 'right',
182: color => '#DDFFFF',
183: format => '%5.2f',
184: sortable => 'yes',
185: graphable => 'yes',
1.76 matthew 186: long_title => 'Average Number of Tries',
187: selectable => 'yes',
1.80 matthew 188: defaultselected => 'yes',
1.76 matthew 189: },
1.49 matthew 190: { name => 'std_tries',
191: title => 'S.D. tries',
192: align => 'right',
193: color => '#DDFFFF',
194: format => '%5.2f',
195: sortable => 'yes',
196: graphable => 'yes',
1.76 matthew 197: long_title => 'Standard Deviation of Number of Tries',
198: selectable => 'yes',
1.80 matthew 199: defaultselected => 'yes',
1.76 matthew 200: },
1.49 matthew 201: { name => 'skew_tries',
202: title => 'Skew Tries',
203: align => 'right',
204: color => '#DDFFFF',
205: format => '%5.2f',
206: sortable => 'yes',
207: graphable => 'yes',
1.76 matthew 208: long_title => 'Skew of Number of Tries',
209: selectable => 'yes',
1.80 matthew 210: defaultselected => 'no',
1.76 matthew 211: },
1.49 matthew 212: { name => 'num_solved',
213: title => '#YES',
214: align => 'right',
215: color => '#FFDDDD',
1.63 matthew 216: format => '%4.1f',# format => '%d',
1.49 matthew 217: sortable => 'yes',
218: graphable => 'yes',
1.76 matthew 219: long_title => 'Number of Students able to Solve',
1.77 matthew 220: selectable => 'yes',
1.80 matthew 221: defaultselected => 'yes',
1.76 matthew 222: },
1.49 matthew 223: { name => 'num_override',
224: title => '#yes',
225: align => 'right',
226: color => '#FFDDDD',
1.63 matthew 227: format => '%4.1f',# format => '%d',
1.49 matthew 228: sortable => 'yes',
229: graphable => 'yes',
1.76 matthew 230: long_title => 'Number of Students given Override',
231: selectable => 'yes',
1.80 matthew 232: defaultselected => 'yes',
1.76 matthew 233: },
1.95 ! matthew 234: { name => 'tries_per_correct',
! 235: title => 'tries/correct',
! 236: align => 'right',
! 237: color => '#FFDDDD',
! 238: format => '%4.1f',
! 239: sortable => 'yes',
! 240: graphable => 'yes',
! 241: long_title => 'Tries per Correct Answer',
! 242: selectable => 'yes',
! 243: defaultselected => 'yes',
! 244: },
1.73 matthew 245: { name => 'num_wrong',
246: title => '#Wrng',
1.49 matthew 247: align => 'right',
1.73 matthew 248: color => '#FFDDDD',
1.49 matthew 249: format => '%4.1f',
250: sortable => 'yes',
251: graphable => 'yes',
1.93 matthew 252: long_title => 'Number of students whose final answer is wrong',
253: selectable => 'yes',
254: defaultselected => 'yes',
255: },
256: { name => 'per_wrong',
257: title => '%Wrng',
258: align => 'right',
259: color => '#FFDDDD',
260: format => '%4.1f',
261: sortable => 'yes',
262: graphable => 'yes',
1.76 matthew 263: long_title => 'Percent of students whose final answer is wrong',
264: selectable => 'yes',
1.80 matthew 265: defaultselected => 'yes',
1.76 matthew 266: },
1.73 matthew 267: { name => 'deg_of_diff',
268: title => 'DoDiff',
269: align => 'right',
270: color => '#FFFFE6',
271: format => '%5.2f',
272: sortable => 'yes',
273: graphable => 'yes',
274: long_title => 'Degree of Difficulty'.
1.76 matthew 275: '[ 1 - ((#YES+#yes) / Tries) ]',
276: selectable => 'yes',
1.80 matthew 277: defaultselected => 'yes',
1.76 matthew 278: },
1.71 matthew 279: { name => 'deg_of_disc',
1.73 matthew 280: title => 'DoDisc',
1.71 matthew 281: align => 'right',
282: color => '#FFFFE6',
283: format => '%4.2f',
284: sortable => 'yes',
285: graphable => 'yes',
1.76 matthew 286: long_title => 'Degree of Discrimination',
287: selectable => 'yes',
1.89 matthew 288: defaultselected => 'yes',
1.76 matthew 289: },
1.85 matthew 290: ## duedate included for research purposes. Commented out most of the time.
291: # { name => 'duedate',
292: # title => 'Due Date',
293: # align => 'left',
294: # color => '#FFFFFF',
295: # sortable => 'yes',
296: # graphable => 'no',
297: # long_title => 'Due date of resource for instructor',
298: # selectable => 'no',
299: # defaultselected => 'yes',
300: # },
301: ## opendate included for research purposes. Commented out most of the time.
302: # { name => 'opendate',
303: # title => 'Open Date',
304: # align => 'left',
305: # color => '#FFFFFF',
306: # sortable => 'yes',
307: # graphable => 'no',
308: # long_title => 'date resource became answerable',
309: # selectable => 'no',
310: # defaultselected => 'yes',
311: # },
312: ## symb included for research purposes. Commented out most of the time.
313: # { name => 'symb',
314: # title => 'Symb',
315: # align => 'left',
316: # color => '#FFFFFF',
317: # sortable => 'yes',
318: # graphable => 'no',
319: # long_title => 'Unique LON-CAPA identifier for problem',
320: # selectable => 'no',
321: # defaultselected => 'yes',
322: # },
1.86 matthew 323: ## resptypes included for research purposes. Commented out most of the time.
324: # { name => 'resptypes',
325: # title => 'Response Types',
326: # align => 'left',
327: # color => '#FFFFFF',
328: # sortable => 'no',
329: # graphable => 'no',
330: # long_title => 'Response Types used in this problem',
331: # selectable => 'no',
332: # defaultselected => 'yes',
333: # },
1.94 matthew 334: ## maxtries included for research purposes. Commented out most of the time.
335: # { name => 'maxtries',
336: # title => 'Maxtries',
337: # align => 'left',
338: # color => '#FFFFFF',
339: # sortable => 'no',
340: # graphable => 'no',
341: # long_title => 'Maximum number of tries',
342: # selectable => 'no',
343: # defaultselected => 'yes',
344: # },
345: ## hinttries included for research purposes. Commented out most of the time.
346: # { name => 'hinttries',
347: # title => 'hinttries',
348: # align => 'left',
349: # color => '#FFFFFF',
350: # sortable => 'no',
351: # graphable => 'no',
352: # long_title => 'Number of tries before a hint appears',
353: # selectable => 'no',
354: # defaultselected => 'yes',
355: # },
1.49 matthew 356: );
357:
1.79 matthew 358: my @SeqFields = (
359: { name => 'title',
360: title => 'Sequence',
361: align => 'left',
362: color => '#FFFFE6',
363: special => 'no',
364: sortable => 'no',
365: selectable => 'yes',
1.80 matthew 366: defaultselected => 'no',
1.79 matthew 367: },
368: { name => 'items',
369: title => '#Items',
370: align => 'right',
371: color => '#FFFFE6',
372: format => '%4d',
373: sortable => 'no',
374: graphable => 'no',
375: long_title => 'Number of Items in Sequence',
376: selectable => 'yes',
1.80 matthew 377: defaultselected => 'no',
1.79 matthew 378: },
379: { name => 'scoremean',
380: title => 'Score Mean',
381: align => 'right',
382: color => '#FFFFE6',
383: format => '%4.2f',
384: sortable => 'no',
385: graphable => 'no',
386: long_title => 'Mean Sequence Score',
387: selectable => 'yes',
1.80 matthew 388: defaultselected => 'no',
1.79 matthew 389: },
390: { name => 'scorestd',
391: title => 'Score STD',
392: align => 'right',
393: color => '#FFFFE6',
394: format => '%4.2f',
395: sortable => 'no',
396: graphable => 'no',
397: long_title => 'Standard Deviation of Sequence Scores',
398: selectable => 'yes',
1.80 matthew 399: defaultselected => 'no',
1.79 matthew 400: },
401: { name => 'scoremax',
402: title => 'Score Max',
403: align => 'right',
404: color => '#FFFFE6',
405: format => '%4.2f',
406: sortable => 'no',
407: graphable => 'no',
408: long_title => 'Maximum Sequence Score',
409: selectable => 'yes',
1.80 matthew 410: defaultselected => 'no',
1.79 matthew 411: },
412: { name => 'scoremin',
413: title => 'Score Min',
414: align => 'right',
415: color => '#FFFFE6',
416: format => '%4.2f',
417: sortable => 'no',
418: graphable => 'no',
419: long_title => 'Minumum Sequence Score',
420: selectable => 'yes',
1.80 matthew 421: defaultselected => 'no',
1.79 matthew 422: },
423: { name => 'scorecount',
424: title => 'Score N',
425: align => 'right',
426: color => '#FFFFE6',
427: format => '%4d',
428: sortable => 'no',
429: graphable => 'no',
430: long_title => 'Number of Students in score computations',
431: selectable => 'yes',
1.80 matthew 432: defaultselected => 'no',
1.79 matthew 433: },
434: { name => 'countmean',
435: title => 'Count Mean',
436: align => 'right',
437: color => '#FFFFFF',
438: format => '%4.2f',
439: sortable => 'no',
440: graphable => 'no',
441: long_title => 'Mean Sequence Score',
442: selectable => 'yes',
1.80 matthew 443: defaultselected => 'no',
1.79 matthew 444: },
445: { name => 'countstd',
446: title => 'Count STD',
447: align => 'right',
448: color => '#FFFFFF',
449: format => '%4.2f',
450: sortable => 'no',
451: graphable => 'no',
452: long_title => 'Standard Deviation of Sequence Scores',
453: selectable => 'yes',
1.80 matthew 454: defaultselected => 'no',
1.79 matthew 455: },
456: { name => 'countmax',
457: title => 'Count Max',
458: align => 'right',
459: color => '#FFFFFF',
460: format => '%4.2f',
461: sortable => 'no',
462: graphable => 'no',
463: long_title => 'Maximum Number of Correct Problems',
464: selectable => 'yes',
1.80 matthew 465: defaultselected => 'no',
1.79 matthew 466: },
467: { name => 'countmin',
468: title => 'Count Min',
469: align => 'right',
470: color => '#FFFFFF',
471: format => '%4.2f',
472: sortable => 'no',
473: graphable => 'no',
474: long_title => 'Minumum Number of Correct Problems',
475: selectable => 'yes',
1.80 matthew 476: defaultselected => 'no',
1.79 matthew 477: },
478: { name => 'count',
479: title => 'Count N',
480: align => 'right',
481: color => '#FFFFFF',
482: format => '%4d',
483: sortable => 'no',
484: graphable => 'no',
485: long_title => 'Number of Students in score computations',
486: selectable => 'yes',
1.80 matthew 487: defaultselected => 'no',
1.79 matthew 488: },
489: { name => 'KR-21',
490: title => 'KR-21',
491: align => 'right',
492: color => '#FFAAAA',
493: format => '%4.2f',
494: sortable => 'no',
495: graphable => 'no',
496: long_title => 'KR-21 reliability statistic',
497: selectable => 'yes',
1.80 matthew 498: defaultselected => 'no',
1.79 matthew 499: },
500: );
501:
1.76 matthew 502: my %SelectedFields;
503:
504: sub parse_field_selection {
505: #
506: # Pull out the defaults
507: if (! defined($ENV{'form.fieldselections'})) {
508: $ENV{'form.fieldselections'} = [];
509: foreach my $field (@Fields) {
510: next if ($field->{'selectable'} ne 'yes');
1.80 matthew 511: if ($field->{'defaultselected'} eq 'yes') {
1.76 matthew 512: push(@{$ENV{'form.fieldselections'}},$field->{'name'});
513: }
514: }
515: }
516: #
1.80 matthew 517: # Make sure the data we are plotting is there
518: my %NeededFields;
519: if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '' &&
520: $ENV{'form.plot'} ne 'none') {
521: if ($ENV{'form.plot'} eq 'degrees') {
522: $NeededFields{'deg_of_diff'}++;
523: $NeededFields{'deg_of_disc'}++;
524: } elsif ($ENV{'form.plot'} eq 'tries statistics') {
525: $NeededFields{'mean_tries'}++;
526: $NeededFields{'std_tries'}++;
527: $NeededFields{'problem_num'}++;
528: } else {
529: $NeededFields{$ENV{'form.plot'}}++;
530: }
531: }
532: #
1.76 matthew 533: # This should not happen, but in case it does...
534: if (ref($ENV{'form.fieldselections'}) ne 'ARRAY') {
535: $ENV{'form.fieldselections'} = [$ENV{'form.fieldselections'}];
536: }
537: #
538: # Set the field data and the selected fields (for easier checking)
539: undef(%SelectedFields);
540: foreach my $field (@Fields) {
1.80 matthew 541: if ($field->{'selectable'} ne 'yes') {
542: $field->{'selected'} = 'yes';
543: } else {
544: $field->{'selected'} = 'no';
545: }
546: if (exists($NeededFields{$field->{'name'}})) {
547: $field->{'selected'} = 'yes';
548: $SelectedFields{$field->{'name'}}++;
549: }
1.76 matthew 550: foreach my $selection (@{$ENV{'form.fieldselections'}}) {
551: if ($selection eq $field->{'name'} || $selection eq 'all') {
552: $field->{'selected'} = 'yes';
553: $SelectedFields{$field->{'name'}}++;
554: }
555: }
556: }
1.82 matthew 557: #
558: # Always show all the sequence statistics (for now)
559: foreach my $field (@SeqFields) {
560: $field->{'selected'} = 'yes';
561: }
1.76 matthew 562: return;
563: }
564:
565: sub field_selection_input {
566: my $Str = '<select name="fieldselections" multiple size="5">'."\n";
567: $Str .= '<option value="all">all</option>'."\n";
568: foreach my $field (@Fields) {
569: next if ($field->{'selectable'} ne 'yes');
570: $Str .= ' <option value="'.$field->{'name'}.'" ';
571: if ($field->{'selected'} eq 'yes') {
572: $Str .= 'selected ';
573: }
574: $Str .= '>'.$field->{'title'}.'</option>'."\n";
575: }
576: $Str .= "</select>\n";
577: }
578:
1.47 matthew 579: ###############################################
580: ###############################################
581:
582: =pod
583:
584: =item &CreateInterface()
585:
586: Create the main intereface for the statistics page. Allows the user to
587: select sections, maps, and output.
588:
589: =cut
1.1 stredwic 590:
1.47 matthew 591: ###############################################
592: ###############################################
1.41 matthew 593: sub CreateInterface {
1.87 matthew 594: my ($r) = @_;
1.80 matthew 595: #
1.76 matthew 596: &parse_field_selection();
1.80 matthew 597: #
1.41 matthew 598: my $Str = '';
1.67 matthew 599: $Str .= &Apache::lonhtmlcommon::breadcrumbs
1.69 matthew 600: (undef,'Overall Problem Statistics','Statistics_Overall_Key');
1.41 matthew 601: $Str .= '<table cellspacing="5">'."\n";
602: $Str .= '<tr>';
1.59 matthew 603: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
604: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
605: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
1.76 matthew 606: $Str .= '<td align="center"><b>'.&mt('Statistics').'</b></td>';
1.70 matthew 607: $Str .= '<td rowspan="2">'.
608: &Apache::lonstathelpers::limit_by_time_form().'</td>';
1.41 matthew 609: $Str .= '</tr>'."\n";
610: #
611: $Str .= '<tr><td align="center">'."\n";
612: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.50 matthew 613: $Str .= '</td><td align="center">';
614: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.41 matthew 615: $Str .= '</td><td align="center">';
616: #
617: my $only_seq_with_assessments = sub {
618: my $s=shift;
619: if ($s->{'num_assess'} < 1) {
620: return 0;
621: } else {
622: return 1;
623: }
624: };
625: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
626: $only_seq_with_assessments);
1.76 matthew 627: $Str .= '</td><td>'.&field_selection_input();
1.41 matthew 628: $Str .= '</td></tr>'."\n";
629: $Str .= '</table>'."\n";
1.87 matthew 630: #
631: $Str .= '<p>'.&mt('Status: [_1]',
632: '<input type="text" '.
633: 'name="stats_status" size="60" value="" />'
634: ).
635: '</nobr></p>';
636: #
1.59 matthew 637: $Str .= '<input type="submit" name="GenerateStatistics" value="'.
638: &mt('Generate Statistics').'" />';
1.54 matthew 639: $Str .= ' 'x5;
1.73 matthew 640: $Str .= 'Plot '.&plot_dropdown().(' 'x10);
1.87 matthew 641: #
1.73 matthew 642: return $Str;
1.41 matthew 643: }
1.25 stredwic 644:
1.41 matthew 645: ###############################################
646: ###############################################
1.28 stredwic 647:
1.47 matthew 648: =pod
649:
650: =item &BuildProblemStatisticsPage()
651:
652: Main interface to problem statistics.
653:
654: =cut
655:
1.41 matthew 656: ###############################################
657: ###############################################
658: sub BuildProblemStatisticsPage {
659: my ($r,$c)=@_;
1.61 matthew 660: #
661: my %Saveable_Parameters = ('Status' => 'scalar',
662: 'statsoutputmode' => 'scalar',
663: 'Section' => 'array',
664: 'StudentData' => 'array',
1.77 matthew 665: 'Maps' => 'array',
666: 'fieldselections'=> 'array');
1.61 matthew 667: &Apache::loncommon::store_course_settings('statistics',
668: \%Saveable_Parameters);
669: &Apache::loncommon::restore_course_settings('statistics',
670: \%Saveable_Parameters);
671: #
672: &Apache::lonstatistics::PrepareClasslist();
1.41 matthew 673: #
1.73 matthew 674: # Clear the package variables
675: undef(@StatsArray);
1.79 matthew 676: undef(%SeqStat);
1.71 matthew 677: #
1.73 matthew 678: # Finally let the user know we are here
1.87 matthew 679: my $interface = &CreateInterface($r);
1.57 matthew 680: $r->print($interface);
1.41 matthew 681: $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
682: '" />');
1.73 matthew 683: #
1.87 matthew 684: my @CacheButtonHTML =
685: &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
686: my $Str;
687: foreach my $html (@CacheButtonHTML) {
688: $Str.=$html.(' 'x5);
689: }
690: #
691: $r->print($Str);
692: if (! exists($ENV{'form.firstrun'})) {
1.73 matthew 693: $r->print('<h3>'.
694: &mt('Press "Generate Statistics" when you are ready.').
695: '</h3><p>'.
696: &mt('It may take some time to update the student data '.
697: 'for the first analysis. Future analysis this session '.
698: ' will not have this delay.').
699: '</p>');
1.41 matthew 700: return;
1.28 stredwic 701: }
1.73 matthew 702: $r->rflush();
1.41 matthew 703: #
1.73 matthew 704: # This probably does not need to be done each time we are called, but
705: # it does not slow things down noticably.
706: &Apache::loncoursedata::populate_weight_table();
1.75 matthew 707: #
1.73 matthew 708: if (exists($ENV{'form.Excel'})) {
709: &Excel_output($r);
1.87 matthew 710: } else {
711: $r->print('<input type="submit" name="Excel" value="'.
712: &mt('Produce Excel Output').'" />'.' 'x5);
713: $r->rflush();
1.75 matthew 714: my $count = 0;
715: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.78 matthew 716: $count += $seq->{'num_assess_parts'};
1.75 matthew 717: }
718: if ($count > 10) {
719: $r->print('<h2>'.
720: &mt('Compiling statistics for [_1] problems',$count).
721: '</h2>');
722: if ($count > 30) {
723: $r->print('<h3>'.&mt('This will take some time.').'</h3>');
724: }
725: $r->rflush();
726: }
727: #
1.73 matthew 728: my $sortby = $ENV{'form.sortby'};
729: $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/);
730: my $plot = $ENV{'form.plot'};
1.75 matthew 731: if ($plot eq '' || $plot eq 'none') {
732: undef($plot);
733: }
1.73 matthew 734: if ($sortby eq 'container' && ! defined($plot)) {
1.79 matthew 735: &output_sequence_statistics($r);
1.73 matthew 736: &output_html_by_sequence($r);
737: } else {
738: if (defined($plot)) {
739: &make_plot($r,$plot);
740: }
741: &output_html_stats($r);
1.79 matthew 742: &output_sequence_statistics($r);
1.73 matthew 743: }
744: }
745: return;
746: }
747:
1.79 matthew 748: sub output_sequence_statistics {
749: my ($r) = @_;
750: my $c=$r->connection();
1.90 matthew 751: $r->print('<h2>'.&mt('Sequence Statistics').
752: &Apache::loncommon::help_open_topic('Statistics_Sequence').
753: '</h2>');
1.79 matthew 754: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
755: '<table border="0" cellpadding="3">'."\n".
756: '<tr bgcolor="#FFFFE6">');
757: $r->print(&sequence_html_header());
758: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
759: last if ($c->aborted);
760: next if ($seq->{'num_assess'} < 1);
761: &compute_sequence_statistics($seq);
762: $r->print(&sequence_html_output($seq));
763: }
764: $r->print('</table>');
765: $r->print('</table>');
766: $r->rflush();
767: return;
768: }
769:
770:
1.73 matthew 771: ##########################################################
772: ##########################################################
773: ##
774: ## HTML output routines
775: ##
776: ##########################################################
777: ##########################################################
778: sub output_html_by_sequence {
779: my ($r) = @_;
780: my $c = $r->connection();
781: $r->print(&html_preamble());
1.41 matthew 782: #
1.73 matthew 783: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
784: last if ($c->aborted);
785: next if ($seq->{'num_assess'} < 1);
786: $r->print("<h3>".$seq->{'title'}."</h3>".
787: '<table border="0"><tr><td bgcolor="#777777">'."\n".
788: '<table border="0" cellpadding="3">'."\n".
789: '<tr bgcolor="#FFFFE6">'.
790: &statistics_table_header('no container')."</tr>\n");
791: my @Data = &compute_statistics_on_sequence($seq);
792: foreach my $data (@Data) {
793: $r->print('<tr>'.&statistics_html_table_data($data,
794: 'no container').
795: "</tr>\n");
1.70 matthew 796: }
1.73 matthew 797: $r->print('</table>'."\n".'</table>'."\n");
1.41 matthew 798: $r->rflush();
1.28 stredwic 799: }
1.41 matthew 800: return;
801: }
1.21 stredwic 802:
1.73 matthew 803: sub output_html_stats {
804: my ($r)=@_;
805: &compute_all_statistics($r);
806: $r->print(&html_preamble());
807: &sort_data($ENV{'form.sortby'});
808: #
809: my $count=0;
810: foreach my $data (@StatsArray) {
811: if ($count++ % 50 == 0) {
812: $r->print("</table>\n</table>\n");
813: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
814: '<table border="0" cellpadding="3">'."\n".
815: '<tr bgcolor="#FFFFE6">'.
816: '<tr bgcolor="#FFFFE6">'.
817: &statistics_table_header().
818: "</tr>\n");
819: }
820: $r->print('<tr>'.&statistics_html_table_data($data)."</tr>\n");
821: }
822: $r->print("</table>\n</table>\n");
823: return;
824: }
1.47 matthew 825:
1.73 matthew 826: sub html_preamble {
827: my $Str='';
828: $Str .= "<h2>".
829: $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
830: "</h2>\n";
831: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
832: if (defined($starttime) || defined($endtime)) {
833: # Inform the user what the time limits on the data are.
834: $Str .= '<h3>'.&mt('Statistics on submissions from [_1] to [_2]',
835: &Apache::lonlocal::locallocaltime($starttime),
836: &Apache::lonlocal::locallocaltime($endtime)
837: ).'</h3>';
838: }
839: $Str .= "<h3>".&mt('Compiled on [_1]',
840: &Apache::lonlocal::locallocaltime(time))."</h3>";
841: return $Str;
842: }
1.47 matthew 843:
844:
1.44 matthew 845: ###############################################
846: ###############################################
1.73 matthew 847: ##
848: ## Misc HTML output routines
849: ##
850: ###############################################
851: ###############################################
852: sub statistics_html_table_data {
853: my ($data,$options) = @_;
854: my $row = '';
855: foreach my $field (@Fields) {
856: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 857: next if ($field->{'selected'} ne 'yes');
1.73 matthew 858: $row .= '<td bgcolor="'.$field->{'color'}.'"';
859: if (exists($field->{'align'})) {
860: $row .= ' align="'.$field->{'align'}.'"';
1.41 matthew 861: }
1.73 matthew 862: $row .= '>';
863: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
864: $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.'">';
1.41 matthew 865: }
1.92 matthew 866: if (exists($field->{'format'}) && $data->{$field->{'name'}} !~ /[A-Z]/i) {
1.73 matthew 867: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
868: } else {
869: $row .= $data->{$field->{'name'}};
870: }
871: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
872: $row.= '</a>';
873: }
874: $row .= '</td>';
1.21 stredwic 875: }
1.73 matthew 876: return $row;
1.41 matthew 877: }
1.25 stredwic 878:
1.73 matthew 879: sub statistics_table_header {
880: my ($options) = @_;
881: my $header_row;
882: foreach my $field (@Fields) {
883: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 884: next if ($field->{'selected'} ne 'yes');
1.73 matthew 885: $header_row .= '<th>';
886: if (exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
887: $header_row .= '<a href="javascript:'.
888: 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
889: ';document.Statistics.submit();">';
890: }
891: $header_row .= &mt($field->{'title'});
892: if ($options =~ /sortable/) {
893: $header_row.= '</a>';
894: }
895: if ($options !~ /no plots/ &&
896: exists($field->{'graphable'}) &&
897: $field->{'graphable'} eq 'yes') {
898: $header_row.=' (';
899: $header_row .= '<a href="javascript:'.
900: "document.Statistics.plot.value='$field->{'name'}'".
901: ';document.Statistics.submit();">';
902: $header_row .= &mt('plot').'</a>)';
903: }
904: $header_row .= '</th>';
905: }
906: return $header_row;
907: }
1.26 stredwic 908:
1.79 matthew 909: sub sequence_html_header {
910: my $Str .= '<tr>';
911: foreach my $field (@SeqFields) {
912: # next if ($field->{'selected'} ne 'yes');
913: $Str .= '<th bgcolor="'.$field->{'color'}.'"';
914: $Str .= '>'.$field->{'title'}.'</th>';
915: }
916: $Str .= '</tr>';
917: return $Str;
918: }
919:
920:
921: sub sequence_html_output {
922: my ($seq) = @_;
923: my $data = $SeqStat{$seq->{'symb'}};
924: my $row = '<tr>';
925: foreach my $field (@SeqFields) {
1.82 matthew 926: next if ($field->{'selected'} ne 'yes');
1.79 matthew 927: $row .= '<td bgcolor="'.$field->{'color'}.'"';
928: if (exists($field->{'align'})) {
929: $row .= ' align="'.$field->{'align'}.'"';
930: }
931: $row .= '>';
932: if (exists($field->{'format'})) {
933: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
934: } else {
935: $row .= $data->{$field->{'name'}};
936: }
937: $row .= '</td>';
938: }
939: $row .= '</tr>'."\n";
940: return $row;
941: }
942:
1.73 matthew 943: ####################################################
944: ####################################################
945: ##
946: ## Plotting Routines
947: ##
948: ####################################################
949: ####################################################
950: sub make_plot {
951: my ($r,$plot) = @_;
952: &compute_all_statistics($r);
953: &sort_data($ENV{'form.sortby'});
954: if ($plot eq 'degrees') {
955: °rees_plot($r);
1.74 matthew 956: } elsif ($plot eq 'tries statistics') {
957: &tries_data_plot($r);
1.73 matthew 958: } else {
959: &make_single_stat_plot($r,$plot);
960: }
961: return;
962: }
1.47 matthew 963:
1.73 matthew 964: sub make_single_stat_plot {
965: my ($r,$datafield) = @_;
1.41 matthew 966: #
1.73 matthew 967: my $title; my $yaxis;
968: foreach my $field (@Fields) {
969: next if ($field->{'name'} ne $datafield);
970: $title = $field->{'long_title'};
971: $yaxis = $field->{'title'};
972: last;
973: }
974: if ($title eq '' || $yaxis eq '') {
975: # datafield is something we do not know enough about to plot
976: $r->print('<h3>'.
977: &mt('Unable to plot the requested statistic.').
978: '</h3>');
979: return;
1.49 matthew 980: }
981: #
1.73 matthew 982: # Build up the data sets to plot
983: my @Labels;
984: my @Data;
985: my $max = 1;
986: foreach my $data (@StatsArray) {
987: push(@Labels,$data->{'problem_num'});
988: push(@Data,$data->{$datafield});
989: if ($data->{$datafield}>$max) {
990: $max = $data->{$datafield};
991: }
992: }
993: foreach (1,2,3,4,5,10,15,20,25,40,50,75,100,150,200,250,300,500,600,750,
994: 1000,1500,2000,2500,3000,3500,4000,5000,7500,10000,15000,20000) {
995: if ($max <= $_) {
996: $max = $_;
997: last;
1.42 matthew 998: }
999: }
1.73 matthew 1000: if ($max > 20000) {
1001: $max = 10000*(int($max/10000)+1);
1.42 matthew 1002: }
1.73 matthew 1003: #
1004: $r->print("<p>".&Apache::loncommon::DrawBarGraph($title,
1005: 'Problem Number',
1006: $yaxis,
1007: $max,
1008: undef, # colors
1009: \@Labels,
1010: \@Data)."</p>\n");
1011: return;
1012: }
1013:
1014: sub degrees_plot {
1015: my ($r)=@_;
1016: my $count = scalar(@StatsArray);
1017: my $width = 50 + 10*$count;
1018: $width = 300 if ($width < 300);
1019: my $height = 300;
1020: my $plot = '';
1021: my $ymax = 0;
1022: my $ymin = 0;
1023: my @Disc; my @Diff; my @Labels;
1024: foreach my $data (@StatsArray) {
1025: push(@Labels,$data->{'problem_num'});
1026: my $disc = $data->{'deg_of_disc'};
1027: my $diff = $data->{'deg_of_diff'};
1028: push(@Disc,$disc);
1029: push(@Diff,$diff);
1030: #
1031: $ymin = $disc if ($ymin > $disc);
1032: $ymin = $diff if ($ymin > $diff);
1033: $ymax = $disc if ($ymax < $disc);
1034: $ymax = $diff if ($ymax < $diff);
1035: }
1036: #
1037: # Make sure we show relevant information.
1038: if ($ymin < 0) {
1039: if (abs($ymin) < 0.05) {
1040: $ymin = 0;
1041: } else {
1042: $ymin = -1;
1.42 matthew 1043: }
1044: }
1.73 matthew 1045: if ($ymax > 0) {
1046: if (abs($ymax) < 0.05) {
1047: $ymax = 0;
1.42 matthew 1048: } else {
1.73 matthew 1049: $ymax = 1;
1.42 matthew 1050: }
1.43 matthew 1051: }
1.49 matthew 1052: #
1.73 matthew 1053: my $xmax = $Labels[-1];
1054: if ($xmax > 50) {
1055: if ($xmax % 10 != 0) {
1056: $xmax = 10 * (int($xmax/10)+1);
1057: }
1058: } else {
1059: if ($xmax % 5 != 0) {
1060: $xmax = 5 * (int($xmax/5)+1);
1.49 matthew 1061: }
1.26 stredwic 1062: }
1.41 matthew 1063: #
1.73 matthew 1064: my $discdata .= '<data>'.join(',',@Labels).'</data>'.$/.
1065: '<data>'.join(',',@Disc).'</data>'.$/;
1066: #
1067: my $diffdata .= '<data>'.join(',',@Labels).'</data>'.$/.
1068: '<data>'.join(',',@Diff).'</data>'.$/;
1069: #
1.81 matthew 1070: my $title = 'Degree of Discrimination\nand Degree of Difficulty';
1071: if ($xmax > 50) {
1072: $title = 'Degree of Discrimination and Degree of Difficulty';
1073: }
1074: #
1.73 matthew 1075: $plot=<<"END";
1076: <gnuplot
1077: texfont="10"
1078: fgcolor="x000000"
1079: plottype="Cartesian"
1080: font="large"
1081: grid="on"
1082: align="center"
1083: border="on"
1084: transparent="on"
1.81 matthew 1085: alttag="Degree of Discrimination and Degree of Difficulty Plot"
1.73 matthew 1086: samples="100"
1087: bgcolor="xffffff"
1088: height="$height"
1089: width="$width">
1090: <key
1091: pos="top right"
1092: title=""
1093: box="off" />
1.81 matthew 1094: <title>$title</title>
1.73 matthew 1095: <axis xmin="0" ymin="$ymin" xmax="$xmax" ymax="$ymax" color="x000000" />
1096: <xlabel>Problem Number</xlabel>
1097: <curve
1098: linestyle="linespoints"
1099: name="DoDisc"
1100: pointtype="0"
1101: color="x000000">
1102: $discdata
1103: </curve>
1104: <curve
1105: linestyle="linespoints"
1106: name="DoDiff"
1107: pointtype="0"
1108: color="xFF0000">
1109: $diffdata
1110: </curve>
1111: </gnuplot>
1112: END
1113: my $plotresult =
1114: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
1115: $r->print($plotresult);
1.41 matthew 1116: return;
1.42 matthew 1117: }
1118:
1.74 matthew 1119: sub tries_data_plot {
1120: my ($r)=@_;
1121: my $count = scalar(@StatsArray);
1122: my $width = 50 + 10*$count;
1123: $width = 300 if ($width < 300);
1124: my $height = 300;
1125: my $plot = '';
1126: my @STD; my @Mean; my @Max; my @Min;
1127: my @Labels;
1128: my $ymax = 5;
1129: foreach my $data (@StatsArray) {
1130: my $max = $data->{'mean_tries'} + $data->{'std_tries'};
1131: $ymax = $max if ($ymax < $max);
1132: $ymax = $max if ($ymax < $max);
1133: push(@Labels,$data->{'problem_num'});
1134: push(@STD,$data->{'std_tries'});
1135: push(@Mean,$data->{'mean_tries'});
1136: }
1137: #
1138: # Make sure we show relevant information.
1139: my $xmax = $Labels[-1];
1140: if ($xmax > 50) {
1141: if ($xmax % 10 != 0) {
1142: $xmax = 10 * (int($xmax/10)+1);
1143: }
1144: } else {
1145: if ($xmax % 5 != 0) {
1146: $xmax = 5 * (int($xmax/5)+1);
1147: }
1148: }
1149: $ymax = int($ymax)+1+2;
1150: #
1151: my $std_data .= '<data>'.join(',',@Labels).'</data>'.$/.
1152: '<data>'.join(',',@Mean).'</data>'.$/;
1153: #
1154: my $std_error_data .= '<data>'.join(',',@Labels).'</data>'.$/.
1155: '<data>'.join(',',@Mean).'</data>'.$/.
1156: '<data>'.join(',',@STD).'</data>'.$/;
1157: #
1.81 matthew 1158: my $title = 'Mean and S.D. of Tries';
1159: if ($xmax > 25) {
1160: $title = 'Mean and Standard Deviation of Tries';
1161: }
1162: #
1.74 matthew 1163: $plot=<<"END";
1164: <gnuplot
1165: texfont="10"
1166: fgcolor="x000000"
1167: plottype="Cartesian"
1168: font="large"
1169: grid="on"
1170: align="center"
1171: border="on"
1172: transparent="on"
1.81 matthew 1173: alttag="Mean and S.D of Tries Plot"
1.74 matthew 1174: samples="100"
1175: bgcolor="xffffff"
1176: height="$height"
1177: width="$width">
1.81 matthew 1178: <title>$title</title>
1.74 matthew 1179: <axis xmin="0" ymin="0" xmax="$xmax" ymax="$ymax" color="x000000" />
1180: <xlabel>Problem Number</xlabel>
1.81 matthew 1181: <ylabel>Number of Tries</ylabel>
1.74 matthew 1182: <curve
1183: linestyle="yerrorbars"
1184: name="S.D. Tries"
1185: pointtype="1"
1186: color="x666666">
1187: $std_error_data
1188: </curve>
1189: <curve
1190: linestyle="points"
1191: name="Mean Tries"
1192: pointtype="1"
1193: color="xCC4444">
1194: $std_data
1195: </curve>
1196: </gnuplot>
1197: END
1198: my $plotresult =
1199: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
1200: $r->print($plotresult);
1201: return;
1202: }
1203:
1.73 matthew 1204: sub plot_dropdown {
1205: my $current = '';
1206: #
1207: if (defined($ENV{'form.plot'})) {
1208: $current = $ENV{'form.plot'};
1209: }
1210: #
1211: my @Additional_Plots = (
1212: { graphable=>'yes',
1213: name => 'degrees',
1.81 matthew 1214: title => 'Difficulty Indexes' },
1.74 matthew 1215: { graphable=>'yes',
1216: name => 'tries statistics',
1.81 matthew 1217: title => 'Tries Statistics' });
1.73 matthew 1218: #
1219: my $Str= "\n".'<select name="plot" size="1">';
1220: $Str .= '<option name="none"></option>'."\n";
1221: $Str .= '<option name="none2">none</option>'."\n";
1.81 matthew 1222: foreach my $field (@Additional_Plots,@Fields) {
1.73 matthew 1223: if (! exists($field->{'graphable'}) ||
1224: $field->{'graphable'} ne 'yes') {
1225: next;
1226: }
1227: $Str .= '<option value="'.$field->{'name'}.'"';
1228: if ($field->{'name'} eq $current) {
1229: $Str .= ' selected ';
1230: }
1231: $Str.= '>'.&mt($field->{'title'}).'</option>'."\n";
1232: }
1233: $Str .= '</select>'."\n";
1234: return $Str;
1235: }
1236:
1.41 matthew 1237: ###############################################
1238: ###############################################
1.73 matthew 1239: ##
1240: ## Excel output routines
1241: ##
1.41 matthew 1242: ###############################################
1243: ###############################################
1.73 matthew 1244: sub Excel_output {
1.44 matthew 1245: my ($r) = @_;
1.73 matthew 1246: $r->print('<h2>'.&mt('Preparing Excel Spreadsheet').'</h2>');
1247: ##
1248: ## Compute the statistics
1249: &compute_all_statistics($r);
1250: my $c = $r->connection;
1251: return if ($c->aborted());
1252: ##
1253: ## Create the excel workbook
1.44 matthew 1254: my $filename = '/prtspool/'.
1255: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1.73 matthew 1256: time.'_'.rand(1000000000).'.xls';
1.70 matthew 1257: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1258: #
1.44 matthew 1259: # Create sheet
1.73 matthew 1260: my $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
1.44 matthew 1261: #
1262: # Check for errors
1263: if (! defined($excel_workbook)) {
1264: $r->log_error("Error creating excel spreadsheet $filename: $!");
1.59 matthew 1265: $r->print(&mt("Problems creating new Excel file. ".
1.44 matthew 1266: "This error has been logged. ".
1.59 matthew 1267: "Please alert your LON-CAPA administrator."));
1.73 matthew 1268: return 0;
1.44 matthew 1269: }
1270: #
1271: # The excel spreadsheet stores temporary data in files, then put them
1272: # together. If needed we should be able to disable this (memory only).
1273: # The temporary directory must be specified before calling 'addworksheet'.
1274: # File::Temp is used to determine the temporary directory.
1275: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
1276: #
1277: # Add a worksheet
1278: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
1279: if (length($sheetname) > 31) {
1280: $sheetname = substr($sheetname,0,31);
1281: }
1.73 matthew 1282: my $excel_sheet = $excel_workbook->addworksheet(
1283: &Apache::loncommon::clean_excel_name($sheetname));
1.82 matthew 1284: #
1285: my $format = &Apache::loncommon::define_excel_formats($excel_workbook);
1.73 matthew 1286: ##
1287: ## Begin creating excel sheet
1288: ##
1289: my ($rows_output,$cols_output) = (0,0);
1.44 matthew 1290: #
1291: # Put the course description in the header
1292: $excel_sheet->write($rows_output,$cols_output++,
1.82 matthew 1293: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
1294: $format->{'h1'});
1.44 matthew 1295: $cols_output += 3;
1296: #
1297: # Put a description of the sections listed
1298: my $sectionstring = '';
1.82 matthew 1299: $excel_sheet->write($rows_output,$cols_output++,
1300: &Apache::lonstathelpers::sections_description
1301: (@Apache::lonstatistics::SelectedSections),
1302: $format->{'h3'});
1303: $cols_output += scalar(@Apache::lonstatistics::SelectedSections);
1.44 matthew 1304: #
1.70 matthew 1305: # Time restrictions
1306: my $time_string;
1307: if (defined($starttime)) {
1308: # call localtime but not lonlocal:locallocaltime because excel probably
1309: # cannot handle localized text. Probably.
1310: $time_string .= 'Data collected from '.localtime($time_string);
1311: if (defined($endtime)) {
1312: $time_string .= ' to '.localtime($endtime);
1313: }
1314: $time_string .= '.';
1315: } elsif (defined($endtime)) {
1316: # See note above about lonlocal:locallocaltime
1317: $time_string .= 'Data collected before '.localtime($endtime).'.';
1318: }
1.82 matthew 1319: if (defined($time_string)) {
1320: $excel_sheet->write($rows_output,$cols_output++,$time_string);
1321: $cols_output+= 5;
1322: }
1.70 matthew 1323: #
1.44 matthew 1324: # Put the date in there too
1325: $excel_sheet->write($rows_output,$cols_output++,
1326: 'Compiled on '.localtime(time));
1327: #
1328: $rows_output++;
1329: $cols_output=0;
1.82 matthew 1330: ##
1331: ## Sequence Statistics
1332: ##
1333: &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
1334: \@SeqFields);
1335: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1336: next if ($seq->{'num_assess'} < 1);
1337: my $data = $SeqStat{$seq->{'symb'}};
1338: $cols_output=0;
1339: foreach my $field (@SeqFields) {
1340: next if ($field->{'selected'} ne 'yes');
1.85 matthew 1341: my $fieldformat = undef;
1342: if (exists($field->{'excel_format'})) {
1343: $fieldformat = $format->{$field->{'excel_format'}};
1344: }
1.55 matthew 1345: $excel_sheet->write($rows_output,$cols_output++,
1.85 matthew 1346: $data->{$field->{'name'}},$fieldformat);
1.55 matthew 1347: }
1.82 matthew 1348: $rows_output++;
1349: $cols_output=0;
1.55 matthew 1350: }
1.82 matthew 1351: ##
1352: ## Resource Statistics
1353: ##
1.55 matthew 1354: $rows_output++;
1355: $cols_output=0;
1.82 matthew 1356: &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
1357: \@Fields);
1358: #
1.73 matthew 1359: foreach my $data (@StatsArray) {
1360: $cols_output=0;
1361: foreach my $field (@Fields) {
1.76 matthew 1362: next if ($field->{'selected'} ne 'yes');
1.73 matthew 1363: next if ($field->{'name'} eq 'problem_num');
1.85 matthew 1364: my $fieldformat = undef;
1365: if (exists($field->{'excel_format'})) {
1366: $fieldformat = $format->{$field->{'excel_format'}};
1367: }
1.73 matthew 1368: $excel_sheet->write($rows_output,$cols_output++,
1.85 matthew 1369: $data->{$field->{'name'}},$fieldformat);
1.44 matthew 1370: }
1.73 matthew 1371: $rows_output++;
1.82 matthew 1372: $cols_output=0;
1.44 matthew 1373: }
1374: #
1375: $excel_workbook->close();
1.73 matthew 1376: #
1.44 matthew 1377: # Tell the user where to get their excel file
1378: $r->print('<br />'.
1.59 matthew 1379: '<a href="'.$filename.'">'.
1380: &mt('Your Excel Spreadsheet').'</a>'."\n");
1.44 matthew 1381: $r->rflush();
1382: return;
1383: }
1384:
1.82 matthew 1385: ##
1386: ## &write_headers
1387: ##
1388: sub write_headers {
1389: my ($excel_sheet,$format,$rows_output,$cols_output,$Fields) = @_;
1390: ##
1391: ## First the long titles
1392: foreach my $field (@{$Fields}) {
1393: next if ($field->{'name'} eq 'problem_num');
1394: next if ($field->{'selected'} ne 'yes');
1395: if (exists($field->{'long_title'})) {
1396: $excel_sheet->write($$rows_output,${$cols_output},
1397: $field->{'long_title'},
1398: $format->{'bold'});
1399: } else {
1400: $excel_sheet->write($$rows_output,${$cols_output},'');
1401: }
1402: ${$cols_output}+= 1;
1403: }
1404: ${$cols_output} =0;
1405: ${$rows_output}+=1;
1406: ##
1407: ## Then the short titles
1408: foreach my $field (@{$Fields}) {
1409: next if ($field->{'selected'} ne 'yes');
1410: next if ($field->{'name'} eq 'problem_num');
1411: # Use english for excel as I am not sure how well excel handles
1412: # other character sets....
1413: $excel_sheet->write($$rows_output,$$cols_output,
1414: $field->{'title'},
1415: $format->{'bold'});
1416: $$cols_output+=1;
1417: }
1418: ${$cols_output} =0;
1419: ${$rows_output}+=1;
1420: return;
1421: }
1422:
1.73 matthew 1423: ##################################################
1424: ##################################################
1425: ##
1426: ## Statistics Gathering and Manipulation Routines
1427: ##
1428: ##################################################
1429: ##################################################
1430: sub compute_statistics_on_sequence {
1431: my ($seq) = @_;
1432: my @Data;
1433: foreach my $res (@{$seq->{'contents'}}) {
1434: next if ($res->{'type'} ne 'assessment');
1435: foreach my $part (@{$res->{'parts'}}) {
1.88 matthew 1436: next if ($res->{'partdata'}->{$part}->{'Survey'});
1.73 matthew 1437: #
1438: # This is where all the work happens
1439: my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
1440: push (@Data,$data);
1441: push (@StatsArray,$data);
1.49 matthew 1442: }
1.26 stredwic 1443: }
1.73 matthew 1444: return @Data;
1.41 matthew 1445: }
1.26 stredwic 1446:
1.73 matthew 1447: sub compute_all_statistics {
1448: my ($r) = @_;
1449: if (@StatsArray > 0) {
1450: # Assume we have already computed the statistics
1451: return;
1452: }
1453: my $c = $r->connection;
1454: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1455: last if ($c->aborted);
1456: next if ($seq->{'num_assess'} < 1);
1.82 matthew 1457: &compute_sequence_statistics($seq);
1.73 matthew 1458: &compute_statistics_on_sequence($seq);
1.49 matthew 1459: }
1460: }
1461:
1.73 matthew 1462: sub sort_data {
1463: my ($sortkey) = @_;
1464: return if (! @StatsArray);
1.45 matthew 1465: #
1.73 matthew 1466: # Sort the data
1467: my $sortby = undef;
1.49 matthew 1468: foreach my $field (@Fields) {
1.73 matthew 1469: if ($sortkey eq $field->{'name'}) {
1470: $sortby = $field->{'name'};
1.45 matthew 1471: }
1.26 stredwic 1472: }
1.73 matthew 1473: if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
1474: $sortby = 'container';
1475: }
1476: if ($sortby ne 'container') {
1477: # $sortby is already defined, so we can charge ahead
1478: if ($sortby =~ /^(title|part)$/i) {
1479: # Alpha comparison
1480: @StatsArray = sort {
1481: lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
1482: lc($a->{'title'}) cmp lc($b->{'title'}) ||
1483: lc($a->{'part'}) cmp lc($b->{'part'});
1484: } @StatsArray;
1.24 stredwic 1485: } else {
1.73 matthew 1486: # Numerical comparison
1487: @StatsArray = sort {
1488: my $retvalue = 0;
1489: if ($b->{$sortby} eq 'nan') {
1490: if ($a->{$sortby} ne 'nan') {
1491: $retvalue = -1;
1492: } else {
1493: $retvalue = 0;
1494: }
1495: }
1496: if ($a->{$sortby} eq 'nan') {
1497: if ($b->{$sortby} ne 'nan') {
1498: $retvalue = 1;
1499: }
1500: }
1501: if ($retvalue eq '0') {
1502: $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
1503: lc($a->{'title'}) <=> lc($b->{'title'}) ||
1504: lc($a->{'part'}) <=> lc($b->{'part'});
1505: }
1506: $retvalue;
1507: } @StatsArray;
1.24 stredwic 1508: }
1509: }
1.45 matthew 1510: #
1.73 matthew 1511: # Renumber the data set
1512: my $count;
1513: foreach my $data (@StatsArray) {
1514: $data->{'problem_num'} = ++$count;
1515: }
1.24 stredwic 1516: return;
1.48 matthew 1517: }
1518:
1.70 matthew 1519: ########################################################
1520: ########################################################
1521:
1522: =pod
1523:
1524: =item &get_statistics()
1525:
1526: Wrapper routine from the call to loncoursedata::get_problem_statistics.
1.73 matthew 1527: Calls lonstathelpers::get_time_limits() to limit the data set by time
1528: and &compute_discrimination_factor
1.70 matthew 1529:
1530: Inputs: $sequence, $resource, $part, $problem_num
1531:
1532: Returns: Hash reference with statistics data from
1533: loncoursedata::get_problem_statistics.
1534:
1535: =cut
1536:
1537: ########################################################
1538: ########################################################
1.48 matthew 1539: sub get_statistics {
1.49 matthew 1540: my ($sequence,$resource,$part,$problem_num) = @_;
1.48 matthew 1541: #
1.70 matthew 1542: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.49 matthew 1543: my $symb = $resource->{'symb'};
1.48 matthew 1544: my $courseid = $ENV{'request.course.id'};
1545: #
1.49 matthew 1546: my $data = &Apache::loncoursedata::get_problem_statistics
1.66 matthew 1547: (\@Apache::lonstatistics::SelectedSections,
1548: $Apache::lonstatistics::enrollment_status,
1.70 matthew 1549: $symb,$part,$courseid,$starttime,$endtime);
1.85 matthew 1550: $data->{'symb'} = $symb;
1.49 matthew 1551: $data->{'part'} = $part;
1552: $data->{'problem_num'} = $problem_num;
1553: $data->{'container'} = $sequence->{'title'};
1554: $data->{'title'} = $resource->{'title'};
1.53 matthew 1555: $data->{'title.link'} = $resource->{'src'}.'?symb='.
1556: &Apache::lonnet::escape($resource->{'symb'});
1.49 matthew 1557: #
1.76 matthew 1558: if ($SelectedFields{'deg_of_disc'}) {
1559: $data->{'deg_of_disc'} =
1560: &compute_discrimination_factor($resource,$part,$sequence);
1561: }
1.83 matthew 1562: #
1563: # Store in metadata if computations were done for all students
1.84 matthew 1564: if ($data->{'num_students'} > 1) {
1565: my @Sections = @Apache::lonstatistics::SelectedSections;
1566: my $sections = '"'.join(' ',@Sections).'"';
1567: $sections =~ s/&+/_/g; # Ensure no special characters
1568: $data->{'sections'}=$sections;
1569: $data->{'course'} = $ENV{'request.course.id'};
1.83 matthew 1570: my $urlres=(&Apache::lonnet::decode_symb($resource->{'symb'}))[2];
1.84 matthew 1571: $data->{'urlres'}=$urlres;
1572: my %storestats =
1573: &LONCAPA::lonmetadata::dynamic_metadata_storage($data);
1.83 matthew 1574: my ($dom,$user) = $urlres=~/^(\w+)\/(\w+)/;
1575: &Apache::lonnet::put('nohist_resevaldata',\%storestats,$dom,$user);
1576: }
1.85 matthew 1577: #
1.95 ! matthew 1578: $data->{'tries_per_correct'} = $data->{'tries'} /
! 1579: ($data->{'num_solved'}+0.1);
! 1580: #
1.85 matthew 1581: # Get the due date for research purposes (commented out most of the time)
1582: # $data->{'duedate'} =
1583: # &Apache::lonnet::EXT('resource.'.$part.'.duedate',$symb);
1584: # $data->{'opendate'} =
1585: # &Apache::lonnet::EXT('resource.'.$part.'.opendate',$symb);
1.94 matthew 1586: # $data->{'maxtries'} =
1587: # &Apache::lonnet::EXT('resource.'.$part.'.maxtries',$symb);
1588: # $data->{'hinttries'} =
1589: # &Apache::lonnet::EXT('resource.'.$part.'.hinttries',$symb);
1.86 matthew 1590: # $data->{'resptypes'} = join(',',@{$resource->{'partdata'}->{$part}->{'ResponseTypes'}});
1.49 matthew 1591: return $data;
1.71 matthew 1592: }
1593:
1594: ###############################################
1595: ###############################################
1596:
1597: =pod
1598:
1599: =item &compute_discrimination_factor()
1600:
1601: Inputs: $Resource, $Sequence
1602:
1603: Returns: integer between -1 and 1
1604:
1605: =cut
1606:
1607: ###############################################
1608: ###############################################
1609: sub compute_discrimination_factor {
1610: my ($resource,$part,$sequence) = @_;
1611: my @Resources;
1612: foreach my $res (@{$sequence->{'contents'}}) {
1613: next if ($res->{'symb'} eq $resource->{'symb'});
1614: push (@Resources,$res->{'symb'});
1615: }
1616: #
1617: # rank
1.83 matthew 1618: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.71 matthew 1619: my $ranking =
1620: &Apache::loncoursedata::rank_students_by_scores_on_resources
1621: (\@Resources,
1622: \@Apache::lonstatistics::SelectedSections,
1.83 matthew 1623: $Apache::lonstatistics::enrollment_status,undef,
1624: $starttime,$endtime);
1.71 matthew 1625: #
1626: # compute their percent scores on the problems in the sequence,
1627: my $number_to_grab = int(scalar(@{$ranking})/4);
1628: my $num_students = scalar(@{$ranking});
1629: my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()];
1630: } @{$ranking}[0..$number_to_grab];
1631: my @TopSet =
1632: map {
1633: $_->[&Apache::loncoursedata::RNK_student()];
1634: } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)];
1.91 matthew 1635: if (! @BottomSet || (@BottomSet == 1 && $BottomSet[0] eq '') ||
1636: ! @TopSet || (@TopSet == 1 && $TopSet[0] eq '')) {
1637: return 'nan';
1638: }
1.71 matthew 1639: my ($bottom_sum,$bottom_max) =
1.83 matthew 1640: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet,
1641: undef,$starttime,$endtime);
1.71 matthew 1642: my ($top_sum,$top_max) =
1.83 matthew 1643: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet,
1644: undef,$starttime,$endtime);
1.71 matthew 1645: my $deg_of_disc;
1646: if ($top_max == 0 || $bottom_max==0) {
1647: $deg_of_disc = 'nan';
1648: } else {
1649: $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
1650: }
1651: #&Apache::lonnet::logthis(' '.$top_sum.'/'.$top_max.
1652: # ' - '.$bottom_sum.'/'.$bottom_max);
1653: return $deg_of_disc;
1.1 stredwic 1654: }
1.12 minaeibi 1655:
1.45 matthew 1656: ###############################################
1657: ###############################################
1.79 matthew 1658: ##
1659: ## Compute KR-21
1660: ##
1661: ## To compute KR-21, you need the following information:
1662: ##
1663: ## K=the number of items in your test
1664: ## M=the mean score on the test
1665: ## s=the standard deviation of the scores on your test
1666: ##
1667: ## then:
1668: ##
1669: ## KR-21 rk= [K/(K-1)] * [1- (M*(K-M))/(K*s^2))]
1670: ##
1671: ###############################################
1672: ###############################################
1673: sub compute_sequence_statistics {
1674: my ($seq) = @_;
1675: my $symb = $seq->{'symb'};
1676: my @Resources;
1677: foreach my $res (@{$seq->{'contents'}}) {
1678: next if ($res->{'type'} ne 'assessment');
1679: push (@Resources,$res->{'symb'});
1680: }
1681: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1682: #
1683: # First compute statistics based on student scores
1684: my ($smin,$smax,$sMean,$sSTD,$scount,$sMAX) =
1685: &Apache::loncoursedata::score_stats
1686: (\@Apache::lonstatistics::SelectedSections,
1687: $Apache::lonstatistics::enrollment_status,
1688: \@Resources,$starttime,$endtime,undef);
1689: $SeqStat{$symb}->{'title'} = $seq->{'title'};
1690: $SeqStat{$symb}->{'scoremax'} = $smax;
1691: $SeqStat{$symb}->{'scoremin'} = $smin;
1692: $SeqStat{$symb}->{'scoremean'} = $sMean;
1693: $SeqStat{$symb}->{'scorestd'} = $sSTD;
1694: $SeqStat{$symb}->{'scorecount'} = $scount;
1695: $SeqStat{$symb}->{'max_possible'} = $sMAX;
1696: #
1697: # Compute statistics based on the number of correct problems
1698: # 'correct' is taken to mean
1699: my ($cmin,$cmax,$cMean,$cSTD,$ccount)=
1700: &Apache::loncoursedata::count_stats
1701: (\@Apache::lonstatistics::SelectedSections,
1702: $Apache::lonstatistics::enrollment_status,
1703: \@Resources,$starttime,$endtime,undef);
1704: my $K = $seq->{'num_assess_parts'};
1705: my $kr_21;
1706: if ($K > 1 && $cSTD > 0) {
1707: $kr_21 = ($K/($K-1)) * (1 - $cMean*($K-$cMean)/($K*$cSTD**2));
1708: } else {
1709: $kr_21 = 'nan';
1710: }
1711: $SeqStat{$symb}->{'countmax'} = $cmax;
1712: $SeqStat{$symb}->{'countmin'} = $cmin;
1713: $SeqStat{$symb}->{'countstd'} = $cSTD;
1.82 matthew 1714: $SeqStat{$symb}->{'countmean'} = $cMean;
1.79 matthew 1715: $SeqStat{$symb}->{'count'} = $ccount;
1716: $SeqStat{$symb}->{'items'} = $K;
1717: $SeqStat{$symb}->{'KR-21'}=$kr_21;
1718: return;
1719: }
1720:
1721:
1.47 matthew 1722:
1723: =pod
1724:
1.73 matthew 1725: =item ProblemStatisticsLegend
1726:
1727: =over 4
1728:
1729: =item #Stdnts
1730: Total number of students attempted the problem.
1731:
1732: =item Tries
1733: Total number of tries for solving the problem.
1.59 matthew 1734:
1.73 matthew 1735: =item Max Tries
1736: Largest number of tries for solving the problem by a student.
1737:
1738: =item Mean
1739: Average number of tries. [ Tries / #Stdnts ]
1740:
1741: =item #YES
1742: Number of students solved the problem correctly.
1743:
1744: =item #yes
1745: Number of students solved the problem by override.
1746:
1747: =item %Wrong
1748: Percentage of students who tried to solve the problem
1749: but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
1750:
1751: =item DoDiff
1752: Degree of Difficulty of the problem.
1753: [ 1 - ((#YES+#yes) / Tries) ]
1754:
1755: =item S.D.
1756: Standard Deviation of the tries.
1757: [ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
1758: where Xi denotes every student\'s tries ]
1759:
1760: =item Skew.
1761: Skewness of the students tries.
1762: [(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
1763:
1764: =item Dis.F.
1765: Discrimination Factor: A Standard for evaluating the
1766: problem according to a Criterion<br>
1767:
1768: =item [Criterion to group students into %27 Upper Students -
1769: and %27 Lower Students]
1770: 1st Criterion for Sorting the Students:
1771: Sum of Partial Credit Awarded / Total Number of Tries
1772: 2nd Criterion for Sorting the Students:
1773: Total number of Correct Answers / Total Number of Tries
1774:
1775: =item Disc.
1776: Number of Students had at least one discussion.
1777:
1778: =back
1.47 matthew 1779:
1780: =cut
1.73 matthew 1781:
1782: ############################################################
1783: ############################################################
1.4 minaeibi 1784:
1.1 stredwic 1785: 1;
1786: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>