Featured image of post Build an Automated E-commerce Analytics Dashboard with DuckDB

Build an Automated E-commerce Analytics Dashboard with DuckDB

Learn how to build a complete automated e-commerce analytics system with DuckDB: from CSV reading, analysis, category-region insights, RFM customer segmentation to automated monthly reports -- all in pure SQL with zero dependencies

Build an Automated E-commerce Analytics Dashboard with DuckDB

💰 ** idea**: Package this system as a SaaS product for small e-commerce sellers at . 100 customers = in recurring revenue.


Why DuckDB for E-commerce Analytics?

Many e-commerce sellers have massive order data (CSV/Excel) but lack automated analysis capabilities. They either manually process tens of thousands of rows in Excel (which crashes), or pay someone to make reports.

DuckDB’s core advantages shine here:

  • Read CSV/Excel/Parquet directly, no database import needed — Sellers send you a file, you read and analyze it instantly
  • SQL is the analysis — No complex Python data processing code needed, one SQL query handles all statistics
  • Deploy in 10 minutes — One Python script + one cron job, fully automatic

The core value of this system: one set of SQL handles all e-commerce analytics, zero dependencies, 10-minute deployment.


Data Preparation: Simulated E-commerce Dataset

First, generate a simulated e-commerce dataset to model a real scenario:

import duckdb
import pandas as pd
import numpy as np

# Generate simulated e-commerce data
np.random.seed(42)
n = 50000

orders = pd.DataFrame({
 'order_id': range(1, n+1),
 'customer_id': np.random.randint(1, 5000, n),
 'product_category': np.random.choice(['Electronics', 'Clothing', 'Food', 'Home', 'Beauty'], n),
 'product_name': [f'Product{i%200+1}' for i in range(n)],
 'quantity': np.random.randint(1, 10, n),
 'unit_price': np.round(np.random.uniform(9.9, 999.9, n), 2),
 'order_date': pd.date_range('2025-01-01', periods=n, freq='min'),
 'province': np.random.choice(['Guangdong', 'Jiangsu', 'Zhejiang', 'Beijing', 'Shanghai', 'Sichuan', 'Hubei', 'Fujian'], n),
 'payment_method': np.random.choice(['Alipay', 'WeChat', 'CreditCard', 'BankCard'], n),
 'is_returned': np.random.choice([0, 1], n, p=[0.95, 0.05])
})

orders['total_amount'] = orders['quantity'] * orders['unit_price'] * (1 - orders['is_returned'])

# Save to CSV
orders.to_csv('ecommerce_orders.csv', index=False, encoding='utf-8-sig')
print(f"✅ Generated {len(orders)} order records")

The dataset contains 50,000 orders across 5 categories, 8 provinces, 4 payment methods, and a 5% return rate. Read with DuckDB:

import duckdb
con = duckdb.connect()
df = con.sql("SELECT * FROM read_csv_auto('ecommerce_orders.csv')").df()
print(df.shape) # (50000, 11)

One line of code, 50,000 rows loaded — no database configuration needed.


Core Analysis Modules

revenue_sql = """
WITH daily_stats AS (
 SELECT 
 DATE(order_date) AS order_date,
 COUNT(*) AS order_count,
 SUM(total_amount) AS daily_revenue,
 AVG(total_amount) AS avg_order_value,
 COUNT(DISTINCT customer_id) AS unique_customers,
 SUM(is_returned) AS return_count,
 ROUND(SUM(is_returned) * 1.0 / COUNT(*), 4) AS return_rate
 FROM read_csv_auto('ecommerce_orders.csv')
 GROUP BY DATE(order_date)
),
cumulative AS (
 SELECT 
 order_date,
 SUM(daily_revenue) OVER (ORDER BY order_date) AS cumulative_revenue,
 SUM(order_count) OVER (ORDER BY order_date) AS cumulative_orders,
 AVG(daily_revenue) OVER (ORDER BY order_date ROWS UNBOUNDED PRECEDING) AS rolling_avg_revenue
 FROM daily_stats
)
SELECT * FROM cumulative 
ORDER BY order_date;
"""

result = duckdb.sql(revenue_sql).df()
print(result.tail(7))

Key metrics explained:

MetricMeaningWarning Threshold
daily_revenueDaily revenueAlert if 3+ days declining
rolling_avg_revenueRolling average revenueSmooths short-term volatility
return_rateReturn rateAlert if > 10%
unique_customersUnique customersJudge traffic health

3.2 Category × Region Cross-Analysis

Find your “cash cow” combinations:

category_region_sql = """
SELECT 
 product_category AS category,
 province AS region,
 COUNT(*) AS order_count,
 SUM(total_amount) AS revenue,
 AVG(total_amount) AS avg_order_value,
 SUM(is_returned) AS return_count,
 ROUND(SUM(is_returned) * 1.0 / COUNT(*), 4) AS return_rate,
 ROUND(SUM(total_amount) * 1.0 / SUM(SUM(total_amount)) OVER (), 4) AS share
FROM read_csv_auto('ecommerce_orders.csv')
GROUP BY product_category, province
HAVING SUM(total_amount) > 10000
ORDER BY revenue DESC
LIMIT 30;
"""

cat_result = duckdb.sql(category_region_sql).df()
print(cat_result.to_string(index=False))

This query helps you quickly identify:

  • High-contribution category+region combinations: Focus retention and ad spend
  • High-return-rate regions: May need logistics or product strategy adjustments
  • Low-share but fast-growing: Potential opportunities to invest in early

3.3 Customer Value Segmentation (RFM Model)

Use DuckDB’s window functions for RFM customer segmentation:

rfm_sql = """
WITH customer_metrics AS (
 SELECT 
 customer_id,
 MAX(order_date) AS last_order_date,
 MIN(order_date) AS first_order_date,
 COUNT(*) AS frequency,
 SUM(total_amount) AS monetary,
 AVG(total_amount) AS avg_order_value
 FROM read_csv_auto('ecommerce_orders.csv')
 GROUP BY customer_id
),
rfm_scores AS (
 SELECT 
 customer_id,
 frequency,
 monetary,
 avg_order_value,
 NTILE(5) OVER (ORDER BY frequency) AS freq_score,
 NTILE(5) OVER (ORDER BY monetary) AS money_score,
 NTILE(5) OVER (ORDER BY avg_order_value) AS avg_score,
 CASE 
 WHEN frequency >= 10 AND monetary >= 5000 THEN 'High Value'
 WHEN frequency >= 5 AND monetary >= 2000 THEN 'Potential'
 WHEN frequency >= 3 AND monetary >= 500 THEN 'Regular'
 WHEN frequency = 1 AND monetary < 200 THEN 'At Risk'
 ELSE 'Other'
 END AS customer_segment
 FROM customer_metrics
)
SELECT 
 customer_segment,
 COUNT(*) AS customer_count,
 ROUND(AVG(monetary), 2) AS avg_spend,
 ROUND(AVG(frequency), 2) AS avg_orders,
 ROUND(SUM(monetary), 2) AS total_contribution
FROM rfm_scores
GROUP BY customer_segment
ORDER BY total_contribution DESC;
"""

rfm_result = duckdb.sql(rfm_sql).df()
print(rfm_result.to_string(index=False))

RFM Segmentation Strategy:

  • High Value: VIP service, exclusive discounts, prevent churn
  • Potential: Increase average order value, cross-sell recommendations
  • Regular: Maintain contact, moderate marketing
  • At Risk: Recall campaigns, limited-time offers

Automated Monthly Report Generation

Package the above analysis into an automated report generation function:

import pandas as pd
from datetime import datetime

def generate_monthly_report(data_path='ecommerce_orders.csv'):
 """Generate a complete monthly e-commerce analysis report"""
 
 # 1. Monthly summary
 monthly_sql = """
 WITH base AS (
 SELECT *, 
 DATE_FORMAT(order_date, '%Y-%m') AS month,
 CASE 
 WHEN hour(order_date) < 12 THEN 'Morning'
 WHEN hour(order_date) < 18 THEN 'Afternoon'
 ELSE 'Evening'
 END AS time_period
 FROM read_csv_auto('$data_path')
 ),
 monthly_summary AS (
 SELECT 
 month,
 COUNT(*) AS total_orders,
 SUM(total_amount) AS total_revenue,
 AVG(total_amount) AS avg_order_value,
 COUNT(DISTINCT customer_id) AS unique_customers,
 SUM(is_returned) AS total_returns,
 ROUND(SUM(is_returned)*1.0/COUNT(*), 4) AS return_rate,
 COUNT(DISTINCT product_category) AS category_count
 FROM base
 GROUP BY month
 )
 SELECT * FROM monthly_summary ORDER BY month DESC;
 """
 
 summary = duckdb.sql(monthly_sql.replace('$data_path', data_path)).df()
 
 # 2. Top 10 products
 top_products_sql = """
 SELECT 
 product_name,
 product_category,
 COUNT(*) AS sales_count,
 ROUND(SUM(total_amount), 2) AS revenue
 FROM read_csv_auto('$data_path')
 GROUP BY product_name, product_category
 ORDER BY revenue DESC
 LIMIT 10;
 """
 
 top_products = duckdb.sql(top_products_sql.replace('$data_path', data_path)).df()
 
 # 3. Time-of-day distribution
 traffic_sql = """
 SELECT 
 CASE 
 WHEN hour(order_date) < 12 THEN 'Morning (0-12)'
 WHEN hour(order_date) < 18 THEN 'Afternoon (12-18)'
 ELSE 'Evening (18-24)'
 END AS time_period,
 COUNT(*) AS orders,
 ROUND(SUM(total_amount), 2) AS revenue,
 ROUND(SUM(total_amount)*1.0/SUM(SUM(total_amount))OVER(), 4) AS share
 FROM read_csv_auto('$data_path')
 GROUP BY time_period;
 """
 
 traffic = duckdb.sql(traffic_sql).df()
 
 return {
 'monthly_summary': summary,
 'top_products': top_products,
 'traffic': traffic
 }

# Execute
report = generate_monthly_report()
print("=== Monthly Summary ===")
print(report['monthly_summary'].to_string(index=False))
print("\n=== Top 10 Products ===")
print(report['top_products'].to_string(index=False))
print("\n=== Traffic by Time Period ===")
print(report['traffic'].to_string(index=False))

Comparison with Traditional Approaches

DimensionExcelPandas + JupyterDuckDB
Startup time30s+ (crashes with large files)5-10 seconds< 1 second
50K row CSVOpens but slow filteringEasy loadMillisecond queries
5M row CSV❌ Cannot open⚠️ Needs optimizationQuery directly, no import
SQL capabilityLimited (pivot tables)Needs extra librariesFull SQL support
Deployment complexityManual operationsNeeds Python environmentSingle file, zero dependencies
AutomationDifficultNeeds scriptscron + Python, one-liner

DuckDB’s core advantage: bringing database capabilities into data analysis scenarios — no need to install PostgreSQL/MySQL, no ETL pipeline required, directly run complex queries on raw files.


Automated Deployment: Weekly Scheduled Reports

Use Python’s schedule library or system cron for fully automated weekly reports:

import schedule
import time
from datetime import datetime

def weekly_report():
 report = generate_monthly_report()
 
 # Generate HTML report
 html = f"""
 <h2>E-commerce Weekly Report - {datetime.now().strftime('%Y-%m-%d')}</h2>
 <h3>Monthly Summary</h3>
 <pre>{report['monthly_summary'].to_string(index=False)}</pre>
 <h3>Top 10 Products</h3>
 <pre>{report['top_products'].to_string(index=False)}</pre>
 """
 
 with open(f'report_{datetime.now().strftime("%Y%m%d")}.html', 'w', encoding='utf-8') as f:
 f.write(html)
 
 print(f"✅ Report generated: report_{datetime.now().strftime('%Y%m%d')}.html")

# Run every Monday at 9 AM
schedule.every().monday.at("09:00").do(weekly_report)

while True:
 schedule.run_pending()
 time.sleep(3600)

Or use system cron (more production-friendly):

# Edit crontab
crontab -e

# Execute every Monday at 9 AM
0 9 * * 1 cd /home/user/report && python3 weekly_report.py

本文信息

项目内容
DuckDB 版本v1.5.x(部分功能基于 v2.0 Preview)
最后验证2026-09-12
测试环境Linux / x86_64 / 16GB RAM
官方文档DuckDB Documentation
GitHubpengzz9527/duckdb-blog

如发现错误,欢迎通过 GitHub Issue 或邮件 [email protected] 反馈。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials