mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-20 18:10:07 +00:00
543 lines
25 KiB
HTML
543 lines
25 KiB
HTML
<p/>
|
|
<center>
|
|
<boldmaroon>Common patterns</boldmaroon>
|
|
</center>
|
|
|
|
POKI_PUT_TOC_HERE
|
|
|
|
<p/>
|
|
<button style="font-weight:bold;color:maroon;border:0" onclick="vis_expand_all_body_sections();" href="javascript:;">Expand all sections</button>
|
|
<button style="font-weight:bold;color:maroon;border:0" onclick="vis_collapse_all_body_sections();" href="javascript:;">Collapse all sections</button>
|
|
|
|
<!-- ================================================================ -->
|
|
<h1>Headerless CSV on input or output</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_headerless_csv');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_headerless_csv" style="display: block">
|
|
|
|
<p/>Sometimes we get CSV files which lack a header. For example:
|
|
|
|
POKI_RUN_COMMAND{{cat data/headerless.csv}}HERE
|
|
|
|
<p/> You can use Miller to add a header. The <code>--implicit-csv-header</code> applies positionally indexed labels:
|
|
|
|
POKI_RUN_COMMAND{{mlr --csv --implicit-csv-header cat data/headerless.csv}}HERE
|
|
|
|
<p/> Following that, you can rename the positionally indexed labels to names with meaning for your context.
|
|
For example:
|
|
|
|
POKI_RUN_COMMAND{{mlr --csv --implicit-csv-header label name,age,status data/headerless.csv}}HERE
|
|
|
|
<p/> Likewise, if you need to produce CSV which is lacking its header, you can pipe Miller’s output
|
|
to the system command <code>sed 1d</code>, or you can use Miller’s <code>--headerless-csv-output</code> option:
|
|
|
|
POKI_RUN_COMMAND{{head -5 data/colored-shapes.dkvp | mlr --ocsv cat}}HERE
|
|
POKI_RUN_COMMAND{{head -5 data/colored-shapes.dkvp | mlr --ocsv --headerless-csv-output cat}}HERE
|
|
|
|
<p/> Lastly, often we say “CSV” or “TSV” when we have
|
|
positionally indexed data in columns which are separated by commas or tabs,
|
|
respectively. In this case it’s perhaps simpler to <b>just use NIDX
|
|
format</b> which was designed for this purpose. (See also
|
|
POKI_PUT_LINK_FOR_PAGE(file-formats.html)HERE.) For example:
|
|
|
|
POKI_RUN_COMMAND{{mlr --inidx --ifs comma --oxtab cut -f 1,3 data/headerless.csv}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Doing multiple joins</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_multiple_joins');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_multiple_joins" style="display: block">
|
|
|
|
<p/>Suppose we have the following data:
|
|
|
|
POKI_RUN_COMMAND{{cat multi-join/input.csv}}HERE
|
|
|
|
<p/>And we want to augment the <code>id</code> column with lookups from the following data files:
|
|
|
|
POKI_RUN_COMMAND{{cat multi-join/name-lookup.csv}}HERE
|
|
POKI_RUN_COMMAND{{cat multi-join/status-lookup.csv}}HERE
|
|
|
|
<p/>We can run the input file through multiple <code>join</code> commands in a <code>then</code>-chain:
|
|
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint join -f multi-join/name-lookup.csv -j id then join -f multi-join/status-lookup.csv -j id multi-join/input.csv}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Bulk rename of fields</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_bulk_rename');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_bulk_rename" style="display: block">
|
|
|
|
<p/>Suppose you want to replace spaces with underscores in your column names:
|
|
|
|
POKI_RUN_COMMAND{{cat data/spaces.csv}}HERE
|
|
|
|
<p/>The simplest way is to use <code>mlr rename</code> with <code>-g</code> (for global
|
|
replace, not just first occurrence of space within each field) and <code>-r</code>
|
|
for pattern-matching (rather than explicit single-column renames):
|
|
|
|
POKI_RUN_COMMAND{{mlr --csv rename -g -r ' ,_' data/spaces.csv}}HERE
|
|
POKI_RUN_COMMAND{{mlr --csv --opprint rename -g -r ' ,_' data/spaces.csv}}HERE
|
|
|
|
<p/>You can also do this with a for-loop:
|
|
|
|
POKI_RUN_COMMAND{{cat data/bulk-rename-for-loop.mlr}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint put -f data/bulk-rename-for-loop.mlr data/spaces.csv}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Search-and-replace over all fields</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_search_and_replace_over_all_fields');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_search_and_replace_over_all_fields" style="display: block">
|
|
|
|
<p/> How to do <code>$name=gsub($name, "old", "new")</code> for all fields?
|
|
|
|
POKI_RUN_COMMAND{{cat data/sar.csv}}HERE
|
|
|
|
POKI_RUN_COMMAND{{cat data/sar.mlr}}HERE
|
|
POKI_RUN_COMMAND{{mlr --csv put -f data/sar.mlr data/sar.csv}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Full field renames and reassigns</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_full_renames_reassigns');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_full_renames_reassigns" style="display: block">
|
|
|
|
<p/>Using Miller 5.0.0’s map literals and assigning to <code>$*</code>, you can fully generalize
|
|
<a href="reference-verbs.html#rename"><code>mlr rename</code></a>,
|
|
<a href="reference-verbs.html#reorder"><code>mlr reorder</code></a>,
|
|
etc.:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/full-reorg.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Numbering and renumbering records</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_renumbering_records');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_renumbering_records" style="display: block">
|
|
|
|
<p/> The <code>awk</code>-like built-in variable <code>NR</code> is incremented for each input record:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$nr = NR' data/small}}HERE
|
|
|
|
<p/> However, this is the record number within the original input stream
|
|
— not after any filtering you may have done:
|
|
|
|
POKI_RUN_COMMAND{{mlr filter '$a == "wye"' then put '$nr = NR' data/small}}HERE
|
|
|
|
<p/> There are two good options here. One is to use the <code>cat</code> verb with <code>-n</code>:
|
|
|
|
POKI_RUN_COMMAND{{mlr filter '$a == "wye"' then cat -n data/small}}HERE
|
|
|
|
<p/> The other is to keep your own counter within the <code>put</code> DSL:
|
|
|
|
POKI_RUN_COMMAND{{mlr filter '$a == "wye"' then put 'begin {@n = 1} $n = @n; @n += 1' data/small}}HERE
|
|
|
|
<p/> The difference is a matter of taste (although <code>mlr cat -n</code> puts the counter first).
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Options for dealing with duplicate rows</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_dedupe');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_dedupe" style="display: block">
|
|
|
|
<p/> If your data has records appearing multiple times, you can use
|
|
<a href="reference-verbs.html#uniq">mlr uniq</a> to show and/or count the unique
|
|
records.
|
|
|
|
<p/> If you want to look at partial uniqueness — for example, show only
|
|
the first record for each unique combination of the <code>account_id</code> and
|
|
<code>account_status</code> fields — you might use <code>mlr head -n 1 -g
|
|
account_id,account_status</code>. Please also see <a
|
|
href="reference-verbs.html#head">mlr head</a>.
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Data-cleaning examples</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_data_cleaning_examples');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_data_cleaning_examples" style="display: block">
|
|
|
|
<p/> Here are some ways to use the type-checking options as described in
|
|
the POKI_PUT_LINK_FOR_PAGE(reference-dsl.html#Type-test_and_type-assertion_expressions)HERE.
|
|
Suppose you have the following data file, with inconsistent typing for boolean.
|
|
(Also imagine that, for the sake of discussion, we have a million-line file
|
|
rather than a four-line file, so we can’t see it all at once and some
|
|
automation is called for.)
|
|
|
|
POKI_RUN_COMMAND{{cat data/het-bool.csv}}HERE
|
|
|
|
<p/> One option is to coerce everything to boolean, or integer:
|
|
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint put '$reachable = boolean($reachable)' data/het-bool.csv}}HERE
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint put '$reachable = int(boolean($reachable))' data/het-bool.csv}}HERE
|
|
|
|
<p/> A second option is to flag badly formatted data within the output stream:
|
|
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint put '$format_ok = is_string($reachable)' data/het-bool.csv}}HERE
|
|
|
|
<p/> Or perhaps to flag badly formatted data outside the output stream:
|
|
|
|
POKI_RUN_COMMAND{{mlr --icsv --opprint put 'if (!is_string($reachable)) {eprint "Malformed at NR=".NR} ' data/het-bool.csv}}HERE
|
|
|
|
<p/> A third way is to abort the process on first instance of bad data:
|
|
|
|
POKI_RUN_COMMAND_TOLERATING_ERROR{{mlr --csv put '$reachable = asserting_string($reachable)' data/het-bool.csv}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Splitting nested fields</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_splitting_nested');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_splitting_nested" style="display: block">
|
|
|
|
<p/> Suppose you have a TSV file like this:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/nested.tsv)HERE
|
|
|
|
<p/> The simplest option is to use <a href="reference-verbs.html#nest"><code>mlr nest</code></a>:
|
|
|
|
POKI_RUN_COMMAND{{mlr --tsv nest --explode --values --across-records -f b --nested-fs : data/nested.tsv}}HERE
|
|
POKI_RUN_COMMAND{{mlr --tsv nest --explode --values --across-fields -f b --nested-fs : data/nested.tsv}}HERE
|
|
|
|
<p/> While <code>mlr nest</code> is simplest, let’s also take a look at a few ways to do this using the
|
|
<code>put</code> DSL.
|
|
|
|
<p/> One option to split out the colon-delimited values in the <code>b</code>
|
|
column is to use <code>splitnv</code> to create an integer-indexed map and loop
|
|
over it, adding new fields to the current record:
|
|
|
|
POKI_RUN_COMMAND{{mlr --from data/nested.tsv --itsv --oxtab put 'o=splitnv($b, ":"); for (k,v in o) {$["p".k]=v}'}}HERE
|
|
|
|
<p/> while another is to loop over the same map from <code>splitnv</code> and use
|
|
it (with <code>put -q</code> to suppress printing the original record) to produce
|
|
multiple records:
|
|
|
|
POKI_RUN_COMMAND{{mlr --from data/nested.tsv --itsv --oxtab put -q 'o=splitnv($b, ":"); for (k,v in o) {emit mapsum($*, {"b":v})}'}}HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/nested.tsv --tsv put -q 'o=splitnv($b, ":"); for (k,v in o) {emit mapsum($*, {"b":v})}'}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Showing differences between successive queries</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_successive_query_deltas');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_successive_query_deltas" style="display: block">
|
|
|
|
<p/> Suppose you have a database query which you run at one point in time, producing the output on the
|
|
left, then again later producing the output on the right:
|
|
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
POKI_RUN_COMMAND{{cat data/previous_counters.csv}}HERE
|
|
</td>
|
|
<td>
|
|
POKI_RUN_COMMAND{{cat data/current_counters.csv}}HERE
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p/> And, suppose you want to compute the differences in the counters between
|
|
adjacent keys. Since the color names aren’t all in the same order, nor
|
|
are they all present on both sides, we can’t just paste the two files
|
|
side-by-side and do some column-four-minus-column-two arithmetic.
|
|
|
|
<p/> First, rename counter columns to make them distinct:
|
|
|
|
POKI_RUN_COMMAND{{mlr --csv rename count,previous_count data/previous_counters.csv > data/prevtemp.csv}}HERE
|
|
POKI_RUN_COMMAND{{cat data/prevtemp.csv}}HERE
|
|
POKI_RUN_COMMAND{{mlr --csv rename count,current_count data/current_counters.csv > data/currtemp.csv}}HERE
|
|
POKI_RUN_COMMAND{{cat data/currtemp.csv}}HERE
|
|
|
|
<p/> Then, join on the key field(s), and use unsparsify to zero-fill counters
|
|
absent on one side but present on the other. Use <code>--ul</code> and
|
|
<code>--ur</code> to emit unpaired records (namely, purple on the left and yellow on the right):
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/previous-to-current.sh)HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Finding missing dates</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_finding_missing_dates');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_finding_missing_dates" style="display: block">
|
|
|
|
<p/>Suppose you have some date-stamped data which may (or may not) be missing entries for one or more dates:
|
|
|
|
POKI_RUN_COMMAND{{head -n 10 data/miss-date.csv}}HERE
|
|
POKI_RUN_COMMAND{{wc -l data/miss-date.csv}}HERE
|
|
|
|
<p/>Since there are 1372 lines in the data file, some automation is called for.
|
|
To find the missing dates, you can convert the dates to seconds since the epoch
|
|
using <code>strptime</code>, then compute adjacent differences (the <code>cat -n</code>
|
|
simply inserts record-counters):
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/miss-date-1.sh)HERE
|
|
|
|
<p/>Then, filter for adjacent difference not being 86400 (the number of seconds in a day):
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/miss-date-2.sh)HERE
|
|
|
|
<p/> Given this, it’s now easy to see where the gaps are:
|
|
|
|
POKI_RUN_COMMAND{{mlr cat -n then filter '$n >= 770 && $n <= 780' data/miss-date.csv}}HERE
|
|
POKI_RUN_COMMAND{{mlr cat -n then filter '$n >= 1115 && $n <= 1125' data/miss-date.csv}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Two-pass algorithms</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_two_pass_algorithms');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_two_pass_algorithms" style="display: block">
|
|
|
|
<p/>Miller is a streaming record processor; commands are performed once per
|
|
record. This makes Miller particularly suitable for single-pass algorithms,
|
|
allowing many of its verbs to process files that are (much) larger than the
|
|
amount of RAM present in your system. (Of course, Miller verbs such as
|
|
<code>sort</code>, </code>tac</code>, etc. all must ingest and retain all input records
|
|
before emitting any output records.) You can also use out-of-stream variables
|
|
to perform multi-pass computations, at the price of retaining all input records
|
|
in memory.
|
|
|
|
<h2>Two-pass algorithms: computation of percentages</h2>
|
|
|
|
<p/> For example, mapping numeric values down a column to the percentage
|
|
between their min and max values is two-pass: on the first pass you find the
|
|
min and max values, then on the second, map each record’s value to a
|
|
percentage.
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/two-pass-percentage.sh)HERE
|
|
|
|
<h2>Two-pass algorithms: line-number ratios</h2>
|
|
|
|
<p/>Similarly, finding the total record count requires first reading through
|
|
all the data:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/two-pass-record-numbers.sh)HERE
|
|
|
|
<h2>Two-pass algorithms: records having max value</h2>
|
|
|
|
<p/>The idea is to retain records having the largest value of <code>n</code> in the
|
|
following data:
|
|
|
|
POKI_RUN_COMMAND{{mlr --itsv --opprint cat data/maxrows.tsv}}HERE
|
|
|
|
<p/>Of course, the largest value of <code>n</code> isn’t known until after
|
|
all data have been read. Using an out-of-stream variable we can retain all
|
|
records as they are read, then filter them at the end:
|
|
|
|
POKI_RUN_COMMAND{{cat data/maxrows.mlr}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr --itsv --opprint put -q -f data/maxrows.mlr data/maxrows.tsv}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Rectangularizing data</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_rectangularizing_data');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_rectangularizing_data" style="display: block">
|
|
|
|
<p/>Suppose you have a method (in whatever language) which is printing things of the form
|
|
|
|
POKI_INCLUDE_ESCAPED(data/rect-outer.txt)HERE
|
|
|
|
and then calls another method which prints things of the form
|
|
|
|
POKI_INCLUDE_ESCAPED(data/rect-middle.txt)HERE
|
|
|
|
and then, perhaps, that second method calls a third method which prints things of the form
|
|
|
|
POKI_INCLUDE_ESCAPED(data/rect-inner.txt)HERE
|
|
|
|
with the result that your program’s output is
|
|
|
|
POKI_INCLUDE_ESCAPED(data/rect.txt)HERE
|
|
|
|
The idea here is that middles starting with a 1 belong to the outer value of 1,
|
|
and so on. (For example, the outer values might be account IDs, the middle
|
|
values might be invoice IDs, and the inner values might be invoice line-items.)
|
|
If you want all the middle and inner lines to have the context of which outers
|
|
they belong to, you can modify your software to pass all those through your
|
|
methods. Alternatively, don’t refactor your code just to handle some
|
|
ad-hoc log-data formatting — instead, use the following to rectangularize
|
|
the data. The idea is to use an out-of-stream variable to accumulate fields
|
|
across records. Clear that variable when you see an outer ID; accumulate
|
|
fields; emit output when you see the inner IDs.
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/rect.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Regularizing ragged CSV</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_rectangularizing_ragged_csv');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_rectangularizing_ragged_csv" style="display: block">
|
|
|
|
<p/>Miller handles compliant CSV: in particular, it’s an error if the
|
|
number of data fields in a given data line don’t match the number of
|
|
header lines. But in the event that you have a CSV file in which some lines
|
|
have less than the full number of fields, you can use Miller to pad them out.
|
|
The trick is to use NIDX format, for which each line stands on its own without
|
|
respect to a header line.
|
|
|
|
POKI_RUN_COMMAND{{cat data/ragged.csv}}HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/ragged-csv.sh)HERE
|
|
|
|
or, more simply,
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/ragged-csv-2.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Feature-counting</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_feature_counting');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_feature_counting" style="display: block">
|
|
|
|
<p/>Suppose you have some heterogeneous data like this:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/features.json)HERE
|
|
|
|
<p/> A reasonable question to ask is, how many occurrences of each field are
|
|
there? And, what percentage of total row count has each of them? Since the
|
|
denominator of the percentage is not known until the end, this is a two-pass
|
|
algorithm:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/feature-count.mlr)HERE
|
|
|
|
<p/> Then
|
|
POKI_RUN_COMMAND{{mlr --json put -q -f data/feature-count.mlr data/features.json}}HERE
|
|
POKI_RUN_COMMAND{{mlr --ijson --opprint put -q -f data/feature-count.mlr data/features.json}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
</div>
|
|
<h1>Unsparsing</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_unsparsing');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_unsparsing" style="display: block">
|
|
|
|
<p/> The previous section discussed how to fill out missing data fields within
|
|
CSV with full header line — so the list of all field names is present
|
|
within the header line. Next, let’s look at a related problem: we have
|
|
data where each record has various key names but we want to produce rectangular
|
|
output having the union of all key names.
|
|
|
|
<p/> For example, suppose you have JSON input like this:
|
|
|
|
POKI_RUN_COMMAND{{cat data/sparse.json}}HERE
|
|
|
|
<p/>There are field names <code>a</code>, <code>b</code>, <code>v</code>, <code>u</code>,
|
|
<code>x</code>, <code>w</code> in the data — but not all in every record. Since
|
|
we don’t know the names of all the keys until we’ve read them all,
|
|
this needs to be a two-pass algorithm. On the first pass, remember all the
|
|
unique key names and all the records; on the second pass, loop through the
|
|
records filling in absent values, then producing output. Use <code>put -q</code>
|
|
since we don’t want to produce per-record output, only emitting output in
|
|
the <code>end</code> block:
|
|
|
|
POKI_RUN_COMMAND{{cat data/unsparsify.mlr}}HERE
|
|
POKI_RUN_COMMAND{{mlr --json put -q -f data/unsparsify.mlr data/sparse.json}}HERE
|
|
POKI_RUN_COMMAND{{mlr --ijson --ocsv put -q -f data/unsparsify.mlr data/sparse.json}}HERE
|
|
POKI_RUN_COMMAND{{mlr --ijson --opprint put -q -f data/unsparsify.mlr data/sparse.json}}HERE
|
|
|
|
<p/> There is a keystroke-saving verb for this: <a href="reference-verbs.html#unsparsify"><code>mlr unsparsify</code></a>.
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Parsing log-file output</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_parsing_log_file_output');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_parsing_log_file_output" style="display: block">
|
|
|
|
<p/>This, of course, depends highly on what’s in your log files. But, as
|
|
an example, suppose you have log-file lines such as
|
|
|
|
POKI_CARDIFY(2015-10-08 08:29:09,445 INFO com.company.path.to.ClassName @ [sometext] various/sorts/of data {& punctuation} hits=1 status=0 time=2.378)HERE
|
|
|
|
I prefer to pre-filter with <code>grep</code> and/or <code>sed</code> to extract the structured text, then hand that to Miller. Example:
|
|
|
|
POKI_CARDIFY(grep 'various sorts' *.log | sed 's/.*} //' | mlr --fs space --repifs --oxtab stats1 -a min,p10,p50,p90,max -f time -g status)HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Memoization with out-of-stream variables</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_memoization_with_oosvars');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_memoization_with_oosvars" style="display: block">
|
|
|
|
<p/> The recursive function for the Fibonacci sequence is famous for its computational complexity.
|
|
Namely, using
|
|
<i>f</i>(0)=1,
|
|
<i>f</i>(1)=1,
|
|
<i>f</i>(<i>n</i>)=<i>f</i>(<i>n</i>-1)+<i>f</i>(<i>n</i>-2) for <i>n</i>≥2,
|
|
the evaluation tree branches left as well as right at each non-trivial level, resulting in millions
|
|
or more paths to the root 0/1 nodes for larger <i>n</i>. This program
|
|
|
|
POKI_INCLUDE_ESCAPED(data/fibo-uncached.sh)HERE
|
|
|
|
<p/> produces output like this:
|
|
|
|
<p/>
|
|
<div class="pokipanel"><pre>
|
|
i o fcount seconds_delta
|
|
1 1 1 0
|
|
2 2 3 0.000039101
|
|
3 3 5 0.000015974
|
|
4 5 9 0.000019073
|
|
5 8 15 0.000026941
|
|
6 13 25 0.000036955
|
|
7 21 41 0.000056028
|
|
8 34 67 0.000086069
|
|
9 55 109 0.000134945
|
|
10 89 177 0.000217915
|
|
11 144 287 0.000355959
|
|
12 233 465 0.000506163
|
|
13 377 753 0.000811815
|
|
14 610 1219 0.001297235
|
|
15 987 1973 0.001960993
|
|
16 1597 3193 0.003417969
|
|
17 2584 5167 0.006215811
|
|
18 4181 8361 0.008294106
|
|
19 6765 13529 0.012095928
|
|
20 10946 21891 0.019592047
|
|
21 17711 35421 0.031193972
|
|
22 28657 57313 0.057254076
|
|
23 46368 92735 0.080307961
|
|
24 75025 150049 0.129482031
|
|
25 121393 242785 0.213325977
|
|
26 196418 392835 0.334423065
|
|
27 317811 635621 0.605969906
|
|
28 514229 1028457 0.971235037
|
|
</pre></div>
|
|
|
|
<p/> Note that the time it takes to evaluate the function is blowing up exponentially as the input argument
|
|
increases. Using <code>@</code>-variables, which persist across records, we can cache and reuse the results
|
|
of previous computations:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/fibo-cached.sh)HERE
|
|
|
|
<p/> with output like this:
|
|
<p/>
|
|
<div class="pokipanel"><pre>
|
|
i o fcount seconds_delta
|
|
1 1 1 0
|
|
2 2 3 0.000053883
|
|
3 3 3 0.000035048
|
|
4 5 3 0.000045061
|
|
5 8 3 0.000014067
|
|
6 13 3 0.000028849
|
|
7 21 3 0.000028133
|
|
8 34 3 0.000027895
|
|
9 55 3 0.000014067
|
|
10 89 3 0.000015020
|
|
11 144 3 0.000012875
|
|
12 233 3 0.000033140
|
|
13 377 3 0.000014067
|
|
14 610 3 0.000012875
|
|
15 987 3 0.000029087
|
|
16 1597 3 0.000013828
|
|
17 2584 3 0.000013113
|
|
18 4181 3 0.000012875
|
|
19 6765 3 0.000013113
|
|
20 10946 3 0.000012875
|
|
21 17711 3 0.000013113
|
|
22 28657 3 0.000013113
|
|
23 46368 3 0.000015974
|
|
24 75025 3 0.000012875
|
|
25 121393 3 0.000013113
|
|
26 196418 3 0.000012875
|
|
27 317811 3 0.000013113
|
|
28 514229 3 0.000012875
|
|
</pre></div>
|
|
|
|
</div>
|