Featured image of post E-Commerce Sales Intelligence with DuckDB: Pareto, MoM Anomaly Detection & RFM Analysis

E-Commerce Sales Intelligence with DuckDB: Pareto, MoM Anomaly Detection & RFM Analysis

Build an e-commerce sales analysis engine with DuckDB covering Pareto analysis, month-over-month anomaly detection, and RFM high-value customer identification. From 2 hours to 30 seconds with zero-copy Python integration.

E-Commerce Sales Intelligence with DuckDB: Pareto, MoM Anomaly Detection & RFM Analysis

Last week I built an automated sales analysis system for a cross-border e-commerce friend using DuckDB. He used the monthly report directly in an investor pitch and paid me 3,000 RMB for the consulting work.

The core idea is simple: DuckDB + Python compresses 2 hours of Excel pivot table work into 30 seconds, while producing actionable insights that drive decisions.

E-Commerce Sales Analysis Engine Architecture


1. Why DuckDB for E-Commerce Analysis?

The friend runs a DTC store with clothing and electronics lines. The data source is Shopify-exported order CSVs (~20K-50K rows per month). The pain points are universal:

  • Monthly manual report creation (trends, category contribution, channel performance) takes over 2 hours
  • When asked “Why did electronics drop this month?” — he can only guess
  • Hiring a data analyst costs 15K+ RMB/month, which has poor ROI

DuckDB hits the sweet spot:

  1. SQL on CSV — most analysts already know SQL; no new language to learn
  2. Zero configurationpip install duckdb and you’re done
  3. 10x+ faster than Pandas — columnar storage + SIMD optimization, sub-second response on 50K rows

Excel chokes on 50K+ rows. Pandas requires verbose Python code. DuckDB lets you express complex analysis logic in familiar SQL while getting near-native performance.


2. The Zero-Copy Trick: con.register()

Before writing any analysis, here’s a critical technique: con.register().

import duckdb
import pandas as pd

df = pd.read_csv("shopify_orders.csv")
con = duckdb.connect(":memory:")
con.register("orders", df)

Many people write con.execute("CREATE TABLE orders AS SELECT * FROM df") instead. That’s unnecessary. register() is DuckDB’s patented feature — it maps a Pandas DataFrame as a virtual table with zero copy, zero delay. For monthly analysis workflows, this eliminates data import overhead so you focus purely on query logic.


The most fundamental layer. The question every boss asks: “Which category performed best last month?”

trend_sql = """
SELECT 
    strftime(order_date, '%Y-%m') as month,
    category,
    SUM(revenue) as monthly_revenue,
    SUM(quantity) as monthly_quantity,
    AVG(revenue) as avg_order_value,
    COUNT(DISTINCT customer_id) as unique_customers
FROM orders 
WHERE order_date >= '2024-01-01'
GROUP BY month, category
ORDER BY month, monthly_revenue DESC
"""
monthly_trend = con.execute(trend_sql).fetchdf()

Key techniques in this query:

  • strftime(order_date, '%Y-%m') formats dates to year-month for monthly aggregation
  • COUNT(DISTINCT customer_id) measures category acquisition power
  • AVG(revenue) computes average order value for high-value category identification

4. Pareto Analysis: Finding the Vital Few

Pareto analysis (the 80/20 rule) answers one question: which 20% of categories drive 80% of revenue?

pareto_sql = """
SELECT 
    category,
    SUM(revenue) as total_revenue,
    ROUND(SUM(revenue) * 100.0 / SUM(SUM(revenue)) OVER (), 2) as revenue_pct,
    ROUND(SUM(revenue) / NULLIF(SUM(quantity), 0), 2) as avg_unit_price,
    SUM(quantity) as total_units
FROM orders 
GROUP BY category
ORDER BY total_revenue DESC
"""
pareto_result = con.execute(pareto_sql).fetchdf()

A common pitfall here: SUM(SUM(revenue)) OVER (). The outer SUM() is a window function (sums the entire result set), while the inner SUM(revenue) is an aggregate (sums per category). This nested pattern is DuckDB’s standard approach for aggregate-window combinations.

NULLIF(SUM(quantity), 0) prevents division-by-zero errors. When quantity is 0, the expression returns NULL gracefully instead of crashing.


5. MoM Anomaly Detection: Automated Alerting

This is the most valuable part of the system. Bosses don’t need a monthly trend chart — they need: “Tell me what’s broken.”

anomaly_sql = """
WITH monthly AS (
    SELECT 
        category,
        strftime(order_date, '%Y-%m') as month,
        SUM(revenue) as revenue
    FROM orders 
    GROUP BY category, month
),
with_lag AS (
    SELECT *,
        LAG(revenue) OVER (PARTITION BY category ORDER BY month) as prev_revenue,
        ROUND((revenue - LAG(revenue) OVER (PARTITION BY category ORDER BY month)) 
              / NULLIF(LAG(revenue) OVER (PARTITION BY category ORDER BY month), 0) * 100, 2) as mom_change
    FROM monthly
),
filtered AS (
    SELECT category, month, revenue, prev_revenue, mom_change
    FROM with_lag
    WHERE mom_change < -15
    ORDER BY mom_change ASC
)
SELECT * FROM filtered
"""
anomalies = con.execute(anomaly_sql).fetchdf()

The query executes in three stages:

  1. CTE monthly: Aggregate revenue by category and month, creating a monthly revenue matrix
  2. CTE with_lag: Use LAG() window function to fetch last month’s revenue, compute MoM change percentage
  3. Final query: Filter records where MoM change is below -15%

LAG(revenue) OVER (PARTITION BY category ORDER BY month) is the key. It tells DuckDB: “For each category, ordered by month, grab the previous row’s revenue.” This is far more elegant than manually computing MoM in Python.


6. RFM High-Value Customer Identification (Top 10%)

RFM analysis is the foundation of customer segmentation. Here we use DuckDB’s PERCENTILE_CONT to automatically identify the top 10% high-value customers:

rfm_sql = """
SELECT 
    customer_id,
    MAX(order_date) - MIN(order_date) as customer_lifespan_days,
    COUNT(*) as total_orders,
    SUM(revenue) as total_revenue,
    AVG(revenue) as avg_order_value
FROM orders 
GROUP BY customer_id
HAVING total_revenue > PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY total_revenue)
ORDER BY total_revenue DESC
LIMIT 50
"""
vip_customers = con.execute(rfm_sql).fetchdf()

PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY total_revenue) is DuckDB’s order-statistic function. It dynamically computes the 90th percentile of all customer revenues, then the HAVING clause filters to customers above that threshold. The benefit: regardless of customer count, you always identify the top 10%.


7. Complete Script: From Data to Insights

Combining all four analyses into one script:

import pandas as pd
import duckdb
from datetime import datetime

# Load data
df = pd.read_csv("shopify_orders.csv")

# Zero-copy registration
con = duckdb.connect(":memory:")
con.register("orders", df)

# 1. Monthly category trends
monthly_trend = con.execute(trend_sql).fetchdf()

# 2. Pareto analysis
pareto_result = con.execute(pareto_sql).fetchdf()

# 3. MoM anomaly detection
anomalies = con.execute(anomaly_sql).fetchdf()

# 4. RFM high-value customers
vip_customers = con.execute(rfm_sql).fetchdf()

con.close()
print(f"📊 Analysis complete: {len(df)} orders processed")
print(f"🔍 Found {len(anomalies)} anomalous category/month combinations")
print(f"💎 VIP customers (TOP 50) avg order value: {vip_customers['avg_order_value'].mean():.2f}")

8. Performance Optimization: CSV to Parquet

When data scales to millions of rows, convert CSV to Parquet:

# One-time conversion
df.to_parquet("orders.parquet", engine="pyarrow")

# DuckDB reads Parquet with automatic predicate pushdown
con = duckdb.connect(":memory:")
result = con.execute("SELECT * FROM 'orders.parquet' WHERE category = 'Electronics'").fetchdf()

DuckDB’s predicate pushdown on Parquet can improve query speed by 5-10x. Because Parquet is columnar, DuckDB reads only the needed columns and skips data blocks that don’t match the filter conditions.


9. Extensibility: Automation & Monetization

The real value lies in reusability. The same code works with any dataset:

  • Weekly automated reports: Pair with cron jobs to run every Monday morning, push reports to Slack or WeChat
  • BI tool integration: DuckDB connects directly to Superset, Metabase for real-time data sources
  • API wrapping: Wrap with FastAPI to expose analysis endpoints, charge per request

10. Monetization Path Summary

The commercial value of this system rests on three pillars:

  1. Efficiency gain: 2 hours → 30 seconds, running automatically every month
  2. Decision quality: Anomaly detection alerts proactively, no longer relying on gut feeling
  3. Reusability: Same codebase serves the next client with a different dataset

If you’re a freelance data analyst or side-hustler, this “small but sharp” toolkit (DuckDB + SQL) is your most effective weapon — low cost, fast delivery, high client perceived value. The 3,000 RMB consultation fee isn’t the end goal; it’s the starting point for a standardized product.


📖 本文的代码模板和完整 CSV 数据文件已上传至 duckdblab.org,包含针对 5 种常见业务场景(电商、SaaS、内容平台、传统零售、跨境电商)的预置分析模板,直接替换数据即可运行。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

Built with Hugo
Theme Stack designed by Jimmy