Added framework for charts
[pspp-builds.git] / src / frequencies.q
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 /*
21   TODO:
22
23   * Remember that histograms, bar charts need mean, stddev.
24 */
25
26 #include <config.h>
27 #include "error.h"
28 #include <math.h>
29 #include <stdlib.h>
30 #include "alloc.h"
31 #include "bitvector.h"
32 #include "case.h"
33 #include "hash.h"
34 #include "pool.h"
35 #include "command.h"
36 #include "lexer.h"
37 #include "moments.h"
38 #include "error.h"
39 #include "algorithm.h"
40 #include "magic.h"
41 #include "misc.h"
42 #include "output.h"
43 #include "som.h"
44 #include "str.h"
45 #include "tab.h"
46 #include "value-labels.h"
47 #include "var.h"
48 #include "vfm.h"
49 #include "settings.h"
50 #include "chart.h"
51
52 #include "debug-print.h"
53
54 /* (specification)
55    FREQUENCIES (frq_):
56      *variables=custom;
57      format=cond:condense/onepage(*n:onepage_limit,"%s>=0")/!standard,
58             table:limit(n:limit,"%s>0")/notable/!table, 
59             labels:!labels/nolabels,
60             sort:!avalue/dvalue/afreq/dfreq,
61             spaces:!single/double,
62             paging:newpage/!oldpage;
63      missing=miss:include/!exclude;
64      barchart(ba_)=:minimum(d:min),
65             :maximum(d:max),
66             scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0");
67      piechart(pie_)=:minimum(d:min),
68             :maximum(d:max),
69             missing:missing/!nomissing;
70      histogram(hi_)=:minimum(d:min),
71             :maximum(d:max),
72             scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"),
73             norm:!nonormal/normal,
74             incr:increment(d:inc,"%s>0");
75      hbar(hb_)=:minimum(d:min),
76             :maximum(d:max),
77             scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"),
78             norm:!nonormal/normal,
79             incr:increment(d:inc,"%s>0");
80      grouped=custom;
81      ntiles=custom;
82      percentiles=custom;
83      statistics[st_]=1|mean,2|semean,3|median,4|mode,5|stddev,6|variance,
84             7|kurtosis,8|skewness,9|range,10|minimum,11|maximum,12|sum,
85             13|default,14|seskewness,15|sekurtosis,all,none.
86 */
87 /* (declarations) */
88 /* (functions) */
89
90 /* Description of a statistic. */
91 struct frq_info
92   {
93     int st_indx;                /* Index into a_statistics[]. */
94     const char *s10;            /* Identifying string. */
95   };
96
97 /* Table of statistics, indexed by dsc_*. */
98 static struct frq_info st_name[frq_n_stats + 1] =
99 {
100   {FRQ_ST_MEAN, N_("Mean")},
101   {FRQ_ST_SEMEAN, N_("S.E. Mean")},
102   {FRQ_ST_MEDIAN, N_("Median")},
103   {FRQ_ST_MODE, N_("Mode")},
104   {FRQ_ST_STDDEV, N_("Std Dev")},
105   {FRQ_ST_VARIANCE, N_("Variance")},
106   {FRQ_ST_KURTOSIS, N_("Kurtosis")},
107   {FRQ_ST_SEKURTOSIS, N_("S.E. Kurt")},
108   {FRQ_ST_SKEWNESS, N_("Skewness")},
109   {FRQ_ST_SESKEWNESS, N_("S.E. Skew")},
110   {FRQ_ST_RANGE, N_("Range")},
111   {FRQ_ST_MINIMUM, N_("Minimum")},
112   {FRQ_ST_MAXIMUM, N_("Maximum")},
113   {FRQ_ST_SUM, N_("Sum")},
114   {-1, 0},
115 };
116
117 /* Percentiles to calculate. */
118
119 struct percentile
120 {
121   double p;        /* the %ile to be calculated */
122   double value;    /* the %ile's value */
123   double x1;       /* The datum value <= the percentile */
124   double x2;       /* The datum value >= the percentile */
125   int flag;        
126   int flag2;       /* Set to 1 if this percentile value has been found */
127 };
128
129 static struct percentile *percentiles;
130 static int n_percentiles;
131
132 static int implicit_50th ; 
133
134 /* Groups of statistics. */
135 #define BI          BIT_INDEX
136 #define frq_default                                                     \
137         (BI (frq_mean) | BI (frq_stddev) | BI (frq_min) | BI (frq_max))
138 #define frq_all                                                 \
139         (BI (frq_sum) | BI(frq_min) | BI(frq_max)               \
140          | BI(frq_mean) | BI(frq_semean) | BI(frq_stddev)       \
141          | BI(frq_variance) | BI(frq_kurt) | BI(frq_sekurt)     \
142          | BI(frq_skew) | BI(frq_seskew) | BI(frq_range)        \
143          | BI(frq_range) | BI(frq_mode) | BI(frq_median))
144
145 /* Statistics; number of statistics. */
146 static unsigned long stats;
147 static int n_stats;
148
149 /* Types of graphs. */
150 enum
151   {
152     GFT_NONE,                   /* Don't draw graphs. */
153     GFT_BAR,                    /* Draw bar charts. */
154     GFT_HIST,                   /* Draw histograms. */
155     GFT_PIE,                    /* Draw piechart */
156     GFT_HBAR                    /* Draw bar charts or histograms at our discretion. */
157   };
158
159 /* Parsed command. */
160 static struct cmd_frequencies cmd;
161
162 /* Summary of the barchart, histogram, and hbar subcommands. */
163 /* FIXME: These should not be mututally exclusive */
164 static int chart;               /* NONE/BAR/HIST/HBAR/PIE. */
165 static double min, max;         /* Minimum, maximum on y axis. */
166 static int format;              /* FREQ/PERCENT: Scaling of y axis. */
167 static double scale, incr;      /* FIXME */
168 static int normal;              /* FIXME */
169
170 /* Variables for which to calculate statistics. */
171 static int n_variables;
172 static struct variable **v_variables;
173
174 /* Arenas used to store semi-permanent storage. */
175 static struct pool *int_pool;   /* Integer mode. */
176 static struct pool *gen_pool;   /* General mode. */
177
178 /* Easier access to a_statistics. */
179 #define stat cmd.a_statistics
180
181 static void determine_charts (void);
182
183 static void calc_stats (struct variable * v, double d[frq_n_stats]);
184
185 static void precalc (void *);
186 static int calc (struct ccase *, void *);
187 static void postcalc (void *);
188
189 static void postprocess_freq_tab (struct variable *);
190 static void dump_full (struct variable *);
191 static void dump_condensed (struct variable *);
192 static void dump_statistics (struct variable *, int show_varname);
193 static void cleanup_freq_tab (struct variable *);
194
195 static hsh_hash_func hash_value_numeric, hash_value_alpha;
196 static hsh_compare_func compare_value_numeric_a, compare_value_alpha_a;
197 static hsh_compare_func compare_value_numeric_d, compare_value_alpha_d;
198 static hsh_compare_func compare_freq_numeric_a, compare_freq_alpha_a;
199 static hsh_compare_func compare_freq_numeric_d, compare_freq_alpha_d;
200 \f
201 /* Parser and outline. */
202
203 static int internal_cmd_frequencies (void);
204
205 int
206 cmd_frequencies (void)
207 {
208   int result;
209
210   int_pool = pool_create ();
211   result = internal_cmd_frequencies ();
212   pool_destroy (int_pool);
213   int_pool=0;
214   pool_destroy (gen_pool);
215   gen_pool=0;
216   free (v_variables);
217   v_variables=0;
218   return result;
219 }
220
221 static int
222 internal_cmd_frequencies (void)
223 {
224   int i;
225
226   n_percentiles = 0;
227   percentiles = NULL;
228
229   n_variables = 0;
230   v_variables = NULL;
231
232   for (i = 0; i < dict_get_var_cnt (default_dict); i++)
233     dict_get_var(default_dict, i)->p.frq.used = 0;
234
235   if (!parse_frequencies (&cmd))
236     return CMD_FAILURE;
237
238   if (cmd.onepage_limit == NOT_LONG)
239     cmd.onepage_limit = 50;
240
241   /* Figure out statistics to calculate. */
242   stats = 0;
243   if (stat[FRQ_ST_DEFAULT] || !cmd.sbc_statistics)
244     stats |= frq_default;
245   if (stat[FRQ_ST_ALL])
246     stats |= frq_all;
247   if (cmd.sort != FRQ_AVALUE && cmd.sort != FRQ_DVALUE)
248     stats &= ~frq_median;
249   for (i = 0; i < frq_n_stats; i++)
250     if (stat[st_name[i].st_indx])
251       stats |= BIT_INDEX (i);
252   if (stats & frq_kurt)
253     stats |= frq_sekurt;
254   if (stats & frq_skew)
255     stats |= frq_seskew;
256
257   /* Calculate n_stats. */
258   n_stats = 0;
259   for (i = 0; i < frq_n_stats; i++)
260     if ((stats & BIT_INDEX (i)))
261       n_stats++;
262
263   /* Charting. */
264   determine_charts ();
265   if (chart != GFT_NONE || cmd.sbc_ntiles)
266     cmd.sort = FRQ_AVALUE;
267
268   /* Do it! */
269   procedure_with_splits (precalc, calc, postcalc, NULL);
270
271   return CMD_SUCCESS;
272 }
273
274 /* Figure out which charts the user requested.  */
275 static void
276 determine_charts (void)
277 {
278   int count = (!!cmd.sbc_histogram) + (!!cmd.sbc_barchart) + 
279     (!!cmd.sbc_hbar) + (!!cmd.sbc_piechart);
280
281   if (!count)
282     {
283       chart = GFT_NONE;
284       return;
285     }
286   else if (count > 1)
287     {
288       chart = GFT_HBAR;
289       msg (SW, _("At most one of BARCHART, HISTOGRAM, or HBAR should be "
290            "given.  HBAR will be assumed.  Argument values will be "
291            "given precedence increasing along the order given."));
292     }
293   else if (cmd.sbc_histogram)
294     chart = GFT_HIST;
295   else if (cmd.sbc_barchart)
296     chart = GFT_BAR;
297   else if (cmd.sbc_piechart)
298     chart = GFT_PIE;
299   else
300     chart = GFT_HBAR;
301
302   min = max = SYSMIS;
303   format = FRQ_FREQ;
304   scale = SYSMIS;
305   incr = SYSMIS;
306   normal = 0;
307
308   if (cmd.sbc_barchart)
309     {
310       if (cmd.ba_min != SYSMIS)
311         min = cmd.ba_min;
312       if (cmd.ba_max != SYSMIS)
313         max = cmd.ba_max;
314       if (cmd.ba_scale == FRQ_FREQ)
315         {
316           format = FRQ_FREQ;
317           scale = cmd.ba_freq;
318         }
319       else if (cmd.ba_scale == FRQ_PERCENT)
320         {
321           format = FRQ_PERCENT;
322           scale = cmd.ba_pcnt;
323         }
324     }
325
326   if (cmd.sbc_histogram)
327     {
328       if (cmd.hi_min != SYSMIS)
329         min = cmd.hi_min;
330       if (cmd.hi_max != SYSMIS)
331         max = cmd.hi_max;
332       if (cmd.hi_scale == FRQ_FREQ)
333         {
334           format = FRQ_FREQ;
335           scale = cmd.hi_freq;
336         }
337       else if (cmd.hi_scale == FRQ_PERCENT)
338         {
339           format = FRQ_PERCENT;
340           scale = cmd.ba_pcnt;
341         }
342       if (cmd.hi_norm != FRQ_NONORMAL )
343         normal = 1;
344       if (cmd.hi_incr == FRQ_INCREMENT)
345         incr = cmd.hi_inc;
346     }
347
348   if (cmd.sbc_hbar)
349     {
350       if (cmd.hb_min != SYSMIS)
351         min = cmd.hb_min;
352       if (cmd.hb_max != SYSMIS)
353         max = cmd.hb_max;
354       if (cmd.hb_scale == FRQ_FREQ)
355         {
356           format = FRQ_FREQ;
357           scale = cmd.hb_freq;
358         }
359       else if (cmd.hb_scale == FRQ_PERCENT)
360         {
361           format = FRQ_PERCENT;
362           scale = cmd.ba_pcnt;
363         }
364       if (cmd.hb_norm)
365         normal = 1;
366       if (cmd.hb_incr == FRQ_INCREMENT)
367         incr = cmd.hb_inc;
368     }
369
370   if (min != SYSMIS && max != SYSMIS && min >= max)
371     {
372       msg (SE, _("MAX must be greater than or equal to MIN, if both are "
373            "specified.  However, MIN was specified as %g and MAX as %g.  "
374            "MIN and MAX will be ignored."), min, max);
375       min = max = SYSMIS;
376     }
377 }
378
379 /* Add data from case C to the frequency table. */
380 static int
381 calc (struct ccase *c, void *aux UNUSED)
382 {
383   double weight;
384   int i;
385   int bad_warn = 1;
386
387   weight = dict_get_case_weight (default_dict, c, &bad_warn);
388
389   for (i = 0; i < n_variables; i++)
390     {
391       struct variable *v = v_variables[i];
392       const union value *val = case_data (c, v->fv);
393       struct freq_tab *ft = &v->p.frq.tab;
394
395       switch (v->p.frq.tab.mode)
396         {
397           case FRQM_GENERAL:
398             {
399
400               /* General mode. */
401               struct freq **fpp = (struct freq **) hsh_probe (ft->data, val);
402
403               if (*fpp != NULL)
404                 (*fpp)->c += weight;
405               else
406                 {
407                   struct freq *fp = *fpp = pool_alloc (gen_pool, sizeof *fp);
408                   fp->v = *val;
409                   fp->c = weight;
410                 }
411             }
412           break;
413         case FRQM_INTEGER:
414           /* Integer mode. */
415           if (val->f == SYSMIS)
416             v->p.frq.tab.sysmis += weight;
417           else if (val->f > INT_MIN+1 && val->f < INT_MAX-1)
418             {
419               int i = val->f;
420               if (i >= v->p.frq.tab.min && i <= v->p.frq.tab.max)
421                 v->p.frq.tab.vector[i - v->p.frq.tab.min] += weight;
422             }
423           else
424             v->p.frq.tab.out_of_range += weight;
425           break;
426         default:
427           assert (0);
428         }
429     }
430   return 1;
431 }
432
433 /* Prepares each variable that is the target of FREQUENCIES by setting
434    up its hash table. */
435 static void
436 precalc (void *aux UNUSED)
437 {
438   int i;
439
440   pool_destroy (gen_pool);
441   gen_pool = pool_create ();
442   
443   for (i = 0; i < n_variables; i++)
444     {
445       struct variable *v = v_variables[i];
446
447       if (v->p.frq.tab.mode == FRQM_GENERAL)
448         {
449           hsh_hash_func *hash;
450           hsh_compare_func *compare;
451
452           if (v->type == NUMERIC) 
453             {
454               hash = hash_value_numeric;
455               compare = compare_value_numeric_a; 
456             }
457           else 
458             {
459               hash = hash_value_alpha;
460               compare = compare_value_alpha_a;
461             }
462           v->p.frq.tab.data = hsh_create (16, compare, hash, NULL, v);
463         }
464       else
465         {
466           int j;
467
468           for (j = (v->p.frq.tab.max - v->p.frq.tab.min); j >= 0; j--)
469             v->p.frq.tab.vector[j] = 0.0;
470           v->p.frq.tab.out_of_range = 0.0;
471           v->p.frq.tab.sysmis = 0.0;
472         }
473     }
474 }
475
476 /* Finishes up with the variables after frequencies have been
477    calculated.  Displays statistics, percentiles, ... */
478 static void
479 postcalc (void *aux UNUSED)
480 {
481   int i;
482
483   for (i = 0; i < n_variables; i++)
484     {
485       struct variable *v = v_variables[i];
486       int n_categories;
487       int dumped_freq_tab = 1;
488
489       postprocess_freq_tab (v);
490
491       /* Frequencies tables. */
492       n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
493       if (cmd.table == FRQ_TABLE
494           || (cmd.table == FRQ_LIMIT && n_categories <= cmd.limit))
495         switch (cmd.cond)
496           {
497           case FRQ_CONDENSE:
498             dump_condensed (v);
499             break;
500           case FRQ_STANDARD:
501             dump_full (v);
502             break;
503           case FRQ_ONEPAGE:
504             if (n_categories > cmd.onepage_limit)
505               dump_condensed (v);
506             else
507               dump_full (v);
508             break;
509           default:
510             assert (0);
511           }
512       else
513         dumped_freq_tab = 0;
514
515       /* Statistics. */
516       if (n_stats)
517         dump_statistics (v, !dumped_freq_tab);
518
519
520       if ( chart == GFT_HIST) 
521         {
522           struct chart ch;
523           double d[frq_n_stats];
524           struct frequencies_proc *frq = &v->p.frq;
525           
526           struct normal_curve norm;
527           norm.N = frq->tab.total_cases ;
528
529           calc_stats(v,d);
530           norm.mean = d[frq_mean];
531           norm.stddev = d[frq_stddev];
532
533           chart_initialise(&ch);
534           draw_histogram(&ch, v_variables[i], "HISTOGRAM",&norm,normal);
535           chart_finalise(&ch);
536         }
537
538
539       if ( chart == GFT_PIE) 
540         {
541           struct chart ch;
542
543           chart_initialise(&ch);
544           
545           draw_piechart(&ch, v_variables[i]);
546
547           chart_finalise(&ch);
548         }
549
550
551       cleanup_freq_tab (v);
552
553     }
554 }
555
556 /* Returns the comparison function that should be used for
557    sorting a frequency table by FRQ_SORT using VAR_TYPE
558    variables. */
559 static hsh_compare_func *
560 get_freq_comparator (int frq_sort, int var_type) 
561 {
562   /* Note that q2c generates tags beginning with 1000. */
563   switch (frq_sort | (var_type << 16))
564     {
565     case FRQ_AVALUE | (NUMERIC << 16):  return compare_value_numeric_a;
566     case FRQ_AVALUE | (ALPHA << 16):    return compare_value_alpha_a;
567     case FRQ_DVALUE | (NUMERIC << 16):  return compare_value_numeric_d;
568     case FRQ_DVALUE | (ALPHA << 16):    return compare_value_alpha_d;
569     case FRQ_AFREQ | (NUMERIC << 16):   return compare_freq_numeric_a;
570     case FRQ_AFREQ | (ALPHA << 16):     return compare_freq_alpha_a;
571     case FRQ_DFREQ | (NUMERIC << 16):   return compare_freq_numeric_d;
572     case FRQ_DFREQ | (ALPHA << 16):     return compare_freq_alpha_d;
573     default: assert (0);
574     }
575
576   return 0;
577 }
578
579 /* Returns nonzero iff the value in struct freq F is non-missing
580    for variable V. */
581 static int
582 not_missing (const void *f_, void *v_) 
583 {
584   const struct freq *f = f_;
585   struct variable *v = v_;
586
587   return !is_missing (&f->v, v);
588 }
589
590 /* Summarizes the frequency table data for variable V. */
591 static void
592 postprocess_freq_tab (struct variable *v)
593 {
594   hsh_compare_func *compare;
595   struct freq_tab *ft;
596   size_t count;
597   void **data;
598   struct freq *freqs, *f;
599   size_t i;
600
601   assert (v->p.frq.tab.mode == FRQM_GENERAL);
602   compare = get_freq_comparator (cmd.sort, v->type);
603   ft = &v->p.frq.tab;
604
605   /* Extract data from hash table. */
606   count = hsh_count (ft->data);
607   data = hsh_data (ft->data);
608
609   /* Copy dereferenced data into freqs. */
610   freqs = xmalloc (count * sizeof *freqs);
611   for (i = 0; i < count; i++) 
612     {
613       struct freq *f = data[i];
614       freqs[i] = *f; 
615     }
616
617   /* Put data into ft. */
618   ft->valid = freqs;
619   ft->n_valid = partition (freqs, count, sizeof *freqs, not_missing, v);
620   ft->missing = freqs + ft->n_valid;
621   ft->n_missing = count - ft->n_valid;
622
623   /* Sort data. */
624   sort (ft->valid, ft->n_valid, sizeof *ft->valid, compare, v);
625   sort (ft->missing, ft->n_missing, sizeof *ft->missing, compare, v);
626
627   /* Summary statistics. */
628   ft->valid_cases = 0.0;
629   for(i = 0 ;  i < ft->n_valid ; ++i ) 
630     {
631       f = &ft->valid[i];
632       ft->valid_cases += f->c;
633
634     }
635
636   ft->total_cases = ft->valid_cases ; 
637   for(i = 0 ;  i < ft->n_missing ; ++i ) 
638     {
639       f = &ft->missing[i];
640       ft->total_cases += f->c;
641     }
642
643 }
644
645 /* Frees the frequency table for variable V. */
646 static void
647 cleanup_freq_tab (struct variable *v)
648 {
649   assert (v->p.frq.tab.mode == FRQM_GENERAL);
650   free (v->p.frq.tab.valid);
651   hsh_destroy (v->p.frq.tab.data);
652 }
653
654 /* Parses the VARIABLES subcommand, adding to
655    {n_variables,v_variables}. */
656 static int
657 frq_custom_variables (struct cmd_frequencies *cmd UNUSED)
658 {
659   int mode;
660   int min = 0, max = 0;
661
662   int old_n_variables = n_variables;
663   int i;
664
665   lex_match ('=');
666   if (token != T_ALL && (token != T_ID
667                          || dict_lookup_var (default_dict, tokid) == NULL))
668     return 2;
669
670   if (!parse_variables (default_dict, &v_variables, &n_variables,
671                         PV_APPEND | PV_NO_SCRATCH))
672     return 0;
673
674   for (i = old_n_variables; i < n_variables; i++)
675     v_variables[i]->p.frq.tab.mode = FRQM_GENERAL;
676
677   if (!lex_match ('('))
678     mode = FRQM_GENERAL;
679   else
680     {
681       mode = FRQM_INTEGER;
682       if (!lex_force_int ())
683         return 0;
684       min = lex_integer ();
685       lex_get ();
686       if (!lex_force_match (','))
687         return 0;
688       if (!lex_force_int ())
689         return 0;
690       max = lex_integer ();
691       lex_get ();
692       if (!lex_force_match (')'))
693         return 0;
694       if (max < min)
695         {
696           msg (SE, _("Upper limit of integer mode value range must be "
697                      "greater than lower limit."));
698           return 0;
699         }
700     }
701
702   for (i = old_n_variables; i < n_variables; i++)
703     {
704       struct variable *v = v_variables[i];
705
706       if (v->p.frq.used != 0)
707         {
708           msg (SE, _("Variable %s specified multiple times on VARIABLES "
709                      "subcommand."), v->name);
710           return 0;
711         }
712       
713       v->p.frq.used = 1;                /* Used simply as a marker. */
714
715       v->p.frq.tab.valid = v->p.frq.tab.missing = NULL;
716
717       if (mode == FRQM_INTEGER)
718         {
719           if (v->type != NUMERIC)
720             {
721               msg (SE, _("Integer mode specified, but %s is not a numeric "
722                          "variable."), v->name);
723               return 0;
724             }
725           
726           v->p.frq.tab.min = min;
727           v->p.frq.tab.max = max;
728           v->p.frq.tab.vector = pool_alloc (int_pool,
729                                             sizeof (struct freq) * (max - min + 1));
730         }
731       else
732         v->p.frq.tab.vector = NULL;
733
734       v->p.frq.n_groups = 0;
735       v->p.frq.groups = NULL;
736     }
737   return 1;
738 }
739
740 /* Parses the GROUPED subcommand, setting the frq.{n_grouped,grouped}
741    fields of specified variables. */
742 static int
743 frq_custom_grouped (struct cmd_frequencies *cmd UNUSED)
744 {
745   lex_match ('=');
746   if ((token == T_ID && dict_lookup_var (default_dict, tokid) != NULL)
747       || token == T_ID)
748     for (;;)
749       {
750         int i;
751
752         /* Max, current size of list; list itself. */
753         int nl, ml;
754         double *dl;
755
756         /* Variable list. */
757         int n;
758         struct variable **v;
759
760         if (!parse_variables (default_dict, &v, &n,
761                               PV_NO_DUPLICATE | PV_NUMERIC))
762           return 0;
763         if (lex_match ('('))
764           {
765             nl = ml = 0;
766             dl = NULL;
767             while (token == T_NUM)
768               {
769                 if (nl >= ml)
770                   {
771                     ml += 16;
772                     dl = pool_realloc (int_pool, dl, ml * sizeof (double));
773                   }
774                 dl[nl++] = tokval;
775                 lex_get ();
776                 lex_match (',');
777               }
778             /* Note that nl might still be 0 and dl might still be
779                NULL.  That's okay. */
780             if (!lex_match (')'))
781               {
782                 free (v);
783                 msg (SE, _("`)' expected after GROUPED interval list."));
784                 return 0;
785               }
786           }
787         else 
788           {
789             nl = 0;
790             dl = NULL;
791           }
792
793         for (i = 0; i < n; i++)
794           {
795             if (v[i]->p.frq.used == 0)
796               msg (SE, _("Variables %s specified on GROUPED but not on "
797                    "VARIABLES."), v[i]->name);
798             if (v[i]->p.frq.groups != NULL)
799               msg (SE, _("Variables %s specified multiple times on GROUPED "
800                    "subcommand."), v[i]->name);
801             else
802               {
803                 v[i]->p.frq.n_groups = nl;
804                 v[i]->p.frq.groups = dl;
805               }
806           }
807         free (v);
808         if (!lex_match ('/'))
809           break;
810         if ((token != T_ID || dict_lookup_var (default_dict, tokid) != NULL)
811             && token != T_ALL)
812           {
813             lex_put_back ('/');
814             break;
815           }
816       }
817
818   return 1;
819 }
820
821 /* Adds X to the list of percentiles, keeping the list in proper
822    order. */
823 static void
824 add_percentile (double x)
825 {
826   int i;
827
828   for (i = 0; i < n_percentiles; i++)
829     if (x <= percentiles[i].p)
830       break;
831
832   if (i >= n_percentiles || tokval != percentiles[i].p)
833     {
834       percentiles
835         = pool_realloc (int_pool, percentiles,
836                         (n_percentiles + 1) * sizeof (struct percentile ));
837
838       if (i < n_percentiles)
839           memmove (&percentiles[i + 1], &percentiles[i],
840                    (n_percentiles - i) * sizeof (struct percentile) );
841
842       percentiles[i].p = x;
843       n_percentiles++;
844     }
845 }
846
847 /* Parses the PERCENTILES subcommand, adding user-specified
848    percentiles to the list. */
849 static int
850 frq_custom_percentiles (struct cmd_frequencies *cmd UNUSED)
851 {
852   lex_match ('=');
853   if (token != T_NUM)
854     {
855       msg (SE, _("Percentile list expected after PERCENTILES."));
856       return 0;
857     }
858   
859   do
860     {
861       if (tokval < 0 || tokval > 100)
862         {
863           msg (SE, _("Percentiles must be between 0 and 100."));
864           return 0;
865         }
866       
867       add_percentile (tokval / 100.0);
868       lex_get ();
869       lex_match (',');
870     }
871   while (token == T_NUM);
872   return 1;
873 }
874
875 /* Parses the NTILES subcommand, adding the percentiles that
876    correspond to the specified evenly-distributed ntiles. */
877 static int
878 frq_custom_ntiles (struct cmd_frequencies *cmd UNUSED)
879 {
880   int i;
881
882   lex_match ('=');
883   if (!lex_force_int ())
884     return 0;
885   for (i = 1; i < lex_integer (); i++)
886     add_percentile (1.0 / lex_integer () * i);
887   lex_get ();
888   return 1;
889 }
890 \f
891 /* Comparison functions. */
892
893 /* Hash of numeric values. */
894 static unsigned
895 hash_value_numeric (const void *value_, void *foo UNUSED)
896 {
897   const struct freq *value = value_;
898   return hsh_hash_double (value->v.f);
899 }
900
901 /* Hash of string values. */
902 static unsigned
903 hash_value_alpha (const void *value_, void *v_)
904 {
905   const struct freq *value = value_;
906   struct variable *v = v_;
907
908   return hsh_hash_bytes (value->v.s, v->width);
909 }
910
911 /* Ascending numeric compare of values. */
912 static int
913 compare_value_numeric_a (const void *a_, const void *b_, void *foo UNUSED)
914 {
915   const struct freq *a = a_;
916   const struct freq *b = b_;
917
918   if (a->v.f > b->v.f)
919     return 1;
920   else if (a->v.f < b->v.f)
921     return -1;
922   else
923     return 0;
924 }
925
926 /* Ascending string compare of values. */
927 static int
928 compare_value_alpha_a (const void *a_, const void *b_, void *v_)
929 {
930   const struct freq *a = a_;
931   const struct freq *b = b_;
932   const struct variable *v = v_;
933
934   return memcmp (a->v.s, b->v.s, v->width);
935 }
936
937 /* Descending numeric compare of values. */
938 static int
939 compare_value_numeric_d (const void *a, const void *b, void *foo UNUSED)
940 {
941   return -compare_value_numeric_a (a, b, foo);
942 }
943
944 /* Descending string compare of values. */
945 static int
946 compare_value_alpha_d (const void *a, const void *b, void *v)
947 {
948   return -compare_value_alpha_a (a, b, v);
949 }
950
951 /* Ascending numeric compare of frequency;
952    secondary key on ascending numeric value. */
953 static int
954 compare_freq_numeric_a (const void *a_, const void *b_, void *foo UNUSED)
955 {
956   const struct freq *a = a_;
957   const struct freq *b = b_;
958
959   if (a->c > b->c)
960     return 1;
961   else if (a->c < b->c)
962     return -1;
963
964   if (a->v.f > b->v.f)
965     return 1;
966   else if (a->v.f < b->v.f)
967     return -1;
968   else
969     return 0;
970 }
971
972 /* Ascending numeric compare of frequency;
973    secondary key on ascending string value. */
974 static int
975 compare_freq_alpha_a (const void *a_, const void *b_, void *v_)
976 {
977   const struct freq *a = a_;
978   const struct freq *b = b_;
979   const struct variable *v = v_;
980
981   if (a->c > b->c)
982     return 1;
983   else if (a->c < b->c)
984     return -1;
985   else
986     return memcmp (a->v.s, b->v.s, v->width);
987 }
988
989 /* Descending numeric compare of frequency;
990    secondary key on ascending numeric value. */
991 static int
992 compare_freq_numeric_d (const void *a_, const void *b_, void *foo UNUSED)
993 {
994   const struct freq *a = a_;
995   const struct freq *b = b_;
996
997   if (a->c > b->c)
998     return -1;
999   else if (a->c < b->c)
1000     return 1;
1001
1002   if (a->v.f > b->v.f)
1003     return 1;
1004   else if (a->v.f < b->v.f)
1005     return -1;
1006   else
1007     return 0;
1008 }
1009
1010 /* Descending numeric compare of frequency;
1011    secondary key on ascending string value. */
1012 static int
1013 compare_freq_alpha_d (const void *a_, const void *b_, void *v_)
1014 {
1015   const struct freq *a = a_;
1016   const struct freq *b = b_;
1017   const struct variable *v = v_;
1018
1019   if (a->c > b->c)
1020     return -1;
1021   else if (a->c < b->c)
1022     return 1;
1023   else
1024     return memcmp (a->v.s, b->v.s, v->width);
1025 }
1026 \f
1027 /* Frequency table display. */
1028
1029 /* Sets the widths of all the columns and heights of all the rows in
1030    table T for driver D. */
1031 static void
1032 full_dim (struct tab_table *t, struct outp_driver *d)
1033 {
1034   int lab = cmd.labels == FRQ_LABELS;
1035   int i;
1036
1037   if (lab)
1038     t->w[0] = min (tab_natural_width (t, d, 0), d->prop_em_width * 15);
1039   for (i = lab; i < lab + 5; i++)
1040     t->w[i] = max (tab_natural_width (t, d, i), d->prop_em_width * 8);
1041   for (i = 0; i < t->nr; i++)
1042     t->h[i] = d->font_height;
1043 }
1044
1045 /* Displays a full frequency table for variable V. */
1046 static void
1047 dump_full (struct variable * v)
1048 {
1049   int n_categories;
1050   struct freq *f;
1051   struct tab_table *t;
1052   int r;
1053   double cum_total = 0.0;
1054   double cum_freq = 0.0;
1055
1056   struct init
1057     {
1058       int c, r;
1059       const char *s;
1060     };
1061
1062   struct init *p;
1063
1064   static struct init vec[] =
1065   {
1066     {4, 0, N_("Valid")},
1067     {5, 0, N_("Cum")},
1068     {1, 1, N_("Value")},
1069     {2, 1, N_("Frequency")},
1070     {3, 1, N_("Percent")},
1071     {4, 1, N_("Percent")},
1072     {5, 1, N_("Percent")},
1073     {0, 0, NULL},
1074     {1, 0, NULL},
1075     {2, 0, NULL},
1076     {3, 0, NULL},
1077     {-1, -1, NULL},
1078   };
1079
1080   int lab = cmd.labels == FRQ_LABELS;
1081
1082   n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
1083   t = tab_create (5 + lab, n_categories + 3, 0);
1084   tab_headers (t, 0, 0, 2, 0);
1085   tab_dim (t, full_dim);
1086
1087   if (lab)
1088     tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value Label"));
1089   for (p = vec; p->s; p++)
1090     tab_text (t, p->c - (p->r ? !lab : 0), p->r,
1091                   TAB_CENTER | TAT_TITLE, gettext (p->s));
1092
1093   r = 2;
1094   for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
1095     {
1096       double percent, valid_percent;
1097
1098       cum_freq += f->c;
1099
1100       percent = f->c / v->p.frq.tab.total_cases * 100.0;
1101       valid_percent = f->c / v->p.frq.tab.valid_cases * 100.0;
1102       cum_total += valid_percent;
1103
1104       if (lab)
1105         {
1106           const char *label = val_labs_find (v->val_labs, f->v);
1107           if (label != NULL)
1108             tab_text (t, 0, r, TAB_LEFT, label);
1109         }
1110
1111       tab_value (t, 0 + lab, r, TAB_NONE, &f->v, &v->print);
1112       tab_float (t, 1 + lab, r, TAB_NONE, f->c, 8, 0);
1113       tab_float (t, 2 + lab, r, TAB_NONE, percent, 5, 1);
1114       tab_float (t, 3 + lab, r, TAB_NONE, valid_percent, 5, 1);
1115       tab_float (t, 4 + lab, r, TAB_NONE, cum_total, 5, 1);
1116       r++;
1117     }
1118   for (; f < &v->p.frq.tab.valid[n_categories]; f++)
1119     {
1120       cum_freq += f->c;
1121
1122       if (lab)
1123         {
1124           const char *label = val_labs_find (v->val_labs, f->v);
1125           if (label != NULL)
1126             tab_text (t, 0, r, TAB_LEFT, label);
1127         }
1128
1129       tab_value (t, 0 + lab, r, TAB_NONE, &f->v, &v->print);
1130       tab_float (t, 1 + lab, r, TAB_NONE, f->c, 8, 0);
1131       tab_float (t, 2 + lab, r, TAB_NONE,
1132                      f->c / v->p.frq.tab.total_cases * 100.0, 5, 1);
1133       tab_text (t, 3 + lab, r, TAB_NONE, _("Missing"));
1134       r++;
1135     }
1136
1137   tab_box (t, TAL_1, TAL_1,
1138            cmd.spaces == FRQ_SINGLE ? -1 : (TAL_1 | TAL_SPACING), TAL_1,
1139            0, 0, 4 + lab, r);
1140   tab_hline (t, TAL_2, 0, 4 + lab, 2);
1141   tab_hline (t, TAL_2, 0, 4 + lab, r);
1142   tab_joint_text (t, 0, r, 0 + lab, r, TAB_RIGHT | TAT_TITLE, _("Total"));
1143   tab_vline (t, TAL_0, 1, r, r);
1144   tab_float (t, 1 + lab, r, TAB_NONE, cum_freq, 8, 0);
1145   tab_float (t, 2 + lab, r, TAB_NONE, 100.0, 5, 1);
1146   tab_float (t, 3 + lab, r, TAB_NONE, 100.0, 5, 1);
1147
1148   tab_title (t, 1, "%s: %s", v->name, v->label ? v->label : "");
1149   tab_submit (t);
1150
1151 }
1152
1153 /* Sets the widths of all the columns and heights of all the rows in
1154    table T for driver D. */
1155 static void
1156 condensed_dim (struct tab_table *t, struct outp_driver *d)
1157 {
1158   int cum_w = max (outp_string_width (d, _("Cum")),
1159                    max (outp_string_width (d, _("Cum")),
1160                         outp_string_width (d, "000")));
1161
1162   int i;
1163
1164   for (i = 0; i < 2; i++)
1165     t->w[i] = max (tab_natural_width (t, d, i), d->prop_em_width * 8);
1166   for (i = 2; i < 4; i++)
1167     t->w[i] = cum_w;
1168   for (i = 0; i < t->nr; i++)
1169     t->h[i] = d->font_height;
1170 }
1171
1172 /* Display condensed frequency table for variable V. */
1173 static void
1174 dump_condensed (struct variable * v)
1175 {
1176   int n_categories;
1177   struct freq *f;
1178   struct tab_table *t;
1179   int r;
1180   double cum_total = 0.0;
1181
1182   n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
1183   t = tab_create (4, n_categories + 2, 0);
1184
1185   tab_headers (t, 0, 0, 2, 0);
1186   tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value"));
1187   tab_text (t, 1, 1, TAB_CENTER | TAT_TITLE, _("Freq"));
1188   tab_text (t, 2, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
1189   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Cum"));
1190   tab_text (t, 3, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
1191   tab_dim (t, condensed_dim);
1192
1193   r = 2;
1194   for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
1195     {
1196       double percent;
1197
1198       percent = f->c / v->p.frq.tab.total_cases * 100.0;
1199       cum_total += f->c / v->p.frq.tab.valid_cases * 100.0;
1200
1201       tab_value (t, 0, r, TAB_NONE, &f->v, &v->print);
1202       tab_float (t, 1, r, TAB_NONE, f->c, 8, 0);
1203       tab_float (t, 2, r, TAB_NONE, percent, 3, 0);
1204       tab_float (t, 3, r, TAB_NONE, cum_total, 3, 0);
1205       r++;
1206     }
1207   for (; f < &v->p.frq.tab.valid[n_categories]; f++)
1208     {
1209       tab_value (t, 0, r, TAB_NONE, &f->v, &v->print);
1210       tab_float (t, 1, r, TAB_NONE, f->c, 8, 0);
1211       tab_float (t, 2, r, TAB_NONE,
1212                  f->c / v->p.frq.tab.total_cases * 100.0, 3, 0);
1213       r++;
1214     }
1215
1216   tab_box (t, TAL_1, TAL_1,
1217            cmd.spaces == FRQ_SINGLE ? -1 : (TAL_1 | TAL_SPACING), TAL_1,
1218            0, 0, 3, r - 1);
1219   tab_hline (t, TAL_2, 0, 3, 2);
1220   tab_title (t, 1, "%s: %s", v->name, v->label ? v->label : "");
1221   tab_columns (t, SOM_COL_DOWN, 1);
1222   tab_submit (t);
1223 }
1224 \f
1225 /* Statistical display. */
1226
1227 /* Calculates all the pertinent statistics for variable V, putting
1228    them in array D[].  FIXME: This could be made much more optimal. */
1229 static void
1230 calc_stats (struct variable * v, double d[frq_n_stats])
1231 {
1232   double W = v->p.frq.tab.valid_cases;
1233   struct moments *m;
1234   struct freq *f=0; 
1235   int most_often;
1236   double X_mode;
1237
1238   double rank;
1239   int i = 0;
1240   int idx;
1241   double *median_value;
1242
1243   /* Calculate percentiles. */
1244
1245   /* If the 50th percentile was not explicitly requested then we must 
1246      calculate it anyway --- it's the median */
1247   median_value = 0 ;
1248   for (i = 0; i < n_percentiles; i++) 
1249     {
1250       if (percentiles[i].p == 0.5)
1251         {
1252           median_value = &percentiles[i].value;
1253           break;
1254         }
1255     }
1256
1257   if ( 0 == median_value )  
1258     {
1259       add_percentile (0.5);
1260       implicit_50th = 1;
1261     }
1262
1263   for (i = 0; i < n_percentiles; i++) 
1264     {
1265       percentiles[i].flag = 0;
1266       percentiles[i].flag2 = 0;
1267     }
1268
1269   rank = 0;
1270   for (idx = 0; idx < v->p.frq.tab.n_valid; ++idx)
1271     {
1272       static double prev_value = SYSMIS;
1273       f = &v->p.frq.tab.valid[idx]; 
1274       rank += f->c ;
1275       for (i = 0; i < n_percentiles; i++) 
1276         {
1277           double tp;
1278           if ( percentiles[i].flag2  ) continue ; 
1279
1280           if ( get_algorithm() != COMPATIBLE ) 
1281             tp = 
1282               (v->p.frq.tab.valid_cases - 1) *  percentiles[i].p;
1283           else
1284             tp = 
1285               (v->p.frq.tab.valid_cases + 1) *  percentiles[i].p - 1;
1286
1287           if ( percentiles[i].flag ) 
1288             {
1289               percentiles[i].x2 = f->v.f;
1290               percentiles[i].x1 = prev_value;
1291               percentiles[i].flag2 = 1;
1292               continue;
1293             }
1294
1295           if (rank >  tp ) 
1296           {
1297             if ( f->c > 1 && rank - (f->c - 1) > tp ) 
1298               {
1299                 percentiles[i].x2 = percentiles[i].x1 = f->v.f;
1300                 percentiles[i].flag2 = 1;
1301               }
1302             else
1303               {
1304                 percentiles[i].flag=1;
1305               }
1306
1307             continue;
1308           }
1309         }
1310       prev_value = f->v.f;
1311     }
1312
1313   for (i = 0; i < n_percentiles; i++) 
1314     {
1315       /* Catches the case when p == 100% */
1316       if ( ! percentiles[i].flag2 ) 
1317         percentiles[i].x1 = percentiles[i].x2 = f->v.f;
1318
1319       /*
1320       printf("percentile %d (p==%.2f); X1 = %g; X2 = %g\n",
1321              i,percentiles[i].p,percentiles[i].x1,percentiles[i].x2);
1322       */
1323     }
1324
1325   for (i = 0; i < n_percentiles; i++) 
1326     {
1327       struct freq_tab *ft = &v->p.frq.tab;
1328       double s;
1329
1330       double dummy;
1331       if ( get_algorithm() != COMPATIBLE ) 
1332         {
1333           s = modf((ft->valid_cases - 1) *  percentiles[i].p , &dummy);
1334         }
1335       else
1336         {
1337           s = modf((ft->valid_cases + 1) *  percentiles[i].p -1, &dummy);
1338         }
1339
1340       percentiles[i].value = percentiles[i].x1 + 
1341         ( percentiles[i].x2 - percentiles[i].x1) * s ; 
1342
1343       if ( percentiles[i].p == 0.50) 
1344         median_value = &percentiles[i].value; 
1345     }
1346
1347
1348   /* Calculate the mode. */
1349   most_often = -1;
1350   X_mode = SYSMIS;
1351   for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
1352     {
1353       if (most_often < f->c) 
1354         {
1355           most_often = f->c;
1356           X_mode = f->v.f;
1357         }
1358       else if (most_often == f->c) 
1359         {
1360           /* A duplicate mode is undefined.
1361              FIXME: keep track of *all* the modes. */
1362           X_mode = SYSMIS;
1363         }
1364     }
1365
1366   /* Calculate moments. */
1367   m = moments_create (MOMENT_KURTOSIS);
1368   for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
1369     moments_pass_one (m, f->v.f, f->c);
1370   for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
1371     moments_pass_two (m, f->v.f, f->c);
1372   moments_calculate (m, NULL, &d[frq_mean], &d[frq_variance],
1373                      &d[frq_skew], &d[frq_kurt]);
1374   moments_destroy (m);
1375                      
1376   /* Formulas below are taken from _SPSS Statistical Algorithms_. */
1377   d[frq_min] = v->p.frq.tab.valid[0].v.f;
1378   d[frq_max] = v->p.frq.tab.valid[v->p.frq.tab.n_valid - 1].v.f;
1379   d[frq_mode] = X_mode;
1380   d[frq_range] = d[frq_max] - d[frq_min];
1381   d[frq_median] = *median_value;
1382   d[frq_sum] = d[frq_mean] * W;
1383   d[frq_stddev] = sqrt (d[frq_variance]);
1384   d[frq_semean] = d[frq_stddev] / sqrt (W);
1385   d[frq_seskew] = calc_seskew (W);
1386   d[frq_sekurt] = calc_sekurt (W);
1387 }
1388
1389 /* Displays a table of all the statistics requested for variable V. */
1390 static void
1391 dump_statistics (struct variable * v, int show_varname)
1392 {
1393   double stat_value[frq_n_stats];
1394   struct tab_table *t;
1395   int i, r;
1396
1397   int n_explicit_percentiles = n_percentiles;
1398
1399   if ( implicit_50th && n_percentiles > 0 ) 
1400     --n_percentiles;
1401
1402   if (v->type == ALPHA)
1403     return;
1404   if (v->p.frq.tab.n_valid == 0)
1405     {
1406       msg (SW, _("No valid data for variable %s; statistics not displayed."),
1407            v->name);
1408       return;
1409     }
1410   calc_stats (v, stat_value);
1411
1412   t = tab_create (3, n_stats + n_explicit_percentiles + 2, 0);
1413   tab_dim (t, tab_natural_dimensions);
1414
1415   tab_box (t, TAL_1, TAL_1, -1, -1 , 0 , 0 , 2, tab_nr(t) - 1) ;
1416
1417
1418   tab_vline (t, TAL_1 , 2, 0, tab_nr(t) - 1);
1419   tab_vline (t, TAL_1 | TAL_SPACING , 1, 0, tab_nr(t) - 1 ) ;
1420   
1421   r=2; /* N missing and N valid are always dumped */
1422
1423   for (i = 0; i < frq_n_stats; i++)
1424     if (stats & BIT_INDEX (i))
1425       {
1426         tab_text (t, 0, r, TAB_LEFT | TAT_TITLE,
1427                       gettext (st_name[i].s10));
1428         tab_float (t, 2, r, TAB_NONE, stat_value[i], 11, 3);
1429         r++;
1430       }
1431
1432   tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("N"));
1433   tab_text (t, 1, 0, TAB_LEFT | TAT_TITLE, _("Valid"));
1434   tab_text (t, 1, 1, TAB_LEFT | TAT_TITLE, _("Missing"));
1435
1436   tab_float(t, 2, 0, TAB_NONE, v->p.frq.tab.valid_cases, 11, 0);
1437   tab_float(t, 2, 1, TAB_NONE, 
1438             v->p.frq.tab.total_cases - v->p.frq.tab.valid_cases, 11, 0);
1439
1440
1441   for (i = 0; i < n_explicit_percentiles; i++, r++) 
1442     {
1443       if ( i == 0 ) 
1444         { 
1445           tab_text (t, 0, r, TAB_LEFT | TAT_TITLE, _("Percentiles"));
1446         }
1447
1448       tab_float (t, 1, r, TAB_LEFT, percentiles[i].p * 100, 3, 0 );
1449       tab_float (t, 2, r, TAB_NONE, percentiles[i].value, 11, 3);
1450
1451     }
1452
1453   tab_columns (t, SOM_COL_DOWN, 1);
1454   if (show_varname)
1455     {
1456       if (v->label)
1457         tab_title (t, 1, "%s: %s", v->name, v->label);
1458       else
1459         tab_title (t, 0, v->name);
1460     }
1461   else
1462     tab_flags (t, SOMF_NO_TITLE);
1463
1464
1465   tab_submit (t);
1466 }
1467 /* 
1468    Local Variables:
1469    mode: c
1470    End:
1471 */