Nushell Carts Combo A developer used Nushell to analyze grocery store transactions and identify the top five product pairs purchased together most often. The analysis revealed that Salsa and Tortilla Chips are the most common pair, followed by Bananas and Milk, Bananas and Bread, Bananas and Salsa, and Bananas and Tortilla Chips. The developer also benchmarked different approaches, with a Polars-based Nushell script completing the task in 0.5 seconds. | /usr/bin/env nu | | | PR 2026-07-15 11:23:32 | | | https://mavenanalytics.io/data-drills/cart-combos | | | Your dataset contains one year of point-of-sale transactions | | | from a grocery store, including approximately 88,000 line items | | | across 20,000 customer orders. | | | | | | Your task is to identify the five product pairs that are purchased | | | together most often. | | | | | | Two products are "purchased together" if they appear in the same | | | transaction | | | | | | A pair must consist of two different products | | | | | | Product pairs are unordered, meaning A, B and B, A should | | | be treated as the same pair | | | | | | Count each pair once per transaction, regardless of the quantity | | | purchased | | | | | | code from gemini 36 seconds | | | Load the transactions and select only the relevant columns | | | open grocery transactions.csv | | | | select transaction id product name | | | Remove duplicate products within the same transaction to count them once per transaction | | | | uniq | | | Group the dataset by transaction ID | | | | group-by transaction id | | | | values | | | For each transaction, generate all unique product pairs | | | | each { |tx| | | | let products = $tx.product name | sort | | | $products | enumerate | each { |item| | | | let idx = $item.index | | | let p1 = $item.item | | | $products | skip $idx + 1 | each { |p2| | | | { product a: $p1, product b: $p2 } | | | } | | | } | flatten | | | } | | | | flatten | | | Count the occurrences of each unique pair | | | | uniq --count | | | Sort by count descending and take the top 5 | | | | sort-by --reverse count | | | | first 5 | | | ╭───┬────────────────────────────────┬───────╮ | | | │ │ value │ count │ | | | ├───┼────────────────────────────────┼───────┤ | | | │ 0 │ ╭───────────┬────────────────╮ │ 5358 │ | | | │ │ │ product a │ Salsa │ │ │ | | | │ │ │ product b │ Tortilla Chips │ │ │ | | | │ │ ╰───────────┴────────────────╯ │ │ | | | │ 1 │ ╭───────────┬─────────╮ │ 4483 │ | | | │ │ │ product a │ Bananas │ │ │ | | | │ │ │ product b │ Milk │ │ │ | | | │ │ ╰───────────┴─────────╯ │ │ | | | │ 2 │ ╭───────────┬─────────╮ │ 2970 │ | | | │ │ │ product a │ Bananas │ │ │ | | | │ │ │ product b │ Bread │ │ │ | | | │ │ ╰───────────┴─────────╯ │ │ | | | │ 3 │ ╭───────────┬─────────╮ │ 2874 │ | | | │ │ │ product a │ Bananas │ │ │ | | | │ │ │ product b │ Salsa │ │ │ | | | │ │ ╰───────────┴─────────╯ │ │ | | | │ 4 │ ╭───────────┬────────────────╮ │ 2857 │ | | | │ │ │ product a │ Bananas │ │ │ | | | │ │ │ product b │ Tortilla Chips │ │ │ | | | │ │ ╰───────────┴────────────────╯ │ │ | | | ╰───┴────────────────────────────────┴───────╯ | | | | | | code from lumo.ai 30 seconds | | | Top 5 product pairs purchased together most often | | | open grocery transactions.csv | | | | select transaction id product name | | | | group-by transaction id | | | | items { |tid, rows| | | | let products = $rows.product name | uniq | sort | | | let n = $products | length | | | if $n < 2 { } else { | | | seq 0 $n - 2 | | | | each { |i| | | | seq $i + 1 $n - 1 | | | | each { |j| | | | $" $products | get $i × $products | get $j " | | | } | | | } | | | | flatten | | | } | | | } | | | | flatten | | | | uniq --count | | | | sort-by count --reverse | | | | first 5 | | | ╭───┬──────────────────────────┬───────╮ | | | │ │ value │ count │ | | | ├───┼──────────────────────────┼───────┤ | | | │ 0 │ Salsa × Tortilla Chips │ 5358 │ | | | │ 1 │ Bananas × Milk │ 4483 │ | | | │ 2 │ Bananas × Bread │ 2970 │ | | | │ 3 │ Bananas × Salsa │ 2874 │ | | | │ 4 │ Bananas × Tortilla Chips │ 2857 │ | | | ╰───┴──────────────────────────┴───────╯ | | | | | | nushell with polars 0.5 seconds | | | from gemini | | | 1. Load the CSV file as a Polars LazyFrame and select relevant columns | | | let df = polars open grocery transactions.csv | | | | polars select transaction id product name | | | Remove duplicate products within the same transaction | | | | polars unique --subset transaction id product name | | | | | | 2. Prepare a clone of the DataFrame for the self-join | | | let df right = $df | | | | polars rename product name product b | | | | | | Rename the original DataFrame's product column for matching | | | let df left = $df | | | | polars rename product name product a | | | | | | 3. Perform the self-join, filter, group, count, sort, and fetch top 5 | | | $df left | | | | polars join $df right transaction id transaction id | | | Keep only pairs where product a comes alphabetically before product b | | | | polars filter-with polars col product a < polars col product b | | | Group by the distinct product pairs | | | | polars group-by product a product b | | | Count occurrences of each pair | | | | polars agg | | | polars col transaction id | polars count | polars as count | | | | | | Sort by count descending Passing ' true ' to '--reverse' | | | | polars sort-by count --reverse true | | | Fetch the top 5 pairs | | | | polars first 5 | | | Convert back to a standard Nushell table to view | | | | polars collect | | | | polars into-nu | | | ╭───┬───────────┬────────────────┬───────╮ | | | │ │ product a │ product b │ count │ | | | ├───┼───────────┼────────────────┼───────┤ | | | │ 0 │ Salsa │ Tortilla Chips │ 5358 │ | | | │ 1 │ Bananas │ Milk │ 4483 │ | | | │ 2 │ Bananas │ Bread │ 2970 │ | | | │ 3 │ Bananas │ Salsa │ 2874 │ | | | │ 4 │ Bananas │ Tortilla Chips │ 2857 │ | | | ╰───┴───────────┴────────────────┴───────╯ | | | | | | nushell with polars 0.5 seconds | | | from luno.ai | | | Load, select relevant columns, deduplicate transaction id, product name pairs | | | let df = | | | polars open grocery transactions.csv | | | | polars select transaction id product name | | | | polars drop-duplicates | | | | | | Create left/right copies with renamed product columns for self-join | | | let df left = $df | polars rename product name prod a | | | let df right = $df | polars rename product name prod b | | | Self-join on transaction id to generate all product combinations per transaction | | | $df left | | | | polars join $df right transaction id transaction id | | | Keep only unique unordered pairs prod a < prod b eliminates self-pairs and dupes | | | | polars filter polars col prod a < polars col prod b | | | Count how many transactions contain each pair | | | | polars group-by prod a prod b | | | | polars agg polars len | polars as "count" | | | | polars collect | | | Sort by count descending and take top 5 | | | | polars sort-by count -r true | | | | polars first 5 | | | ╭───┬─────────┬────────────────┬───────╮ | | | │ │ prod a │ prod b │ count │ | | | ├───┼─────────┼────────────────┼───────┤ | | | │ 0 │ Salsa │ Tortilla Chips │ 5358 │ | | | │ 1 │ Bananas │ Milk │ 4483 │ | | | │ 2 │ Bananas │ Bread │ 2970 │ | | | │ 3 │ Bananas │ Salsa │ 2874 │ | | | │ 4 │ Bananas │ Tortilla Chips │ 2857 │ | | | ╰───┴─────────┴────────────────┴───────╯ | | | | | | xan code with nushell 0.5 seconds | | | from lumo.ai | | | Top 5 product pairs using Xan final corrected version | | | try {rm temp.csv} | | | Step 1: Select and deduplicate | | | xan select transaction id,product name grocery transactions.csv | | | | xan dedup -s transaction id,product name | | | | save temp.csv | | | Step 2: Self-join, filter to unique unordered pairs, create pair column | | | xan join -r right -D left transaction id temp.csv temp.csv | | | | xan filter "lt product name, right product name " | | | | xan map "concat product name, ' & ', right product name as pair" | | | Step 3: Frequency already returns results sorted by count descending | | | frequency -s pair -l 5 gives us the top 5 pairs by count | | | | xan frequency -s pair -l 5 | | | Step 4: Convert to Nushell table | | | | from csv | | | Cleanup | | | rm temp.csv | | | ╭───┬───────┬──────────────────────────┬────────╮ | | | │ │ field │ value │ count │ | | | ├───┼───────┼──────────────────────────┼────────┤ | | | │ 0 │ pair │ Salsa & Tortilla Chips │ 5358 │ | | | │ 1 │ pair │ Bananas & Milk │ 4483 │ | | | │ 2 │ pair │ Bananas & Bread │ 2970 │ | | | │ 3 │ pair │ Bananas & Salsa │ 2874 │ | | | │ 4 │ pair │ Bananas & Tortilla Chips │ 2857 │ | | | │ 5 │ pair │