使用 DuckDB 计算描述性统计量
大家好!在本文中,我们将一起探索如何使用 DuckDB 对 CSV 数据进行常见的描述性统计计算。
无需导入库、无需创建表格,直接使用 SQL 语句就能快速对数据集进行初步分析。
我们会一起学习如何:
- 直接读取并查询 CSV 文件
- 计算数据的平均值和中位数
- 求出标准差与方差
- 找出最小值、最大值以及数据范围
- 计算百分位数
- 理解这些统计指标背后的含义
通过这篇文章,你将掌握如何用简单高效的 SQL 语句,快速了解数据的集中趋势、波动情况,甚至发现潜在的异常值。
下面用一个国内奶茶店外卖订单的场景演示。
假设有一份 waimai_orders.csv,字段包括:
order_id, order_time, shop_name, category, amount, delivery_fee, distance_km, delivery_min
我们要回答几个问题:
- 平均每单多少钱?中位数多少?
- 配送费平均多少?
- 配送距离和配送时长波动大不大?
- 最贵、最便宜、最远、最慢分别是多少?
- 大部分订单金额落在什么区间?
所有操作都依靠 DuckDB 来完成。
1. 环境配置与数据加载
先安装 DuckDB 和 pandas:
pip install duckdb pandas
然后在 Python 里直接查 CSV:
import duckdb
con = duckdb.connect()
con.sql("""
CREATE OR REPLACE VIEW orders AS
SELECT * FROM read_csv_auto('/opt/share/waimai_orders.csv')
""")
con.sql("SELECT * FROM orders LIMIT 5").df()
| order_id | order_time | shop_name | category | amount | delivery_fee | distance_km | delivery_min |
|---|---|---|---|---|---|---|---|
| 1001 | 2025-03-01 02:41:00 | 茶百道(春熙路店) | 奶茶 | 20.7 | 3.0 | 1.5 | 21 |
| 1002 | 2025-03-01 03:09:00 | 喜茶(太古里店) | 果茶 | 9.9 | 5.0 | 2.9 | 34 |
| 1003 | 2025-03-01 03:10:00 | 古茗(天府三街店) | 果茶 | 11.8 | 4.0 | 1.0 | 23 |
| 1004 | 2025-03-01 03:17:00 | 喜茶(太古里店) | 咖啡 | 38.0 | 3.0 | 1.0 | 18 |
| 1005 | 2025-03-01 03:26:00 | 喜茶(太古里店) | 奶茶 | 38.5 | 3.0 | 4.0 | 37 |
这里 DuckDB 的优势是:read_csv_auto 自动推断字段类型,CSV 直接当表查。
不用建表,不用导数,不用起服务,对探索式分析来说很省事。
2. 计算均值与中位数
均值和中位数要一起看。均值容易被极端大单拉高,中位数更接近“典型订单”。
con.sql("""
SELECT
AVG(amount) AS avg_amount,
MEDIAN(amount) AS median_amount,
AVG(delivery_fee) AS avg_delivery_fee,
MEDIAN(delivery_fee) AS median_delivery_fee,
AVG(distance_km) AS avg_distance,
MEDIAN(distance_km) AS median_distance,
AVG(delivery_min) AS avg_delivery_min,
MEDIAN(delivery_min) AS median_delivery_min
FROM orders
""").df()
| 指标 | 数值 |
|---|---|
平均金额 avg_amount |
45.9058 |
中位金额 median_amount |
29.15 |
平均金额(45.91 元)比中位数(29.15 元)高不少,说明有少数大单把平均值拉高了。
3. 计算标准差与方差
标准差衡量数值偏离均值的程度。
标准差越大,数据越分散。方差也是离散度,但单位是平方,解读不如标准差直观。
con.sql("""
SELECT
STDDEV(amount) AS std_amount,
VARIANCE(amount) AS var_amount,
STDDEV(delivery_min) AS std_delivery_min,
VARIANCE(delivery_min) AS var_delivery_min,
STDDEV(distance_km) AS std_distance,
VARIANCE(distance_km) AS var_distance
FROM orders
""").df()
| 指标 | 数值 |
|---|---|
订单金额标准差 std_amount |
50.17 |
订单金额方差 var_amount |
2517.48 |
配送时长标准差 std_delivery_min |
10.49 |
配送时长方差 var_delivery_min |
110.10 |
配送距离标准差 std_distance |
1.50 |
配送距离方差 var_distance |
2.26 |
订单金额标准差约为 50.17,说明订单金额差异非常大——有的单十几块,有的单可能上百块,分布很分散。
配送时长的标准差约为 10.49 分钟,意味着配送耗时波动明显:以平均配送时长(约 20 ~ 30 分钟)为中心,大部分订单送达时间会落在 均值 ±10 分钟左右 的范围内,也就是既有 20 多分钟就能送到的单,也可能出现 40 ~ 50 分钟甚至更久的订单。
配送时长的这种波动值得重点关注。
4. 计算最小值、最大值与范围
最小值、最大值看边界。范围就是最大值减最小值,快速了解数据跨度。
con.sql("""
SELECT
MIN(amount) AS min_amount,
MAX(amount) AS max_amount,
MAX(amount) - MIN(amount) AS range_amount,
MIN(delivery_min) AS min_delivery_min,
MAX(delivery_min) AS max_delivery_min,
MAX(delivery_min) - MIN(delivery_min) AS range_delivery_min,
MIN(distance_km) AS min_distance,
MAX(distance_km) AS max_distance,
MAX(distance_km) - MIN(distance_km) AS range_distance
FROM orders
""").df()
| 指标 | 数值 |
|---|---|
最小金额 min_amount |
9.9 元 |
最大金额 max_amount |
289.3 元 |
金额极差 range_amount |
279.4 元 |
最慢配送 max_delivery_min |
89 分钟 |
最快配送 min_delivery_min |
18 分钟 |
配送时长极差 range_delivery_min |
71 分钟 |
最远距离 max_distance |
12.3 公里 |
最近距离 min_distance |
0.5 公里 |
距离极差 range_distance |
11.8 公里 |
最贵的订单约 289 元 ,可能是团餐;但金额极差达到 279 元,说明客单价跨度非常大。
最远配送距离约 12.3 公里 、最慢的订单用了 89 分钟 才送到,这种单虽然占比可能不高,但会明显拉低整体配送体验。
如果这类"远距离/超时长"订单的占比不低,就需要考虑是否要加收配送费,或者适当调整配送覆盖范围。
5. 计算百分位数
百分位数比均值更能看清分布。四分位数最常用:Q1 是 25% 分位,Q2 是中位数,Q3 是 75% 分位。
con.sql("""
SELECT
QUANTILE_CONT(amount, 0.25) AS q1_amount,
QUANTILE_CONT(amount, 0.50) AS median_amount,
QUANTILE_CONT(amount, 0.75) AS q3_amount,
QUANTILE_CONT(delivery_min, 0.25) AS q1_delivery_min,
QUANTILE_CONT(delivery_min, 0.50) AS median_delivery_min,
QUANTILE_CONT(delivery_min, 0.75) AS q3_delivery_min,
QUANTILE_CONT(distance_km, 0.25) AS q1_distance,
QUANTILE_CONT(distance_km, 0.50) AS median_distance,
QUANTILE_CONT(distance_km, 0.75) AS q3_distance
FROM orders
""").df()
| 指标 | Q1 | 中位数 | Q3 |
|---|---|---|---|
| 金额 | 19.775 | 29.15 | 44.675 |
| 配送时长 | 22.0 | 28.0 | 36.0 |
| 配送距离 | 1.4 | 2.2 | 3.3 |
这说明:
- 一半订单金额在 约 20 到 45 元 之间(Q1≈19.8 元,Q3≈44.7 元);
- 75% 的订单金额低于 约 45 元;
- 75% 的订单配送时长在 36 分钟 以内;
- 75% 的订单配送距离在 3.3 公里 以内。
这些数字可以直接用来做决策:重点服务 3.3 公里以内(75% 的订单都在这范围内),超过这个距离可以加配送费;满减门槛可以围绕 45 元 附近设计,而不是拍脑袋定。
6. 总结与建议
DuckDB 在这类场景里很顺手:
- 直接查 CSV,不用建表、不用导库;
- SQL 写起来清楚,多个统计指标一次查完;
- 输出
.df()就能继续用 pandas、matplotlib; - 数据大一点也不虚,列式引擎,聚合快;
- 改条件快,加个
WHERE就能只看某天、某品类、某门店。
描述性统计不是终点,但它是理解数据的第一步。
先算清楚均值、中位数、标准差、范围和四分位数,再决定下一步怎么分析。
比如继续 GROUP BY category 看品类,或者 date_trunc('day', order_time) 看每天趋势。
DuckDB 改 SQL 很快,适合这种快速探索。

浙公网安备 33010602011771号