<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>terminal-viz on Alán's blog</title><link>https://quasimorphic.com/tags/terminal-viz/</link><description>Recent content in terminal-viz on Alán's blog</description><generator>Hugo</generator><language>en-uk</language><lastBuildDate>Wed, 22 Oct 2025 20:34:00 -0400</lastBuildDate><atom:link href="https://quasimorphic.com/tags/terminal-viz/index.xml" rel="self" type="application/rss+xml"/><item><title>Calculate the cumulative sum of a column using DuckDB</title><link>https://quasimorphic.com/archive/duckdb_cumsum/</link><pubDate>Wed, 22 Oct 2025 20:34:00 -0400</pubDate><guid>https://quasimorphic.com/archive/duckdb_cumsum/</guid><description>&lt;p>Duckdb, the (tabular) data exploration tool I use supports window operations. I recently discovered that it can also perform cumulative sums in a very efficient manner.&lt;/p>
&lt;p>Let us generate a toy dataset where we want to calculate the sum of one column relative to the order of another one.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">-- seeding for reproducibility, creating a table to hide output
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> seed &lt;span style="color:#66d9ef">AS&lt;/span> &lt;span style="color:#66d9ef">SELECT&lt;/span> SETSEED(&lt;span style="color:#ae81ff">0&lt;/span>.&lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- Create a mock dataset with two integer columns
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">OR&lt;/span> &lt;span style="color:#66d9ef">REPLACE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span> my_table &lt;span style="color:#66d9ef">AS&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">#&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">AS&lt;/span> column_1,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">CAST&lt;/span>(FLOOR(RANDOM() &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">100&lt;/span>) &lt;span style="color:#66d9ef">AS&lt;/span> INT) &lt;span style="color:#66d9ef">AS&lt;/span> column_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span> generate_series(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">10&lt;/span>); &lt;span style="color:#75715e">-- This generates 10 rows
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">FROM&lt;/span> my_table;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">-- We write it to a csv for future use
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> my_table &lt;span style="color:#66d9ef">TO&lt;/span> my_table.csv;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>┌──────────┬──────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ column_1 │ column_2 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ int64 │ int32 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├──────────┼──────────┤
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 1 │ 27 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 2 │ 45 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 3 │ 2 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 4 │ 84 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 5 │ 84 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 6 │ 26 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 7 │ 18 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 8 │ 65 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 9 │ 97 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 10 │ 11 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├──────────┴──────────┤
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 10 rows 2 columns │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└─────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we wanted to calculate the distribution of the cumulative sum of the table we could use the &lt;code>OVER&lt;/code> clause to perform the sum of &lt;code>column_2&lt;/code> in the order defined by &lt;code>column_1&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">SELECT&lt;/span> &lt;span style="color:#f92672">*&lt;/span>, &lt;span style="color:#66d9ef">sum&lt;/span>(column_2) OVER (&lt;span style="color:#66d9ef">ORDER&lt;/span> &lt;span style="color:#66d9ef">by&lt;/span> column_1) &lt;span style="color:#66d9ef">AS&lt;/span> cumulative_sum &lt;span style="color:#66d9ef">FROM&lt;/span> my_table
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>┌──────────┬──────────┬────────────────┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ column_1 │ column_2 │ cumulative_sum │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ int64 │ int32 │ int128 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├──────────┼──────────┼────────────────┤
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 1 │ 27 │ 27 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 2 │ 45 │ 72 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 3 │ 2 │ 74 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 4 │ 84 │ 158 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 5 │ 84 │ 242 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 6 │ 26 │ 268 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 7 │ 18 │ 286 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 8 │ 65 │ 351 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 9 │ 97 │ 448 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 10 │ 11 │ 459 │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├──────────┴──────────┴────────────────┤
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│ 10 rows 3 columns │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└──────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The cumulative sum can be pretty handy to get a general notion of a distribution. As a bonus tip, I&amp;rsquo;ll show how to use duckdb in a one-liner to plot the data
directly in a terminal by using &lt;a href="https://gnuplotting.org/">gnuplot&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>duckdb -csv -c &lt;span style="color:#e6db74">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> SELECT *, sum(column_2) OVER (ORDER by column_1) AS cumulative_sum
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> FROM read_csv(&amp;#39;my_table.csv&amp;#39;);&amp;#34;&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gnuplot -e &lt;span style="color:#e6db74">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set terminal dumb;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set datafile separator &amp;#39;,&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set style data histograms;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set style fill solid 1.00 border -1;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set xlabel &amp;#39;Column 1&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set ylabel &amp;#39;CSum&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set title &amp;#39;Cumulative Sum of values&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> plot &amp;#39;-&amp;#39; using 3:xtic(1);&amp;#34;&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tr -d &lt;span style="color:#e6db74">&amp;#39;\014&amp;#39;&lt;/span> &lt;span style="color:#75715e"># Remove a pesky ^L at the top&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Cumulative Sum of values
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 500 +-----------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | + + + + + + + + + ++ |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 450 |-+ &amp;#39;-&amp;#39; using 3:xtic+-+ +-||--+-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 400 |-+ |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 350 |-+ ++ |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | || |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 300 |-+ +-+ || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 250 |-+ ++ |#| || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CSum | +-+ || |#| || |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 200 |-+ |#| || |#| || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | |#| || |#| || |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 150 |-+ ++ |#| || |#| || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | || |#| || |#| || |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 100 |-+ +-+ || |#| || |#| || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 50 |-+ ++ |#| || |#| || |#| || |#| || +-|
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | +-+ || |#| || |#| || |#| || |#| || |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 0 +-----------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 1 2 3 4 5 6 7 8 9 10
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Column 1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We get a cute ascii-like plot! That is a bit too long of a &amp;ldquo;one-liner&amp;rdquo;, I&amp;rsquo;ll go through the commands:&lt;/p>
&lt;ul>
&lt;li>Run a &lt;code>duckdb&lt;/code> command (&lt;code>-c&lt;/code>) that reads the previously-saved table. The &lt;code>-csv&lt;/code> flag at the starts converts the output to csv.&lt;/li>
&lt;li>Run gnuplot with certain specifications:
&lt;ul>
&lt;li>The flag &lt;code>-e&lt;/code> Allows to pass a series of commands without an interactive session.&lt;/li>
&lt;li>&lt;code>set terminal dumb&lt;/code>: it will send as plain text to stdout.&lt;/li>
&lt;li>&lt;code>set datafiler separator &amp;quot;,&amp;quot;&lt;/code>: The input is a CSV file.&lt;/li>
&lt;li>&lt;code>set style data histograms&lt;/code>: Changes the plotting style into a barplot.&lt;/li>
&lt;li>&lt;code>set style fill solid ...&lt;/code>: Visual adjustments to the bars for clarity.&lt;/li>
&lt;li>&lt;code>set xlabel ...&lt;/code> Adds the axis labels. Similar for &lt;code>ylabel&lt;/code> and &lt;code>title&lt;/code>.&lt;/li>
&lt;li>&lt;code>plot '-' using 3:xtic(1)&lt;/code>: Use stdin data to Plot the columns 3 on the y axis (&lt;code>cumulative_sum&lt;/code>) and the first column in the x-axis (&lt;code>column_1&lt;/code>).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Lastly, use the &lt;code>tr&lt;/code> command line tool to remove a &lt;code>^L&lt;/code> That appeared at the start of the output and was bothering me too much.&lt;/li>
&lt;/ul>
&lt;p>While there are a many other ways to wrangle tables such as via pandas or polars in Python, I find duckdb to be a powerful tool for exploratory analyses and data wrangling (often from within Python). It is flexible enough to be used by itself, via bindings in another language, or directly on the command line. Lastly, I showed that when used as a Command Line Interface (CLI) duckdb synergises with other tools for data visualisation from the comfort(?) of the terminal.&lt;/p></description></item>/</channel></rss>