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