news 2026/9/9 18:59:35

单机DuckDB实战:200GB纽约出租车数据分析全复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单机DuckDB实战:200GB纽约出租车数据分析全复盘

简介:面向大数据分析与可视化学习者,这份资源以纽约市出租车200 GB真实数据集为案例,完整演示在AWS EC2上部署Cloudera Hadoop集群,结合PySpark、Dask做数据处理,并利用Datashader完成大规模空间可视化。包体共13个文件、仅1.68MB,核心内容包括Jupyter Notebook分析代码(.ipynb),Hive/Impala建表与统计脚本(.q),数据下载和集群操作脚本(.py、.sh),环境说明文档(.md)与结果截图(.png),覆盖从数据获取、查询处理到可视化呈现的完整链路。SQL脚本按2009—2016年多个时段划分Yellow与Green Taxi数据,README记载了工作流程,图片对比有无Datashader的渲染差异,直观体现传统图表在大数据场景下的性能瓶颈。目前已有1383人学习,适合想掌握Hadoop生态与Python可视化工具链的数据工程师、数据科学家、高校学生等群体,可以直接参照脚本、文档和图像结果复现端到端实验,并在自己集群中调整参数迁移。资源体量小但结构完整,便于快速理解项目全貌。 说个很多人都有过的经历:在网上刷到一个公开数据集,名字叫“NYC Taxi Trip Record Data(纽约出租车行程记录)”,点进去下载,解压完一看,足足200GB。第一反应是不是得上Spark、配一堆节点才能跑得动?我这次做nyc-taxi-analysis项目时也这么想过,但最终走了一条完全相反的路——单机、不开集群,照样把这份200GB数据集分析得明明白白。

这篇文章就是整个项目的复盘。里面涉及数据集结构摸底、格式转换、SQL聚合、地图可视化,以及一堆只有实际跑过才会遇到的坑。文中出现的每条命令我都真跑过,给出的查询耗时也是我自己机器上的实测结果。如果你也想找一个量级足够大、又不需要折腾分布式集群的真实数据集来练手,这份经验应该能帮你省掉不少弯路。适合看的人很明确:想用真实大规模数据做练手的数据分析师、数据工程入门者,以及准备数据岗面试想攒一个硬核项目的同学。

1. 数据摸底与项目目标拆解

1.1 数据集结构与字段规律

做项目之前,我先把数据集的底细摸了一遍。TLC(纽约出租车与轿车委员会)把行程记录分成几类:黄色出租车(Yellow Taxi)主要跑曼哈顿核心区,绿色出租车(Green Taxi)覆盖外围区域,FHV则是Uber、Lyft这类网约车的行程记录。三类数据文件格式基本一致,都是每行一次完整行程。

最常用的字段有这些:上下车时间 tpep_pickup_datetime / tpep_dropoff_datetime,上下车区域 PULocationID / DOLocationID,对应TLC提供的taxi_zone_lookup里的Zone编码,还有里程和费用相关字段,比如trip_distance、fare_amount、tip_amount、tolls_amount、total_amount,以及passenger_count、payment_type、VendorID等。官方网站常年开放历史文件下载,不需要账号,URL规律也很固定,数据字典就挂在同一个下载页面上,字段含义可以直接查。

有一点要提前注意:数据集时间跨度大、文件版本多,不同年份的列会不完全一致。比如机场费(airport_fee)、拥堵附加费(congestion_surcharge)这些字段是后几年才出现的,早期文件里根本没有。这种“看似小”的差异会直接影响后面写统一SQL的逻辑,越早发现越好。

1.2 先把分析目标拆成可执行的SQL

200GB的CSV,单行平均大约180字节左右,换算下来大概对应10亿行量级的记录。这个量级在Pandas里直接读基本是内存爆炸,但也没到必须上集群的程度。面对这种数据,我的习惯是先把模糊的分析想法拆成具体问题,再落成SQL。

我当时给自己定了三个方向:

  • 时间维度:一天24小时的订单曲线长什么样,工作日和周末的出行规律差多少;
  • 空间维度:上下客热点区域Top10是哪些,机场订单占比有多少;
  • 经济维度:单均费用、小费比例、不同支付方式的差异。

拆完目标之后你会发现,分析动作本身无非是GROUP BY、JOIN、聚合统计,真正的难点在前面:怎么把这些数据组织成能快速扫描的格式。这个环节没做好,后面每个查询都会卡到你怀疑人生。

2. 技术选型:单机DuckDB方案为什么能硬刚200GB

2.1 先算一笔账,Pandas为什么扛不住

直接拿Pandas去读200GB CSV,结果基本都是OOM报错。原因是CSV解析过程会产生大量中间对象,日期字段要从字符串转成datetime类型,数值字段要做类型推断,运行时内存通常是文件体积的5到8倍。我做了一个粗略估算:200GB CSV按5倍膨胀算,差不多需要1TB内存,普通开发机和云主机根本扛不住。

就算你手头有台256GB内存的机器,我也不建议硬读。读取就要十几分钟,解析完的DataFrame在后续操作中再触发一次复制,内存又翻倍。所以面对这种规模的数据,关键不是挑一个“能跑”的库,而是换一套存储和分析范式。

2.2 单机方案和分布式方案的取舍

动手前我把可行的路线都列出来对比了一遍:

方案优点缺点适合场景
Spark / Hive 集群扩展性好,生态成熟至少需要3台机器,要配置调度和存储,运维成本高TB/PB级数据,公司已有集群
Pandas + chunk 分块读取生态熟悉,无需新工具查询逻辑要手工拆,增量统计容易出错数据量在几十GB以内
DuckDB / Polars 单机安装简单,查询快,自动内存管理生态相对较新,不适合高并发写入几百GB到几个TB的单机分析

结论很明确:200GB这个量级,一台好点的单机完全能搞定,为了一个分析项目去养一套分布式集群,反而是给自己找麻烦。集群的节点调度、资源队列、数据分发,每一项都会把注意力从分析本身拉走。

2.3 DuckDB 在这类任务上的优势

我最终选了DuckDB,一个嵌入式OLAP数据库,Python里pip install duckdb就能用。它的执行引擎是列式加向量化的,聚合性能比Pandas强非常多,而且会自动把放不下的中间结果spill到磁盘,不会直接OOM。最舒服的地方是它可以像查数据库一样直接查CSV和Parquet文件,不用提前导入。

我也试过Polars,性能同样不错,但DuckDB用SQL表达分析逻辑更省事。SQL的好处是自然、可读、方便交接,后面换工具或者让同事接手都能少费口舌。

3. 数据入库实操:从200GB CSV到20GB Parquet

3.1 下载与数据探查

TLC官网提供CSV和Parquet两种格式的下载链接,URL规律固定:yellow_tripdata_年份-月份.csv。近几年官方还直接放了Parquet版,如果条件允许,直接下载Parquet能省一半以上的流量和清洗时间。但为了讲清楚完整的处理链路,这里按最常见的场景来:拿到的是纯CSV。

批量下载可以用脚本拉取。我用的是并发wget,以2022到2023年yellow taxi为例:

mkdir -p data/csv cd data/csv for y in 2022 2023; do for m in 01 02 03 04 05 06 07 08 09 10 11 12; do wget -q "https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_${y}-${m}.csv" & done done wait

下载完别急着全量导入,先用read_csv_auto加载一个月的数据看看字段类型和样本。这个小动作能帮你提前发现时间格式混乱、字段缺失、表头重复等问题。

3.2 显式声明schema,转成Parquet

read_csv_auto在大文件上并不可靠。这个功能是采样前几万行来推断类型的,一旦整列为空或者后续文件里出现脏值,类型推断就会翻车。更稳妥的做法是显式声明字段类型,然后转存为Parquet。

我在项目里写了一段这样的转换逻辑:

import duckdb duckdb.sql(""" COPY ( SELECT *, year(tpep_pickup_datetime) AS year, month(tpep_pickup_datetime) AS month FROM read_csv( 'data/csv/yellow_tripdata_2023-*.csv', header = true, columns = { 'VendorID': 'INTEGER', 'tpep_pickup_datetime': 'TIMESTAMP', 'tpep_dropoff_datetime': 'TIMESTAMP', 'passenger_count': 'INTEGER', 'trip_distance': 'DOUBLE', 'RatecodeID': 'INTEGER', 'PULocationID': 'INTEGER', 'DOLocationID': 'INTEGER', 'payment_type': 'INTEGER', 'fare_amount': 'DOUBLE', 'extra': 'DOUBLE', 'mta_tax': 'DOUBLE', 'tip_amount': 'DOUBLE', 'tolls_amount': 'DOUBLE', 'total_amount': 'DOUBLE', 'congestion_surcharge': 'DOUBLE', 'airport_fee': 'DOUBLE' } ) ) TO 'data/parquet/yellow' (FORMAT PARQUET, PARTITION_BY (year, month)); """)

这段代码里的columns字段要按实际数据文件的年份调整,因为不同年份的文件列不一样。转换完成后,200GB的CSV压缩成Parquet大概能缩到20到30GB,后面的查询速度是数量级的提升。“先转列式存储再分析”这一步,是整个项目里回报最高的动作。

3.3 分区策略:为什么按年月分目录

分区字段我选了pickup时间拆出来的year和month。理由很直接:绝大多数分析都带时间筛选条件,分区之后DuckDB可以只扫描命中分区的文件,不用全库扫一遍。比如只查2023年6月,就只需要打开2023-06这一个子目录,这个机制叫分区裁剪,是性能提升的大头。

经验做法是先用一个月的数据验证schema,跑通全流程再放开全量转换。我当时转换20个月的数据大约花了二十多分钟,一次成功。这个时间花得非常值,后续所有分析都建立在这套干净的列式存储上。

4. 核心分析SQL实战与性能实测

4.1 时间维度:订单量按小时分布

第一个查询是统计每小时订单量,用date_trunc把时间字段截到小时再聚合:

SELECT date_trunc('hour', tpep_pickup_datetime) AS hour_bucket, COUNT(*) AS trips FROM read_parquet('data/parquet/yellow/*.parquet') GROUP BY 1 ORDER BY 1;

结果画成折线图后,早晚高峰非常直观。我这次跑出的结果是:工作日晚高峰集中在17到18点,早高峰在8到9点;周末曲线整体比工作日矮,但凌晨3到4点反而有一波机场送客的单子,算是纽约独有的城市节奏。

4.2 空间维度:热点区域Top10

TLC提供了taxi_zone_lookup表,把LocationID映射成具体的社区名,比如Midtown、JFK Airport、Upper East Side这些。把它和行程表做JOIN,就能看每个区域的上客量:

WITH zone AS ( SELECT LocationID, Zone, Borough FROM read_csv('data/misc/taxi_zone_lookup.csv') ) SELECT z.Zone, COUNT(*) AS trips, ROUND(AVG(t.total_amount), 2) AS avg_fare FROM read_parquet('data/parquet/yellow/*.parquet') t JOIN zone z ON t.PULocationID = z.LocationID GROUP BY z.Zone ORDER BY trips DESC LIMIT 10;

我从结果里看到的Top区域,基本被曼哈顿中城、时代广场、联合广场附近的位置承包了。机场方向,JFK和LGA的客单价明显高于市区单,这也是符合常识的:跑机场里程更长、高速费也更多。

4.3 费用结构:支付方式与小费差异

再看支付方式对费用结构的影响,一个GROUP BY就能看清楚:

SELECT payment_type, COUNT(*) AS trips, ROUND(AVG(fare_amount), 2) AS avg_fare, ROUND(AVG(tip_amount), 2) AS avg_tip, ROUND(AVG(total_amount), 2) AS avg_total FROM read_parquet('data/parquet/yellow/*.parquet') WHERE total_amount > 0 GROUP BY 1 ORDER BY trips DESC;

结果和直觉一致:信用卡支付占比最高,平均小费也明显高于现金单。这背后其实是纽约出租车的结算习惯——现金单往往按整数找零凑整,信用卡单则直接在终端上按百分比选小费,平均比例自然更高。

4.4 性能实测:扫200GB大概要多久

我用的是一台16核32线程、64GB内存的Linux机器,数据放在NVMe SSD上。简单GROUP BY查询,全量扫描200GB Parquet分区文件,耗时大约1到2分钟;如果SQL里带了year或month过滤条件,通常能压到10秒以内。这个性能对个人分析项目来说完全够用,也验证了开头的判断:单机方案对付这个量级,一点都不虚。

5. 把结果画出来,直观看到城市脉搏

5.1 时间趋势与费用分布

分析结果最终要落到图上。时间维度的折线图我用Plotly画,交互效果好,导出成HTML后可以直接发给别人看:

import duckdb import pandas as pd import plotly.express as px df = duckdb.sql(""" SELECT hour_bucket, trips FROM read_parquet('data/analysis/hourly_trips.parquet') """).df() fig = px.line(df, x='hour_bucket', y='trips', title='NYC Yellow Taxi 每小时订单量') fig.show()

费用分布建议用箱线图而不是直接画均值。出租车费用数据存在大量极端值,比如有人坐车花了几百美元,直接把均值拉得面目全非,箱线图能让你一眼看到中位数和离群点。

5.2 地理热力图:kepler.gl 与 Folium

热点区域的地图,我推荐用kepler.gl,在Jupyter里使用非常方便,支持一次性加载几十万个点的聚合渲染。做法是先把聚合结果导成带经纬度或Zone的CSV,再用Python接口加载并选择heatmap图层:

from keplergl import KeplerGl map_1 = KeplerGl(height=600) map_1.add_data(data=df_zones, name='nyc_taxi_hotspot') map_1.save_to_html(file_name='nyc_taxi_hotspot.html')

如果只需要把最终结论固定成报告插图,用Folium轻量一些,可以把Zone边界和热力图叠加在一起。两条路线我都试过:交互探索用kepler.gl,输出报告插图用Folium,分工明确。

这里有个小提醒:画地理数据前,一定要检查经纬度里有没有(0, 0)这类脏值。我见过很多人把整份数据一股脑丢进地图,结果发现一堆点齐刷刷落在非洲西海岸附近的海洋里,看着特别诡异——其实就是缺失坐标被填成了0。

6. 踩过的坑与排查技巧速查

6.1 典型问题排查表

把这轮踩过的坑整理成一张速查表,可以直接当参考:

症状原因解决办法
查询报Out of MemoryDuckDB内存上限不够,或返回结果集过大设置memory_limit和temp_directory;先聚合再取数
日期字段全是NULLCSV里是“1/15/2023 14:30”这类格式用STRPTIME手动转换,或设置dateformat参数
同一个月文件读两遍结果不一致文件里有重复表头行先head或grep检查文件头尾,再调整skip参数
带时间条件的SQL仍然很慢查询没有走分区裁剪用EXPLAIN查看实际扫描的文件,确认SQL带year/month过滤
地图画出来一堆点在海洋里坐标存在(0,0)绘图前过滤掉trip_distance<=0或经纬度为0的记录

6.2 几条独家避坑经验

第一,先跑一个月,再跑全量。我强烈建议把最小可用数据范围先跑通:下载、建表、聚合、出图,全流程走一遍再放开全量。200GB的数据出问题时排查起来极其痛苦,但一个月的数据几十秒就能暴露问题。

第二,下载文件时别光顾着并发拉取。我用的wget脚本确实快,但中途也有文件损坏的情况。建议下完后统一核对文件大小,或者用脚本数一下每个文件的行数,否则会在读取CSV阶段遇到各种奇怪的解析错误,排查起来很浪费时间。

第三,对TLC这类多年份数据集,字段不一致的问题几乎一定会遇到。我当时的做法是按年份分别建表,统一列名后再合并。后来发现如果是DuckDB,用UNION BY NAME关键字可以自动按列名对齐,能省不少手工活。这个坑虽然不大,但第一次撞上时确实让人摸不着头脑。

第四,分析结果的落地也很重要。聚合出的千万级结果不要直接导进Excel,可以先缩到Zone、小时这种粒度,再输出成Parquet或CSV。这样后续可视化、汇报、交接都会轻松很多。

项目跑到最后,我最大的感受是:200GB听起来吓人,真正的难点不在“大”,而在“乱”。下载、清洗、转格式花了大半天,后面的统计和可视化反而顺畅得出乎意料。如果下次再碰类似的数据集,我一定会先把每个月数据的schema差异和脏值情况摸清楚再动手。如果你也在折腾真实的大数据练手项目,强烈建议试试“单机+DuckDB+Parquet”这条路线,处理几百GB的公开数据集真的绰绰有余。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 18:58:57

电热综合能源系统分布鲁棒优化:数据驱动模糊集与Matlab实现全解析

做电热综合能源系统优化有一段时间了&#xff0c;说实话&#xff0c;最头疼的不是模型本身&#xff0c;而是不确定性。天气一变&#xff0c;风电出力和热负荷预测误差就会被放大&#xff0c;这时候如果还沿用传统的确定性调度&#xff0c;一个极端场景就可能让系统直接越限。我…

作者头像 李华
网站建设 2026/9/9 18:58:35

瑜伽馆健身房管理系统怎么选?从约课、会员、提成三大维度分析

瑜伽馆、健身房的核心经营痛点&#xff0c;集中在课程预约混乱、会员留存困难、员工业绩核算繁琐三大板块。多数场馆经营效率偏低&#xff0c;并非客流不足&#xff0c;而是人工管控模式无法匹配日常运营节奏。一套适配健身业态的管理系统&#xff0c;核心价值不是替代人工&…

作者头像 李华
网站建设 2026/9/9 18:58:12

健康资讯网站的信息可信度怎么判断

健康资讯网站的信息可信度怎么判断&#xff1f; 判断健康资讯网站可不可信&#xff0c;先看四件事&#xff1a;有没有标明内容由谁发布、能不能区分本站解读和外部报道、有没有更正机制&#xff0c;以及是否写清不能替代就诊。明白健康&#xff08;https://healviews.com/&…

作者头像 李华
网站建设 2026/9/9 18:57:58

αβ坐标变换下的两级VSC实时功率控制器Simulink建模

时间有点晚了&#xff0c;但上个月在调一个VSC并网模型的动态性能时踩了大坑&#xff0c;今晚抽空把这套带电流控制的“两级”电压源变流器&#xff08;VSC&#xff09;实时无功-有功控制器建模思路完整复盘出来。先说结论&#xff1a;在αβ坐标系里做PQ控制&#xff0c;跟常见…

作者头像 李华
网站建设 2026/9/9 18:56:25

Spec Kit CLI 如何升级并在升级后更新项目文件与已装扩展

Spec Kit CLI 如何升级并在升级后更新项目文件与已装扩展 【免费下载链接】spec-kit &#x1f4ab; Toolkit to help you get started with Spec-Driven Development 项目地址: https://gitcode.com/GitHub_Trending/sp/spec-kit 当你已经通过 uv tool 或 pipx 安装了 s…

作者头像 李华