MATRIX DATA: Make N subcommand work properly without ROWTYPE_.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 11 May 2023 02:29:56 +0000 (19:29 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 11 May 2023 02:29:56 +0000 (19:29 -0700)
I found this bug myself while experimenting.

src/language/commands/matrix-data.c
tests/language/commands/matrix-data.at

index 8c8c45bc886ea61fa86b794bfa3e390489aef8b4..365caf6fd38a9e53a5e4a6a4f5743c20401dfa78 100644 (file)
@@ -546,6 +546,35 @@ matrix_sched_init (const struct matrix_format *mf, enum rowtype rt,
       gsl_matrix_set (m, y, x, y == x ? diagonal : SYSMIS);
 }
 
+static struct ccase *
+matrix_sched_output_create_case (const struct matrix_format *mf,
+                                 enum rowtype rt, const struct variable *var,
+                                 const double *d, int split_num,
+                                 struct casewriter *w)
+{
+  struct ccase *c = case_create (casewriter_get_proto (w));
+  for (size_t i = 0; mf->input_vars[i] != mf->cvars[0]; i++)
+    if (mf->input_vars[i] != mf->rowtype)
+      *case_num_rw (c, mf->input_vars[i]) = d[i];
+  if (mf->n_svars && !mf->svar_indexes)
+    *case_num_rw (c, mf->svars[0]) = split_num;
+  set_string (c, mf->rowtype, rowtype_name (rt));
+  const char *varname = var ? var_get_name (var) : "";
+  set_string (c, mf->varname, ss_cstr (varname));
+  return c;
+}
+
+static void
+matrix_sched_output_n (const struct matrix_format *mf, double n,
+                       const double *d, int split_num, struct casewriter *w)
+{
+  struct ccase *c = matrix_sched_output_create_case (mf, C_N, NULL, d,
+                                                     split_num, w);
+  for (int x = 0; x < mf->n_cvars; x++)
+    *case_num_rw (c, mf->cvars[x]) = n;
+  casewriter_write (w, c);
+}
+
 static void
 matrix_sched_output (const struct matrix_format *mf, enum rowtype rt,
                      gsl_matrix *m, const double *d, int split_num,
@@ -556,37 +585,21 @@ matrix_sched_output (const struct matrix_format *mf, enum rowtype rt,
 
   if (rt == C_N_SCALAR)
     {
-      for (size_t x = 1; x < mf->n_cvars; x++)
-        gsl_matrix_set (m, 0, x, gsl_matrix_get (m, 0, 0));
-      rt = C_N;
+      matrix_sched_output_n (mf, gsl_matrix_get (m, 0, 0), d, split_num, w);
+      return;
     }
 
   for (int y = 0; y < ms->nr; y++)
     {
-      struct ccase *c = case_create (casewriter_get_proto (w));
-      for (size_t i = 0; mf->input_vars[i] != mf->cvars[0]; i++)
-        if (mf->input_vars[i] != mf->rowtype)
-          *case_num_rw (c, mf->input_vars[i]) = d[i];
-      if (mf->n_svars && !mf->svar_indexes)
-        *case_num_rw (c, mf->svars[0]) = split_num;
-      set_string (c, mf->rowtype, rowtype_name (rt));
-      const char *varname = n_dims == 2 ? var_get_name (mf->cvars[y]) : "";
-      set_string (c, mf->varname, ss_cstr (varname));
+      const struct variable *var = n_dims == 2 ? mf->cvars[y] : NULL;
+      struct ccase *c = matrix_sched_output_create_case (mf, rt, var, d,
+                                                         split_num, w);
       for (int x = 0; x < mf->n_cvars; x++)
         *case_num_rw (c, mf->cvars[x]) = gsl_matrix_get (m, y, x);
       casewriter_write (w, c);
     }
 }
 
-static void
-matrix_sched_output_n (const struct matrix_format *mf, double n,
-                       gsl_matrix *m, const double *d, int split_num,
-                       struct casewriter *w)
-{
-  gsl_matrix_set (m, 0, 0, n);
-  matrix_sched_output (mf, C_N_SCALAR, m, d, split_num, w);
-}
-
 static void
 check_eol (const struct matrix_format *mf, struct substring *p,
            struct dfm_reader *r)
@@ -635,7 +648,7 @@ parse_data_with_rowtype (const struct matrix_format *mf,
          record. */
       if (mf->n >= 0 && (!prev || !equal_split_columns (mf, prev, d)))
         {
-          matrix_sched_output_n (mf, mf->n, m, d, 0, w);
+          matrix_sched_output_n (mf, mf->n, d, 0, w);
 
           if (!prev)
             prev = xnmalloc (mf->n_input_vars, sizeof *prev);
@@ -729,7 +742,7 @@ static void
 parse_matrix_without_rowtype (const struct matrix_format *mf,
                               struct substring *p, struct dfm_reader *r,
                               gsl_matrix *m, enum rowtype rowtype, bool pooled,
-                              int split_num, struct casewriter *w)
+                              int split_num, bool *first, struct casewriter *w)
 {
   int n_dims = rowtype_dimensions (rowtype);
   const struct matrix_sched *ms = &mf->ms[n_dims];
@@ -778,6 +791,14 @@ parse_matrix_without_rowtype (const struct matrix_format *mf,
       check_eol (mf, p, r);
     }
 
+  /* If there's an N subcommand, and this is a new split, then output an N
+     record. */
+  if (mf->n >= 0 && *first)
+    {
+      *first = false;
+      matrix_sched_output_n (mf, mf->n, d, 0, w);
+    }
+
   matrix_sched_output (mf, rowtype, m, d, split_num, w);
 exit:
   free (d);
@@ -796,6 +817,7 @@ parse_data_without_rowtype (const struct matrix_format *mf,
   int split_num = 1;
   do
     {
+      bool first = true;
       for (size_t i = 0; i < mf->n_contents; )
         {
           size_t j = i;
@@ -809,11 +831,11 @@ parse_data_without_rowtype (const struct matrix_format *mf,
                 for (size_t h = i; h <= j; h++)
                   parse_matrix_without_rowtype (mf, &p, r, m,
                                                 mf->contents[h].rowtype, false,
-                                                split_num, w);
+                                                split_num, &first, w);
             }
           else
             parse_matrix_without_rowtype (mf, &p, r, m, mf->contents[i].rowtype,
-                                          true, split_num, w);
+                                          true, split_num, &first, w);
           i = j + 1;
         }
 
index 1e8e3f84bfd7b7d03c4576aecfd5aece0bec6cd4..8f56e2530333e9589f2212e6133580ae44c40282 100644 (file)
@@ -921,6 +921,45 @@ CORR,var08,.17,.29,-.05,.20,.27,.20,.04,1.00
 ])
 AT_CLEANUP
 
+dnl Keep this test in sync with Example 6 in doc/matrices.texi.
+AT_SETUP([MATRIX DATA - LOWER DIAGONAL with N and without ROWTYPE_])
+AT_DATA([matrix-data.sps], [dnl
+MATRIX DATA
+    VARIABLES=var01 TO var08
+   /CONTENTS=MEAN SD CORR
+   /N 92.
+BEGIN DATA.
+24.3   5.4  69.7  20.1  13.4   2.7  27.9   3.7
+ 5.7   1.5  23.5   5.8   2.8   4.5   5.4   1.5
+1.00
+ .18  1.00
+-.22  -.17  1.00
+ .36   .31  -.14  1.00
+ .27   .16  -.12   .22  1.00
+ .33   .15  -.17   .24   .21  1.00
+ .50   .29  -.20   .32   .12   .38  1.00
+ .17   .29  -.05   .20   .27   .20   .04  1.00
+END DATA.
+FORMATS var01 TO var08(F5.2).
+LIST.
+])
+AT_CHECK([pspp matrix-data.sps -O format=csv], [0], [dnl
+Table: Data List
+ROWTYPE_,VARNAME_,var01,var02,var03,var04,var05,var06,var07,var08
+N,,92.00,92.00,92.00,92.00,92.00,92.00,92.00,92.00
+MEAN,,24.30,5.40,69.70,20.10,13.40,2.70,27.90,3.70
+STDDEV,,5.70,1.50,23.50,5.80,2.80,4.50,5.40,1.50
+CORR,var01,1.00,.18,-.22,.36,.27,.33,.50,.17
+CORR,var02,.18,1.00,-.17,.31,.16,.15,.29,.29
+CORR,var03,-.22,-.17,1.00,-.14,-.12,-.17,-.20,-.05
+CORR,var04,.36,.31,-.14,1.00,.22,.24,.32,.20
+CORR,var05,.27,.16,-.12,.22,1.00,.21,.12,.27
+CORR,var06,.33,.15,-.17,.24,.21,1.00,.38,.20
+CORR,var07,.50,.29,-.20,.32,.12,.38,1.00,.04
+CORR,var08,.17,.29,-.05,.20,.27,.20,.04,1.00
+])
+AT_CLEANUP
+
 AT_SETUP([MATRIX DATA - extraneous data without ROWTYPE_])
 AT_DATA([matrix-data.sps], [dnl
 MATRIX DATA
@@ -1019,6 +1058,63 @@ s1,ROWTYPE_,VARNAME_,var01,var02,var03,var04
 ])
 AT_CLEANUP
 
+dnl Keep this test in sync with Example 7 in doc/matrices.texi.
+AT_SETUP([MATRIX DATA - Split variables with explicit values with N and without ROWTYPE_])
+AT_DATA([matrix-data.sps], [dnl
+MATRIX DATA
+    VARIABLES=s1 var01 TO var04
+    /SPLIT=s1
+    /FORMAT=FULL
+    /CONTENTS=MEAN SD CORR
+    /N=99.
+BEGIN DATA.
+0 34 35 36 37
+0 22 11 55 66
+0  1 .9 .8 .7
+0 .9  1 .6 .5
+0 .8 .6  1 .4
+0 .7 .5 .4  1
+1 44 45 34 39
+1 23 15 51 46
+1  1 .2 .3 .4
+1 .2  1 .5 .6
+1 .3 .5  1 .7
+1 .4 .6 .7  1
+END DATA.
+FORMATS var01 TO var04(F5.2).
+LIST.
+])
+AT_CHECK([pspp matrix-data.sps -O format=csv], [0], [dnl
+Table: Split Values
+Variable,Value
+s1,0
+
+Table: Data List
+s1,ROWTYPE_,VARNAME_,var01,var02,var03,var04
+0,N,,99.00,99.00,99.00,99.00
+0,MEAN,,34.00,35.00,36.00,37.00
+0,STDDEV,,22.00,11.00,55.00,66.00
+0,CORR,var01,1.00,.90,.80,.70
+0,CORR,var02,.90,1.00,.60,.50
+0,CORR,var03,.80,.60,1.00,.40
+0,CORR,var04,.70,.50,.40,1.00
+
+Table: Split Values
+Variable,Value
+s1,1
+
+Table: Data List
+s1,ROWTYPE_,VARNAME_,var01,var02,var03,var04
+1,N,,99.00,99.00,99.00,99.00
+1,MEAN,,44.00,45.00,34.00,39.00
+1,STDDEV,,23.00,15.00,51.00,46.00
+1,CORR,var01,1.00,.20,.30,.40
+1,CORR,var02,.20,1.00,.50,.60
+1,CORR,var03,.30,.50,1.00,.70
+1,CORR,var04,.40,.60,.70,1.00
+])
+AT_CLEANUP
+
 dnl Keep this test in sync with Example 8 in doc/matrices.texi.
 AT_SETUP([MATRIX DATA - Split variable with sequential values without ROWTYPE_])
 AT_DATA([matrix-data.sps], [dnl