Move all command implementations into a single 'commands' directory.
[pspp] / src / language / stats / roc.c
diff --git a/src/language/stats/roc.c b/src/language/stats/roc.c
deleted file mode 100644 (file)
index adfc5d0..0000000
+++ /dev/null
@@ -1,1036 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <config.h>
-
-#include "language/stats/roc.h"
-
-#include <gsl/gsl_cdf.h>
-
-#include "data/casegrouper.h"
-#include "data/casereader.h"
-#include "data/casewriter.h"
-#include "data/dataset.h"
-#include "data/dictionary.h"
-#include "data/format.h"
-#include "data/subcase.h"
-#include "language/command.h"
-#include "language/lexer/lexer.h"
-#include "language/lexer/value-parser.h"
-#include "language/lexer/variable-parser.h"
-#include "libpspp/misc.h"
-#include "math/sort.h"
-#include "output/charts/roc-chart.h"
-#include "output/pivot-table.h"
-
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-#define N_(msgid) msgid
-
-struct cmd_roc
-{
-  size_t n_vars;
-  const struct variable **vars;
-  const struct dictionary *dict;
-
-  const struct variable *state_var;
-  union value state_value;
-  size_t state_var_width;
-
-  /* Plot the roc curve */
-  bool curve;
-  /* Plot the reference line */
-  bool reference;
-
-  double ci;
-
-  bool print_coords;
-  bool print_se;
-  bool bi_neg_exp; /* True iff the bi-negative exponential critieria
-                     should be used */
-  enum mv_class exclude;
-
-  bool invert; /* True iff a smaller test result variable indicates
-                  a positive result */
-
-  double pos;
-  double neg;
-  double pos_weighted;
-  double neg_weighted;
-};
-
-static int run_roc (struct dataset *, struct cmd_roc *);
-static void do_roc (struct cmd_roc *, struct casereader *, struct dictionary *);
-
-
-int
-cmd_roc (struct lexer *lexer, struct dataset *ds)
-{
-  const struct dictionary *dict = dataset_dict (ds);
-
-  struct cmd_roc roc = {
-    .exclude = MV_ANY,
-    .curve = true,
-    .ci = 95,
-    .dict = dict,
-    .state_var_width = -1,
-  };
-
-  lex_match (lexer, T_SLASH);
-  if (!parse_variables_const (lexer, dict, &roc.vars, &roc.n_vars,
-                             PV_APPEND | PV_NO_DUPLICATE | PV_NUMERIC))
-    goto error;
-
-  if (!lex_force_match (lexer, T_BY))
-    goto error;
-
-  roc.state_var = parse_variable (lexer, dict);
-  if (!roc.state_var)
-    goto error;
-
-  if (!lex_force_match (lexer, T_LPAREN))
-    goto error;
-
-  roc.state_var_width = var_get_width (roc.state_var);
-  value_init (&roc.state_value, roc.state_var_width);
-  if (!parse_value (lexer, &roc.state_value, roc.state_var)
-      || !lex_force_match (lexer, T_RPAREN))
-    goto error;
-
-  while (lex_token (lexer) != T_ENDCMD)
-    {
-      lex_match (lexer, T_SLASH);
-      if (lex_match_id (lexer, "MISSING"))
-        {
-          lex_match (lexer, T_EQUALS);
-          while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
-            {
-             if (lex_match_id (lexer, "INCLUDE"))
-                roc.exclude = MV_SYSTEM;
-             else if (lex_match_id (lexer, "EXCLUDE"))
-                roc.exclude = MV_ANY;
-             else
-               {
-                  lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
-                 goto error;
-               }
-           }
-       }
-      else if (lex_match_id (lexer, "PLOT"))
-       {
-         lex_match (lexer, T_EQUALS);
-         if (lex_match_id (lexer, "CURVE"))
-           {
-             roc.curve = true;
-             if (lex_match (lexer, T_LPAREN))
-               {
-                 roc.reference = true;
-                 if (!lex_force_match_id (lexer, "REFERENCE")
-                      || !lex_force_match (lexer, T_RPAREN))
-                   goto error;
-               }
-           }
-         else if (lex_match_id (lexer, "NONE"))
-            roc.curve = false;
-         else
-           {
-             lex_error_expecting (lexer, "CURVE", "NONE");
-             goto error;
-           }
-       }
-      else if (lex_match_id (lexer, "PRINT"))
-       {
-         lex_match (lexer, T_EQUALS);
-          while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
-           {
-             if (lex_match_id (lexer, "SE"))
-                roc.print_se = true;
-             else if (lex_match_id (lexer, "COORDINATES"))
-                roc.print_coords = true;
-             else
-               {
-                 lex_error_expecting (lexer, "SE", "COORDINATES");
-                 goto error;
-               }
-           }
-       }
-      else if (lex_match_id (lexer, "CRITERIA"))
-       {
-         lex_match (lexer, T_EQUALS);
-          while (lex_token (lexer) != T_ENDCMD && lex_token (lexer) != T_SLASH)
-           {
-             if (lex_match_id (lexer, "CUTOFF"))
-               {
-                 if (!lex_force_match (lexer, T_LPAREN))
-                   goto error;
-                 if (lex_match_id (lexer, "INCLUDE"))
-                    roc.exclude = MV_SYSTEM;
-                 else if (lex_match_id (lexer, "EXCLUDE"))
-                    roc.exclude = MV_USER | MV_SYSTEM;
-                 else
-                   {
-                     lex_error_expecting (lexer, "INCLUDE", "EXCLUDE");
-                     goto error;
-                   }
-                 if (!lex_force_match (lexer, T_RPAREN))
-                   goto error;
-               }
-             else if (lex_match_id (lexer, "TESTPOS"))
-               {
-                 if (!lex_force_match (lexer, T_LPAREN))
-                   goto error;
-                 if (lex_match_id (lexer, "LARGE"))
-                    roc.invert = false;
-                 else if (lex_match_id (lexer, "SMALL"))
-                    roc.invert = true;
-                 else
-                   {
-                     lex_error_expecting (lexer, "LARGE", "SMALL");
-                     goto error;
-                   }
-                 if (!lex_force_match (lexer, T_RPAREN))
-                   goto error;
-               }
-             else if (lex_match_id (lexer, "CI"))
-               {
-                 if (!lex_force_match (lexer, T_LPAREN))
-                   goto error;
-                 if (!lex_force_num (lexer))
-                   goto error;
-                 roc.ci = lex_number (lexer);
-                 lex_get (lexer);
-                 if (!lex_force_match (lexer, T_RPAREN))
-                   goto error;
-               }
-             else if (lex_match_id (lexer, "DISTRIBUTION"))
-               {
-                 if (!lex_force_match (lexer, T_LPAREN))
-                   goto error;
-                 if (lex_match_id (lexer, "FREE"))
-                    roc.bi_neg_exp = false;
-                 else if (lex_match_id (lexer, "NEGEXPO"))
-                    roc.bi_neg_exp = true;
-                 else
-                   {
-                     lex_error_expecting (lexer, "FREE", "NEGEXPO");
-                     goto error;
-                   }
-                 if (!lex_force_match (lexer, T_RPAREN))
-                   goto error;
-               }
-             else
-               {
-                 lex_error_expecting (lexer, "CUTOFF", "TESTPOS", "CI",
-                                       "DISTRIBUTION");
-                 goto error;
-               }
-           }
-       }
-      else
-       {
-         lex_error_expecting (lexer, "MISSING", "PLOT", "PRINT", "CRITERIA");
-         goto error;
-       }
-    }
-
-  if (!run_roc (ds, &roc))
-    goto error;
-
-  if (roc.state_var)
-    value_destroy (&roc.state_value, roc.state_var_width);
-  free (roc.vars);
-  return CMD_SUCCESS;
-
- error:
-  if (roc.state_var)
-    value_destroy (&roc.state_value, roc.state_var_width);
-  free (roc.vars);
-  return CMD_FAILURE;
-}
-
-static int
-run_roc (struct dataset *ds, struct cmd_roc *roc)
-{
-  struct dictionary *dict = dataset_dict (ds);
-  struct casereader *group;
-
-  struct casegrouper *grouper = casegrouper_create_splits (proc_open (ds), dict);
-  while (casegrouper_get_next_group (grouper, &group))
-    do_roc (roc, group, dataset_dict (ds));
-
-  bool ok = casegrouper_destroy (grouper);
-  ok = proc_commit (ds) && ok;
-  return ok;
-}
-
-#if 0
-static void
-dump_casereader (struct casereader *reader)
-{
-  struct ccase *c;
-  struct casereader *r = casereader_clone (reader);
-
-  for (; (c = casereader_read (r)); case_unref (c))
-    {
-      for (size_t i = 0; i < case_get_n_values (c); ++i)
-        printf ("%g ", case_num_idx (c, i));
-      printf ("\n");
-    }
-
-  casereader_destroy (r);
-}
-#endif
-
-
-/*
-   Return true iff the state variable indicates that C has positive actual state.
-
-   As a side effect, this function also accumulates the roc->{pos,neg} and
-   roc->{pos,neg}_weighted counts.
- */
-static bool
-match_positives (const struct ccase *c, void *aux)
-{
-  struct cmd_roc *roc = aux;
-  const struct variable *wv = dict_get_weight (roc->dict);
-  const double weight = wv ? case_num (c, wv) : 1.0;
-
-  const bool positive =
-  (0 == value_compare_3way (case_data (c, roc->state_var), &roc->state_value,
-    var_get_width (roc->state_var)));
-
-  if (positive)
-    {
-      roc->pos++;
-      roc->pos_weighted += weight;
-    }
-  else
-    {
-      roc->neg++;
-      roc->neg_weighted += weight;
-    }
-
-  return positive;
-}
-
-
-#define VALUE  0
-#define N_EQ   1
-#define N_PRED 2
-
-/* Some intermediate state for calculating the cutpoints and the
-   standard error values */
-struct roc_state
-{
-  double auc;  /* Area under the curve */
-
-  double n1;  /* total weight of positives */
-  double n2;  /* total weight of negatives */
-
-  /* intermediates for standard error */
-  double q1hat;
-  double q2hat;
-
-  /* intermediates for cutpoints */
-  struct casewriter *cutpoint_wtr;
-  struct casereader *cutpoint_rdr;
-  double prev_result;
-  double min;
-  double max;
-};
-
-/*
-   Return a new casereader based upon CUTPOINT_RDR.
-   The number of "positive" cases are placed into
-   the position TRUE_INDEX, and the number of "negative" cases
-   into FALSE_INDEX.
-   POS_COND and RESULT determine the semantics of what is
-   "positive".
-   WEIGHT is the value of a single count.
- */
-static struct casereader *
-accumulate_counts (struct casereader *input,
-                  double result, double weight,
-                  bool (*pos_cond) (double, double),
-                  int true_index, int false_index)
-{
-  const struct caseproto *proto = casereader_get_proto (input);
-  struct casewriter *w =
-    autopaging_writer_create (proto);
-  struct ccase *cpc;
-  double prev_cp = SYSMIS;
-
-  for (; (cpc = casereader_read (input)); case_unref (cpc))
-    {
-      struct ccase *new_case;
-      const double cp = case_num_idx (cpc, ROC_CUTPOINT);
-
-      assert (cp != SYSMIS);
-
-      /* We don't want duplicates here */
-      if (cp == prev_cp)
-       continue;
-
-      new_case = case_clone (cpc);
-
-      int index = pos_cond (result, cp) ? true_index : false_index;
-      *case_num_rw_idx (new_case, index) += weight;
-
-      prev_cp = cp;
-
-      casewriter_write (w, new_case);
-    }
-  casereader_destroy (input);
-
-  return casewriter_make_reader (w);
-}
-
-
-
-static void output_roc (struct roc_state *rs, const struct cmd_roc *roc);
-
-/*
-  This function does 3 things:
-
-  1. Counts the number of cases which are equal to every other case in READER,
-  and those cases for which the relationship between it and every other case
-  satifies PRED (normally either > or <).  VAR is variable defining a case's value
-  for this purpose.
-
-  2. Counts the number of true and false cases in reader, and populates
-  CUTPOINT_RDR accordingly.  TRUE_INDEX and FALSE_INDEX are the indices
-  which receive these values.  POS_COND is the condition defining true
-  and false.
-
-  3. CC is filled with the cumulative weight of all cases of READER.
-*/
-static struct casereader *
-process_group (const struct variable *var, struct casereader *reader,
-              bool (*pred) (double, double),
-              const struct dictionary *dict,
-              double *cc,
-              struct casereader **cutpoint_rdr,
-              bool (*pos_cond) (double, double),
-              int true_index,
-              int false_index)
-{
-  const struct variable *w = dict_get_weight (dict);
-
-  struct casereader *r1 =
-    casereader_create_distinct (sort_execute_1var (reader, var), var, w);
-
-  const int weight_idx  = w ? var_get_case_index (w) :
-    caseproto_get_n_widths (casereader_get_proto (r1)) - 1;
-
-  struct ccase *c1;
-
-  struct casereader *rclone = casereader_clone (r1);
-  struct casewriter *wtr;
-  struct caseproto *proto = caseproto_create ();
-
-  proto = caseproto_add_width (proto, 0);
-  proto = caseproto_add_width (proto, 0);
-  proto = caseproto_add_width (proto, 0);
-
-  wtr = autopaging_writer_create (proto);
-
-  *cc = 0;
-
-  for (; (c1 = casereader_read (r1)); case_unref (c1))
-    {
-      struct ccase *new_case = case_create (proto);
-      struct ccase *c2;
-      struct casereader *r2 = casereader_clone (rclone);
-
-      const double weight1 = case_num_idx (c1, weight_idx);
-      const double d1 = case_num (c1, var);
-      double n_eq = 0.0;
-      double n_pred = 0.0;
-
-      *cutpoint_rdr = accumulate_counts (*cutpoint_rdr, d1, weight1,
-                                        pos_cond,
-                                        true_index, false_index);
-
-      *cc += weight1;
-
-      for (; (c2 = casereader_read (r2)); case_unref (c2))
-       {
-         const double d2 = case_num (c2, var);
-         const double weight2 = case_num_idx (c2, weight_idx);
-
-         if (d1 == d2)
-           {
-             n_eq += weight2;
-             continue;
-           }
-         else  if (pred (d2, d1))
-           {
-             n_pred += weight2;
-           }
-       }
-
-      *case_num_rw_idx (new_case, VALUE) = d1;
-      *case_num_rw_idx (new_case, N_EQ) = n_eq;
-      *case_num_rw_idx (new_case, N_PRED) = n_pred;
-
-      casewriter_write (wtr, new_case);
-
-      casereader_destroy (r2);
-    }
-
-  casereader_destroy (r1);
-  casereader_destroy (rclone);
-
-  caseproto_unref (proto);
-
-  return casewriter_make_reader (wtr);
-}
-
-/* Some more indeces into case data */
-#define N_POS_EQ 1  /* number of positive cases with values equal to n */
-#define N_POS_GT 2  /* number of positive cases with values greater than n */
-#define N_NEG_EQ 3  /* number of negative cases with values equal to n */
-#define N_NEG_LT 4  /* number of negative cases with values less than n */
-
-static bool
-gt (double d1, double d2)
-{
-  return d1 > d2;
-}
-
-
-static bool
-ge (double d1, double d2)
-{
-  return d1 > d2;
-}
-
-static bool
-lt (double d1, double d2)
-{
-  return d1 < d2;
-}
-
-
-/*
-  Return a casereader with width 3,
-  populated with cases based upon READER.
-  The cases will have the values:
-  (N, number of cases equal to N, number of cases greater than N)
-  As a side effect, update RS->n1 with the number of positive cases.
-*/
-static struct casereader *
-process_positive_group (const struct variable *var, struct casereader *reader,
-                       const struct dictionary *dict,
-                       struct roc_state *rs)
-{
-  return process_group (var, reader, gt, dict, &rs->n1,
-                       &rs->cutpoint_rdr,
-                       ge,
-                       ROC_TP, ROC_FN);
-}
-
-/*
-  Return a casereader with width 3,
-  populated with cases based upon READER.
-  The cases will have the values:
-  (N, number of cases equal to N, number of cases less than N)
-  As a side effect, update RS->n2 with the number of negative cases.
-*/
-static struct casereader *
-process_negative_group (const struct variable *var, struct casereader *reader,
-                       const struct dictionary *dict,
-                       struct roc_state *rs)
-{
-  return process_group (var, reader, lt, dict, &rs->n2,
-                       &rs->cutpoint_rdr,
-                       lt,
-                       ROC_TN, ROC_FP);
-}
-
-
-
-
-static void
-append_cutpoint (struct casewriter *writer, double cutpoint)
-{
-  struct ccase *cc = case_create (casewriter_get_proto (writer));
-
-  *case_num_rw_idx (cc, ROC_CUTPOINT) = cutpoint;
-  *case_num_rw_idx (cc, ROC_TP) = 0;
-  *case_num_rw_idx (cc, ROC_FN) = 0;
-  *case_num_rw_idx (cc, ROC_TN) = 0;
-  *case_num_rw_idx (cc, ROC_FP) = 0;
-
-  casewriter_write (writer, cc);
-}
-
-/*
-   Create and initialise the rs[x].cutpoint_rdr casereaders.  That is, the
-   readers will be created with width 5, ready to take the values (cutpoint,
-   ROC_TP, ROC_FN, ROC_TN, ROC_FP), and the reader will be populated with its
-   final number of cases.  However on exit from this function, only
-   ROC_CUTPOINT entries will be set to their final value.  The other entries
-   will be initialised to zero.
-*/
-static struct roc_state *
-prepare_cutpoints (struct cmd_roc *roc, struct casereader *input)
-{
-  struct casereader *r = casereader_clone (input);
-  struct ccase *c;
-
-  struct subcase ordering;
-  subcase_init (&ordering, ROC_CUTPOINT, 0, SC_ASCEND);
-
-  struct caseproto *proto = caseproto_create ();
-  proto = caseproto_add_width (proto, 0); /* cutpoint */
-  proto = caseproto_add_width (proto, 0); /* ROC_TP */
-  proto = caseproto_add_width (proto, 0); /* ROC_FN */
-  proto = caseproto_add_width (proto, 0); /* ROC_TN */
-  proto = caseproto_add_width (proto, 0); /* ROC_FP */
-
-  struct roc_state *rs = xnmalloc (roc->n_vars, sizeof *rs);
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    rs[i] = (struct roc_state) {
-      .cutpoint_wtr = sort_create_writer (&ordering, proto),
-      .prev_result = SYSMIS,
-      .max = -DBL_MAX,
-      .min = DBL_MAX,
-    };
-
-  caseproto_unref (proto);
-  subcase_uninit (&ordering);
-
-  for (; (c = casereader_read (r)) != NULL; case_unref (c))
-    for (size_t i = 0; i < roc->n_vars; ++i)
-      {
-        const union value *v = case_data (c, roc->vars[i]);
-        const double result = v->f;
-
-        if (mv_is_value_missing (var_get_missing_values (roc->vars[i]), v)
-            & roc->exclude)
-          continue;
-
-        minimize (&rs[i].min, result);
-        maximize (&rs[i].max, result);
-
-        if (rs[i].prev_result != SYSMIS && rs[i].prev_result != result)
-          {
-            const double mean = (result + rs[i].prev_result) / 2.0;
-            append_cutpoint (rs[i].cutpoint_wtr, mean);
-          }
-
-        rs[i].prev_result = result;
-      }
-  casereader_destroy (r);
-
-  /* Append the min and max cutpoints */
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    {
-      append_cutpoint (rs[i].cutpoint_wtr, rs[i].min - 1);
-      append_cutpoint (rs[i].cutpoint_wtr, rs[i].max + 1);
-
-      rs[i].cutpoint_rdr = casewriter_make_reader (rs[i].cutpoint_wtr);
-    }
-
-  return rs;
-}
-
-static void
-do_roc (struct cmd_roc *roc, struct casereader *reader, struct dictionary *dict)
-{
-  struct casereader *input = casereader_create_filter_missing (
-    reader, roc->vars, roc->n_vars, roc->exclude, NULL, NULL);
-  input = casereader_create_filter_missing (
-    input, &roc->state_var, 1, roc->exclude, NULL, NULL);
-
-  struct roc_state *rs = prepare_cutpoints (roc, input);
-
-  /* Separate the positive actual state cases from the negative ones */
-  struct casewriter *neg_wtr
-    = autopaging_writer_create (casereader_get_proto (input));
-  struct casereader *positives = casereader_create_filter_func (
-    input, match_positives, NULL, roc, neg_wtr);
-
-  struct caseproto *n_proto = caseproto_create ();
-  for (size_t i = 0; i < 5; i++)
-    n_proto = caseproto_add_width (n_proto, 0);
-
-  struct subcase up_ordering;
-  struct subcase down_ordering;
-  subcase_init (&up_ordering, VALUE, 0, SC_ASCEND);
-  subcase_init (&down_ordering, VALUE, 0, SC_DESCEND);
-
-  struct casereader *negatives = NULL;
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    {
-      const struct variable *var = roc->vars[i];
-
-      struct casereader *pos = casereader_clone (positives);
-
-      struct casereader *n_pos_reader =
-       process_positive_group (var, pos, dict, &rs[i]);
-
-      if (!negatives)
-        negatives = casewriter_make_reader (neg_wtr);
-
-      struct casereader *neg = casereader_clone (negatives);
-      struct casereader *n_neg_reader
-        = process_negative_group (var, neg, dict, &rs[i]);
-
-      /* Merge the n_pos and n_neg casereaders */
-      struct casewriter *w = sort_create_writer (&up_ordering, n_proto);
-      struct ccase *cpos;
-      for (; (cpos = casereader_read (n_pos_reader)); case_unref (cpos))
-       {
-         struct ccase *pos_case = case_create (n_proto);
-         const double jpos = case_num_idx (cpos, VALUE);
-
-         struct ccase *cneg;
-         while ((cneg = casereader_read (n_neg_reader)))
-           {
-             struct ccase *nc = case_create (n_proto);
-
-             const double jneg = case_num_idx (cneg, VALUE);
-
-             *case_num_rw_idx (nc, VALUE) = jneg;
-             *case_num_rw_idx (nc, N_POS_EQ) = 0;
-
-             *case_num_rw_idx (nc, N_POS_GT) = SYSMIS;
-
-             *case_data_rw_idx (nc, N_NEG_EQ) = *case_data_idx (cneg, N_EQ);
-             *case_data_rw_idx (nc, N_NEG_LT) = *case_data_idx (cneg, N_PRED);
-
-             casewriter_write (w, nc);
-
-             case_unref (cneg);
-             if (jneg > jpos)
-               break;
-           }
-
-         *case_num_rw_idx (pos_case, VALUE) = jpos;
-         *case_data_rw_idx (pos_case, N_POS_EQ) = *case_data_idx (cpos, N_EQ);
-         *case_data_rw_idx (pos_case, N_POS_GT) = *case_data_idx (cpos, N_PRED);
-         *case_num_rw_idx (pos_case, N_NEG_EQ) = 0;
-         *case_num_rw_idx (pos_case, N_NEG_LT) = SYSMIS;
-
-         casewriter_write (w, pos_case);
-       }
-
-      casereader_destroy (n_pos_reader);
-      casereader_destroy (n_neg_reader);
-
-      struct casereader *r = casewriter_make_reader (w);
-
-      /* Propagate the N_POS_GT values from the positive cases
-        to the negative ones */
-      double prev_pos_gt = rs[i].n1;
-      w = sort_create_writer (&down_ordering, n_proto);
-
-      struct ccase *c;
-      for (; (c = casereader_read (r)); case_unref (c))
-        {
-          double n_pos_gt = case_num_idx (c, N_POS_GT);
-          struct ccase *nc = case_clone (c);
-
-          if (n_pos_gt == SYSMIS)
-            {
-              n_pos_gt = prev_pos_gt;
-              *case_num_rw_idx (nc, N_POS_GT) = n_pos_gt;
-            }
-
-          casewriter_write (w, nc);
-          prev_pos_gt = n_pos_gt;
-        }
-      casereader_destroy (r);
-      r = casewriter_make_reader (w);
-
-      /* Propagate the N_NEG_LT values from the negative cases
-        to the positive ones */
-      double prev_neg_lt = rs[i].n2;
-      w = sort_create_writer (&up_ordering, n_proto);
-
-      for (; (c = casereader_read (r)); case_unref (c))
-        {
-          double n_neg_lt = case_num_idx (c, N_NEG_LT);
-          struct ccase *nc = case_clone (c);
-
-          if (n_neg_lt == SYSMIS)
-            {
-              n_neg_lt = prev_neg_lt;
-              *case_num_rw_idx (nc, N_NEG_LT) = n_neg_lt;
-            }
-
-          casewriter_write (w, nc);
-          prev_neg_lt = n_neg_lt;
-        }
-
-      casereader_destroy (r);
-      r = casewriter_make_reader (w);
-
-      struct ccase *prev_case = NULL;
-      for (; (c = casereader_read (r)); case_unref (c))
-        {
-          struct ccase *next_case = casereader_peek (r, 0);
-
-          const double j = case_num_idx (c, VALUE);
-          double n_pos_eq = case_num_idx (c, N_POS_EQ);
-          double n_pos_gt = case_num_idx (c, N_POS_GT);
-          double n_neg_eq = case_num_idx (c, N_NEG_EQ);
-          double n_neg_lt = case_num_idx (c, N_NEG_LT);
-
-          if (prev_case && j == case_num_idx (prev_case, VALUE))
-            {
-              if (0 ==  case_num_idx (c, N_POS_EQ))
-                {
-                  n_pos_eq = case_num_idx (prev_case, N_POS_EQ);
-                  n_pos_gt = case_num_idx (prev_case, N_POS_GT);
-                }
-
-              if (0 ==  case_num_idx (c, N_NEG_EQ))
-                {
-                  n_neg_eq = case_num_idx (prev_case, N_NEG_EQ);
-                  n_neg_lt = case_num_idx (prev_case, N_NEG_LT);
-                }
-            }
-
-          if (NULL == next_case || j != case_num_idx (next_case, VALUE))
-            {
-              rs[i].auc += n_pos_gt * n_neg_eq + (n_pos_eq * n_neg_eq) / 2.0;
-
-              rs[i].q1hat +=
-                n_neg_eq * (pow2 (n_pos_gt) + n_pos_gt * n_pos_eq + pow2 (n_pos_eq) / 3.0);
-              rs[i].q2hat +=
-                n_pos_eq * (pow2 (n_neg_lt) + n_neg_lt * n_neg_eq + pow2 (n_neg_eq) / 3.0);
-
-            }
-
-          case_unref (next_case);
-          case_unref (prev_case);
-          prev_case = case_clone (c);
-        }
-      casereader_destroy (r);
-      case_unref (prev_case);
-
-      rs[i].auc /= rs[i].n1 * rs[i].n2;
-      if (roc->invert)
-        rs[i].auc = 1 - rs[i].auc;
-
-      if (roc->bi_neg_exp)
-        {
-          rs[i].q1hat = rs[i].auc / (2 - rs[i].auc);
-          rs[i].q2hat = 2 * pow2 (rs[i].auc) / (1 + rs[i].auc);
-        }
-      else
-        {
-          rs[i].q1hat /= rs[i].n2 * pow2 (rs[i].n1);
-          rs[i].q2hat /= rs[i].n1 * pow2 (rs[i].n2);
-        }
-    }
-
-  casereader_destroy (positives);
-  casereader_destroy (negatives);
-
-  caseproto_unref (n_proto);
-  subcase_uninit (&up_ordering);
-  subcase_uninit (&down_ordering);
-
-  output_roc (rs, roc);
-
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    casereader_destroy (rs[i].cutpoint_rdr);
-
-  free (rs);
-}
-
-static void
-show_auc (struct roc_state *rs, const struct cmd_roc *roc)
-{
-  struct pivot_table *table = pivot_table_create (N_("Area Under the Curve"));
-
-  struct pivot_dimension *statistics = pivot_dimension_create (
-    table, PIVOT_AXIS_COLUMN, N_("Statistics"),
-    N_("Area"), PIVOT_RC_OTHER);
-  if (roc->print_se)
-    {
-      pivot_category_create_leaves (
-        statistics->root,
-        N_("Std. Error"), PIVOT_RC_OTHER,
-        N_("Asymptotic Sig."), PIVOT_RC_SIGNIFICANCE);
-      struct pivot_category *interval = pivot_category_create_group__ (
-        statistics->root,
-        pivot_value_new_text_format (N_("Asymp. %g%% Confidence Interval"),
-                                     roc->ci));
-      pivot_category_create_leaves (interval,
-                                    N_("Lower Bound"), PIVOT_RC_OTHER,
-                                    N_("Upper Bound"), PIVOT_RC_OTHER);
-    }
-
-  struct pivot_dimension *variables = pivot_dimension_create (
-    table, PIVOT_AXIS_ROW, N_("Variable under test"));
-  variables->root->show_label = true;
-
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    {
-      int var_idx = pivot_category_create_leaf (
-        variables->root, pivot_value_new_variable (roc->vars[i]));
-
-      pivot_table_put2 (table, 0, var_idx, pivot_value_new_number (rs[i].auc));
-
-      if (roc->print_se)
-       {
-         double se = (rs[i].auc * (1 - rs[i].auc)
-                       + (rs[i].n1 - 1) * (rs[i].q1hat - pow2 (rs[i].auc))
-                       + (rs[i].n2 - 1) * (rs[i].q2hat - pow2 (rs[i].auc)));
-         se /= rs[i].n1 * rs[i].n2;
-         se = sqrt (se);
-
-         double ci = 1 - roc->ci / 100.0;
-         double yy = gsl_cdf_gaussian_Qinv (ci, se);
-
-         double sd_0_5 = sqrt ((rs[i].n1 + rs[i].n2 + 1) /
-                                (12 * rs[i].n1 * rs[i].n2));
-          double sig = 2.0 * gsl_cdf_ugaussian_Q (fabs ((rs[i].auc - 0.5)
-                                                        / sd_0_5));
-          double entries[] = { se, sig, rs[i].auc - yy, rs[i].auc + yy };
-          for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
-            pivot_table_put2 (table, i + 1, var_idx,
-                              pivot_value_new_number (entries[i]));
-       }
-    }
-
-  pivot_table_submit (table);
-}
-
-static void
-show_summary (const struct cmd_roc *roc)
-{
-  struct pivot_table *table = pivot_table_create (N_("Case Summary"));
-
-  struct pivot_dimension *statistics = pivot_dimension_create (
-    table, PIVOT_AXIS_COLUMN, N_("Valid N (listwise)"),
-    N_("Unweighted"), PIVOT_RC_INTEGER,
-    N_("Weighted"), PIVOT_RC_OTHER);
-  statistics->root->show_label = true;
-
-  struct pivot_dimension *cases = pivot_dimension_create__ (
-    table, PIVOT_AXIS_ROW, pivot_value_new_variable (roc->state_var));
-  cases->root->show_label = true;
-  pivot_category_create_leaves (cases->root, N_("Positive"), N_("Negative"));
-
-  struct entry
-    {
-      int stat_idx;
-      int case_idx;
-      double x;
-    }
-  entries[] = {
-    { 0, 0, roc->pos },
-    { 0, 1, roc->neg },
-    { 1, 0, roc->pos_weighted },
-    { 1, 1, roc->neg_weighted },
-  };
-  for (size_t i = 0; i < sizeof entries / sizeof *entries; i++)
-    {
-      const struct entry *e = &entries[i];
-      pivot_table_put2 (table, e->stat_idx, e->case_idx,
-                        pivot_value_new_number (e->x));
-    }
-  pivot_table_submit (table);
-}
-
-static void
-show_coords (struct roc_state *rs, const struct cmd_roc *roc)
-{
-  struct pivot_table *table = pivot_table_create (
-    N_("Coordinates of the Curve"));
-
-  pivot_dimension_create (table, PIVOT_AXIS_COLUMN, N_("Statistics"),
-                          N_("Positive if greater than or equal to"),
-                          N_("Sensitivity"), N_("1 - Specificity"));
-
-  struct pivot_dimension *coordinates = pivot_dimension_create (
-    table, PIVOT_AXIS_ROW, N_("Coordinates"));
-  coordinates->hide_all_labels = true;
-
-  struct pivot_dimension *variables = pivot_dimension_create (
-    table, PIVOT_AXIS_ROW, N_("Test variable"));
-  variables->root->show_label = true;
-
-
-  int n_coords = 0;
-  for (size_t i = 0; i < roc->n_vars; ++i)
-    {
-      struct casereader *r = casereader_clone (rs[i].cutpoint_rdr);
-
-      int var_idx = pivot_category_create_leaf (
-        variables->root, pivot_value_new_variable (roc->vars[i]));
-
-      struct ccase *cc;
-      int coord_idx = 0;
-      for (; (cc = casereader_read (r)) != NULL; case_unref (cc))
-       {
-         const double se = case_num_idx (cc, ROC_TP) /
-           (case_num_idx (cc, ROC_TP) + case_num_idx (cc, ROC_FN));
-
-         const double sp = case_num_idx (cc, ROC_TN) /
-           (case_num_idx (cc, ROC_TN) + case_num_idx (cc, ROC_FP));
-
-          if (coord_idx >= n_coords)
-            {
-              assert (coord_idx == n_coords);
-              pivot_category_create_leaf (
-                coordinates->root, pivot_value_new_integer (++n_coords));
-            }
-
-          pivot_table_put3 (
-            table, 0, coord_idx, var_idx,
-            pivot_value_new_var_value (roc->vars[i],
-                                       case_data_idx (cc, ROC_CUTPOINT)));
-
-          pivot_table_put3 (table, 1, coord_idx, var_idx,
-                            pivot_value_new_number (se));
-          pivot_table_put3 (table, 2, coord_idx, var_idx,
-                            pivot_value_new_number (1 - sp));
-          coord_idx++;
-       }
-
-      casereader_destroy (r);
-    }
-
-  pivot_table_submit (table);
-}
-
-static void
-output_roc (struct roc_state *rs, const struct cmd_roc *roc)
-{
-  show_summary (roc);
-
-  if (roc->curve)
-    {
-      struct roc_chart *rc = roc_chart_create (roc->reference);
-      for (size_t i = 0; i < roc->n_vars; i++)
-        roc_chart_add_var (rc, var_get_name (roc->vars[i]),
-                           rs[i].cutpoint_rdr);
-      roc_chart_submit (rc);
-    }
-
-  show_auc (rs, roc);
-
-  if (roc->print_coords)
-    show_coords (rs, roc);
-}
-