diff --git a/docs/src/ai-support.md b/docs/src/ai-support.md index 7fb26f9dc..105b713c1 100644 --- a/docs/src/ai-support.md +++ b/docs/src/ai-support.md @@ -64,7 +64,7 @@ one-line summary (here trimmed, and then counted, using Miller itself):
 [
 {
-  "count": 662
+  "count": 664
 }
 ]
 
diff --git a/docs/src/manpage.md b/docs/src/manpage.md index 38091ec62..0f6e75a95 100644 --- a/docs/src/manpage.md +++ b/docs/src/manpage.md @@ -257,14 +257,14 @@ This is simply a copy of what you should see on running `man mlr` at a command p null_count os percentile percentiles pow qnorm reduce regextract regextract_or_else rightpad round roundm rstrip sec2dhms sec2gmt sec2gmtdate sec2hms sec2localdate sec2localtime select sgn sha1 sha256 sha512 sin sinh - skewness sort sort_collection splita splitax splitkv splitkvx splitnv splitnvx - sqrt ssub stat stddev strfntime strfntime_local strftime strftime_local string - strip strlen strmatch strmatchx strpntime strpntime_local strptime - strptime_local sub substr substr0 substr1 sum sum2 sum3 sum4 sysntime system - systime systimeint tan tanh tolower toupper truncate typeof unflatten unformat - unformatx upntime uptime urand urand32 urandelement urandint urandrange - utf8_to_latin1 variance version ! != !=~ % & && * ** + - . .* .+ .- ./ / // < - << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~ + skewness sort sort_collection sparkline splita splitax splitkv splitkvx + splitnv splitnvx sqrt ssub stat stddev strfntime strfntime_local strftime + strftime_local string strip strlen strmatch strmatchx strpntime + strpntime_local strptime strptime_local sub substr substr0 substr1 sum sum2 + sum3 sum4 sysntime system systime systimeint tan tanh tolower toupper truncate + typeof unflatten unformat unformatx upntime uptime urand urand32 urandelement + urandint urandrange utf8_to_latin1 variance version ! != !=~ % & && * ** + - . + .* .+ .- ./ / // < << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~ 1mCOMMENTS-IN-DATA FLAGS0m Miller lets you put comments in your data, such as @@ -1462,7 +1462,11 @@ This is simply a copy of what you should see on running `man mlr` at a command p --auto Automatically computes limits, ignoring --lo and --hi. Holds all values in memory before producing any output. -o {prefix} Prefix for output field name. Default: no prefix. + -s Print a one-line Unicode sparkline per field instead of per-bin + counts. -h|--help Show this message. + With -s, output is one record per value-field, with a sparkline field + instead of one record per bin. 1mjson-parse0m Usage: mlr json-parse [options] @@ -3284,6 +3288,12 @@ This is simply a copy of what you should see on running `man mlr` at a command p 1msort_collection0m (class=stats #args=1) This is a helper function for the percentiles function; please see its online help for details. + 1msparkline0m + (class=stats #args=1) Returns a string of Unicode block characters (one of per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types. + Examples: + sparkline([1,2,3,4,5,6,7,8]) is "" + sparkline([3,3,3]) is "" + 1msplita0m (class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on. Example: @@ -4062,5 +4072,5 @@ This is simply a copy of what you should see on running `man mlr` at a command p MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite https://miller.readthedocs.io - 2026-07-06 4mMILLER24m(1) + 2026-07-07 4mMILLER24m(1) diff --git a/docs/src/manpage.txt b/docs/src/manpage.txt index b0d6322ca..56e848bb8 100644 --- a/docs/src/manpage.txt +++ b/docs/src/manpage.txt @@ -209,8 +209,8 @@ histogram json-parse json-stringify join label latin1-to-utf8 least-frequent merge-fields most-frequent nest nothing put regularize remove-empty-columns rename reorder repeat reshape sample sec2gmtdate sec2gmt seqgen shuffle - skip-trivial-records sort sort-within-records sparsify split ssub stats1 - stats2 step sub summary surv tac tail tee template top utf8-to-latin1 + skip-trivial-records sort sort-within-records sparkline sparsify split ssub + stats1 stats2 step sub summary surv tac tail tee template top utf8-to-latin1 unflatten uniq unspace unsparsify 1mFUNCTION LIST0m @@ -236,14 +236,14 @@ null_count os percentile percentiles pow qnorm reduce regextract regextract_or_else rightpad round roundm rstrip sec2dhms sec2gmt sec2gmtdate sec2hms sec2localdate sec2localtime select sgn sha1 sha256 sha512 sin sinh - skewness sort sort_collection splita splitax splitkv splitkvx splitnv splitnvx - sqrt ssub stat stddev strfntime strfntime_local strftime strftime_local string - strip strlen strmatch strmatchx strpntime strpntime_local strptime - strptime_local sub substr substr0 substr1 sum sum2 sum3 sum4 sysntime system - systime systimeint tan tanh tolower toupper truncate typeof unflatten unformat - unformatx upntime uptime urand urand32 urandelement urandint urandrange - utf8_to_latin1 variance version ! != !=~ % & && * ** + - . .* .+ .- ./ / // < - << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~ + skewness sort sort_collection sparkline splita splitax splitkv splitkvx + splitnv splitnvx sqrt ssub stat stddev strfntime strfntime_local strftime + strftime_local string strip strlen strmatch strmatchx strpntime + strpntime_local strptime strptime_local sub substr substr0 substr1 sum sum2 + sum3 sum4 sysntime system systime systimeint tan tanh tolower toupper truncate + typeof unflatten unformat unformatx upntime uptime urand urand32 urandelement + urandint urandrange utf8_to_latin1 variance version ! != !=~ % & && * ** + - . + .* .+ .- ./ / // < << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~ 1mCOMMENTS-IN-DATA FLAGS0m Miller lets you put comments in your data, such as @@ -1441,7 +1441,11 @@ --auto Automatically computes limits, ignoring --lo and --hi. Holds all values in memory before producing any output. -o {prefix} Prefix for output field name. Default: no prefix. + -s Print a one-line Unicode sparkline per field instead of per-bin + counts. -h|--help Show this message. + With -s, output is one record per value-field, with a sparkline field + instead of one record per bin. 1mjson-parse0m Usage: mlr json-parse [options] @@ -2062,6 +2066,16 @@ -n Sort field names naturally (e.g. 2 before 12). Combines with -f/-r. -h|--help Show this message. + 1msparkline0m + Usage: mlr sparkline [options] + Reduces numeric field(s), across all records in input order, to a compact + Unicode sparkline -- one block character per record -- for visualizing + trends. Emits one output record per field. Holds all records in memory + before producing any output. + Options: + -f {a,b,c} Field names to sparkline. + -h|--help Show this message. + 1msparsify0m Usage: mlr sparsify [options] Unsets fields for which the key is the empty string (or, optionally, another @@ -3263,6 +3277,12 @@ 1msort_collection0m (class=stats #args=1) This is a helper function for the percentiles function; please see its online help for details. + 1msparkline0m + (class=stats #args=1) Returns a string of Unicode block characters (one of per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types. + Examples: + sparkline([1,2,3,4,5,6,7,8]) is "" + sparkline([3,3,3]) is "" + 1msplita0m (class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on. Example: @@ -4041,4 +4061,4 @@ MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite https://miller.readthedocs.io - 2026-07-06 4mMILLER24m(1) + 2026-07-07 4mMILLER24m(1) diff --git a/docs/src/reference-dsl-builtin-functions.md b/docs/src/reference-dsl-builtin-functions.md index c7e684846..c918cb8a5 100644 --- a/docs/src/reference-dsl-builtin-functions.md +++ b/docs/src/reference-dsl-builtin-functions.md @@ -59,7 +59,7 @@ Operators are listed here along with functions. In this case, the argument count * [**Hashing functions**](#hashing-functions): [md5](#md5), [sha1](#sha1), [sha256](#sha256), [sha512](#sha512). * [**Higher-order-functions functions**](#higher-order-functions-functions): [any](#any), [apply](#apply), [every](#every), [fold](#fold), [reduce](#reduce), [select](#select), [sort](#sort). * [**Math functions**](#math-functions): [abs](#abs), [acos](#acos), [acosh](#acosh), [asin](#asin), [asinh](#asinh), [atan](#atan), [atan2](#atan2), [atanh](#atanh), [cbrt](#cbrt), [ceil](#ceil), [cos](#cos), [cosh](#cosh), [erf](#erf), [erfc](#erfc), [exp](#exp), [expm1](#expm1), [floor](#floor), [invqnorm](#invqnorm), [log](#log), [log10](#log10), [log1p](#log1p), [logifit](#logifit), [max](#max), [min](#min), [qnorm](#qnorm), [round](#round), [roundm](#roundm), [sgn](#sgn), [sin](#sin), [sinh](#sinh), [sqrt](#sqrt), [tan](#tan), [tanh](#tanh), [urand](#urand), [urand32](#urand32), [urandelement](#urandelement), [urandint](#urandint), [urandrange](#urandrange). -* [**Stats functions**](#stats-functions): [antimode](#antimode), [count](#count), [distinct_count](#distinct_count), [kurtosis](#kurtosis), [maxlen](#maxlen), [mean](#mean), [meaneb](#meaneb), [median](#median), [minlen](#minlen), [mode](#mode), [null_count](#null_count), [percentile](#percentile), [percentiles](#percentiles), [skewness](#skewness), [sort_collection](#sort_collection), [stddev](#stddev), [sum](#sum), [sum2](#sum2), [sum3](#sum3), [sum4](#sum4), [variance](#variance). +* [**Stats functions**](#stats-functions): [antimode](#antimode), [count](#count), [distinct_count](#distinct_count), [kurtosis](#kurtosis), [maxlen](#maxlen), [mean](#mean), [meaneb](#meaneb), [median](#median), [minlen](#minlen), [mode](#mode), [null_count](#null_count), [percentile](#percentile), [percentiles](#percentiles), [skewness](#skewness), [sort_collection](#sort_collection), [sparkline](#sparkline), [stddev](#stddev), [sum](#sum), [sum2](#sum2), [sum3](#sum3), [sum4](#sum4), [variance](#variance). * [**String functions**](#string-functions): [base64_decode](#base64_decode), [base64_encode](#base64_encode), [capitalize](#capitalize), [clean_whitespace](#clean_whitespace), [collapse_whitespace](#collapse_whitespace), [contains](#contains), [format](#format), [gssub](#gssub), [gsub](#gsub), [hex_decode](#hex_decode), [hex_encode](#hex_encode), [index](#index), [latin1_to_utf8](#latin1_to_utf8), [leftpad](#leftpad), [lstrip](#lstrip), [regextract](#regextract), [regextract_or_else](#regextract_or_else), [rightpad](#rightpad), [rstrip](#rstrip), [ssub](#ssub), [strip](#strip), [strlen](#strlen), [strmatch](#strmatch), [strmatchx](#strmatchx), [sub](#sub), [substr](#substr), [substr0](#substr0), [substr1](#substr1), [tolower](#tolower), [toupper](#toupper), [truncate](#truncate), [unformat](#unformat), [unformatx](#unformatx), [utf8_to_latin1](#utf8_to_latin1), [\.](#dot). * [**System functions**](#system-functions): [exec](#exec), [hostname](#hostname), [next](#next), [os](#os), [stat](#stat), [system](#system), [version](#version). * [**Time functions**](#time-functions): [dhms2fsec](#dhms2fsec), [dhms2sec](#dhms2sec), [fsec2dhms](#fsec2dhms), [fsec2hms](#fsec2hms), [gmt2localtime](#gmt2localtime), [gmt2nsec](#gmt2nsec), [gmt2sec](#gmt2sec), [hms2fsec](#hms2fsec), [hms2sec](#hms2sec), [localtime2gmt](#localtime2gmt), [localtime2nsec](#localtime2nsec), [localtime2sec](#localtime2sec), [nsec2gmt](#nsec2gmt), [nsec2gmtdate](#nsec2gmtdate), [nsec2localdate](#nsec2localdate), [nsec2localtime](#nsec2localtime), [sec2dhms](#sec2dhms), [sec2gmt](#sec2gmt), [sec2gmtdate](#sec2gmtdate), [sec2hms](#sec2hms), [sec2localdate](#sec2localdate), [sec2localtime](#sec2localtime), [strfntime](#strfntime), [strfntime_local](#strfntime_local), [strftime](#strftime), [strftime_local](#strftime_local), [strpntime](#strpntime), [strpntime_local](#strpntime_local), [strptime](#strptime), [strptime_local](#strptime_local), [sysntime](#sysntime), [systime](#systime), [systimeint](#systimeint), [upntime](#upntime), [uptime](#uptime). @@ -1156,6 +1156,15 @@ sort_collection (class=stats #args=1) This is a helper function for the percent +### sparkline +
+sparkline  (class=stats #args=1) Returns a string of Unicode block characters (one of ▁▂▃▄▅▆▇█ per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types.
+Examples:
+sparkline([1,2,3,4,5,6,7,8]) is "▁▂▃▄▅▆▇█"
+sparkline([3,3,3]) is "▁▁▁"
+
+ + ### stddev
 stddev  (class=stats #args=1) Returns the sample standard deviation of values in an array or map. Returns empty string AKA void for array/map of length less than two; returns error for non-array/non-map types.
diff --git a/docs/src/reference-verbs.md b/docs/src/reference-verbs.md
index 4a9eeb42b..a2fa85aee 100644
--- a/docs/src/reference-verbs.md
+++ b/docs/src/reference-verbs.md
@@ -54,7 +54,7 @@ These fall into categories as follows:
 
 * `awk`-like functionality: [filter](reference-verbs.md#filter), [put](reference-verbs.md#put), [sec2gmt](reference-verbs.md#sec2gmt), [sec2gmtdate](reference-verbs.md#sec2gmtdate), [step](reference-verbs.md#step), [tee](reference-verbs.md#tee).
 
-* Statistically oriented: [bar](reference-verbs.md#bar), [bootstrap](reference-verbs.md#bootstrap), [decimate](reference-verbs.md#decimate), [histogram](reference-verbs.md#histogram), [least-frequent](reference-verbs.md#least-frequent), [most-frequent](reference-verbs.md#most-frequent), [sample](reference-verbs.md#sample), [shuffle](reference-verbs.md#shuffle), [stats1](reference-verbs.md#stats1), [stats2](reference-verbs.md#stats2).
+* Statistically oriented: [bar](reference-verbs.md#bar), [bootstrap](reference-verbs.md#bootstrap), [decimate](reference-verbs.md#decimate), [histogram](reference-verbs.md#histogram), [least-frequent](reference-verbs.md#least-frequent), [most-frequent](reference-verbs.md#most-frequent), [sample](reference-verbs.md#sample), [shuffle](reference-verbs.md#shuffle), [sparkline](reference-verbs.md#sparkline), [stats1](reference-verbs.md#stats1), [stats2](reference-verbs.md#stats2).
 
 * Particularly oriented toward [Record Heterogeneity](record-heterogeneity.md), although all Miller commands can handle heterogeneous records: [group-by](reference-verbs.md#group-by), [group-like](reference-verbs.md#group-like), [having-fields](reference-verbs.md#having-fields).
 
@@ -1787,7 +1787,11 @@ Options:
 --auto      Automatically computes limits, ignoring --lo and --hi. Holds all
             values in memory before producing any output.
 -o {prefix} Prefix for output field name. Default: no prefix.
+-s          Print a one-line Unicode sparkline per field instead of per-bin
+            counts.
 -h|--help   Show this message.
+With -s, output is one record per value-field, with a sparkline field
+instead of one record per bin.
 
This is just a histogram; there's not too much to say here. A note about binning, by example: Suppose you use `--lo 0.0 --hi 1.0 --nbins 10 -f x`. The input numbers less than 0 or greater than 1 aren't counted in any bin. Input numbers equal to 1 are counted in the last bin. That is, bin 0 has `0.0 < x < 0.1`, bin 1 has `0.1 < x < 0.2`, etc., but bin 9 has `0.9 < x < 1.0`. @@ -1830,6 +1834,20 @@ my_bin_lo my_bin_hi my_x_count my_x2_count my_x3_count 0.9 1 1013 507 341 +Use `-s` for a compact one-line Unicode sparkline per field, rather than one output record per bin. Note this sparklines the *binned counts* -- i.e. the shape of the value distribution -- not the field's raw values in record order; for the latter, use [sparkline](reference-verbs.md#sparkline) instead: + +
+mlr --opprint put '$x2=$x**2;$x3=$x2*$x' \
+  then histogram -f x,x2,x3 --lo 0 --hi 1 --nbins 10 -s \
+  data/medium
+
+
+field lo hi sparkline
+x     0  1  █▁▆▄▂▄▅▅▄▅
+x2    0  1  █▃▂▂▂▁▁▁▁▁
+x3    0  1  █▂▂▂▁▁▁▁▁▁
+
+ ## join
@@ -3392,6 +3410,38 @@ a b c
 9 8 7
 
+## sparkline + +
+mlr sparkline --help
+
+
+Usage: mlr sparkline [options]
+Reduces numeric field(s), across all records in input order, to a compact
+Unicode sparkline -- one block character per record -- for visualizing
+trends. Emits one output record per field. Holds all records in memory
+before producing any output.
+Options:
+-f {a,b,c} Field names to sparkline.
+-h|--help  Show this message.
+
+ +This reduces one or more numeric fields, in input-record order, to a compact +Unicode sparkline -- a quick way to eyeball a trend across records without +plotting software. Contrast with [histogram](reference-verbs.md#histogram) +`-s`, which sparklines the *distribution* of a field's values (binned by +value, order-independent) rather than the field's values in record order. + +
+mlr --icsv --opprint sparkline -f index,quantity,rate example.csv
+
+
+field    n  lo      hi     sparkline
+index    10 11      91     ▁▁▁▄▅▆▆▆██
+quantity 10 13.8103 81.229 ▄█▁████▆▆▇
+rate     10 0.013   9.887  █▁▃▆▇█▅▄▇▇
+
+ ## sparsify
diff --git a/docs/src/reference-verbs.md.in b/docs/src/reference-verbs.md.in
index d469ffb80..ae897cf51 100644
--- a/docs/src/reference-verbs.md.in
+++ b/docs/src/reference-verbs.md.in
@@ -27,7 +27,7 @@ These fall into categories as follows:
 
 * `awk`-like functionality: [filter](reference-verbs.md#filter), [put](reference-verbs.md#put), [sec2gmt](reference-verbs.md#sec2gmt), [sec2gmtdate](reference-verbs.md#sec2gmtdate), [step](reference-verbs.md#step), [tee](reference-verbs.md#tee).
 
-* Statistically oriented: [bar](reference-verbs.md#bar), [bootstrap](reference-verbs.md#bootstrap), [decimate](reference-verbs.md#decimate), [histogram](reference-verbs.md#histogram), [least-frequent](reference-verbs.md#least-frequent), [most-frequent](reference-verbs.md#most-frequent), [sample](reference-verbs.md#sample), [shuffle](reference-verbs.md#shuffle), [stats1](reference-verbs.md#stats1), [stats2](reference-verbs.md#stats2).
+* Statistically oriented: [bar](reference-verbs.md#bar), [bootstrap](reference-verbs.md#bootstrap), [decimate](reference-verbs.md#decimate), [histogram](reference-verbs.md#histogram), [least-frequent](reference-verbs.md#least-frequent), [most-frequent](reference-verbs.md#most-frequent), [sample](reference-verbs.md#sample), [shuffle](reference-verbs.md#shuffle), [sparkline](reference-verbs.md#sparkline), [stats1](reference-verbs.md#stats1), [stats2](reference-verbs.md#stats2).
 
 * Particularly oriented toward [Record Heterogeneity](record-heterogeneity.md), although all Miller commands can handle heterogeneous records: [group-by](reference-verbs.md#group-by), [group-like](reference-verbs.md#group-like), [having-fields](reference-verbs.md#having-fields).
 
@@ -579,6 +579,14 @@ mlr --opprint put '$x2=$x**2;$x3=$x2*$x' \
   data/medium
 GENMD-EOF
 
+Use `-s` for a compact one-line Unicode sparkline per field, rather than one output record per bin. Note this sparklines the *binned counts* -- i.e. the shape of the value distribution -- not the field's raw values in record order; for the latter, use [sparkline](reference-verbs.md#sparkline) instead:
+
+GENMD-RUN-COMMAND
+mlr --opprint put '$x2=$x**2;$x3=$x2*$x' \
+  then histogram -f x,x2,x3 --lo 0 --hi 1 --nbins 10 -s \
+  data/medium
+GENMD-EOF
+
 ## join
 
 GENMD-RUN-COMMAND
@@ -1017,6 +1025,22 @@ GENMD-RUN-COMMAND
 mlr --ijson --opprint sort-within-records data/sort-within-records.json
 GENMD-EOF
 
+## sparkline
+
+GENMD-RUN-COMMAND
+mlr sparkline --help
+GENMD-EOF
+
+This reduces one or more numeric fields, in input-record order, to a compact
+Unicode sparkline -- a quick way to eyeball a trend across records without
+plotting software. Contrast with [histogram](reference-verbs.md#histogram)
+`-s`, which sparklines the *distribution* of a field's values (binned by
+value, order-independent) rather than the field's values in record order.
+
+GENMD-RUN-COMMAND
+mlr --icsv --opprint sparkline -f index,quantity,rate example.csv
+GENMD-EOF
+
 ## sparsify
 
 GENMD-RUN-COMMAND
diff --git a/man/manpage.txt b/man/manpage.txt
index b0d6322ca..56e848bb8 100644
--- a/man/manpage.txt
+++ b/man/manpage.txt
@@ -209,8 +209,8 @@
        histogram json-parse json-stringify join label latin1-to-utf8 least-frequent
        merge-fields most-frequent nest nothing put regularize remove-empty-columns
        rename reorder repeat reshape sample sec2gmtdate sec2gmt seqgen shuffle
-       skip-trivial-records sort sort-within-records sparsify split ssub stats1
-       stats2 step sub summary surv tac tail tee template top utf8-to-latin1
+       skip-trivial-records sort sort-within-records sparkline sparsify split ssub
+       stats1 stats2 step sub summary surv tac tail tee template top utf8-to-latin1
        unflatten uniq unspace unsparsify
 
 1mFUNCTION LIST0m
@@ -236,14 +236,14 @@
        null_count os percentile percentiles pow qnorm reduce regextract
        regextract_or_else rightpad round roundm rstrip sec2dhms sec2gmt sec2gmtdate
        sec2hms sec2localdate sec2localtime select sgn sha1 sha256 sha512 sin sinh
-       skewness sort sort_collection splita splitax splitkv splitkvx splitnv splitnvx
-       sqrt ssub stat stddev strfntime strfntime_local strftime strftime_local string
-       strip strlen strmatch strmatchx strpntime strpntime_local strptime
-       strptime_local sub substr substr0 substr1 sum sum2 sum3 sum4 sysntime system
-       systime systimeint tan tanh tolower toupper truncate typeof unflatten unformat
-       unformatx upntime uptime urand urand32 urandelement urandint urandrange
-       utf8_to_latin1 variance version ! != !=~ % & && * ** + - . .* .+ .- ./ / // <
-       << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~
+       skewness sort sort_collection sparkline splita splitax splitkv splitkvx
+       splitnv splitnvx sqrt ssub stat stddev strfntime strfntime_local strftime
+       strftime_local string strip strlen strmatch strmatchx strpntime
+       strpntime_local strptime strptime_local sub substr substr0 substr1 sum sum2
+       sum3 sum4 sysntime system systime systimeint tan tanh tolower toupper truncate
+       typeof unflatten unformat unformatx upntime uptime urand urand32 urandelement
+       urandint urandrange utf8_to_latin1 variance version ! != !=~ % & && * ** + - .
+       .* .+ .- ./ / // < << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~
 
 1mCOMMENTS-IN-DATA FLAGS0m
        Miller lets you put comments in your data, such as
@@ -1441,7 +1441,11 @@
        --auto      Automatically computes limits, ignoring --lo and --hi. Holds all
                    values in memory before producing any output.
        -o {prefix} Prefix for output field name. Default: no prefix.
+       -s          Print a one-line Unicode sparkline per field instead of per-bin
+                   counts.
        -h|--help   Show this message.
+       With -s, output is one record per value-field, with a sparkline field
+       instead of one record per bin.
 
    1mjson-parse0m
        Usage: mlr json-parse [options]
@@ -2062,6 +2066,16 @@
        -n         Sort field names naturally (e.g. 2 before 12). Combines with -f/-r.
        -h|--help  Show this message.
 
+   1msparkline0m
+       Usage: mlr sparkline [options]
+       Reduces numeric field(s), across all records in input order, to a compact
+       Unicode sparkline -- one block character per record -- for visualizing
+       trends. Emits one output record per field. Holds all records in memory
+       before producing any output.
+       Options:
+       -f {a,b,c} Field names to sparkline.
+       -h|--help  Show this message.
+
    1msparsify0m
        Usage: mlr sparsify [options]
        Unsets fields for which the key is the empty string (or, optionally, another
@@ -3263,6 +3277,12 @@
    1msort_collection0m
         (class=stats #args=1) This is a helper function for the percentiles function; please see its online help for details.
 
+   1msparkline0m
+        (class=stats #args=1) Returns a string of Unicode block characters (one of  per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types.
+       Examples:
+       sparkline([1,2,3,4,5,6,7,8]) is ""
+       sparkline([3,3,3]) is ""
+
    1msplita0m
         (class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on.
        Example:
@@ -4041,4 +4061,4 @@
        MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
        https://miller.readthedocs.io
 
-                                  2026-07-06                         4mMILLER24m(1)
+                                  2026-07-07                         4mMILLER24m(1)
diff --git a/man/mlr.1 b/man/mlr.1
index 1578a7e64..1d955610a 100644
--- a/man/mlr.1
+++ b/man/mlr.1
@@ -2,12 +2,12 @@
 .\"     Title: mlr
 .\"    Author: [see the "AUTHOR" section]
 .\" Generator: ./mkman.rb
-.\"      Date: 2026-07-06
+.\"      Date: 2026-07-07
 .\"    Manual: \ \&
 .\"    Source: \ \&
 .\"  Language: English
 .\"
-.TH "MILLER" "1" "2026-07-06" "\ \&" "\ \&"
+.TH "MILLER" "1" "2026-07-07" "\ \&" "\ \&"
 .\" -----------------------------------------------------------------
 .\" * Portability definitions
 .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -251,8 +251,8 @@ format-values fraction gap grep group-by group-like gsub having-fields head
 histogram json-parse json-stringify join label latin1-to-utf8 least-frequent
 merge-fields most-frequent nest nothing put regularize remove-empty-columns
 rename reorder repeat reshape sample sec2gmtdate sec2gmt seqgen shuffle
-skip-trivial-records sort sort-within-records sparsify split ssub stats1
-stats2 step sub summary surv tac tail tee template top utf8-to-latin1
+skip-trivial-records sort sort-within-records sparkline sparsify split ssub
+stats1 stats2 step sub summary surv tac tail tee template top utf8-to-latin1
 unflatten uniq unspace unsparsify
 .fi
 .if n \{\
@@ -284,14 +284,14 @@ minlen mmul mode msub next nsec2gmt nsec2gmtdate nsec2localdate nsec2localtime
 null_count os percentile percentiles pow qnorm reduce regextract
 regextract_or_else rightpad round roundm rstrip sec2dhms sec2gmt sec2gmtdate
 sec2hms sec2localdate sec2localtime select sgn sha1 sha256 sha512 sin sinh
-skewness sort sort_collection splita splitax splitkv splitkvx splitnv splitnvx
-sqrt ssub stat stddev strfntime strfntime_local strftime strftime_local string
-strip strlen strmatch strmatchx strpntime strpntime_local strptime
-strptime_local sub substr substr0 substr1 sum sum2 sum3 sum4 sysntime system
-systime systimeint tan tanh tolower toupper truncate typeof unflatten unformat
-unformatx upntime uptime urand urand32 urandelement urandint urandrange
-utf8_to_latin1 variance version ! != !=~ % & && * ** + - . .* .+ .- ./ / // <
-<< <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~
+skewness sort sort_collection sparkline splita splitax splitkv splitkvx
+splitnv splitnvx sqrt ssub stat stddev strfntime strfntime_local strftime
+strftime_local string strip strlen strmatch strmatchx strpntime
+strpntime_local strptime strptime_local sub substr substr0 substr1 sum sum2
+sum3 sum4 sysntime system systime systimeint tan tanh tolower toupper truncate
+typeof unflatten unformat unformatx upntime uptime urand urand32 urandelement
+urandint urandrange utf8_to_latin1 variance version ! != !=~ % & && * ** + - .
+\&.* .+ .- ./ / // < << <= <=> == =~ > >= >> >>> ?: ?? ??? ^ ^^ | || ~
 .fi
 .if n \{\
 .RE
@@ -1791,7 +1791,11 @@ Options:
 --auto      Automatically computes limits, ignoring --lo and --hi. Holds all
             values in memory before producing any output.
 -o {prefix} Prefix for output field name. Default: no prefix.
+-s          Print a one-line Unicode sparkline per field instead of per-bin
+            counts.
 -h|--help   Show this message.
+With -s, output is one record per value-field, with a sparkline field
+instead of one record per bin.
 .fi
 .if n \{\
 .RE
@@ -2564,6 +2568,22 @@ Options:
 .fi
 .if n \{\
 .RE
+.SS "sparkline"
+.if n \{\
+.RS 0
+.\}
+.nf
+Usage: mlr sparkline [options]
+Reduces numeric field(s), across all records in input order, to a compact
+Unicode sparkline -- one block character per record -- for visualizing
+trends. Emits one output record per field. Holds all records in memory
+before producing any output.
+Options:
+-f {a,b,c} Field names to sparkline.
+-h|--help  Show this message.
+.fi
+.if n \{\
+.RE
 .SS "sparsify"
 .if n \{\
 .RS 0
@@ -4931,6 +4951,18 @@ Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":
 .fi
 .if n \{\
 .RE
+.SS "sparkline"
+.if n \{\
+.RS 0
+.\}
+.nf
+ (class=stats #args=1) Returns a string of Unicode block characters (one of ▁▂▃▄▅▆▇█ per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types.
+Examples:
+sparkline([1,2,3,4,5,6,7,8]) is "▁▂▃▄▅▆▇█"
+sparkline([3,3,3]) is "▁▁▁"
+.fi
+.if n \{\
+.RE
 .SS "splita"
 .if n \{\
 .RS 0
diff --git a/pkg/bifs/sparkline.go b/pkg/bifs/sparkline.go
new file mode 100644
index 000000000..e791200b7
--- /dev/null
+++ b/pkg/bifs/sparkline.go
@@ -0,0 +1,72 @@
+package bifs
+
+import (
+	"github.com/johnkerl/miller/v6/pkg/mlrval"
+)
+
+// sparklineTicks are the eighth-height Unicode block characters used to
+// render array/map values as a compact one-line bar chart.
+var sparklineTicks = []rune("▁▂▃▄▅▆▇█")
+
+func BIF_sparkline(collection *mlrval.Mlrval) *mlrval.Mlrval {
+	ok, valueIfNot := check_collection(collection, "sparkline")
+	if !ok {
+		return valueIfNot
+	}
+
+	var floatValues []float64
+	if collection.IsArray() {
+		array := collection.AcquireArrayValue()
+		floatValues = make([]float64, 0, len(array))
+		for _, element := range array {
+			floatValue, isFloat := element.GetNumericToFloatValue()
+			if !isFloat {
+				return mlrval.FromNotNumericError("sparkline", element)
+			}
+			floatValues = append(floatValues, floatValue)
+		}
+	} else {
+		m := collection.AcquireMapValue()
+		floatValues = make([]float64, 0, m.FieldCount)
+		for pe := m.Head; pe != nil; pe = pe.Next {
+			floatValue, isFloat := pe.Value.GetNumericToFloatValue()
+			if !isFloat {
+				return mlrval.FromNotNumericError("sparkline", pe.Value)
+			}
+			floatValues = append(floatValues, floatValue)
+		}
+	}
+
+	if len(floatValues) == 0 {
+		return mlrval.VOID
+	}
+
+	lo := floatValues[0]
+	hi := floatValues[0]
+	for _, floatValue := range floatValues[1:] {
+		if floatValue < lo {
+			lo = floatValue
+		}
+		if floatValue > hi {
+			hi = floatValue
+		}
+	}
+
+	numTicks := len(sparklineTicks)
+	runes := make([]rune, len(floatValues))
+	for i, floatValue := range floatValues {
+		if hi == lo {
+			runes[i] = sparklineTicks[0]
+			continue
+		}
+		tickIndex := int(float64(numTicks-1)*(floatValue-lo)/(hi-lo) + 0.5)
+		if tickIndex < 0 {
+			tickIndex = 0
+		} else if tickIndex >= numTicks {
+			tickIndex = numTicks - 1
+		}
+		runes[i] = sparklineTicks[tickIndex]
+	}
+
+	return mlrval.FromString(string(runes))
+}
diff --git a/pkg/bifs/sparkline_test.go b/pkg/bifs/sparkline_test.go
new file mode 100644
index 000000000..858ab8c16
--- /dev/null
+++ b/pkg/bifs/sparkline_test.go
@@ -0,0 +1,60 @@
+package bifs
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/johnkerl/miller/v6/pkg/mlrval"
+)
+
+func TestBIF_sparkline(t *testing.T) {
+	// Needs array or map
+	input := mlrval.FromInt(3)
+	output := BIF_sparkline(input)
+	assert.True(t, output.IsError())
+
+	// Non-numeric element
+	input = mlrval.FromArray([]*mlrval.Mlrval{
+		mlrval.FromInt(1),
+		mlrval.FromString("abc"),
+	})
+	output = BIF_sparkline(input)
+	assert.True(t, output.IsError())
+
+	// Empty array is void
+	input = mlrval.FromArray([]*mlrval.Mlrval{})
+	output = BIF_sparkline(input)
+	assert.True(t, output.IsVoid())
+
+	// Ascending values span the full tick range, low to high
+	input = mlrval.FromArray([]*mlrval.Mlrval{
+		mlrval.FromInt(1),
+		mlrval.FromInt(2),
+		mlrval.FromInt(3),
+		mlrval.FromInt(4),
+		mlrval.FromInt(5),
+		mlrval.FromInt(6),
+		mlrval.FromInt(7),
+		mlrval.FromInt(8),
+	})
+	output = BIF_sparkline(input)
+	assert.True(t, mlrval.Equals(output, mlrval.FromString("▁▂▃▄▅▆▇█")))
+
+	// Same value throughout maps to the lowest tick
+	input = mlrval.FromArray([]*mlrval.Mlrval{
+		mlrval.FromInt(3),
+		mlrval.FromInt(3),
+		mlrval.FromInt(3),
+	})
+	output = BIF_sparkline(input)
+	assert.True(t, mlrval.Equals(output, mlrval.FromString("▁▁▁")))
+
+	// Map input follows insertion order
+	input = array_to_map_for_test(mlrval.FromArray([]*mlrval.Mlrval{
+		mlrval.FromInt(1),
+		mlrval.FromInt(8),
+	}))
+	output = BIF_sparkline(input)
+	assert.True(t, mlrval.Equals(output, mlrval.FromString("▁█")))
+}
diff --git a/pkg/dsl/cst/builtin_function_manager.go b/pkg/dsl/cst/builtin_function_manager.go
index 953717308..b5130afbd 100644
--- a/pkg/dsl/cst/builtin_function_manager.go
+++ b/pkg/dsl/cst/builtin_function_manager.go
@@ -1097,6 +1097,17 @@ is normally distributed.`,
 			},
 		},
 
+		{
+			name:      "sparkline",
+			class:     FUNC_CLASS_STATS,
+			help:      `Returns a string of Unicode block characters (one of ▁▂▃▄▅▆▇█ per element) representing the relative magnitudes of values in an array or map, for a compact ASCII/Unicode bar chart. Returns error for non-array/non-map types.`,
+			unaryFunc: bifs.BIF_sparkline,
+			examples: []string{
+				`sparkline([1,2,3,4,5,6,7,8]) is "▁▂▃▄▅▆▇█"`,
+				`sparkline([3,3,3]) is "▁▁▁"`,
+			},
+		},
+
 		{
 			name:      "mode",
 			class:     FUNC_CLASS_STATS,
diff --git a/pkg/transformers/aaa_transformer_table.go b/pkg/transformers/aaa_transformer_table.go
index a0f4aab5d..bb49e0098 100644
--- a/pkg/transformers/aaa_transformer_table.go
+++ b/pkg/transformers/aaa_transformer_table.go
@@ -62,6 +62,7 @@ var TRANSFORMER_LOOKUP_TABLE = []TransformerSetup{
 	SkipTrivialRecordsSetup,
 	SortSetup,
 	SortWithinRecordsSetup,
+	SparklineSetup,
 	SparsifySetup,
 	SplitSetup,
 	SsubSetup,
diff --git a/pkg/transformers/histogram.go b/pkg/transformers/histogram.go
index 53ccdc859..2c71e5474 100644
--- a/pkg/transformers/histogram.go
+++ b/pkg/transformers/histogram.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/johnkerl/miller/v6/pkg/bifs"
 	"github.com/johnkerl/miller/v6/pkg/cli"
 	"github.com/johnkerl/miller/v6/pkg/lib"
 	"github.com/johnkerl/miller/v6/pkg/mlrval"
@@ -21,6 +22,7 @@ var histogramOptions = []OptionSpec{
 	{Flag: "--nbins", Arg: "{n}", Type: "int", Desc: "Number of histogram bins. Defaults to 20."},
 	{Flag: "--auto", Type: "bool", Desc: "Automatically computes limits, ignoring --lo and --hi. Holds all values in memory before producing any output."},
 	{Flag: "-o", Arg: "{prefix}", Type: "string", Desc: "Prefix for output field name. Default: no prefix."},
+	{Flag: "-s", Type: "bool", Desc: "Print a one-line Unicode sparkline per field instead of per-bin counts."},
 }
 
 var HistogramSetup = TransformerSetup{
@@ -39,6 +41,8 @@ func transformerHistogramUsage(
 	fmt.Fprintf(o, "Just a histogram. Input values < lo or > hi are not counted.\n")
 	fmt.Fprintf(o, "Usage: %s %s [options]\n", argv0, verb)
 	WriteVerbOptions(o, histogramOptions)
+	fmt.Fprintf(o, "With -s, output is one record per value-field, with a sparkline field\n")
+	fmt.Fprintf(o, "instead of one record per bin.\n")
 }
 
 func transformerHistogramParseCLI(
@@ -61,6 +65,7 @@ func transformerHistogramParseCLI(
 	hi := 0.0
 	doAuto := false
 	outputPrefix := ""
+	doSparkline := false
 
 	var err error
 	for argi < argc /* variable increment: 1 or 2 depending on flag */ {
@@ -111,6 +116,9 @@ func transformerHistogramParseCLI(
 				return nil, err
 			}
 
+		case "-s":
+			doSparkline = true
+
 		default:
 			return nil, cli.VerbErrorf(verb, "option \"%s\" not recognized", opt)
 		}
@@ -140,6 +148,7 @@ func transformerHistogramParseCLI(
 		hi,
 		doAuto,
 		outputPrefix,
+		doSparkline,
 	)
 	if err != nil {
 		return nil, err
@@ -160,6 +169,7 @@ type TransformerHistogram struct {
 	countsByField      map[string][]int64
 	vectorsByFieldName map[string][]float64 // For auto-mode
 	outputPrefix       string
+	doSparkline        bool
 
 	recordTransformerFunc RecordTransformerFunc
 }
@@ -171,6 +181,7 @@ func NewTransformerHistogram(
 	hi float64,
 	doAuto bool,
 	outputPrefix string,
+	doSparkline bool,
 ) (*TransformerHistogram, error) {
 
 	countsByField := make(map[string][]int64)
@@ -185,6 +196,7 @@ func NewTransformerHistogram(
 		valueFieldNames: valueFieldNames,
 		countsByField:   countsByField,
 		outputPrefix:    outputPrefix,
+		doSparkline:     doSparkline,
 		nbins:           nbins,
 	}
 
@@ -256,10 +268,40 @@ func (tr *TransformerHistogram) ingestNonAuto(
 	}
 }
 
+// sparklineRecord summarizes a field's binned counts as a single record with
+// a Unicode-block sparkline field, rather than one record per bin.
+func (tr *TransformerHistogram) sparklineRecord(
+	valueFieldName string,
+	lo float64,
+	hi float64,
+) *mlrval.Mlrmap {
+	counts := tr.countsByField[valueFieldName]
+	countMlrvals := make([]*mlrval.Mlrval, len(counts))
+	for i, count := range counts {
+		countMlrvals[i] = mlrval.FromInt(count)
+	}
+	sparkline := bifs.BIF_sparkline(mlrval.FromArray(countMlrvals))
+
+	outrec := mlrval.NewMlrmapAsRecord()
+	outrec.PutReference(tr.outputPrefix+"field", mlrval.FromString(valueFieldName))
+	outrec.PutReference(tr.outputPrefix+"lo", mlrval.FromFloat(lo))
+	outrec.PutReference(tr.outputPrefix+"hi", mlrval.FromFloat(hi))
+	outrec.PutReference(tr.outputPrefix+"sparkline", sparkline)
+	return outrec
+}
+
 func (tr *TransformerHistogram) emitNonAuto(
 	endOfStreamContext *types.Context,
 	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
 ) {
+	if tr.doSparkline {
+		for _, valueFieldName := range tr.valueFieldNames {
+			outrec := tr.sparklineRecord(valueFieldName, tr.lo, tr.hi)
+			*outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(outrec, endOfStreamContext))
+		}
+		return
+	}
+
 	countFieldNames := make(map[string]string)
 	for _, valueFieldName := range tr.valueFieldNames {
 		countFieldNames[valueFieldName] = tr.outputPrefix + valueFieldName + "_count"
@@ -363,6 +405,14 @@ func (tr *TransformerHistogram) emitAuto(
 		}
 	}
 
+	if tr.doSparkline {
+		for _, valueFieldName := range tr.valueFieldNames {
+			outrec := tr.sparklineRecord(valueFieldName, lo, hi)
+			*outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(outrec, endOfStreamContext))
+		}
+		return
+	}
+
 	// Emission pass
 	countFieldNames := make(map[string]string)
 	for _, valueFieldName := range tr.valueFieldNames {
diff --git a/pkg/transformers/sparkline.go b/pkg/transformers/sparkline.go
new file mode 100644
index 000000000..5bf0bf802
--- /dev/null
+++ b/pkg/transformers/sparkline.go
@@ -0,0 +1,182 @@
+package transformers
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/johnkerl/miller/v6/pkg/bifs"
+	"github.com/johnkerl/miller/v6/pkg/cli"
+	"github.com/johnkerl/miller/v6/pkg/mlrval"
+	"github.com/johnkerl/miller/v6/pkg/types"
+)
+
+const verbNameSparkline = "sparkline"
+
+var sparklineOptions = []OptionSpec{
+	{Flag: "-f", Arg: "{a,b,c}", Type: "csv-list", Desc: "Field names to sparkline."},
+}
+
+var SparklineSetup = TransformerSetup{
+	Verb:         verbNameSparkline,
+	UsageFunc:    transformerSparklineUsage,
+	ParseCLIFunc: transformerSparklineParseCLI,
+	IgnoresInput: false,
+	Options:      sparklineOptions,
+}
+
+func transformerSparklineUsage(
+	o *os.File,
+) {
+	fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameSparkline)
+	fmt.Fprintf(o, "Reduces numeric field(s), across all records in input order, to a compact\n")
+	fmt.Fprintf(o, "Unicode sparkline -- one block character per record -- for visualizing\n")
+	fmt.Fprintf(o, "trends. Emits one output record per field. Holds all records in memory\n")
+	fmt.Fprintf(o, "before producing any output.\n")
+	WriteVerbOptions(o, sparklineOptions)
+}
+
+func transformerSparklineParseCLI(
+	pargi *int,
+	argc int,
+	args []string,
+	_ *cli.TOptions,
+	doConstruct bool, // false for first pass of CLI-parse, true for second pass
+) (RecordTransformer, error) {
+
+	// Skip the verb name from the current spot in the mlr command line
+	argi := *pargi
+	verb := args[argi]
+	argi++
+
+	// Parse local flags
+	var err error
+	var fieldNames []string = nil
+
+	for argi < argc /* variable increment: 1 or 2 depending on flag */ {
+		opt := args[argi]
+		if !strings.HasPrefix(opt, "-") {
+			break // No more flag options to process
+		}
+		if args[argi] == "--" {
+			break // All transformers must do this so main-flags can follow verb-flags
+		}
+		argi++
+
+		switch opt {
+		case "-h", "--help":
+			transformerSparklineUsage(os.Stdout)
+			return nil, cli.ErrHelpRequested
+
+		case "-f":
+			fieldNames, err = cli.VerbGetStringArrayArg(verb, opt, args, &argi, argc)
+			if err != nil {
+				return nil, err
+			}
+
+		default:
+			return nil, cli.VerbErrorf(verb, "option \"%s\" not recognized", opt)
+		}
+	}
+
+	if fieldNames == nil {
+		return nil, cli.VerbErrorf(verb, "-f field names required")
+	}
+
+	*pargi = argi
+	if !doConstruct { // All transformers must do this for main command-line parsing
+		return nil, nil
+	}
+
+	transformer, err := NewTransformerSparkline(fieldNames)
+	if err != nil {
+		return nil, err
+	}
+
+	return transformer, nil
+}
+
+type TransformerSparkline struct {
+	fieldNames    []string
+	valuesByField map[string][]*mlrval.Mlrval
+}
+
+func NewTransformerSparkline(
+	fieldNames []string,
+) (*TransformerSparkline, error) {
+	valuesByField := make(map[string][]*mlrval.Mlrval)
+	for _, fieldName := range fieldNames {
+		valuesByField[fieldName] = make([]*mlrval.Mlrval, 0)
+	}
+	return &TransformerSparkline{
+		fieldNames:    fieldNames,
+		valuesByField: valuesByField,
+	}, nil
+}
+
+func (tr *TransformerSparkline) Transform(
+	inrecAndContext *types.RecordAndContext,
+	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
+	inputDownstreamDoneChannel <-chan bool,
+	outputDownstreamDoneChannel chan<- bool,
+) {
+	HandleDefaultDownstreamDone(inputDownstreamDoneChannel, outputDownstreamDoneChannel)
+
+	if !inrecAndContext.EndOfStream {
+		inrec := inrecAndContext.Record
+		for _, fieldName := range tr.fieldNames {
+			mvalue := inrec.Get(fieldName)
+			if mvalue != nil {
+				tr.valuesByField[fieldName] = append(tr.valuesByField[fieldName], mvalue.Copy())
+			}
+		}
+		return
+	}
+
+	// Else, end of stream: emit one summary record per field.
+	for _, fieldName := range tr.fieldNames {
+		values := tr.valuesByField[fieldName]
+
+		outrec := mlrval.NewMlrmapAsRecord()
+		outrec.PutReference("field", mlrval.FromString(fieldName))
+		outrec.PutReference("n", mlrval.FromInt(int64(len(values))))
+
+		sparkline := bifs.BIF_sparkline(mlrval.FromArray(values))
+		if !sparkline.IsError() {
+			lo, hi, haveLoHi := floatRangeOf(values)
+			if haveLoHi {
+				outrec.PutReference("lo", mlrval.FromFloat(lo))
+				outrec.PutReference("hi", mlrval.FromFloat(hi))
+			}
+		}
+		outrec.PutReference("sparkline", sparkline)
+
+		*outputRecordsAndContexts = append(*outputRecordsAndContexts, types.NewRecordAndContext(outrec, &inrecAndContext.Context))
+	}
+
+	*outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext) // Emit the end-of-stream marker
+}
+
+// floatRangeOf returns the min and max of the numeric values, and whether
+// any were found.
+func floatRangeOf(values []*mlrval.Mlrval) (lo float64, hi float64, haveLoHi bool) {
+	for _, value := range values {
+		floatValue, isFloat := value.GetNumericToFloatValue()
+		if !isFloat {
+			continue
+		}
+		if !haveLoHi {
+			lo = floatValue
+			hi = floatValue
+			haveLoHi = true
+		} else {
+			if floatValue < lo {
+				lo = floatValue
+			}
+			if floatValue > hi {
+				hi = floatValue
+			}
+		}
+	}
+	return lo, hi, haveLoHi
+}
diff --git a/test/cases/cli-help/0001/expout b/test/cases/cli-help/0001/expout
index 51ccd63c4..9399b12b3 100644
--- a/test/cases/cli-help/0001/expout
+++ b/test/cases/cli-help/0001/expout
@@ -474,7 +474,11 @@ Options:
 --auto      Automatically computes limits, ignoring --lo and --hi. Holds all
             values in memory before producing any output.
 -o {prefix} Prefix for output field name. Default: no prefix.
+-s          Print a one-line Unicode sparkline per field instead of per-bin
+            counts.
 -h|--help   Show this message.
+With -s, output is one record per value-field, with a sparkline field
+instead of one record per bin.
 
 ================================================================
 json-parse
@@ -1120,6 +1124,17 @@ Options:
 -n         Sort field names naturally (e.g. 2 before 12). Combines with -f/-r.
 -h|--help  Show this message.
 
+================================================================
+sparkline
+Usage: mlr sparkline [options]
+Reduces numeric field(s), across all records in input order, to a compact
+Unicode sparkline -- one block character per record -- for visualizing
+trends. Emits one output record per field. Holds all records in memory
+before producing any output.
+Options:
+-f {a,b,c} Field names to sparkline.
+-h|--help  Show this message.
+
 ================================================================
 sparsify
 Usage: mlr sparsify [options]
diff --git a/test/cases/dsl-stats/sparkline/various/cmd b/test/cases/dsl-stats/sparkline/various/cmd
new file mode 100644
index 000000000..8e64fdff2
--- /dev/null
+++ b/test/cases/dsl-stats/sparkline/various/cmd
@@ -0,0 +1 @@
+mlr -n --ofmtf 6 --xtab put -f ${CASEDIR}/mlr
diff --git a/test/cases/dsl-stats/sparkline/various/experr b/test/cases/dsl-stats/sparkline/various/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/dsl-stats/sparkline/various/expout b/test/cases/dsl-stats/sparkline/various/expout
new file mode 100644
index 000000000..32ab67b85
--- /dev/null
+++ b/test/cases/dsl-stats/sparkline/various/expout
@@ -0,0 +1,22 @@
+sparkline_0                     (error)
+sparkline_0_type                error
+sparkline_null                  (error)
+sparkline_null_type             error
+sparkline_empty_array           
+sparkline_empty_array_type      empty
+sparkline_array_1               ▁
+sparkline_array_1_type          string
+sparkline_array_flat            ▁▁▁
+sparkline_array_flat_type       string
+sparkline_array_ascending       ▁▂▃▄▅▆▇█
+sparkline_array_ascending_type  string
+sparkline_array_descending      █▇▆▅▄▃▂▁
+sparkline_array_descending_type string
+sparkline_array_nonnumeric      (error)
+sparkline_array_nonnumeric_type error
+sparkline_empty_map             
+sparkline_empty_map_type        empty
+sparkline_map_1                 ▁
+sparkline_map_1_type            string
+sparkline_map_ascending         ▁▄█
+sparkline_map_ascending_type    string
diff --git a/test/cases/dsl-stats/sparkline/various/mlr b/test/cases/dsl-stats/sparkline/various/mlr
new file mode 100644
index 000000000..c99e5796f
--- /dev/null
+++ b/test/cases/dsl-stats/sparkline/various/mlr
@@ -0,0 +1,27 @@
+end {
+    outputs = {};
+
+    outputs["sparkline_0"] = sparkline(0);
+    outputs["sparkline_null"] = sparkline(null);
+    outputs["sparkline_nonesuch"] = sparkline(nonesuch);
+
+    outputs["sparkline_empty_array"] = sparkline([]);
+    outputs["sparkline_array_1"] = sparkline([7]);
+    outputs["sparkline_array_flat"] = sparkline([3,3,3]);
+    outputs["sparkline_array_ascending"] = sparkline([1,2,3,4,5,6,7,8]);
+    outputs["sparkline_array_descending"] = sparkline([8,7,6,5,4,3,2,1]);
+    outputs["sparkline_array_nonnumeric"] = sparkline([1,"abc"]);
+
+    outputs["sparkline_empty_map"] = sparkline({});
+    outputs["sparkline_map_1"] = sparkline({ "a" : 7 });
+    outputs["sparkline_map_ascending"] = sparkline({ "a" : 1, "b" : 4, "c" : 8 });
+
+    typed_outputs = {};
+
+    for (k, v in outputs) {
+        typed_outputs[k] = v;
+        typed_outputs[k."_type"] = typeof(v);
+    }
+
+    emit typed_outputs;
+}
diff --git a/test/cases/verb-histogram/0008/cmd b/test/cases/verb-histogram/0008/cmd
new file mode 100644
index 000000000..7f38f998b
--- /dev/null
+++ b/test/cases/verb-histogram/0008/cmd
@@ -0,0 +1 @@
+mlr --icsv --opprint histogram --nbins 9 --auto -f x,y -s test/input/histo2.csv
diff --git a/test/cases/verb-histogram/0008/experr b/test/cases/verb-histogram/0008/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/verb-histogram/0008/expout b/test/cases/verb-histogram/0008/expout
new file mode 100644
index 000000000..fb9591d33
--- /dev/null
+++ b/test/cases/verb-histogram/0008/expout
@@ -0,0 +1,3 @@
+field lo          hi           sparkline
+x     20.00000000 300.00000000 ▁▁▅▁▁█▁▁▅
+y     20.00000000 300.00000000 █▃▁▁▁▁▁▁▁
diff --git a/test/cases/verb-sparkline/0001/cmd b/test/cases/verb-sparkline/0001/cmd
new file mode 100644
index 000000000..a9f76465a
--- /dev/null
+++ b/test/cases/verb-sparkline/0001/cmd
@@ -0,0 +1 @@
+mlr --ojson sparkline -f i,x,y test/input/abixy
diff --git a/test/cases/verb-sparkline/0001/experr b/test/cases/verb-sparkline/0001/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/verb-sparkline/0001/expout b/test/cases/verb-sparkline/0001/expout
new file mode 100644
index 000000000..64dfa4158
--- /dev/null
+++ b/test/cases/verb-sparkline/0001/expout
@@ -0,0 +1,23 @@
+[
+{
+  "field": "i",
+  "n": 10,
+  "lo": 1.00000000,
+  "hi": 10.00000000,
+  "sparkline": "▁▂▃▃▄▅▆▆▇█"
+},
+{
+  "field": "x",
+  "n": 10,
+  "lo": 0.03144188,
+  "hi": 0.75867996,
+  "sparkline": "▄█▃▄▆▆▇▆▁▆"
+},
+{
+  "field": "y",
+  "n": 10,
+  "lo": 0.13418874,
+  "hi": 0.97618139,
+  "sparkline": "▆▄▃▁▇▄▁█▆█"
+}
+]
diff --git a/test/cases/verb-sparkline/0002/cmd b/test/cases/verb-sparkline/0002/cmd
new file mode 100644
index 000000000..8f0ce6a63
--- /dev/null
+++ b/test/cases/verb-sparkline/0002/cmd
@@ -0,0 +1 @@
+mlr --opprint sparkline -f i test/input/abixy
diff --git a/test/cases/verb-sparkline/0002/experr b/test/cases/verb-sparkline/0002/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/verb-sparkline/0002/expout b/test/cases/verb-sparkline/0002/expout
new file mode 100644
index 000000000..5b20af9bc
--- /dev/null
+++ b/test/cases/verb-sparkline/0002/expout
@@ -0,0 +1,2 @@
+field n  lo         hi          sparkline
+i     10 1.00000000 10.00000000 ▁▂▃▃▄▅▆▆▇█
diff --git a/test/cases/verb-sparkline/0003/cmd b/test/cases/verb-sparkline/0003/cmd
new file mode 100644
index 000000000..cb9aaf4c2
--- /dev/null
+++ b/test/cases/verb-sparkline/0003/cmd
@@ -0,0 +1 @@
+mlr sparkline --help
diff --git a/test/cases/verb-sparkline/0003/experr b/test/cases/verb-sparkline/0003/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/verb-sparkline/0003/expout b/test/cases/verb-sparkline/0003/expout
new file mode 100644
index 000000000..07eea812c
--- /dev/null
+++ b/test/cases/verb-sparkline/0003/expout
@@ -0,0 +1,8 @@
+Usage: mlr sparkline [options]
+Reduces numeric field(s), across all records in input order, to a compact
+Unicode sparkline -- one block character per record -- for visualizing
+trends. Emits one output record per field. Holds all records in memory
+before producing any output.
+Options:
+-f {a,b,c} Field names to sparkline.
+-h|--help  Show this message.
diff --git a/test/cases/verb-sparkline/0004/cmd b/test/cases/verb-sparkline/0004/cmd
new file mode 100644
index 000000000..cd795af18
--- /dev/null
+++ b/test/cases/verb-sparkline/0004/cmd
@@ -0,0 +1 @@
+mlr --ojson sparkline -f nonesuch,a test/input/abixy
diff --git a/test/cases/verb-sparkline/0004/experr b/test/cases/verb-sparkline/0004/experr
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/cases/verb-sparkline/0004/expout b/test/cases/verb-sparkline/0004/expout
new file mode 100644
index 000000000..4e6d4f65b
--- /dev/null
+++ b/test/cases/verb-sparkline/0004/expout
@@ -0,0 +1,12 @@
+[
+{
+  "field": "nonesuch",
+  "n": 0,
+  "sparkline": ""
+},
+{
+  "field": "a",
+  "n": 10,
+  "sparkline": (error)
+}
+]