ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

大数据分析实战:从数据清洗到concat合并的完整流程

大数据分析实战:从数据清洗到concat合并的完整流程 1. 大数据分析的“全局观”从数据到结论到底走几步1.1 我在Day46里重新理解的“大数据”写这个每日总结系列到今天已经是第46天。前面45天我聊过数据抓取、清洗、可视化、机器学习基础但说实话直到昨天完整跑完一个“旅游网站订单数据分析”的小项目我才真正想明白一件事所谓大数据分析核心不是“大”而是“分析”。很多人被“大数据”三个字吓住觉得一定要上Hadoop、Spark才叫大数据。我一开始也这么认为后来发现真正决定分析价值的是你有没有能力把一堆乱糟糟的数据变成一张清晰的表格再变成一套能指导业务的结论。我用到的工具就是Python配上pandas、NumPy这些常见库数据量也就几十万行。可这不妨碍它是一次标准的大数据分析流程。那大数据分析和普通数据分析到底差在哪我的理解是分析流程的结构化程度要求更高数据准备阶段占的时间更长坑也更多。比如你拿到的数据可能是从多个渠道拼出来的格式不同、字段名不同、时间格式不统一甚至同一批数据里混着Excel导出的科学计数法——这些都要在进入分析环节之前处理干净。Day46这一天的学习我基本就泡在“处理数据”这件事上。1.2 一套能落地的分析流程框架我习惯把一次完整的数据分析项目拆成五个阶段这套思路适用于大部分业务场景需求定义搞清楚你要回答什么问题而不是先找数据。数据获取爬虫抓取、数据库导出、第三方API、手工整理都算。数据清洗处理缺失值、重复值、异常值、类型错误、格式不统一。数据合并与加工把多张表关联起来生成新字段这一步经常用到concat。分析与呈现聚合统计、可视化最终形成结论。这五步里数据清洗和合并往往占掉整个项目70%的时间。原因很简单真实数据永远是脏的。比如我从某个旅游网站上抓下来的数据订单金额字段里混着“¥1,299”、“1299元”、“1,299.00元”三种格式日期的写法有“2024-03-15”也有“2024/3/15”更离谱的是有一列明明应该是整数ID结果某一行变成了“3.21E12”——这就是后面要说的科学计数法问题。遇到这些第一步永远是写清洗逻辑把数据标准化。所以这篇总结我打算沿着“抓取-清洗-合并-分析”这条线把Day46做旅游网站数据分析时的思考和实操记录下来尤其是concat函数的各种细节和几个容易踩的坑。2. 数据从哪来抓取与接入的几种常见姿势2.1 旅游网站场景下的数据抓取思路我这次用的数据一部分来自一个旅游网站的公开页面一部分来自本地数据库导出。抓取环节我没有上Selenium这种重型工具而是先用requests直接请求接口因为很多现代网站的数据都是通过JSON接口异步加载的。你在浏览器里能看到列表页但数据其实是后台接口返回的JSON直接在开发者工具里找到这个接口比解析HTML高效太多。举个实际例子。我要抓某个目的地的酒店列表打开开发者工具切到Network面板刷新页面看到返回数据的XHR请求复制它的URL。一般这种URL里会带上城市ID、页码、每页条数这些参数。我用requests模拟请求加一个常见的User-Agent头就能拿到JSON数据。操作大概是这样的import requests url https://example.com/api/hotel/list params { city_id: 1024, page: 1, page_size: 50 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json()拿到JSON之后先把它转成DataFrame看一眼结构。这一步的目标不是立刻做分析而是搞清楚有哪些字段、哪些字段为空、字段类型是什么。我一般会打印列名和行数再用df.info()看整体概况。这里有个小经验第一次抓数据先只抓一页确认结构无误再循环抓全量。否则接口参数写错抓了几千条全是一个城市的后面清洗白干。2.2 数据接入的格式与工具选型不管数据是从接口抓来的、数据库导出的还是Excel手工整理的最后都要统一到DataFrame里。这个阶段我用到两个高频操作pd.read_csv()和pd.read_excel()。如果是从MySQL这类数据库导数据可以用pymysql或者直接在可视化工具里导出CSV再读。这里顺便提一个我踩过多次的坑用可视化工具导出的CSV编码经常是GBK或者带BOM的UTF-8直接用pandas默认参数读会乱码。所以我的通用读法是这样的import pandas as pd df pd.read_csv(hotel_data.csv, encodingutf-8-sig)utf-8-sig这个编码能自动处理BOM头而且对Excel另存的CSV兼容性比较好。如果你发现读出来的中文还是乱就把编码换成gbk试试。这个小细节在数据接入阶段特别实用尤其是当你手里的文件是别人发来的你根本不知道对方用什么编码存的。数据接入完成后我会习惯性地做一次“结构确认”打印前5行、看列名清单、看每列的空值数量和类型。这一遍可能只需几分钟但它决定了后续清洗策略怎么写。3. 数据清洗脏数据才是分析的真正门槛3.1 缺失值、重复值和类型问题的处理顺序数据清洗最容易犯的错误是“想到哪洗到哪”。如果每一列都单独处理最后字段之间很容易产生矛盾。我自己的经验是固定一个处理顺序反复用这样思路清晰代码也容易复用。我的顺序是先去重再处理缺失值最后统一类型和格式。去重放前面是因为重复记录会干扰缺失值的统计。比如同一个订单被插了两遍第一遍金额为空第二遍金额正常如果先去重留下的是第二遍那这个缺失值天然就被解决了。去重用drop_duplicates()注意subset参数。我这次处理订单数据时一个订单ID对应一行所以按客户ID加订单时间两个字段一起去重df df.drop_duplicates(subset[customer_id, order_time])这一步看着简单但有个细节容易忽略drop_duplicates()默认保留前面的行如果后面的行数据更完整需要加keeplast。我的经验是先按某个主键排序把数据质量好的行放前面再去重。缺失值处理要看字段性质和缺失比例。如果缺失比例超过70%这个字段基本可以放弃如果缺失值集中在某一小段可以考虑行删除如果字段是数值型缺失量不大可以用中位数或均值填充。不要上来就fillna(0)把缺失值填成0会让聚合结果严重失真。比如酒店价格列如果缺失就填0平均价格会被拉到一个完全没参考价值的数字。3.2 清洗实操一个旅游订单数据的小例子这里用一个小例子演示清洗过程。假设我拿到的订单数据有三列订单日期、支付金额、客户城市。实际数据如下订单日期支付金额客户城市2024/3/1¥1,299.00上海2024-03-02899元北京2024/3/21,299.00元上海2024/03/03599成都这短短四行就集体示范了格式不统一的问题。日期有三种写法金额有符号、中文单位、小数位数不统一城市空格不规则。我的清洗思路是分列处理import re # 日期标准化 df[订单日期] pd.to_datetime(df[订单日期]) # 金额清洗去掉符号、中文、千分位只留数字 def clean_amount(x): s str(x) s s.replace(¥, ).replace(元, ).replace(,, ).strip() return float(s) df[支付金额] df[支付金额].apply(clean_amount) # 城市去空格 df[客户城市] df[客户城市].str.strip()pd.to_datetime()能自动识别大部分常见日期格式这一步非常省心。金额清洗用自定义函数加正则也行但这里因为模式很规整直接替换就够了。需要注意的一点是清洗操作必须新建列或者直接覆盖原列最好保留一份原始备份。我习惯在处理前先执行df_raw df.copy()万一后面发现清洗逻辑写错了还能回头对比。做完这些之后建议立刻用df.dtypes检查每列类型再用df[支付金额].describe()看一眼数据分布。如果发现最大值是负数或者最小值是0那很可能清洗逻辑有漏洞得回头查。4. concat()函数纵向与横向合并的核心细节4.1 axis参数决定方向纵向堆叠还是横向拼接concat是pandas里最常用的合并函数之一也是我Day46重点复盘的内容。它最核心的参数就是axis。axis0是纵向合并把多张表按行方向堆起来axis1是横向合并把多张表按列方向并排拼起来。直观理解纵向合并就像把两摞砖上下叠在一起总高度变高横向合并就像把两张桌子并排放总宽度变宽。这个方向性问题搞不清楚后面所有数据都会错位。我这次遇到的具体场景是网站订单数据按月份分成了三张表分别是1月、2月、3月的订单。现在要把三个月的数据合并成一张季度订单表这时候必须用纵向合并df_q1 pd.concat([df_jan, df_feb, df_mar], axis0, ignore_indexTrue)注意我加了ignore_indexTrue。这个参数很关键如果原表索引是0、1、2各自排列直接concat后索引会重复后续按索引筛选数据时会出问题。加上ignore_indexTruepandas会重新生成0到N-1的新索引相当于把三张表彻底重排。4.2 join、ignore_index等参数的实操对比除了axisconcat还有几个参数在实战中特别关键。第一个是join它控制合并时如何对待列名。纵向合并时joinouter会保留所有表的列joininner只保留所有表都有的列。横向合并时joinouter会保留所有表的行行按索引对齐joininner只保留两边索引重叠的行。我举个例子。1月订单表有“订单号、金额、城市”三列2月订单表有“订单号、金额、城市、备注”四列。如果纵向concat不指定join默认是outer那1月的数据在“备注”列就会填空值。这个行为多数时候是我们想要的。但如果你明确知道两表结构完全一致用joininner可以主动校验一下只要有列名不一致立刻报错反而能帮你提前发现问题。第二个是ignore_index刚才已经提到了。第三个是keys参数它的作用是在纵向合并时给每块数据加一个分组标签方便追踪数据来源df_all pd.concat( [df_jan, df_feb, df_mar], axis0, keys[jan, feb, mar] )这样合并后索引层级会多一层用df_all.loc[jan]就能单独取出1月的数据。我在做月度对比分析时很喜欢用这个参数省得额外加一列月份字段。第四个是横向合并时索引对齐的问题。如果你有两张表分别是订单基本信息和订单支付信息它们的行数一样但顺序不一样直接用axis1concat会把两边的数据错位拼在一起。这时绝对不能直接concat而应该先按公共键排序或者用merge按订单号关联。concat的横向拼接适合“列顺序完全一致”的场景比如两张表都按同样的订单号排序一旦需要按字段匹配两张表还是用merge更稳妥。4.3 concat与merge、join的选择逻辑很多初学者搞不清concat、merge和join的区别。我总结了一个简单的选择逻辑需要把多张结构相同或相似的表“堆”成一张长表用concataxis0。需要给一张表“额外增加列”且这些列来自另一张表按某个共同字段匹配用merge。DataFrame的索引对齐拼接用join但要确认索引语义一致。上次毕设答辩时有个学弟被问到“concat和merge都是合并有什么区别”他答得模棱两可。其实一句话就能说清concat是“物理拼接”不管内容是否有关联只负责把数据按方向拼起来merge是“逻辑关联”按照一个或多个键把两张表的行匹配起来。理解了这个你在选函数时就不会犹豫。5. 分析落地与可视化别让结果停在DataFrame里5.1 分组聚合与指标拆解数据清洗合并完之后终于进入分析环节。这次旅游网站的数据我主要想看这几个问题不同城市的订单量分布、各月份的订单金额趋势、以及客单价是否有明显变化。第一个问题用groupby加size()就行city_count df.groupby(客户城市).size().sort_values(ascendingFalse)第二个问题需要先按月分组再对金额求和df[月份] df[订单日期].dt.month monthly_amount df.groupby(月份)[支付金额].sum()这里能看出清洗阶段标准化日期字段的价值。如果日期还是“2024/3/1”和“2024-03-02”混在一起dt.month根本提取不了月份聚合分析就卡住了。所以清洗不是“做完就行”它是所有后续分析的地基。第三个问题算客单价直接总金额除以订单总量。这个指标适合看整体消费水平但如果有异常大额订单会被拉高。所以我在计算前先看一波金额分布用quantile找出99分位线超过这个线的订单单独标记出来看看是不是异常数据。这一步不是必须的但在真实项目里异常值对决策的影响往往比你想的大得多。5.2 可视化表达的几个习惯分析结果光靠数字输出说服力不够。我习惯用matplotlib快速画几张图辅助理解数据。但可视化有几个我自己总结的习惯能画图就画图不要只打印数据表。图上的标题和轴标签必须写清楚否则别人看不懂。先画分布再画趋势最后画对比。每张图只表达一个核心信息。比如订单量城市分布用横向条形图最直观因为城市名在左侧轴容易阅读月度趋势用折线图能看出变化趋势客单价对比用柱状图就好。下面是画城市订单量Top10的示例import matplotlib.pyplot as plt ax city_count.head(10).plot(kindbarh, figsize(10, 6)) ax.set_xlabel(订单量) ax.set_ylabel(城市) plt.title(Top10城市订单量分布) plt.gca().invert_yaxis() plt.tight_layout() plt.show()invert_yaxis()这步是我踩坑之后加上的因为pandas默认的barh图数据从下往上排第一个元素显示在最底部视觉上看着不舒服反转一下让最大的在最上面读起来更自然。这种细节不影响正确性但会影响看图人的理解效率。6. 实操中的经典问题与避坑记录6.1 dbeaver导出数据变成科学计数法了这个坑我最近遇到频率特别高必须单独拿出来讲。用DBeaver导出数据为CSV时如果某一列是超过15位的数值ID导出来经常会变成3.21E12这种科学计数法格式。这其实不是DBeaver的问题而是Excel和很多文本编辑器对超长数字的默认显示逻辑。我第一次遇到时直接把CSV读进pandas那列ID全部变成了浮点数后面关联别的表时怎么都对不上。后来排查才明白这列是类似“321098765432123”的订单号csv文件里存的是3.21098765432123E14pandas读进来就按浮点处理了。数字精度丢失后几位全部变成0。解决方案有两个。方案一在DBeaver导出时把字段格式设置为文本或字符串强制导出时不转科学计数法。具体操作是在导出对话框里选择格式选项把“Data formats”里的数字格式改成文本。方案二如果导出的文件已经变成了科学计数法pandas读取时指定列类型df pd.read_csv(orders.csv, dtype{order_id: str})必须用dtype强制指定不能加载后再astype(str)因为精度在读取那一刻已经丢了astype(str)救不回来。如果你非要在加载后处理可以用pd.set_option(display.float_format, lambda x: f{x:.0f})但这只是显示层面修复底层精度已经没了关联键还是匹配不上。这个问题的本质是大数据处理中精度损失是“一次性的、不可逆的”必须在入口处堵住。不管是DBeaver导出、Excel打开还是CSV读取遇到超长数字第一时间就要想到“它是不是会被当作浮点数处理”然后提前干预。6.2 其他几个高频坑的速查除了科学计数法Day46实操中我还遇到几个问题顺手整理成一张速查表问题现象根本原因解决方案读CSV中文乱码文件编码是GBKpandas默认UTF-8encodingutf-8-sig或encodinggbkconcat后索引重复原表各自有0~N索引加ignore_indexTrue横向concat数据错位两表顺序不一致concat按位置拼接改用merge按字段关联金额列混有“¥”、“元”网页抽取未清洗自定义替换函数统一为标准数字时间列格式不一致不同来源日期格式不同pd.to_datetime()统一转换DataFrame打印科学计数法pandas默认浮点显示pd.set_option(display.float_format, lambda x: f{x:.0f})这里多说一句pd.set_option那条只是让显示好看分析时如果需要精确输出最好还是把列转成字符串并设置显示参数。我一般在最后的汇总统计里同时用好几种方式验证比如用df.info()看类型、用df.head(10)看样例、用df.describe()看分布三个一起看才安心。还有一个很小的坑也值得提。用pd.read_csv读取时如果CSV里含空行pandas默认会跳过但跳过之后索引不是连续的后面用reset_index(dropTrue)重置一下就好。类似的细节在不同项目中反复出现我把它们都记在当天的总结笔记里时间长了就形成了一套自己的避坑清单。这也是写每日总结最大的价值今天是Day46回看第10天的自己很多现在看来“理所当然”的操作当时其实卡了很久。7. 多说两句这套流程怎么迁移到你的项目里写了这么多最后想说点实在的。如果你也正在刷数据科学的系列课或者正准备数据科学与大数据技术方向的毕设我建议你别只看概念而是完整跑一遍今天这套流程抓一个公开网站的数据存成CSV清洗合并聚合可视化。哪怕数据量只有几千行也足够让你把pandas几个核心函数练熟。我个人的体会是数据科学的瓶颈从来不是模型而是数据工程。很多新手一上来就研究算法调参却忽略了对数据进行合理加工的能力。而恰恰是数据处理的基本功决定了你能在真实项目里走多远。Day46学到的最重要的东西依然是做分析之前先把数据伺候明白。
返回列表