行业资讯
📅 2026/9/7 17:01:33
共享单车数据分析实战:从Pandas清洗到业务洞察
“共享单车数据分析”这个项目是我在头歌平台上刷过好几遍的实训题。原因很简单它表面上只是个“入门级”练习但真正做一遍下来你会发现数据清洗、特征工程、可视化表达、业务洞察这些数据分析日常工作的核心环节它全串起来了。很多人把它当成一个交作业的关卡过关就删其实挺可惜的这套题里埋了不少可复用的方法论。这个项目适合的人很广。刚学Python、想找一套完整练手案例的初学者能在这里把pandas和matplotlib的常用操作走通工作了两三年、想从“跑数的人”往“分析问题的人”转的数据分析师也能从它的业务分析维度里找到一些思路哪怕是准备面试、想整理一份能写进简历的数据分析项目的人这个题都是一个不错的底子。接下来我就以自己当时的实操过程为线索把这套题从做题思路到具体代码再到踩坑记录完整拆开讲一遍。文中涉及的数据文件、字段名和题目要求各版本可能略有差异但分析方法是完全通用的。1. 项目解读与整体设计思路1.1 共享单车数据分析到底在分析什么先把问题想清楚一堆共享单车的骑行订单记录能分析出什么名堂往大了说是城市出行规律往小了说是不同人群的骑行偏好。放到头歌这个项目里题目通常会围绕几个固定维度去做探索性分析骑行时长分布一次骑行一般持续多久长尾在哪里。时间段规律一天之中哪些小时是租车高峰工作日和周末有什么区别。站点热度哪些站点是热门出发地/目的地潮汐现象是否明显。用户结构订阅用户Subscriber和临时用户Customer的行为差异。人口特征骑行者的年龄分布、性别占比。这些维度不是凭空想出来的它们对应着共享单车运营方的真实业务问题车辆怎么调度、站点怎么布局、定价怎么设计、新用户怎么拉新。所以这个项目表面上是做数据统计实际是在培养一种“拿到数据先想业务问题”的思维方式。1.2 头歌平台的实训机制和你平时自己写代码不一样头歌这类实训平台有一个特点它不是让你交一份报告而是把整个分析流程拆成若干个小关卡每一关给你一段不完整的代码你在指定位置补全然后平台自动评测输出结果。这意味着两件事。第一代码必须能在服务器环境里直接跑通不能用本地绝对路径也不能依赖没装好的第三方库。第二评测比对的是标准输出哪怕你的分析思路是对的只要输出格式和预期不一致这一关就是过不了。很多人卡住不是因为不会分析而是因为输出多了一个空格、列名顺序不对、小数位数不一致这类细节。所以做这个项目时我给自己定了一条原则先看清这道题要什么输出再动手写代码。数据结构是不是干净、用什么算法更优雅这些当然重要但在这个场景下和题目要求的匹配度才是第一位的。1.3 技术选型Python Pandas Matplotlib一个都不多头歌上这个项目的主流技术栈就是Python的Pandas数据处理加Matplotlib/Seaborn可视化偶尔会用到Numpy做数值计算。这个组合对我来说是在预期之内的也是目前做中小规模表格数据分析最顺手的一套方案。有一个点值得展开说说为什么不建议在这个项目里引入太重的东西有些人一听说“数据分析”就想上Hadoop、Spark但头歌这个数据集的量级基本是几万到几十万行单机Pandas处理起来是毫秒级的事完全不需要分布式计算框架。这也是数据分析里很重要的一条经验先评估数据量级再选工具杀鸡不用牛刀。真到了数据量大到单机扛不住的时候再考虑Spark也不迟而且到时候你会发现你在Pandas里练熟的聚合思路、分组逻辑迁到Spark的DataFrame API上几乎是平滑过渡的。2. 数据准备与预处理实操2.1 数据字段逐项拆解先把“家底”摸清楚我拿到数据后做的第一件事不是写代码而是用Excel打开原始CSV肉眼过了一遍字段。这个习惯是我工作后养成的别看这一步土它能帮你省掉后面大量的返工时间。我这个版本的数据集长这样字段名含义类型典型问题duration_sec骑行时长秒int存在0值和极大异常值start_time开始时间string需要转为datetimeend_time结束时间string同上start_station_id开始站点IDstring缺失start_station_name开始站点名string缺失end_station_id结束站点IDstring缺失end_station_name结束站点名string缺失bike_id车辆IDint无user_type用户类型stringSubscriber / Customermember_birth_year出生年份int缺失且存在异常年份member_gender性别string缺失分类值不统一把这些字段列出来之后整个项目的轮廓就清楚了。每个字段对应一类操作时间字段要转换经纬度类字段可以做站点聚合用户属性字段可以做分组对比。如果跳过这一步直接开跑很容易出现用到某个字段时才发现提前没清洗、只能回头补的情况。2.2 缺失值与异常值的处理边界数据清洗最忌讳的就是“一刀切”。我见过不少人上来就dropna()一把梭把所有带缺失值的行都删掉这样做在这个项目里会出问题。举个例子member_gender字段的缺失并不代表这条骑行记录无效它只说明这个用户没填性别信息。如果把所有性别缺失的行删掉会影响后续“按性别分析骑行时长”的样本量更关键的是有些关卡在统计总量时会和评测预期的数字对不上。我的处理原则是这样的核心分析字段缺失比如start_time、start_station_name直接删除相关行因为这些字段缺失后这条记录基本没法用了。非核心属性字段缺失比如gender、birth_year先不急着删分析到这个维度时再决定。如果缺失占比很小比如不到5%可以删除如果占比大可以考虑单独归为“未知”类别。异常值要区分是数据录入错误还是真实极端情况。骑行时长0秒肯定是异常但骑行时长4小时不一定是错误可能是用户骑到半路停下来办事。不能一看到数字大就删。骑行时长这个字段我单独说一句它是后面很多分析的基础所以我做了两道过滤小于60秒的记录视为无效骑行可能是用户刚解锁就发现车有问题还回去了大于4小时的记录视为异常长时占用可能是用户忘了还车这两部分在真实运营场景里都不属于“正常骑行”分析时剔除会比较合理。2.3 时间字段转换与特征衍生80%的分析都从这里来时间字段是整个数据集里信息密度最高的字段。一个start_time通过pandas的dt访问器可以拆出小时、星期、月份、是否工作日、是否节假日等一堆特征。这个项目里最关键的几个衍生特征就是“小时”和“星期几”。处理代码非常固定import pandas as pd df pd.read_csv(bike_data.csv) df[start_time] pd.to_datetime(df[start_time]) df[end_time] pd.to_datetime(df[end_time]) df[hour] df[start_time].dt.hour df[weekday] df[start_time].dt.dayofweek # 周一为0周日为6 df[date] df[start_time].dt.date这里面有一个细节新手特别容易踩pd.to_datetime()在遇到无法解析的字符串时会直接抛异常或者变成NaT所以转换之前最好先看一眼原始数据的格式。比如有的数据集里时间是3/1/2018 08:32有的是2018-03-01 08:32:00不加format参数虽然也能解析但速度会慢很多而且偶尔会解析错。我的习惯是转换时手动指定format一是准二是快。比如df[start_time] pd.to_datetime(df[start_time], format%Y-%m-%d %H:%M:%S)如果日期格式是斜杠那种对应的format是%m/%d/%Y %H:%M写错一个字母都会报错。真不确定的时候先df[start_time].head()打印几条原数据出来对着strftime的文档写就行。3. 核心分析维度与可视化实现3.1 骑行时长数据清洗后的第一份“体检报告”骑行时长是共享单车分析里最基础也最能反映用户体验的指标。清洗完异常值之后我通常先看一眼describe()结果print(df[duration_sec].describe())输出里最值得关注的是中位数50%和均值mean的差异。共享单车的骑行时长分布是典型的右偏分布大多数人骑10-20分钟就还车但总有少数人骑了1小时以上把均值拉得很高。如果你发现均值远大于中位数这就是一个可以直接写进报告的业务结论共享单车的使用场景以短途接驳为主但存在明显的长尾需求。可视化方面直方图是最直观的。需要注意的一点是原始时长范围太大直接画图会把短时段的分布压扁。我会先过滤掉超长时长再对分钟数做分布图import matplotlib.pyplot as plt df[duration_min] df[duration_sec] / 60 df_short df[df[duration_min] 60] plt.figure(figsize(10, 6)) plt.hist(df_short[duration_min], bins60, edgecolorblack, alpha0.7) plt.xlabel(骑行时长分钟) plt.ylabel(骑行次数) plt.title(骑行时长分布仅显示60分钟内) plt.show()这样画出来的图能看到一个清晰的单峰形态峰值基本落在5-15分钟区间这就是“最后一公里”场景的直观证据。3.2 高峰时段订单量按时段聚合挖掘城市出行节奏时间特征衍生完之后我最先做的是“按小时统计订单量”。这一步极简单但能直接看出共享单车的早晚双峰结构。hour_order df.groupby(hour).size()然后画个折线图你会发现典型的M形曲线早上7-9点一个高峰下午17-19点一个高峰中午和晚上相对平缓。这种双峰和通勤时间完全吻合说明共享单车的核心使用场景就是上下班通勤。更有意思的是将工作日和周末分开看。工作日的早晚高峰非常明显周末则变成了中午和下午的单峰形态。这说明共享单车同时服务通勤和休闲两种需求只是两类人群的使用时间完全错开。这个洞察放到运营上意味着工作日的早高峰需要重点保障地铁站周边的车辆供给而周末则应该把运力倾斜到公园、商圈等休闲目的地。这里有一个细节分组聚合用size()还是count()是有区别的。size()会对每一个分组计数不忽略缺失值count()只能统计非缺失值。在这个场景下因为前面已经清洗过数据两者结果一样但如果你在别的项目里遇到分组后数字对不上可以先看看是不是把这两个函数用混了。3.3 站点热度谁是城市的“骑行枢纽”站点维度主要通过统计每个站点的出发次数和到达次数来衡量。出发次数多说明这个站点是主要的租车点通常靠近居民区或写字楼到达次数多说明是主要的还车点通常是地铁站或商圈。start_counts df.groupby(start_station_name).size().sort_values(ascendingFalse) end_counts df.groupby(end_station_name).size().sort_values(ascendingFalse)把Top 10站点画成横向条形图能很直观地看出“热门出发地”和“热门到达地”的重叠情况。有些站点两边都上榜说明它是区域级的中转枢纽如果某些站点只在出发榜、不在到达榜说明它是一个“租车多、还车少”的点这类站点是调度问题的源头——早晚高峰时段车辆会从这类点流向还车点造成分布失衡。实际做这个可视化时站点名通常比较长横向条形图比纵向更合适不然标签会重叠看不清。3.4 用户画像订阅用户和临时用户是两个物种把user_type加进来分组分析是项目里业务味最浓的一部分。Subscriber订阅用户通常是本地通勤者买的是月卡年卡骑行频次高、单次时长短、骑行路径规律Customer临时用户通常是游客或偶尔使用的人单次骑行时长明显更长更多在周末出行。我一般会做两张图第一张两类用户的骑行时长对比df.groupby(user_type)[duration_min].median()第二张两类用户在不同工作日的骑行量对比user_weekday df.groupby([user_type, weekday]).size().unstack()这两张图放一起能得出一个很清晰的结论订阅用户的工作日骑行量是周末的2-3倍临时用户则在周末达到峰值。这说明订阅用户是通勤刚需临时用户是休闲旅游需求。对运营来说给订阅用户推通勤卡权益、给临时用户推景区周边骑行路线推荐方向就完全不一样了。3.5 年龄与性别数据质量不完美但趋势仍然有效年龄和性别字段在这个数据集里质量一般缺失多、异常值多。我的处理手段是先算当前年份减去出生年份得到年龄然后把小于10岁和大于80岁的异常值过滤掉缺失性别统一归为“未知”。做完清洗后再看性别占比和年龄分布仍然能看到有价值的信号男性骑行者占比明显高于女性年龄分布集中在25-35岁这个群体恰好是共享单车的主要用户他们接受新事物快、通勤距离适中、对价格敏感。不过坦白讲这类人口属性字段在真实数据里误差很大分析结果只能看趋势方向不能当精确统计用。这个结论我会留在报告里告诉读者展示的是一种处理不完美数据的态度而不是盲目相信字段名。4. 完整实操流程与代码解析4.1 开始前的环境准备与数据读取我使用的环境是Python 3.8以上版本pandas 1.xmatplotlib 3.x。如果你连着seaborn一起用了也不冲突但在头歌平台上seaborn不是每个镜像都预装所以我会尽量只用pandas和matplotlib降低环境依赖风险。数据读取这一步看起来简单但有两个细节会影响后面所有代码第一CSV文件路径。头歌平台每个关卡的工作目录可能不一样我习惯在代码开头先列一下当前目录下的文件确认文件名和实际环境一致import os print(os.listdir(./))第二读取时的编码参数。CSV文件可能是utf-8也可能是gbk读出来乱码时不一定是数据坏了先换编码试试df pd.read_csv(bike_data.csv, encodingutf-8) # 如果报UnicodeDecodeError就改成 encodinggbk4.2 关键代码逐段拆解从原始数据到可分析表以下是我在这个项目里反复使用的一组核心处理流程你可以直接参考。import pandas as pd import numpy as np import matplotlib.pyplot as plt # 1. 读取数据 df pd.read_csv(bike_data.csv) # 2. 删除完全重复的行如果有 df df.drop_duplicates() # 3. 删除核心字段缺失的记录 df df.dropna(subset[start_time, end_time, start_station_name, end_station_name]) # 4. 时间字段转换 df[start_time] pd.to_datetime(df[start_time]) df[end_time] pd.to_datetime(df[end_time]) # 5. 计算骑行时长秒并同时生成分钟列 df[duration_sec] (df[end_time] - df[start_time]).dt.total_seconds().astype(int) df[duration_min] df[duration_sec] / 60 # 6. 过滤异常骑行时长 df df[(df[duration_sec] 60) (df[duration_sec] 14400)] # 7. 衍生时间特征 df[hour] df[start_time].dt.hour df[weekday] df[start_time].dt.dayofweek df[month] df[start_time].dt.month df[is_weekend] df[weekday].apply(lambda x: 1 if x 5 else 0)第2步的去重很多人会忽略但现实中导出的数据偶尔会出现完全相同的重复行不去重的话后面所有统计数字都会偏大。第5步计算骑行时长时我倾向于不直接用原始CSV自带的duration_sec字段而是用start_time和end_time自己算一遍这样能顺便校验原始数据的准确性——如果自己算的结果和原始字段差异很大说明原数据本身有问题要警惕。4.3 可视化代码让图表自己“说话”数据分析项目里图表不是装饰是论证工具。我会在报告中嵌三张核心图骑行时长的直方图、各小时骑行量的折线图、用户类型的对比图。各小时骑行量的折线图是整份报告里信息量最大的一张图hour_counts df.groupby(hour).size() plt.figure(figsize(12, 6)) plt.plot(hour_counts.index, hour_counts.values, markero, linewidth2) plt.xticks(range(0, 24)) plt.grid(True, alpha0.3) plt.xlabel(小时) plt.ylabel(骑行订单量) plt.title(全天各小时骑行量分布) plt.show()这张图跑出来早晚双峰一目了然。如果你想把工作日和周末叠加在同一张图上对比可以这样写weekday_counts df[df[is_weekend] 0].groupby(hour).size() weekend_counts df[df[is_weekend] 1].groupby(hour).size() plt.figure(figsize(12, 6)) plt.plot(weekday_counts.index, weekday_counts.values, label工作日, markero) plt.plot(weekend_counts.index, weekend_counts.values, label周末, markers) plt.xticks(range(0, 24)) plt.legend() plt.show()两张线放一起工作日和周末的出行模式差异会非常直观工作日早上7点就开始爬升周末要到10点后才慢慢起来。4.4 从图表到业务结论做分析的最后一公里图表画完之后我建议你逼自己写三段话这张图说明了什么事实这个事实背后的可能原因是什么如果我是运营我会做什么动作拿“工作日早高峰骑行量大”这个现象举例原因大概率是通勤接驳需求用户从地铁站出来骑到公司。运营动作可以是在高峰时段之前把车辆从居住区调度到地铁站出口同时如果早高峰时某个站点的还车量远大于租车量说明这个站点周边是办公密集区可以考虑增设停车桩位。把这三段话写下来数据项目的价值才算真正闭环。头歌平台虽然不要求你交报告但如果你是在准备面试项目这部分才是面试官真正想听的东西。5. 常见问题与排查技巧实录5.1 平台评测不通过的非代码原因头歌这类平台最容易让人心态崩的地方在于本地跑得好好的代码提交上去就是“评测不通过”。我遇到的绝大多数情况都不是算法逻辑错而是这些原因现象常见原因处理办法输出结果和预期不一致打印了多余内容检查print语句只输出题目要求的字段列名对不上大小写或空格差异对比题目样例输出逐字符核列名小数位不同浮点数精度用round()或者格式化输出行数对不上清洗规则过严/过松重新核对缺失值和异常值过滤逻辑报错ModuleNotFoundError第三方库缺失改用pandas/numpy/matplotlib等预装库有一个很实用的排查方法先把题目样例输入手动算一遍搞清预期结果长什么样再回头检查自己代码。很多时候不是代码坏了而是你根本没理解题目到底要什么输出。5.2 Pandas数据处理的两个经典坑第一个坑是链式赋值警告。很多人在清洗数据时会写df[df[duration_sec] 60][duration_sec] 0这行代码看起来很合理但pandas会警告而且这个赋值可能根本没写回原DataFrame。正确写法是使用df.locdf.loc[df[duration_sec] 60, duration_sec] 0第二个坑是groupby之后忘记reset_index。groupby后的结果是一个GroupBy对象索引是分组键。如果你想把它当成普通DataFrame继续操作或导出最好加上reset_index()hour_counts df.groupby(hour).size().reset_index(namecount)不加的话hour列会变成索引而不是普通列后面按列名取数时就会踩坑。5.3 可视化中文与样式问题matplotlib默认字体不支持中文直接画图会出现一个个方块。这个问题在头歌上尤其常见因为平台环境里不一定装了中文字体。我的处理方式是plt.rcParams[font.sans-serif] [SimHei] # 或者 [Arial Unicode MS] plt.rcParams[axes.unicode_minus] False如果SimHei也不存在更稳妥的办法是图表标题和坐标轴标签全部用英文。虽然中英文混排看起来没那么本地化但至少图能正常显示不会在评测时因为字体问题报错。另外一个小Tip画图前先plt.figure(figsize(12, 6))把画布调大不然标签容易重叠画完用plt.tight_layout()自动调整布局保存时用plt.savefig(xxx.png, dpi150)本地做报告这样保存最稳。5.4 数据量大时的性能优化技巧这个项目的数据量虽然不夸张但如果你在本地跑几百万行的数据用Pandas处理还是会有卡顿感。我平时会做两个优化第一读取时只加载需要的列df pd.read_csv(bike_data.csv, usecols[start_time, end_time, start_station_name, user_type])第二把重复值多的字符串列转成category类型节省内存df[user_type] df[user_type].astype(category)这两个改动对数据分析过程的提速非常明显尤其是在大数据集上做分组聚合时。6. 业务洞察与扩展方向6.1 一份分析结果如何落为运营动作数据分析的价值最终表现在决策上。这个共享单车项目的结论有一组可以直接落到运营端的动作早晚高峰时段7-9点、17-19点城市核心地铁站周边的车辆需求最大调度系统应在早高峰前将车辆提前布放到居住区附近的地铁站晚高峰前反向调度。周末骑行高峰出现在午后热门站点集中在商圈和公园可以针对周末推出景区骑行路线推荐或休闲骑行优惠。订阅用户是通勤主力可在工作日的早晚高峰推出骑行积分翻倍活动临时用户骑行时间长可以按单次计费逻辑设计“新手体验卡”引导其转化为订阅用户。某些站点“租车量远大于还车量”在高峰时段容易出现无车可用或车位爆满的情况建议在这些站点动态调整停车位配额。这些都是从数据分布里直接推导出来的结论不需要复杂的机器学习模型但足够说明数据能支撑决策。6.2 从单机分析到Spark分布式处理的演进做完头歌这个项目之后如果你有兴趣往更深处走一个很自然的进阶方向是把这个数据集的分析逻辑迁移到Spark上。Pandas里的groupby、agg、join在Spark SQL里都有对应的操作迁移成本不高但需要额外学习分布式计算的思维方式。网上流传很广的“spark数据分析案例”里共享单车是典型的教学数据之一。因为它的字段规整、业务逻辑清晰特别适合用来练习Spark的DataFrame API和SQL操作。如果你打算学大数据方向的工具链完全可以拿这份数据练手从RDD到DataFrame、从HDFS到Hive表一遍走下来比看十遍文档都管用。当然要不要学Spark取决于你的职业方向。如果目标是做数据分析师Pandas SQL 可视化基本够用把业务分析方法打磨好更重要如果目标是数据工程师那分布式计算框架就是必修课这个项目可以当做一个很好的入门跳板。做完整个项目再回头看最大的感受是头歌这个实训题在“引导学习者建立数据分析直觉”这件事上做得挺到位的。每一项任务都不难但串起来就是一个完整的数据分析流程——拿到数据先摸结构再清洗然后从一个一个维度去拆解最后把结论落到业务上。这套流程才是这个项目真正值钱的地方。如果让我给一个具体建议我会说不要只满足于“过关”试着把每一步的代码都自己重写一遍每张图表都尝试调一下参数把业务结论都写成文字。用这种方式完整刷一遍这套题你收获的会远超“又多刷了一题”的成就感。