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.21E+12”——这就是后面要说的科学计数法问题。遇到这些,第一步永远是写清洗逻辑,把数据标准化。
所以这篇总结我打算沿着“抓取-清洗-合并-分析”这条线,把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, params=params, headers=headers, timeout=10) 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", encoding="utf-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()默认保留前面的行,如果后面的行数据更完整,需要加keep="last"。我的经验是,先按某个主键排序,把数据质量好的行放前面,再去重。
缺失值处理要看字段性质和缺失比例。如果缺失比例超过70%,这个字段基本可以放弃;如果缺失值集中在某一小段,可以考虑行删除;如果字段是数值型,缺失量不大,可以用中位数或均值填充。不要上来就fillna(0),把缺失值填成0会让聚合结果严重失真。比如酒店价格列,如果缺失就填0,平均价格会被拉到一个完全没参考价值的数字。
3.2 清洗实操:一个旅游订单数据的小例子
这里用一个小例子演示清洗过程。假设我拿到的订单数据有三列:订单日期、支付金额、客户城市。实际数据如下:
| 订单日期 | 支付金额 | 客户城市 |
|---|---|---|
| 2024/3/1 | ¥1,299.00 | 上海 |
| 2024-03-02 | 899元 | 北京 |
| 2024/3/2 | 1,299.00元 | 上海 |
| 2024/03/03 | 599 | 成都 |
这短短四行就集体示范了格式不统一的问题。日期有三种写法,金额有符号、中文单位、小数位数不统一,城市空格不规则。我的清洗思路是分列处理:
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。axis=0是纵向合并,把多张表按行方向堆起来;axis=1是横向合并,把多张表按列方向并排拼起来。
直观理解:纵向合并就像把两摞砖上下叠在一起,总高度变高;横向合并就像把两张桌子并排放,总宽度变宽。这个方向性问题搞不清楚,后面所有数据都会错位。
我这次遇到的具体场景是:网站订单数据按月份分成了三张表,分别是1月、2月、3月的订单。现在要把三个月的数据合并成一张季度订单表,这时候必须用纵向合并:
df_q1 = pd.concat([df_jan, df_feb, df_mar], axis=0, ignore_index=True)注意我加了ignore_index=True。这个参数很关键,如果原表索引是0、1、2各自排列,直接concat后索引会重复,后续按索引筛选数据时会出问题。加上ignore_index=True,pandas会重新生成0到N-1的新索引,相当于把三张表彻底重排。
4.2 join、ignore_index等参数的实操对比
除了axis,concat还有几个参数在实战中特别关键。第一个是join,它控制合并时如何对待列名。
- 纵向合并时,
join="outer"会保留所有表的列;join="inner"只保留所有表都有的列。 - 横向合并时,
join="outer"会保留所有表的行(行按索引对齐);join="inner"只保留两边索引重叠的行。
我举个例子。1月订单表有“订单号、金额、城市”三列,2月订单表有“订单号、金额、城市、备注”四列。如果纵向concat不指定join,默认是outer,那1月的数据在“备注”列就会填空值。这个行为多数时候是我们想要的。但如果你明确知道两表结构完全一致,用join="inner"可以主动校验一下,只要有列名不一致,立刻报错,反而能帮你提前发现问题。
第二个是ignore_index,刚才已经提到了。第三个是keys参数,它的作用是在纵向合并时,给每块数据加一个分组标签,方便追踪数据来源:
df_all = pd.concat( [df_jan, df_feb, df_mar], axis=0, keys=["jan", "feb", "mar"] )这样合并后,索引层级会多一层,用df_all.loc["jan"]就能单独取出1月的数据。我在做月度对比分析时很喜欢用这个参数,省得额外加一列月份字段。
第四个是横向合并时索引对齐的问题。如果你有两张表,分别是订单基本信息和订单支付信息,它们的行数一样,但顺序不一样,直接用axis=1concat会把两边的数据错位拼在一起。这时绝对不能直接concat,而应该先按公共键排序,或者用merge按订单号关联。concat的横向拼接适合“列顺序完全一致”的场景,比如两张表都按同样的订单号排序;一旦需要按字段匹配两张表,还是用merge更稳妥。
4.3 concat与merge、join的选择逻辑
很多初学者搞不清concat、merge和join的区别。我总结了一个简单的选择逻辑:
- 需要把多张结构相同或相似的表“堆”成一张长表,用concat,axis=0。
- 需要给一张表“额外增加列”,且这些列来自另一张表,按某个共同字段匹配,用merge。
- DataFrame的索引对齐拼接,用join,但要确认索引语义一致。
上次毕设答辩时,有个学弟被问到“concat和merge都是合并,有什么区别”,他答得模棱两可。其实一句话就能说清:concat是“物理拼接”,不管内容是否有关联,只负责把数据按方向拼起来;merge是“逻辑关联”,按照一个或多个键把两张表的行匹配起来。理解了这个,你在选函数时就不会犹豫。
5. 分析落地与可视化:别让结果停在DataFrame里
5.1 分组聚合与指标拆解
数据清洗合并完之后,终于进入分析环节。这次旅游网站的数据,我主要想看这几个问题:不同城市的订单量分布、各月份的订单金额趋势、以及客单价是否有明显变化。
第一个问题用groupby加size()就行:
city_count = df.groupby("客户城市").size().sort_values(ascending=False)第二个问题需要先按月分组,再对金额求和:
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(kind="barh", 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.21E+12这种科学计数法格式。这其实不是DBeaver的问题,而是Excel和很多文本编辑器对超长数字的默认显示逻辑。
我第一次遇到时,直接把CSV读进pandas,那列ID全部变成了浮点数,后面关联别的表时怎么都对不上。后来排查才明白,这列是类似“321098765432123”的订单号,csv文件里存的是3.21098765432123E+14,pandas读进来就按浮点处理了。数字精度丢失,后几位全部变成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中文乱码 | 文件编码是GBK,pandas默认UTF-8 | encoding="utf-8-sig"或encoding="gbk" |
| concat后索引重复 | 原表各自有0~N索引 | 加ignore_index=True |
| 横向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(drop=True)重置一下就好。类似的细节在不同项目中反复出现,我把它们都记在当天的总结笔记里,时间长了就形成了一套自己的避坑清单。这也是写每日总结最大的价值:今天是Day46,回看第10天的自己,很多现在看来“理所当然”的操作,当时其实卡了很久。
7. 多说两句:这套流程怎么迁移到你的项目里
写了这么多,最后想说点实在的。如果你也正在刷数据科学的系列课,或者正准备数据科学与大数据技术方向的毕设,我建议你别只看概念,而是完整跑一遍今天这套流程:抓一个公开网站的数据,存成CSV,清洗,合并,聚合,可视化。哪怕数据量只有几千行,也足够让你把pandas几个核心函数练熟。
我个人的体会是,数据科学的瓶颈从来不是模型,而是数据工程。很多新手一上来就研究算法调参,却忽略了对数据进行合理加工的能力。而恰恰是数据处理的基本功,决定了你能在真实项目里走多远。Day46学到的最重要的东西依然是:做分析之前,先把数据伺候明白。