在 DuckDB 中执行假设检验

你有没有想过,用 SQL 就能做假设检验?

今天我们就来聊聊,如何用 DuckDB 快速完成数据分析中的两个经典任务:

  • 分类变量做卡方检验
  • 连续变量做相关性分析

整个流程非常丝滑:

  1. 用 DuckDB 直接读取 CSV 或 Parquet 文件
  2. 用熟悉的 SQL 做聚合计算
  3. 把精简后的结果交给 Python 计算 p 值
  4. 连续变量甚至可以直接用 DuckDB 内置的 corr() 函数搞定

看完你会发现自己又掌握了一个“快、准、狠”的数据分析利器!

下面用共享单车骑行记录来演示。

数据文件叫 bike_rides.csv,字段大概这些:

  • ride_id
  • user_type:月卡 / 单次
  • is_weekend:是 / 否
  • distance_km:骑行距离
  • duration_min:骑行时长
  • start_hour:开始小时
  • bike_type:普通车 / 电助力车

1. 环境准备

# pip install duckdb pandas scipy
import duckdb
from scipy.stats import chi2_contingency

con = duckdb.connect()

DuckDB 的好处是,不用先建表,也不用把 CSV 导进数据库。直接查文件就行。

con.execute("""
SELECT *
FROM read_csv_auto('bike_rides.csv')
LIMIT 5
""").df()
ride_id user_type is_weekend distance_km duration_min start_hour bike_type
1 月卡 2.5 13 9 普通车
2 月卡 3.2 15 10 电助力车
3 月卡 1.8 10 14 普通车
4 月卡 4.5 19 16 电助力车
5 月卡 5.1 21 8 普通车

如果是 Parquet,就换成:

read_parquet('bike_rides.parquet')

这一步只是确认数据能读进来。真正做检验时,我们不会把全量数据拉到 Python,而是先用 DuckDB 做聚合。

2. 假设检验一:月卡用户和单次用户,周末骑行比例一样吗?

在我们探索数据的过程中,一个很自然的问题是:不同类型的用户在周末骑行的偏好上是否存在差异?

为了回答这个问题,我们可以借助分类变量的常用分析方法——卡方检验。
首先,我们设定一个简单的假设:

  • H₀(原假设):用户类型与周末是否骑行之间没有关联。
  • H₁(备择假设):用户类型与周末是否骑行之间存在某种关联。

这样的假设设定让我们可以更系统地探索数据背后的故事,而不是仅仅依赖直观感受。

接下来,我们就可以通过卡方检验来验证这些假设,看看数据是否支持我们拒绝原假设。

用 DuckDB 先聚合出列联表:

df = con.execute("""
SELECT
    user_type,
    SUM(CASE WHEN is_weekend = '是' THEN 1 ELSE 0 END) AS weekend,
    SUM(CASE WHEN is_weekend = '否' THEN 1 ELSE 0 END) AS weekday
FROM read_csv_auto('bike_rides.csv')
GROUP BY user_type
ORDER BY user_type
""").df()

print(df)
user_type weekend weekday
单次 4.0 16.0
月卡 14.0 6.0

拿到列联表后,交给 scipy 做卡方检验:

table = df[['weekend', 'weekday']].values
chi2, p, dof, expected = chi2_contingency(table)

print(f"卡方统计量: {chi2:.4f}")
print(f"p 值: {p:.4f}")
卡方统计量: 8.1818
p 值: 0.0042

结果分析:

  • p = 0.0042 < 0.05:拒绝 H₀,说明用户类型和周末骑行比例有关系。
  • 卡方统计量为 8.1818,在显著性水平 α = 0.05 下达到显著,有足够证据表明不同用户类型(单次 vs 月卡)在周末/工作日骑行比例上存在显著差异。

DuckDB 没有内置卡方检验,所以这里的分工是:DuckDB 负责把大表聚合成小列联表,scipy 负责算检验。

这样不会把全量骑行记录拉到 Python,内存压力小很多。

3. 假设检验二:骑行距离和骑行时长相关吗?

接下来,我们看看骑行的距离和骑行的时长之间到底有没有什么关系?

其实吧,这个问题听起来挺直觉的——骑得久,自然骑得远嘛。

但作为一个喜欢数据的人,咱不能光靠感觉说话,我们得拿出点真凭实据来。
所以我们可以提出两个假设:

  • 原假设(H₀):这两者之间啥关系都没有,也就是说,骑行距离和骑行时长完全是“各自为政”,互不干扰。
  • 备择假设(H₁):它们之间其实是有关系的,而且这个关系还挺明显的,不是巧合。

接下来,我们就可以用一些统计方法来检验一下,看看到底哪个假设更靠谱。

比如说,我们可以做一下相关性检验,算算它们之间的相关系数,看看是不是显著不为零。

这个可以直接用 DuckDB 的 corr()

corr_df = con.execute("""
SELECT
    corr(distance_km, duration_min) AS corr
FROM read_csv_auto('bike_rides.csv')
WHERE distance_km IS NOT NULL
  AND duration_min IS NOT NULL
""").df()

print(corr_df)

计算出来的 corr=0.996897

结果怎么读:

  • corr 接近 1:强正相关,距离越远,时长越长。
  • corr 接近 -1:强负相关。
  • corr 接近 0:基本没有线性相关。

DuckDB 直接算相关系数,不用把两列数据全部拉到 Python。数据量大时,这一点很省事。
如果你还想要 p 值,可以再用 scipy.stats.pearsonr,但通常先看 corr() 就能判断方向和强度。

4. 为什么这套流程适合用 DuckDB

  • 直接读 CSV / Parquet,不用先导入数据库。
  • SQL 聚合快,适合先做分组、计数、求和。
  • 卡方检验只需要列联表,DuckDB 聚合后数据很小,再给 scipy 很合适。
  • 相关性检验可以直接用 corr(),不用把全量数据搬到 Python
  • Python 衔接顺,con.execute(...).df() 直接拿 DataFrame

5. 小结

这篇文章用共享单车场景演示了两个常见假设检验:

  1. 分类变量比较:用户类型 vs 是否周末骑行,用卡方检验。
  2. 连续变量关系:骑行距离 vs 骑行时长,用 Pearson 相关系数。

核心套路是:

  • 能用 SQL 聚合的,先让 DuckDB 做。
  • 需要统计检验的,再把小结果交给 Python
  • 能直接用 DuckDB 内置函数的,比如 corr(),就别把数据拉出来。

换成其他字段,比如“不同品牌单车是否影响骑行时长”“早晚高峰与骑行距离”,流程基本一样。

posted @ 2026-09-24 12:18  wang_yb  阅读(0)  评论(0)    收藏  举报