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