简介:这份PPT课件面向Python数据分析的初学者与入门学习者,系统梳理了数据分析的基本概念、完整流程与典型应用场景,帮助读者建立对数据分析领域的整体认知框架。课件内容涵盖狭义数据分析与数据挖掘的区别、需求分析到部署的六步流程、客户分析与营销分析等七类应用场景,并对比了Python、R、MATLAB三种主流语言的优劣,介绍了NumPy、Pandas、Matplotlib、Scikit-learn等常用库及Anaconda发行版与Jupyter Notebook的使用。资源包内含1个PPT文件,压缩包大小约4.07MB,以幻灯片形式呈现,结构清晰、图文并茂,适合课堂讲授或自学翻阅。目前已有135人学习下载,可作为数据分析入门阶段的知识梳理材料,帮助读者快速了解Python数据分析的工具生态与学习路径。
1. 从一份课件标题说起:Python 数据分析到底在分析什么
很多人第一次看到「Python 数据分析概述」这个标题,脑子里浮现的是 pandas 的read_csv、matplotlib 画几条折线,然后就没有然后了。我在带新人的时候发现一个反直觉的现象:真正卡住他们的从来不是某个 API 记不住,而是拿到一份业务数据后不知道该问什么问题、该把哪一列当维度、该用什么粒度聚合。工具是熟的,思路是空的。
这个标题背后其实是一条完整的落地链路:数据从哪来、用什么结构装、怎么清洗、怎么算、怎么把结论讲清楚。它适合两类人——刚转数据方向、需要一套能跑通的最小闭环的开发者;以及已经在写脚本、但每次都要重新翻文档、想把手艺固化成流程的从业者。接下来我不讲概念史,直接按「装什么、怎么读、怎么算、坑在哪」把这条链路走一遍,每一步都给能抄的命令和参数。
2. 环境与数据容器:把第一行代码跑起来之前要定的事
2.1 为什么优先用 conda 而不是裸 pip
数据分析的依赖有个特点:科学计算栈里 numpy、pandas、scipy 之间存在二进制 ABI 耦合,裸 pip 在部分平台上会去编译源码,一编译就牵扯 BLAS、Fortran 编译器,翻车概率陡增。常见做法是用 conda 建独立环境,让二进制包走预编译渠道,pip 只用来装纯 Python 的补充库。
# 创建名为 da 的独立环境,锁定 python 3.11 conda create -n da python=3.11 -y conda activate da # 科学计算三件套走 conda 渠道,避免源码编译 conda install numpy pandas matplotlib -y # 纯 Python 工具用 pip 补,互不干扰 pip install openpyxl jupyterlab逻辑说明:-n da是环境名,建议按项目命名而不是按人命名,方便多人复现;python=3.11显式锁版本,因为不同小版本对 pandas 的 wheel 支持不同。参数上,-y只是跳过交互确认,生产脚本里建议去掉,强制人工确认。装完用conda list核对 numpy 是否来自 conda 渠道而非 pypi,这一步能提前排掉后面 80% 的「import 报错但看不出原因」。
2.2 DataFrame 不是表格,是带索引的列式容器
新手最容易把 DataFrame 当成 Excel 的内存版,于是写出for循环逐行改值的代码。它的真实结构是「行索引 + 列标签 + 每列一个同质 dtype 的数组」,列式存储意味着按列操作是向量化的、按行遍历是昂贵的。理解这一点,后面所有性能问题都有了解释。
import pandas as pd # 显式指定 dtype,避免 pandas 猜错(比如把编号猜成 int 再丢前导零) df = pd.read_csv( "orders.csv", dtype={"order_id": "string", "amount": "float64"}, parse_dates=["created_at"], # 直接解析成 datetime,省一次转换 na_values=["", "NULL", "-"], # 统一空值标记 ) print(df.dtypes) print(df.shape)逻辑说明:dtype显式声明是防坑关键,编号类字段一律用string而不是int,否则00123会变成123。parse_dates在读取阶段就完成时间解析,比读进来再pd.to_datetime快且省内存。na_values把多种脏空值统一成NaN,避免后面== ""判断漏掉NULL。参数上,数据量超过内存一半时加chunksize=100000分块读,这是后面会展开的技巧。
2.3 索引该不该设:一个影响后续所有操作的决策
set_index不是可选项,它决定了你后面用loc还是iloc、能不能做时间切片。我的习惯是:只要这列唯一且会被反复用来筛选,就设成索引。
# 把时间列设为索引,之后可以直接 df.loc["2024-01"] df = df.set_index("created_at").sort_index() # 验证唯一性,不唯一就别设,否则 loc 会返回多行 assert df.index.is_unique, "索引不唯一,改用普通列筛选"逻辑说明:sort_index()是必须的,时间索引不排序,切片会得到错误结果且不报错,这是最阴的坑之一。assert那行是后悔药,索引不唯一时loc返回 DataFrame 而非 Series,后续代码类型全乱。参数上,如果只是偶尔筛选,别设索引,多一次reset_index反而增加心智负担。
3. 清洗与聚合:把脏数据变成能算的指标
3.1 缺失值处理:先分类,再决定删还是填
上来就dropna()是最常见的误用。缺失分三类:完全随机缺失、与某维度相关缺失、业务上代表「无」的缺失。第三类填 0,第二类要按分组填,第一类才考虑删。
# 先量化缺失,别凭感觉 miss = df.isna().mean().sort_values(ascending=False) print(miss[miss > 0]) # 按业务含义分治 df["amount"] = df["amount"].fillna(0) # 无金额=0 df["city"] = df.groupby("region")["city"].transform( lambda s: s.fillna(s.mode().iloc[0] if not s.mode().empty else "unknown") ) df = df.dropna(subset=["order_id"]) # 主键缺失只能删逻辑说明:isna().mean()给出每列缺失比例,比info()更直观。groupby().transform()是按组填充的标准写法,transform保证返回长度与原表一致,不会错位。mode().iloc[0]取众数,空众数要兜底,否则iloc[0]直接抛异常。参数上,填充阈值我一般定在缺失率 5% 以内才填,超过就单独建「缺失」类别,因为强行填会引入偏差。
3.2 分组聚合:agg 的三种写法与性能差异
groupby().agg()是数据分析的心脏,但写法不同性能差好几倍。命名聚合(named aggregation)是现在推荐的方式,输出列名可控、可读性好。
result = df.groupby(["region", "city"]).agg( order_cnt=("order_id", "count"), total_amt=("amount", "sum"), avg_amt=("amount", "mean"), max_amt=("amount", "max"), ).reset_index() # 多指标一次算完,避免多次 groupby print(result.sort_values("total_amt", ascending=False).head(10))逻辑说明:order_cnt=("order_id", "count")中左边是输出列名,右边是「源列, 聚合函数」元组,这是 pandas 0.25 之后的标准写法,比传字典更清晰。一次groupby算多个指标,比循环里反复分组快得多,因为分组键的哈希只建一次。参数上,count不计NaN,要计空值用size,这两个混用是高频 bug。
3.3 时间维度:重采样与滚动窗口
只要数据带时间,resample和rolling就是绕不开的。前者改变时间粒度,后者做平滑。
# 按周汇总,closed/ label 决定区间边界归属 weekly = ( df.resample("W-MON", closed="left", label="left")["amount"] .sum() .to_frame("weekly_amt") ) # 4 周滚动均值,min_periods 控制前几周是否出值 weekly["ma4"] = weekly["weekly_amt"].rolling(4, min_periods=2).mean()逻辑说明:W-MON表示以周一为锚点的周频,closed="left"和label="left"一起用才能保证区间不重不漏,这两个参数不配对是时间聚合最常见的错位来源。min_periods=2让前两周也有值,否则开头永远是NaN,画图会断。参数上,做同比就resample("M")后pct_change(12),别手动错位相减。
4. 避坑与排查:那些让脚本静默出错的细节
4.1 SettingWithCopyWarning:链式索引的连锁反应
现象:赋值时报警告,或者更糟——赋值没生效但也不报错。原因:df[df.a > 1]["b"] = 0这种链式写法,中间那步返回的是视图还是副本由 pandas 内部决定,你无法控制。解决:用.loc一步到位。
# 错误写法,可能静默失败 # df[df["amount"] > 100]["level"] = "high" # 正确写法 df.loc[df["amount"] > 100, "level"] = "high"4.2 内存爆炸:object 列是隐形杀手
现象:几十万行数据占了几个 G。原因:字符串列默认objectdtype,每个元素是独立 Python 对象,开销巨大。解决:转category或string。
for col in ["region", "city", "channel"]: if df[col].nunique() / len(df) < 0.5: # 基数低于一半才值得转 df[col] = df[col].astype("category") print(df.memory_usage(deep=True).sum() / 1e6, "MB")deep=True才会统计对象内部占用,不加这个参数看到的数字是骗人的。
4.3 浮点相等判断:金额对不上账
现象:两列金额本该相等,==却返回 False。原因:浮点二进制表示误差。解决:用容差比较。
import numpy as np mask = np.isclose(df["a"], df["b"], rtol=1e-9, atol=1e-6)金额场景建议全程用Decimal或整数分存储,别用 float。
4.4 中文列名与编码:读进来是乱码
现象:read_csv后列名是\xef\xbf\xbd。原因:文件不是 UTF-8。解决:显式指定编码,或先探测。
df = pd.read_csv("data.csv", encoding="gbk") # 常见于国内导出文件 # 不确定时先试 # import chardet; print(chardet.detect(open("data.csv","rb").read()))4.5 groupby 后索引丢失:reset_index 忘了加
现象:聚合结果想按列筛选,发现分组键变成了索引,df[df.region=="x"]报 KeyError。原因:groupby默认把分组键设为索引。解决:聚合后reset_index(),或groupby(..., as_index=False)。
5. 从脚本到可复用:把分析流程固化成三个习惯
走到这里,单次分析已经能跑通。但真正拉开差距的是「第二次做同类分析时能不能十分钟复现」。我自己的做法是三个习惯,都是踩坑踩出来的。
第一个习惯:把读取和清洗写成函数,参数化路径和列名。不要每次复制粘贴改路径,那样迟早改漏一处。
def load_orders(path: str, tz: str = "Asia/Shanghai") -> pd.DataFrame: df = pd.read_csv(path, dtype={"order_id": "string"}, parse_dates=["created_at"], na_values=["", "NULL"]) df["created_at"] = df["created_at"].dt.tz_localize("UTC").dt.tz_convert(tz) return df.set_index("created_at").sort_index()时区那行是血泪经验:跨区域数据不带时区,跨天聚合必错,而且错得悄无声息。tz_localize声明原始时区,tz_convert转到目标时区,顺序不能反。
第二个习惯:用pipe把清洗步骤串成管道,每一步一个函数,出问题能定位到具体环节。
def drop_bad_ids(df): return df[df["order_id"].str.len() == 12] def cap_amount(df, upper=1e6): return df.assign(amount=df["amount"].clip(upper=upper)) clean = (load_orders("orders.csv") .pipe(drop_bad_ids) .pipe(cap_amount, upper=5e5))pipe的好处是每个函数只关心自己的输入输出,测试时可以单独喂数据。assign返回新对象,不修改原表,避免副作用。
第三个习惯:关键结论落成断言,让数据异常在流程里就暴露,而不是等汇报时被问住。
assert clean["amount"].ge(0).all(), "存在负金额" assert clean.index.is_monotonic_increasing, "时间索引未排序"验证方法上,我一般会抽一天的数据手工用 Excel 算一遍,和脚本结果对,对上了才敢信整条链路。这个「手工对账」看着笨,但它是唯一能发现逻辑错误而非语法错误的手段。参数边界上,clip的上限别拍脑袋定,先看分位数,df["amount"].quantile([0.99, 0.999]),超过 99.9 分位的再考虑截断,否则会把真实大单误伤。
最后一个技巧:把常用聚合封装成配置字典,换指标不用改代码。
AGG = { "order_cnt": ("order_id", "count"), "total_amt": ("amount", "sum"), "avg_amt": ("amount", "mean"), } result = df.groupby("region").agg(**AGG).reset_index()**AGG把字典解包成命名聚合参数,加指标只改字典。这套东西我用了几年,最大的体会是:数据分析的难点从来不在算法,而在「你以为数据是干净的」这个假设上。每次我偷懒跳过缺失值分类那一步,后面总要用两倍时间还债。希望帮到你。
本文还有配套的精品资源,点击获取