☰
Python数据科学入门:从数据管道到Pandas数据清洗实战
2026/10/20 10:53:09 网站建设 项目流程

1. 为什么我建议从"数据管道"的角度入门,而不是直接啃语法

很多初学者学 Python 数据科学,拿到手的通常是这么一条路:先学基础语法、再学 NumPy、再学 Pandas、然后是可视化、最后接触机器学习。这套路本身没错,但我带过几轮新人之后发现,真正让人坚持不下去的,恰恰是"学了一堆语法却不知道自己能干什么"。

我建议换个切入点——把 Python 数据科学当成一条"数据管道"来看。数据从哪来、怎么读进来、怎么整理干净、怎么分组统计、怎么转成图表、怎么输出结论,这是一个完整闭环。你不需要先精通所有语法再动手,而是顺着这条管道走一遍,用到什么学什么。这篇文章是第一篇,我先把这条管道的"入口"部分讲透:环境、数据读取、数据结构基础、以及第一个完整的小项目闭环。

这篇内容适合什么人?完全没写过 Python 的纯新手可以照着操作,学过 Python 但对数据分析没概念的后端开发也能在这里补齐"用数据说话"的思维。我会尽量少讲纯理论,多放能直接抄的代码和踩坑记录。

2. 环境搭建:别在安装这一步浪费超过二十分钟

2.1 用 Anaconda 还是纯 Python 加 pip

这是新人问得最多的问题。我的答案是:做数据科学相关的事情,直接装 Anaconda,省心。它是 Python 发行版,里面预装了大量数据科学库,最核心的价值是 conda 可以帮你处理库与库之间的依赖关系,不会出现装 A 库把 B 库搞崩的情况。

纯 Python 加 pip 的方式不是不行,但对于入门阶段的人来说,版本冲突是最大的隐形杀手。我见过不止一个人折腾两天装环境,最后连 matplotlib 都导入不了,原因就是 NumPy 版本跟其他库不兼容。如果你非得用纯 Python,记住一个原则:创建虚拟环境再加库,别往全局环境里塞东西。

安装完成之后打开终端(Windows 用 Anaconda Prompt 或 PowerShell,macOS/Linux 用自带的终端),输入python --version检查一下版本。如果有多个 Python 版本共存的环境,建议用python -V和pip -V确认下当前用的是哪个。

2.2 准备项目目录和第一个文件

我习惯每个小项目单独建一个文件夹,里面放数据文件和脚本文件。比如:

data-science-beginner/ ├── data/ │ └── sales.csv └── notebooks/ └── first_analysis.ipynb

你可以用 Jupyter Notebook 写代码,也可以用 VS Code 搭配 Python 插件。我个人推荐新手先从 Jupyter Notebook 开始,因为可以一格一格地运行代码,随时看到中间结果,交互感强,特别适合探索数据。但要注意,不要只会在 Notebook 里写代码,我后面会讲怎么把探索用的脚本转成正式脚本。

提示:如果用的是 Jupyter,第一次运行某个库之前先等几秒,首次导入会加载依赖,之后再运行就快了。这不是卡住,别急着关。

3. 数据从哪来:读取 CSV 是第一堂必修课

3.1 真实世界里大部分数据都不是完美格式

打开一个 CSV 文件你会看到什么?通常情况下不是教科书里那种干干净净的表格,而是满屏的幺蛾子:列名带中文、行尾有多余逗号、中间有空行、日期格式是2024/05/06和2024-05-06混用的,某些单元格里还带着换行符。这才是真实数据的常态。

Pandas 的read_csv函数是读取这类文件的主力工具。它的强大之处在于支持非常多的参数来应对混乱情况。我先把最常用的几个参数列出来,后续会结合例子讲:

import pandas as pd df = pd.read_csv( "data/sales.csv", encoding="utf-8", sep=",", skipinitialspace=True, parse_dates=["日期"], dtype={"数量": "int64"} )
参数作用什么时候用
encoding指定文件编码遇到UnicodeDecodeError时,试gbk或utf-8-sig
sep分隔符文件用\t分隔时改为sep="\t"
skipinitialspace忽略分隔符后多余空格常见于手工编辑过的 CSV
parse_dates将某列解析为日期类型后续需要按时间排序或按月统计时
dtype强制指定列的数据类型防止 ID 列被读成数值导致丢前导零
skiprows跳过文件开头若干行文件前几行有说明性文字时

我见过有人强烈推荐"先读进来再慢慢处理列类型",从效率上讲这是最省事的方式,但如果你明确知道某些列的类型,在读取时指定dtype会省去后面不少麻烦。比如订单号001235,如果不指定dtype,Pandas 会把它读成整数1235,前导零直接没了,等你反应过来已经丢了一批数据。

3.2 先用info()和head()感知数据

读进来之后别急着分析,先看一眼数据长什么样:

print(df.head()) print(df.info())

head()看前五行,info()看每列的类型、非空数量、内存占用。这两步能让你快速发现明显的异常:列数对不对、数据量多大、有没有整列是空值、日期列是不是被读成了字符串。

我踩过的一个坑:某次读一个销售明细表,info()显示count为 1.8 万行但日期列非空数量只有 1.2 万,马上就意识到有大量日期缺失。后面 [[section 5|清洗的时候花了很多时间]] 才补上,如果当时读取时就用parse_dates配合keep_default_na控制空值识别,能省不少事。从这次之后我养成了习惯:读取之后第一件事永远是"体检"。

4. DataFrame 的核心操作:结构与筛选,别急着学全部方法

4.1 用一组例子理解行列定位逻辑

DataFrame可以简单理解成"带标签的表格",既有行索引又有列名。这里最核心的概念是"按标签定位"和"按位置定位"的区别。用loc是按标签名字取,用iloc是按整数位置取。

# 按标签取行:索引为 0~4 的行,取"数量"列 rows_by_label = df.loc[0:4, "数量"] # 按位置取:前 5 行,第 2 到第 4 列 rows_by_position = df.iloc[0:5, 1:4]

这两个方法对应的逻辑差异很容易被忽略,loc[0:4]是包含5 这个边界位置的,iloc[0:5]也是取前 5 行,但如果你混用,内心想的是"从第一行到第五行",嘴里写的是iloc[0:4],就会漏掉一行。

所有 DataFrame 操作中,我建议入门期把loc、iloc、at、iat这四个搞清楚就够了,不需要急着学query()、isin()这些进阶筛选。筛选的核心是三个概念:行是条件筛选,列是字段提取,值是对应条件的取值。把这三个概念记住,剩下的方法无非是换写法。

4.1.1 条件筛选的秘密

先说条件筛选,它背后的逻辑是"布尔索引"。你写df[df["数量"] > 100],实际上分成两步:首先df["数量"] > 100生成一个布尔序列,长度跟行数一致,True的位置表示满足条件,然后 DataFrame 像筛子一样只保留布尔序列中True对应的行。理解了这个机制,复杂的多条件组合就不会乱。

# 多条件筛选:数量大于 100 且单价小于 50 的行 condition = (df["数量"] > 100) & (df["单价"] < 50) filtered = df[condition]

两个细节新手经常踩:一是多个条件必须用括号括起来,不然&的优先级会出问题;二是"且"用&,"或"用|,跟 Python 原生的and、or不能混用。原生操作符会尝试对整个 Series 做布尔判断,结果就是报错或者返回一个让人摸不着头脑的标量。

4.2 分组聚合不是groupby一句话那么简单

groupby是 Pandas 里最常用也最容易被误解的 API。初学时以为它跟 Excel 的数据透视表一样,拖两个字段就出结果,其实它的运行机制是"拆分-应用-合并"三步:先按某个列的值把数据拆成若干小组,再对每个组应用聚合函数,最后把结果合并回一个表。

# 按"商品类别"分组,求"数量"总和 summary = df.groupby("商品类别")["数量"].sum()

注意这里["数量"]是必须的,它告诉 Pandas"分组之后只对这个列做聚合"。如果直接写df.groupby("商品类别").sum(),Pandas 会对所有数值列都求和,如果你表格里恰好有个列是订单号这种数值型的"伪数值",结果里就会多一个毫无意义的求和列。

更进一步的用法是多个聚合函数一起做:

summary_multi = df.groupby("商品类别").agg( total_qty=("数量", "sum"), avg_price=("单价", "mean"), max_qty=("数量", "max") )

agg配合元组列表的写法,在后期做业务报表时几乎天天用到。它能一次把不同列用不同函数处理完,而且新列名字自己起。入门阶段把这一个函数吃透,比死记十几个sum()、mean()、count()的调用方式更有效。

5. 数据清洗入门:真实项目中大部分时间都花在这里

5.1 空值处理的三条原则

数据清洗的"脏数据"来源非常集中,第一就是空值。面对空值,你应该先想三件事:这个字段是否允许为空?这一行是否还有保留价值?这个字段的缺失值是否可以通过规则推导补全?这三个问题的答案决定了处理策略。

处理方式对应代码适用场景
直接丢弃含空值的行df.dropna()空值占比很小,且该行其他字段价值有限
按常量填充df["列名"].fillna("未知")分类字段的空值本身也是一种信息
按均值/中位数填充df["列名"].fillna(df["列名"].median())数值型字段,分布比较集中时用中位数更稳

注意dropna()默认是所有列都非空才保留,如果你想"只要某列非空就保留",要写成df.dropna(subset=["订单号"])。这个参数别说新手,老手有时候也会忽略,结果把明明还有用的行误删了。

5.2 重复值比空值更难缠

重复值分两种:完全重复的行,和"关键列重复但其他列不同"的行。前者用df.drop_duplicates()直接去重;后者要先把"判断是否重复"的列名传给subset参数,然后自己决定保留下哪一条。

# 按订单号和商品ID判断重复,保留最后一条记录 df_dedup = df.drop_duplicates(subset=["订单号", "商品ID"], keep="last")

keep除了"last"还有"first"和False。False会把所有重复的行全部干掉的,如果数据本身就需要保留一处,就别用False。这个参数的语义在实际使用中一定要先验证再大批量执行,不然数据量大的时候你根本发现不了误删。

5.3 数据类型清洗:字符串和日期的坑

从 CSV 读进来的数据,默认都是字符串。你看着 Excel 里是数字,读进 Pandas 它也显示数字,但dtype可能还是object,这样后面算平均值会报错。

# 清洗时统一转数值类型,coerce 让无法转换的变成 NaN df["售价"] = pd.to_numeric(df["售价"], errors="coerce") df["日期"] = pd.to_datetime(df["日期"], errors="coerce", format="%Y-%m-%d")

这一个习惯能帮你提前排查掉 80% 的"为什么结果不对"问题。errors="coerce"的意思是转换失败的置为 NaN,然后你可以再用空值处理的方法去处理它们,不会因为某个脏数据导致整个脚本崩溃。

6. 防御式编程思维:让数据管道跑得更稳

6.1 断言就是给数据管道装保险丝

处理数据的过程中,时不时要停下来验证一下当前的数据状态是否符合预期。Python 的断言语句assert在这里特别好用,条件不满足就会抛出异常,让你立刻知道哪步出了问题。

# 检查清洗后没有空值 assert df["订单号"].notna().all(), "有订单号为空,请检查清洗逻辑" # 检查去重后最小订单数大于 0 assert len(df) > 0, "数据为空,后续操作无意义"

有人会觉得断言会拖慢速度,实际上对于数据分析场景,这点开销完全值得。它相当于在流水线关键节点装了安全阀门,数据不对就停下来,而不是带着问题跑到底,最后输出一个看起来正常实际全是错的结论。

6.2 用"管道的每一段只做一件事"来设计代码

我见过很多新人把整个数据流程写成一长串操作,二十行代码挤在一起,中间任何一步出错都很难定位。更好的做法是划分成几个函数,每个函数负责一段逻辑:读取、清洗、统计、可视化各管各的。

def load_data(path: str) -> pd.DataFrame: return pd.read_csv(path, encoding="utf-8") def clean_data(df: pd.DataFrame) -> pd.DataFrame: df_clean = df.dropna(subset=["订单号"]) df_clean["售价"] = pd.to_numeric(df_clean["售价"], errors="coerce") return df_clean def summarize(df: pd.DataFrame): return df.groupby("商品类别")["数量"].sum()

这样的结构有三个好处:每段可以单独调试;后续数据源变了只需改load_data;统计逻辑变了只需改summarize。四个模块独立进化,互不干扰。

提示:函数参数的: str和-> pd.DataFrame这种是类型注解,Python 本身不做强制检查,但配合 IDE 写代码时提示特别友好,也能让后来的读者一看就知道该传什么类型。建议从入门第一天就养成写的习惯。

7. 把第一个"读取-清洗-统计-可视化"闭环跑通

7.1 自建一箱"脏数据"练手

纸上谈兵没意义,我提供一个模拟的销售数据生成脚本,故意混入各种真实世界的脏数据。你可以建立文件make_dirty_data.py运行一次生成,或者直接在 Jupyter 里跑:

import pandas as pd import numpy as np np.random.seed(42) data = { "订单号": ["A001", "A002", "A003", "A003", "A005"], "商品类别": ["食品", "数码", "食品", "数码", "日用"], "数量": [3, 2, 0, 5, 1], "单价": [12.5, 399, "15", "待定", 8.9], "日期": ["2024-05-01", "2024-05-01", "2024/05/03", "2024-05-03", "2024-05-04"] } # 故意混入空值 df_raw = pd.DataFrame(data) df_raw.loc[3, "单价"] = np.nan df_raw.to_csv("data/sales_demo.csv", index=False, encoding="utf-8")

这份数据里既有重复订单 A003,又有字符串类型的数字、中文脏数据,还有日期格式混用,几乎把前面提到的坑都集齐了。

7.2 完整代码:16 行完成从读到图表

import pandas as pd import matplotlib.pyplot as plt # 1. 读取 df = pd.read_csv("data/sales_demo.csv", encoding="utf-8") # 2. 清洗 df["单价"] = pd.to_numeric(df["单价"], errors="coerce") df["日期"] = pd.to_datetime(df["日期"], errors="coerce") df = df.dropna(subset=["单价"]) df = df.drop_duplicates(subset=["订单号"]) # 3. 过滤掉数量为 0 的无效记录 df = df[df["数量"] > 0] # 4. 统计各品类销售额 df["销售额"] = df["数量"] * df["单价"] summary = df.groupby("商品类别")["销售额"].sum().sort_values(ascending=False) # 5. 可视化 summary.plot(kind="bar") plt.title("各品类销售额") plt.ylabel("销售额") plt.show()

看一遍语法你可能大部分都认识,但真正"读懂"还需要理解每一步在清洗层面对应的"脏数据病灶"。为了说明清洗的重要性,我建议你先故意不改任何清洗步骤跑一次原始sales_demo.csv,看看结果有多离谱:"待定"和np.nan会让销售额直接一整列变成缺失或报错,根本画不出图。

跑出图之后,建议再做三件事:把plot(kind="bar")换成kind="barh"感受下横向柱状图;把.sort_values(ascending=False)去掉再画一次,看看排序对柱状图可读性的影响;最后把清洗步骤逐行注释掉,观察每一步对最终结果的影响。这三步做完,你对管道调优的理解会比看十篇文章都深。

8. 踩坑记录:新人最容易掉进去的三个"数值陷阱"

8.1 陷阱一:整数值误判为空值

用pd.to_numeric(..., errors="coerce")转换时,Pandas 有时会把空字符串和空格也转成NaN,这本来是好事情,但如果你原本是"空即有特殊含义"的数据,比如"该商品不出售所以数量为空",也会一起被干掉。这种时候得先问业务含义,再决定要不要转。

8.2 陷阱二:日期解析的夏令时问题

我一度以为只有洋数据才有时区麻烦,直到某次处理跨区域线上销售的订单表才发现,不同系统导出的日期,有的带+08:00,有的是 UTC 零时区。pd.to_datetime虽然会自动推断,但当数据量庞大时它会优先按第一行格式推断,后面出现不同格式就全乱了。

稳妥做法是指定format参数,或者用errors="coerce"把解析失败的置空,再看日志排查。日期时间在数据分析里是绝对的"高危区",宁可多花两步检查,也不要信"默认没问题"。

8.3 陷阱三:布尔索引的"与"和"或"到底是什么语义

这前面提过,但因为它太常出错,我再单独强调一次。Python 里and、or是逻辑运算符,作用于布尔值;Pandas 里&、|是按位运算符,作用于布尔序列。你判断两个条件时如果用and,Pandas 会尝试把一个数组作为整体求布尔值,然后抛错。这个报错信息对新手非常不友好,反反复复。

# 对,Pandas 的多个条件用 & | valid = df[(df["数量"] > 0) & (df["单价"].notna())] # 错,Python 的 and 无法处理 Series invalid = df[(df["数量"] > 0) and (df["单价"].notna())]

9. 这个系列的下一篇会讲什么

到这里,第一篇的全部核心内容已经走完了:环境准备、CSV 读取、DataFrame 基础操作、清洗方法、分组聚合、以及一个完整的可视化闭环。这一篇是数据管道的"入口阶段",解决的是"把数据读进来并整理成可用的形态"。

下一篇我会沿着管道继续往前走,重点讲三个方向:第一个是数据探索性分析(EDA),包括怎么用describe()和分组统计发现异常分布;第二个是时间序列的常见预处理,比如按月重采样和滑动平均;第三个是更多实战场景中的多表连接。到那时候,你手里的工具会比现在多得多,但核心思维仍然是"读取-清洗-统计-输出"这个循环。先把这篇里的每个代码至少亲手跑三遍,下一篇见。

我在实际带新人的过程中反复强调一个观点:数据科学入门最难的从来不是某个算法或者某个函数的参数,而是建立"每一步都在和脏数据打交道"的警觉心。你有 Python 基础语法背景当然好,没有也问题不大,只要跟着这篇文章先把管道走通一遍,后面所有的知识都会找到它们该去的位置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询