Memory leak patrol.
[pspp-builds.git] / src / language / stats / rank.q
1 /* PSPP - RANK. -*-c-*-
2
3 Copyright (C) 2005, 2006 Free Software Foundation, Inc.
4 Author: John Darrington <john@darrington.wattle.id.au>, 
5         Ben Pfaff <blp@gnu.org>.
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301, USA. */
21
22 #include <config.h>
23
24 #include "sort-criteria.h"
25
26 #include <data/dictionary.h>
27 #include <data/procedure.h>
28 #include <data/variable.h>
29 #include <data/case.h>
30 #include <data/casefile.h>
31 #include <data/fastfile.h>
32 #include <data/storage-stream.h>
33 #include <language/command.h>
34 #include <language/stats/sort-criteria.h>
35 #include <limits.h>
36 #include <libpspp/compiler.h>
37 #include <math/sort.h>
38 #include <output/table.h>
39 #include <output/manager.h>
40
41 #include <gsl/gsl_cdf.h>
42 #include <math.h>
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 /* (headers) */
48
49 /* (specification)
50    "RANK" (rank_):
51    *^variables=custom;
52    +rank=custom;
53    +normal=custom;
54    +percent=custom;
55    +ntiles=custom;
56    +rfraction=custom;
57    +proportion=custom;
58    +n=custom;
59    +savage=custom;
60    +print=print:!yes/no;
61    +fraction=fraction:!blom/tukey/vw/rankit;
62    +ties=ties:!mean/low/high/condense;
63    missing=miss:!exclude/include.
64 */
65 /* (declarations) */
66 /* (functions) */
67
68 typedef double (*rank_function_t) (double c, double cc, double cc_1, 
69                                  int i, double w);
70
71 static double rank_proportion (double c, double cc, double cc_1, 
72                                int i, double w);
73
74 static double rank_normal (double c, double cc, double cc_1, 
75                            int i, double w);
76
77 static double rank_percent (double c, double cc, double cc_1, 
78                             int i, double w);
79
80 static double rank_rfraction (double c, double cc, double cc_1, 
81                               int i, double w);
82
83 static double rank_rank (double c, double cc, double cc_1, 
84                          int i, double w);
85
86 static double rank_n (double c, double cc, double cc_1, 
87                       int i, double w);
88
89 static double rank_savage (double c, double cc, double cc_1, 
90                       int i, double w);
91
92 static double rank_ntiles (double c, double cc, double cc_1, 
93                       int i, double w);
94
95
96 enum RANK_FUNC
97   {
98     RANK,
99     NORMAL,
100     PERCENT,
101     RFRACTION,
102     PROPORTION,
103     N,
104     NTILES,
105     SAVAGE,
106     n_RANK_FUNCS
107   };
108
109 static const struct fmt_spec dest_format[n_RANK_FUNCS] = {
110   {FMT_F, 9, 3}, /* rank */
111   {FMT_F, 6, 4}, /* normal */
112   {FMT_F, 6, 2}, /* percent */
113   {FMT_F, 6, 4}, /* rfraction */
114   {FMT_F, 6, 4}, /* proportion */
115   {FMT_F, 6, 0}, /* n */
116   {FMT_F, 3, 0}, /* ntiles */
117   {FMT_F, 8, 4}  /* savage */
118 };
119
120 static const char *function_name[n_RANK_FUNCS] = {
121   "RANK",
122   "NORMAL",
123   "PERCENT",
124   "RFRACTION",
125   "PROPORTION",
126   "N",
127   "NTILES",
128   "SAVAGE"
129 };
130
131 static rank_function_t rank_func[n_RANK_FUNCS] = {
132   rank_rank,
133   rank_normal,
134   rank_percent,
135   rank_rfraction,
136   rank_proportion,
137   rank_n,
138   rank_ntiles,
139   rank_savage 
140   };
141
142
143 struct rank_spec
144 {
145   enum RANK_FUNC rfunc;
146   struct variable **destvars;
147 };
148
149
150 /* Function to use for testing for missing values */
151 static is_missing_func *value_is_missing;
152
153 static struct rank_spec *rank_specs;
154 static size_t n_rank_specs;
155
156 static struct sort_criteria *sc;
157
158 static struct variable **group_vars;
159 static size_t n_group_vars;
160
161 static struct variable **src_vars;
162 static size_t n_src_vars;
163
164
165 static int k_ntiles;
166
167 static struct cmd_rank cmd;
168
169 static struct casefile *rank_sorted_casefile (struct casefile *cf, 
170                                               const struct sort_criteria *, 
171                                               const struct rank_spec *rs, 
172                                               int n_rank_specs,
173                                               int idx,
174                                               const struct missing_values *miss
175                                               );
176 static const char *
177 fraction_name(void)
178 {
179   static char name[10];
180   switch ( cmd.fraction ) 
181     {
182     case RANK_BLOM:
183       strcpy (name, "BLOM");
184       break;
185     case RANK_RANKIT:
186       strcpy (name, "RANKIT");
187       break;
188     case RANK_TUKEY:
189       strcpy (name, "TUKEY");
190       break;
191     case RANK_VW:
192       strcpy (name, "VW");
193       break;
194     default:
195       NOT_REACHED ();
196     }
197   return name;
198 }
199
200 /* Create a label on DEST_VAR, describing its derivation from SRC_VAR and F */
201 static void
202 create_var_label (struct variable *dest_var, 
203                   const struct variable *src_var, enum RANK_FUNC f)
204 {
205   struct string label;
206   ds_init_empty (&label);
207
208   if ( n_group_vars > 0 ) 
209     {
210       struct string group_var_str;
211       int g;
212
213       ds_init_empty (&group_var_str);
214
215       for (g = 0 ; g < n_group_vars ; ++g ) 
216         {
217           if ( g > 0 ) ds_put_cstr (&group_var_str, " ");
218           ds_put_cstr (&group_var_str, group_vars[g]->name);
219         }
220
221       ds_put_format (&label, _("%s of %s by %s"), function_name[f], 
222                      src_var->name, ds_cstr (&group_var_str));
223       ds_destroy (&group_var_str);
224     }
225   else
226     ds_put_format (&label,_("%s of %s"), function_name[f], src_var->name);  
227
228   dest_var->label = strdup (ds_cstr (&label) );
229
230   ds_destroy (&label);
231 }
232
233
234 static bool 
235 rank_cmd (const struct sort_criteria *sc, 
236       const struct rank_spec *rank_specs, int n_rank_specs)
237 {
238   struct sort_criteria criteria;
239   bool result = true;
240   int i;
241   const int n_splits = dict_get_split_cnt (default_dict);
242
243   criteria.crit_cnt = n_splits + n_group_vars + 1;
244   criteria.crits = xnmalloc (criteria.crit_cnt, sizeof *criteria.crits);
245   for (i = 0; i < n_splits ; i++) 
246     {
247       struct variable *v = dict_get_split_vars (default_dict)[i];
248       criteria.crits[i].fv = v->fv;
249       criteria.crits[i].width = v->width;
250       criteria.crits[i].dir = SRT_ASCEND;
251     }
252   for (i = 0; i < n_group_vars; i++) 
253     {
254       criteria.crits[i + n_splits].fv = group_vars[i]->fv;
255       criteria.crits[i + n_splits].width = group_vars[i]->width;
256       criteria.crits[i + n_splits].dir = SRT_ASCEND;
257     }
258   for (i = 0 ; i < sc->crit_cnt ; ++i )
259     {
260       struct casefile *out ;
261       struct casefile *cf ; 
262       struct casereader *reader ;
263       struct casefile *sorted_cf ;
264
265       /* Obtain active file in CF. */
266       if (!procedure (NULL, NULL))
267         goto error;
268
269       cf = proc_capture_output ();
270
271       /* Sort CF into SORTED_CF. */
272       reader = casefile_get_destructive_reader (cf) ;
273       criteria.crits[criteria.crit_cnt - 1] = sc->crits[i];
274       assert ( sc->crits[i].fv == src_vars[i]->fv );
275       sorted_cf = sort_execute (reader, &criteria);
276       casefile_destroy (cf);
277
278       out = rank_sorted_casefile (sorted_cf, &criteria,
279                                   rank_specs, n_rank_specs, 
280                                   i, &src_vars[i]->miss)  ;
281       if ( NULL == out ) 
282         {
283           result = false ;
284           continue ;
285         }
286       
287       proc_set_source (storage_source_create (out));
288     }
289
290   free (criteria.crits);
291   return result ; 
292
293 error:
294   free (criteria.crits);
295   return false ;
296 }
297
298 /* Hardly a rank function !! */
299 static double 
300 rank_n (double c UNUSED, double cc UNUSED, double cc_1 UNUSED, 
301           int i UNUSED, double w)
302 {
303   return w;
304 }
305
306
307 static double 
308 rank_rank (double c, double cc, double cc_1, 
309           int i, double w UNUSED)
310 {
311   double rank;
312   if ( c >= 1.0 ) 
313     {
314       switch (cmd.ties)
315         {
316         case RANK_LOW:
317           rank = cc_1 + 1;
318           break;
319         case RANK_HIGH:
320           rank = cc;
321           break;
322         case RANK_MEAN:
323           rank = cc_1 + (c + 1.0)/ 2.0;
324           break;
325         case RANK_CONDENSE:
326           rank = i;
327           break;
328         default:
329           NOT_REACHED ();
330         }
331     }
332   else
333     {
334       switch (cmd.ties)
335         {
336         case RANK_LOW:
337           rank = cc_1;
338           break;
339         case RANK_HIGH:
340           rank = cc;
341           break;
342         case RANK_MEAN:
343           rank = cc_1 + c / 2.0 ;
344           break;
345         case RANK_CONDENSE:
346           rank = i;
347           break;
348         default:
349           NOT_REACHED ();
350         }
351     }
352
353   return rank;
354 }
355
356
357 static double 
358 rank_rfraction (double c, double cc, double cc_1, 
359                 int i, double w)
360 {
361   return rank_rank (c, cc, cc_1, i, w) / w ;
362 }
363
364
365 static double 
366 rank_percent (double c, double cc, double cc_1, 
367                 int i, double w)
368 {
369   return rank_rank (c, cc, cc_1, i, w) * 100.0 / w ;
370 }
371
372
373 static double 
374 rank_proportion (double c, double cc, double cc_1, 
375                  int i, double w)
376 {
377   const double r =  rank_rank (c, cc, cc_1, i, w) ;
378
379   double f;
380   
381   switch ( cmd.fraction ) 
382     {
383     case RANK_BLOM:
384       f =  (r - 3.0/8.0) / (w + 0.25);
385       break;
386     case RANK_RANKIT:
387       f = (r - 0.5) / w ;
388       break;
389     case RANK_TUKEY:
390       f = (r - 1.0/3.0) / (w + 1.0/3.0);
391       break;
392     case RANK_VW:
393       f = r / ( w + 1.0);
394       break;
395     default:
396       NOT_REACHED ();
397     }
398
399
400   return (f > 0) ? f : SYSMIS;
401 }
402
403 static double 
404 rank_normal (double c, double cc, double cc_1, 
405              int i, double w)
406 {
407   double f = rank_proportion (c, cc, cc_1, i, w);
408   
409   return gsl_cdf_ugaussian_Pinv (f);
410 }
411
412 static double 
413 rank_ntiles (double c, double cc, double cc_1, 
414                 int i, double w)
415 {
416   double r = rank_rank (c, cc, cc_1, i, w);  
417
418
419   return ( floor (( r * k_ntiles) / ( w + 1) ) + 1);
420 }
421
422 /* Expected value of the order statistics from an exponential distribution */
423 static double
424 ee (int j, double w_star)
425 {
426   int k;
427   double sum = 0.0;
428   
429   for (k = 1 ; k <= j; k++) 
430     sum += 1.0 / ( w_star + 1 - k );
431
432   return sum;
433 }
434
435
436 static double 
437 rank_savage (double c, double cc, double cc_1, 
438                 int i UNUSED, double w)
439 {
440   double int_part;
441   const int i_1 = floor (cc_1);
442   const int i_2 = floor (cc);
443
444   const double w_star = (modf (w, &int_part) == 0 ) ? w : floor (w) + 1;
445
446   const double g_1 = cc_1 - i_1;
447   const double g_2 = cc - i_2;
448
449   /* The second factor is infinite, when the first is zero.
450      Therefore, evaluate the second, only when the first is non-zero */
451   const double expr1 =  (1 - g_1) ? (1 - g_1) * ee(i_1+1, w_star) : ( 1 - g_1);
452   const double expr2 =  g_2 ? g_2 * ee (i_2+1, w_star) : g_2 ;
453   
454   if ( i_1 == i_2 ) 
455     return ee (i_1 + 1, w_star) - 1;
456   
457   if ( i_1 + 1 == i_2 )
458     return ( ( expr1 + expr2 )/c ) - 1;
459
460   if ( i_1 + 2 <= i_2 ) 
461     {
462       int j;
463       double sigma = 0.0;
464       for (j = i_1 + 2 ; j <= i_2; ++j )
465         sigma += ee (j, w_star);
466       return ( (expr1 + expr2 + sigma) / c) -1;
467     }
468
469   NOT_REACHED();
470 }
471
472
473 /* Rank the casefile belonging to CR, starting from the current
474    postition of CR continuing up to and including the ENDth case.
475
476    RS points to an array containing  the rank specifications to
477    use. N_RANK_SPECS is the number of elements of RS.
478
479
480    DEST_VAR_INDEX is the index into the rank_spec destvar element 
481    to be used for this ranking.
482
483    Prerequisites: 1. The casefile must be sorted according to CRITERION.
484                   2. W is the sum of the non-missing caseweights for this 
485                   range of the casefile.
486 */
487 static void
488 rank_cases (struct casereader *cr,
489             unsigned long end,
490             const struct sort_criterion *criterion,
491             const struct missing_values *mv,
492             double w,
493             const struct rank_spec *rs, 
494             int n_rank_specs, 
495             int dest_var_index,
496             struct casefile *dest)
497 {
498   bool warn = true;
499   double cc = 0.0;
500   double cc_1;
501   int iter = 1;
502
503   const int fv = criterion->fv;
504   const int width = criterion->width;
505
506   while (casereader_cnum (cr) < end)
507     {
508       struct casereader *lookahead;
509       const union value *this_value;
510       struct ccase this_case, lookahead_case;
511       double c;
512       int i;
513       size_t n = 0;
514
515       if (!casereader_read_xfer (cr, &this_case))
516         break;
517       
518       this_value = case_data (&this_case, fv);
519       c = dict_get_case_weight (default_dict, &this_case, &warn);
520               
521       lookahead = casereader_clone (cr);
522       n = 0;
523       while (casereader_cnum (lookahead) < end
524              && casereader_read_xfer (lookahead, &lookahead_case))
525         {
526           const union value *lookahead_value = case_data (&lookahead_case, fv);
527           int diff = compare_values (this_value, lookahead_value, width);
528
529           if (diff != 0) 
530             {
531               /* Make sure the casefile was sorted */
532               assert ( diff == ((criterion->dir == SRT_ASCEND) ? -1 :1));
533
534               case_destroy (&lookahead_case);
535               break; 
536             }
537
538           c += dict_get_case_weight (default_dict, &lookahead_case, &warn);
539           case_destroy (&lookahead_case);
540           n++;
541         }
542       casereader_destroy (lookahead);
543
544       cc_1 = cc;
545       if ( !value_is_missing (mv, this_value) )
546         cc += c;
547
548       do
549         {
550           for (i = 0; i < n_rank_specs; ++i) 
551             {
552               const int dest_idx = rs[i].destvars[dest_var_index]->fv;
553
554               if  ( value_is_missing (mv, this_value) )
555                 case_data_rw (&this_case, dest_idx)->f = SYSMIS;
556               else
557                 case_data_rw (&this_case, dest_idx)->f = 
558                   rank_func[rs[i].rfunc](c, cc, cc_1, iter, w);
559             }
560           casefile_append_xfer (dest, &this_case); 
561         }
562       while (n-- > 0 && casereader_read_xfer (cr, &this_case));
563
564       if ( !value_is_missing (mv, this_value) )
565         iter++;
566     }
567
568   /* If this isn't true, then all the results will be wrong */
569   assert ( w == cc );
570 }
571
572 static bool
573 same_group (const struct ccase *a, const struct ccase *b,
574             const struct sort_criteria *crit)
575 {
576   size_t i;
577
578   for (i = 0; i < crit->crit_cnt - 1; i++)
579     {
580       struct sort_criterion *c = &crit->crits[i];
581       if (compare_values (case_data (a, c->fv), case_data (b, c->fv),
582                           c->width) != 0)
583         return false;
584     }
585
586   return true;
587 }
588
589 static struct casefile *
590 rank_sorted_casefile (struct casefile *cf, 
591                       const struct sort_criteria *crit, 
592                       const struct rank_spec *rs, 
593                       int n_rank_specs, 
594                       int dest_idx, 
595                       const struct missing_values *mv)
596 {
597   struct casefile *dest = fastfile_create (casefile_get_value_cnt (cf));
598   struct casereader *lookahead = casefile_get_reader (cf);
599   struct casereader *pos = casereader_clone (lookahead);
600   struct ccase group_case;
601   bool warn = true;
602
603   struct sort_criterion *ultimate_crit = &crit->crits[crit->crit_cnt - 1];
604
605   if (casereader_read (lookahead, &group_case)) 
606     {
607       struct ccase this_case;
608       const union value *this_value ;
609       double w = 0.0;
610       this_value = case_data( &group_case, ultimate_crit->fv);
611
612       if ( !value_is_missing(mv, this_value) )
613         w = dict_get_case_weight (default_dict, &group_case, &warn);
614
615       while (casereader_read (lookahead, &this_case)) 
616         {
617           const union value *this_value = 
618             case_data(&this_case, ultimate_crit->fv);
619           double c = dict_get_case_weight (default_dict, &this_case, &warn);
620           if (!same_group (&group_case, &this_case, crit)) 
621             {
622               rank_cases (pos, casereader_cnum (lookahead) - 1,
623                           ultimate_crit, 
624                           mv, w, 
625                           rs, n_rank_specs, 
626                           dest_idx, dest);
627
628               w = 0.0;
629               case_move (&group_case, &this_case);
630             }
631           if ( !value_is_missing (mv, this_value) )
632             w += c;
633         }
634       rank_cases (pos, ULONG_MAX, ultimate_crit, mv, w,
635                   rs, n_rank_specs, dest_idx, dest);
636     }
637
638   if (casefile_error (dest))
639     {
640       casefile_destroy (dest);
641       dest = NULL;
642     }
643   
644   casefile_destroy (cf);
645   return dest;
646 }
647
648
649 /* Transformation function to enumerate all the cases */
650 static int 
651 create_resort_key (void *key_var_, struct ccase *cc, casenum_t case_num)
652 {
653   struct variable *key_var = key_var_;
654
655   case_data_rw(cc, key_var->fv)->f = case_num;
656   
657   return TRNS_CONTINUE;
658 }
659
660
661 /* Create and return a new variable in which to store the ranks of SRC_VAR
662    accoring to the rank function F.
663    VNAME is the name of the variable to be created.
664    If VNAME is NULL, then a name will be automatically chosen.
665  */
666 static struct variable *
667 create_rank_variable (enum RANK_FUNC f, 
668                       const struct variable *src_var, 
669                       const char *vname)
670 {
671   int i;
672   struct variable *var = NULL; 
673   char name[SHORT_NAME_LEN + 1];
674
675   if ( vname ) 
676     var = dict_create_var(default_dict, vname, 0);
677
678   if ( NULL == var )
679     {
680       snprintf(name, SHORT_NAME_LEN + 1, "%c%s", 
681                function_name[f][0], src_var->name);
682   
683       var = dict_create_var(default_dict, name, 0);
684     }
685
686   i = 1;
687   while( NULL == var )
688     {
689       char func_abb[4];
690       snprintf(func_abb, 4, "%s", function_name[f]);
691       snprintf(name, SHORT_NAME_LEN + 1, "%s%03d", func_abb, 
692                i);
693
694       var = dict_create_var(default_dict, name, 0);
695       if (i++ >= 999) 
696         break;
697     }
698
699   i = 1;
700   while ( NULL == var )
701     {
702       char func_abb[3];
703       snprintf(func_abb, 3, "%s", function_name[f]);
704
705       snprintf(name, SHORT_NAME_LEN + 1, 
706                "RNK%s%02d", func_abb, i);
707
708       var = dict_create_var(default_dict, name, 0);
709       if ( i++ >= 99 ) 
710         break;
711     }
712   
713   if ( NULL == var ) 
714     {
715       msg(ME, _("Cannot create new rank variable.  All candidates in use."));
716       return NULL;
717     }
718
719   var->write = var->print = dest_format[f];
720
721   return var;
722 }
723
724 int cmd_rank(void);
725
726 static void
727 rank_cleanup(void)
728 {
729   int i;
730
731   free (group_vars);
732   group_vars = NULL;
733   n_group_vars = 0;
734   
735   for (i = 0 ; i <  n_rank_specs ; ++i )
736     {
737       free (rank_specs[i].destvars);
738     }
739       
740   free (rank_specs);
741   rank_specs = NULL;
742   n_rank_specs = 0;
743
744   sort_destroy_criteria (sc);
745   sc = NULL;
746
747   free (src_vars);
748   src_vars = NULL;
749   n_src_vars = 0;
750 }
751
752 int
753 cmd_rank(void)
754 {
755   bool result;
756   struct variable *order;
757   size_t i;
758   n_rank_specs = 0;
759
760   if ( !parse_rank(&cmd, NULL) )
761     {
762       rank_cleanup ();
763     return CMD_FAILURE;
764     }
765
766   /* If /MISSING=INCLUDE is set, then user missing values are ignored */
767   if (cmd.miss == RANK_INCLUDE ) 
768     value_is_missing = mv_is_value_system_missing;
769   else
770     value_is_missing = mv_is_value_missing;
771
772
773   /* Default to /RANK if no function subcommands are given */
774   if ( !( cmd.sbc_normal  || cmd.sbc_ntiles || cmd.sbc_proportion || 
775           cmd.sbc_rfraction || cmd.sbc_savage || cmd.sbc_n || 
776           cmd.sbc_percent || cmd.sbc_rank ) )
777     {
778       assert ( n_rank_specs == 0 );
779       
780       rank_specs = xmalloc (sizeof (*rank_specs));
781       rank_specs[0].rfunc = RANK;
782       rank_specs[0].destvars = 
783         xcalloc (sc->crit_cnt, sizeof (struct variable *));
784
785       n_rank_specs = 1;
786     }
787
788   assert ( sc->crit_cnt == n_src_vars);
789
790   /* Create variables for all rank destinations which haven't
791      already been created with INTO.
792      Add labels to all the destination variables.
793   */
794   for (i = 0 ; i <  n_rank_specs ; ++i )
795     {
796       int v;
797       for ( v = 0 ; v < n_src_vars ;  v ++ ) 
798         {
799           if ( rank_specs[i].destvars[v] == NULL ) 
800             {
801               rank_specs[i].destvars[v] = 
802                 create_rank_variable (rank_specs[i].rfunc, src_vars[v], NULL);
803             }
804       
805           create_var_label ( rank_specs[i].destvars[v],
806                              src_vars[v],
807                              rank_specs[i].rfunc);
808         }
809     }
810
811   if ( cmd.print == RANK_YES ) 
812     {
813       int v;
814
815       tab_output_text (0, _("Variables Created By RANK"));
816       tab_output_text (0, "\n");
817   
818       for (i = 0 ; i <  n_rank_specs ; ++i )
819         {
820           for ( v = 0 ; v < n_src_vars ;  v ++ ) 
821             {
822               if ( n_group_vars > 0 )
823                 {
824                   struct string varlist;
825                   int g;
826
827                   ds_init_empty (&varlist);
828                   for ( g = 0 ; g < n_group_vars ; ++g ) 
829                     {
830                       ds_put_cstr (&varlist, group_vars[g]->name);
831
832                       if ( g < n_group_vars - 1)
833                         ds_put_cstr (&varlist, " ");
834                     }
835
836                   if ( rank_specs[i].rfunc == NORMAL || 
837                        rank_specs[i].rfunc == PROPORTION ) 
838                     tab_output_text (TAT_PRINTF,
839                                      _("%s into %s(%s of %s using %s BY %s)"), 
840                                      src_vars[v]->name,
841                                      rank_specs[i].destvars[v]->name,
842                                      function_name[rank_specs[i].rfunc],
843                                      src_vars[v]->name,
844                                      fraction_name(),
845                                      ds_cstr (&varlist)
846                                      );
847                     
848                   else
849                     tab_output_text (TAT_PRINTF,
850                                      _("%s into %s(%s of %s BY %s)"), 
851                                      src_vars[v]->name,
852                                      rank_specs[i].destvars[v]->name,
853                                      function_name[rank_specs[i].rfunc],
854                                      src_vars[v]->name,
855                                      ds_cstr (&varlist)
856                                      );
857                   ds_destroy (&varlist);
858                 }
859               else
860                 {
861                   if ( rank_specs[i].rfunc == NORMAL || 
862                        rank_specs[i].rfunc == PROPORTION ) 
863                     tab_output_text (TAT_PRINTF,
864                                      _("%s into %s(%s of %s using %s)"), 
865                                      src_vars[v]->name,
866                                      rank_specs[i].destvars[v]->name,
867                                      function_name[rank_specs[i].rfunc],
868                                      src_vars[v]->name,
869                                      fraction_name()
870                                      );
871                     
872                   else
873                     tab_output_text (TAT_PRINTF,
874                                      _("%s into %s(%s of %s)"), 
875                                      src_vars[v]->name,
876                                      rank_specs[i].destvars[v]->name,
877                                      function_name[rank_specs[i].rfunc],
878                                      src_vars[v]->name
879                                      );
880                 }
881             }
882         }
883     }
884
885   if ( cmd.sbc_fraction && 
886        ( ! cmd.sbc_normal && ! cmd.sbc_proportion) )
887     msg(MW, _("FRACTION has been specified, but NORMAL and PROPORTION rank functions have not been requested.  The FRACTION subcommand will be ignored.") );
888
889   /* Add a variable which we can sort by to get back the original
890      order */
891   order = dict_create_var_assert (default_dict, "$ORDER_", 0);
892
893   add_transformation (create_resort_key, 0, order);
894
895   /* Do the ranking */
896   result = rank_cmd (sc, rank_specs, n_rank_specs);
897
898   /* Put the active file back in its original order */
899   {
900     struct sort_criteria criteria;
901     struct sort_criterion restore_criterion ;
902     restore_criterion.fv = order->fv;
903     restore_criterion.width = 0;
904     restore_criterion.dir = SRT_ASCEND;
905
906     criteria.crits = &restore_criterion;
907     criteria.crit_cnt = 1;
908     
909     sort_active_file_in_place (&criteria);
910 }
911
912   /* ... and we don't need our sort key anymore. So delete it */
913   dict_delete_var (default_dict, order);
914
915   rank_cleanup();
916
917   return (result ? CMD_SUCCESS : CMD_CASCADING_FAILURE);
918 }
919
920
921 /* Parser for the variables sub command  
922    Returns 1 on success */
923 static int
924 rank_custom_variables(struct cmd_rank *cmd UNUSED, void *aux UNUSED)
925 {
926   static const int terminators[2] = {T_BY, 0};
927
928   lex_match('=');
929
930   if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
931       && token != T_ALL)
932       return 2;
933
934   sc = sort_parse_criteria (default_dict, 
935                             &src_vars, &n_src_vars, 0, terminators);
936
937   if ( lex_match(T_BY)  )
938     {
939       if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL))
940         {
941           return 2;
942         }
943
944       if (!parse_variables (default_dict, &group_vars, &n_group_vars,
945                             PV_NO_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH) )
946         {
947           free (group_vars);
948           return 0;
949         }
950     }
951
952   return 1;
953 }
954
955
956 /* Parse the [/rank INTO var1 var2 ... varN ] clause */
957 static int
958 parse_rank_function(struct cmd_rank *cmd UNUSED, enum RANK_FUNC f)
959 {
960   int var_count = 0;
961   
962   n_rank_specs++;
963   rank_specs = xnrealloc(rank_specs, n_rank_specs, sizeof *rank_specs);
964   rank_specs[n_rank_specs - 1].rfunc = f;
965   rank_specs[n_rank_specs - 1].destvars = NULL;
966
967   rank_specs[n_rank_specs - 1].destvars = 
968             xcalloc (sc->crit_cnt, sizeof (struct variable *));
969           
970   if (lex_match_id("INTO"))
971     {
972       struct variable *destvar;
973
974       while( token == T_ID ) 
975         {
976
977           if ( dict_lookup_var (default_dict, tokid) != NULL )
978             {
979               msg(SE, _("Variable %s already exists."), tokid);
980               return 0;
981             }
982           if ( var_count >= sc->crit_cnt ) 
983             {
984               msg(SE, _("Too many variables in INTO clause."));
985               return 0;
986             }
987
988           destvar = create_rank_variable (f, src_vars[var_count], tokid);
989           rank_specs[n_rank_specs - 1].destvars[var_count] = destvar ;
990
991           lex_get();
992           ++var_count;
993         }
994     }
995
996   return 1;
997 }
998
999
1000 static int
1001 rank_custom_rank(struct cmd_rank *cmd, void *aux UNUSED )
1002 {
1003   return parse_rank_function(cmd, RANK);
1004 }
1005
1006 static int
1007 rank_custom_normal(struct cmd_rank *cmd, void *aux UNUSED )
1008 {
1009   return parse_rank_function(cmd, NORMAL);
1010 }
1011
1012 static int
1013 rank_custom_percent(struct cmd_rank *cmd, void *aux UNUSED )
1014 {
1015   return parse_rank_function (cmd, PERCENT);
1016 }
1017
1018 static int
1019 rank_custom_rfraction(struct cmd_rank *cmd, void *aux UNUSED )
1020 {
1021   return parse_rank_function(cmd, RFRACTION);
1022 }
1023
1024 static int
1025 rank_custom_proportion(struct cmd_rank *cmd, void *aux UNUSED )
1026 {
1027   return parse_rank_function(cmd, PROPORTION);
1028 }
1029
1030 static int
1031 rank_custom_n(struct cmd_rank *cmd, void *aux UNUSED )
1032 {
1033   return parse_rank_function(cmd, N);
1034 }
1035
1036 static int
1037 rank_custom_savage(struct cmd_rank *cmd, void *aux UNUSED )
1038 {
1039   return parse_rank_function(cmd, SAVAGE);
1040 }
1041
1042
1043 static int
1044 rank_custom_ntiles(struct cmd_rank *cmd, void *aux UNUSED )
1045 {
1046   if ( lex_force_match('(') ) 
1047     {
1048       if ( lex_force_int() ) 
1049         {
1050           k_ntiles = lex_integer ();
1051           lex_get();
1052           lex_force_match(')');
1053         }
1054       else
1055         return 0;
1056     }
1057   else
1058     return 0;
1059
1060   return parse_rank_function(cmd, NTILES);
1061 }