Skip to content

Commit dbc2454

Browse files
authored
Document mkstan/unstan JSON-array-column recipe (#2216)
Adds a "Columns as JSON arrays" section to shapes-of-data.md covering the reshape-to-column-arrays and reshape-back techniques from issue 392 (e.g. for feeding data to Stan), using arrayify()/emit1 to produce real JSON arrays rather than a dedicated file format. Closes #392.
1 parent af7f58d commit dbc2454

3 files changed

Lines changed: 165 additions & 0 deletions

File tree

docs/src/data/stan-example.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"shape": ["triangle", "square", "circle", "square", "triangle", "square", "triangle", "circle", "circle", "square"],
3+
"rate": [9.8870, 0.0130, 2.9010, 7.4670, 8.5910, 9.5310, 5.8240, 4.2370, 8.3350, 8.2430]
4+
}

docs/src/shapes-of-data.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,94 @@ output -- as any full transpose must.
474474

475475
Thanks to @Fravadona on [issue 321](https://github.com/johnkerl/miller/issues/321)
476476
for the original version of this recipe.
477+
478+
## Columns as JSON arrays
479+
480+
Some downstream tools -- for example the [Stan](https://mc-stan.org/) modeling
481+
language -- want their input as a JSON object whose values are arrays, one
482+
array per column, rather than the more usual array-of-records shape:
483+
484+
<pre class="pre-non-highlight-non-pair">
485+
{
486+
"shape": ["triangle", "square", "circle"],
487+
"rate": [9.8870, 0.0130, 2.9010]
488+
}
489+
</pre>
490+
491+
rather than
492+
493+
<pre class="pre-non-highlight-non-pair">
494+
[
495+
{"shape": "triangle", "rate": 9.8870},
496+
{"shape": "square", "rate": 0.0130},
497+
{"shape": "circle", "rate": 2.9010}
498+
]
499+
</pre>
500+
501+
There's no dedicated Miller file format for this -- it's just a particular
502+
shape of JSON, and the same [out-of-stream
503+
variable](reference-dsl-variables.md#out-of-stream-variables) technique from
504+
the transposing example above gets you there, keying by field name first and
505+
row number second (rather than the other way around). The
506+
[`arrayify`](reference-dsl-builtin-functions.md#arrayify) function turns the
507+
per-column maps (keyed `"1"`, `"2"`, ...) into real JSON arrays, and
508+
[`emit1`](reference-dsl-output-statements.md#emit1-and-emitemitpemitf) emits the
509+
whole thing as a single record rather than splitting it one-record-per-key
510+
the way plain `emit` would:
511+
512+
<pre class="pre-highlight-in-pair">
513+
<b>mlr --icsv --ojson cut -f shape,rate then put -q '</b>
514+
<b> for (k, v in $*) {</b>
515+
<b> @output_record[k][NR] = v;</b>
516+
<b> }</b>
517+
<b> end {</b>
518+
<b> emit1 arrayify(@output_record);</b>
519+
<b> }</b>
520+
<b>' example.csv</b>
521+
</pre>
522+
<pre class="pre-non-highlight-in-pair">
523+
[
524+
{
525+
"shape": ["triangle", "square", "circle", "square", "triangle", "square", "triangle", "circle", "circle", "square"],
526+
"rate": [9.8870, 0.0130, 2.9010, 7.4670, 8.5910, 9.5310, 5.8240, 4.2370, 8.3350, 8.2430]
527+
}
528+
]
529+
</pre>
530+
531+
To go the other way -- expanding column-arrays back into one record per row
532+
-- find the longest array in the record, then re-key by row index first and
533+
field name second:
534+
535+
<pre class="pre-highlight-in-pair">
536+
<b>mlr --ijson --ocsv put -q '</b>
537+
<b> n = 0;</b>
538+
<b> for (k, v in $*) {</b>
539+
<b> n = max(n, length(v));</b>
540+
<b> }</b>
541+
<b> keys = get_keys($*);</b>
542+
<b> for (int i = 1; i <= n; i += 1) {</b>
543+
<b> map row = {};</b>
544+
<b> for (k in keys) {</b>
545+
<b> row[k] = $[k][i];</b>
546+
<b> }</b>
547+
<b> emit row;</b>
548+
<b> }</b>
549+
<b>' data/stan-example.json</b>
550+
</pre>
551+
<pre class="pre-non-highlight-in-pair">
552+
shape,rate
553+
triangle,9.8870
554+
square,0.0130
555+
circle,2.9010
556+
square,7.4670
557+
triangle,8.5910
558+
square,9.5310
559+
triangle,5.8240
560+
circle,4.2370
561+
circle,8.3350
562+
square,8.2430
563+
</pre>
564+
565+
Save either of these as a `.mlr` file and pull it in with `put -q -f
566+
mkstan.mlr` or `put -q -f unstan.mlr` to reuse them without retyping. See also
567+
[issue 392](https://github.com/johnkerl/miller/issues/392).

docs/src/shapes-of-data.md.in

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,73 @@ output -- as any full transpose must.
260260

261261
Thanks to @Fravadona on [issue 321](https://github.com/johnkerl/miller/issues/321)
262262
for the original version of this recipe.
263+
264+
## Columns as JSON arrays
265+
266+
Some downstream tools -- for example the [Stan](https://mc-stan.org/) modeling
267+
language -- want their input as a JSON object whose values are arrays, one
268+
array per column, rather than the more usual array-of-records shape:
269+
270+
GENMD-CARDIFY
271+
{
272+
"shape": ["triangle", "square", "circle"],
273+
"rate": [9.8870, 0.0130, 2.9010]
274+
}
275+
GENMD-EOF
276+
277+
rather than
278+
279+
GENMD-CARDIFY
280+
[
281+
{"shape": "triangle", "rate": 9.8870},
282+
{"shape": "square", "rate": 0.0130},
283+
{"shape": "circle", "rate": 2.9010}
284+
]
285+
GENMD-EOF
286+
287+
There's no dedicated Miller file format for this -- it's just a particular
288+
shape of JSON, and the same [out-of-stream
289+
variable](reference-dsl-variables.md#out-of-stream-variables) technique from
290+
the transposing example above gets you there, keying by field name first and
291+
row number second (rather than the other way around). The
292+
[`arrayify`](reference-dsl-builtin-functions.md#arrayify) function turns the
293+
per-column maps (keyed `"1"`, `"2"`, ...) into real JSON arrays, and
294+
[`emit1`](reference-dsl-output-statements.md#emit1-and-emitemitpemitf) emits the
295+
whole thing as a single record rather than splitting it one-record-per-key
296+
the way plain `emit` would:
297+
298+
GENMD-RUN-COMMAND
299+
mlr --icsv --ojson cut -f shape,rate then put -q '
300+
for (k, v in $*) {
301+
@output_record[k][NR] = v;
302+
}
303+
end {
304+
emit1 arrayify(@output_record);
305+
}
306+
' example.csv
307+
GENMD-EOF
308+
309+
To go the other way -- expanding column-arrays back into one record per row
310+
-- find the longest array in the record, then re-key by row index first and
311+
field name second:
312+
313+
GENMD-RUN-COMMAND
314+
mlr --ijson --ocsv put -q '
315+
n = 0;
316+
for (k, v in $*) {
317+
n = max(n, length(v));
318+
}
319+
keys = get_keys($*);
320+
for (int i = 1; i <= n; i += 1) {
321+
map row = {};
322+
for (k in keys) {
323+
row[k] = $[k][i];
324+
}
325+
emit row;
326+
}
327+
' data/stan-example.json
328+
GENMD-EOF
329+
330+
Save either of these as a `.mlr` file and pull it in with `put -q -f
331+
mkstan.mlr` or `put -q -f unstan.mlr` to reuse them without retyping. See also
332+
[issue 392](https://github.com/johnkerl/miller/issues/392).

0 commit comments

Comments
 (0)