Data-Science-For-Beginners 数据准备教程:基于 pandas 的 DataFrame 探查、缺失值与重复数据清洗完整指南
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
本文基于 Data-Science-For-Beginners 课程第 8 课(数据准备 / Data Preparation)编写,系统讲解如何用 Python + pandas 完成数据清洗的三大核心任务:探查 DataFrame 元信息、处理缺失值(识别、删除、填充)以及去除重复数据。学完本篇,你将掌握info()/head()/tail()、isnull()/dropna()/fillna()、duplicated()/drop_duplicates()的完整用法与参数细节,并能结合课程配套 Notebook 将清洗流程扩展到真实脏数据的质检场景。
1. 为什么要做数据清洗
根据数据来源不同,原始数据往往包含各种不一致之处,这些问题会在分析与建模阶段带来麻烦。用课程文档(translations/et/2-Working-With-Data/08-data-preparation/README.md,对应英文原文 2-Working-With-Data/08-data-preparation/README.md)的话说:这类数据可以被视为“脏数据”(dirty data),必须被清理。本课聚焦于清洗与转换数据、应对缺失、不准确或不完整数据的技术手段,全部技术点通过 Python 与 pandas 在课程配套 notebook.ipynb 中演示。
课程将数据清洗的价值归纳为三点:
- 易用与可复用(Ease of use and reuse):数据被妥善组织与规范化后,更容易被检索、使用和与他人共享。
- 一致性(Consistency):数据科学经常需要同时处理多个数据集,且来自不同来源的数据集必须拼接在一起。确保每个独立数据集都经过统一标准化,才能保证它们合并成一个数据集后依然可用。
- 模型精度(Model accuracy):清洗过的数据能提升依赖它构建的模型的准确度。
2. 常见的清洗目标与策略
文档总结了四类最常见的清洗目标,它们共同构成了一个数据准备项目的检查清单:
- 数据集探查(Exploring a dataset):数据探索(详见课程后续章节 4-Data-Science-Lifecycle/15-analyzing/README.md)有助于发现需要被清洗的数据。直观观察数据集中的取值,可以建立对剩余数据形态的预期,或提示可解决的问题。探索手段包括基础查询、可视化与抽样。
- 格式化(Formatting):取决于来源,数据的呈现方式可能不一致,导致“值在数据集中存在、却无法正确检索或展示”的问题。常见的格式问题包括空白字符(whitespace)、日期与数据类型的处理。格式问题的修复通常由数据的使用方负责,例如日期与数字的表示标准可能因国家而异。仓库中的 data/form.csv 就是一个典型的格式问题样本:同一个月份存在
January、JAN、january三种写法,同一个州存在California/CA、Florida/FL等混用形式——这正是配套作业要处理的真实场景。 - 重复数据(Duplications):出现多次的数据可能产生不准确的结果,通常应当移除。这在拼接两个或更多数据集时尤为常见。但注意:有些合并后产生的重复行携带额外信息,可能恰恰需要保留。
- 缺失数据(Missing Data):缺失值会造成不准确、偏弱或有偏的结果。解决方式包括:重新加载数据、用计算与代码(如 Python)填补缺失值、或直接移除该值及其对应的数据。缺失的原因多种多样,采取的补救措施取决于数据是“如何、为何”缺失的。
3. 探查 DataFrame 的基本信息
学习目标:本节结束后,应能自如地从 pandas DataFrame 中查找数据的一般信息。
数据加载进 pandas 后,大概率存放在 DataFrame 中(DataFrame 的完整介绍见前一课 2-Working-With-Data/07-python/README.md)。但如果你的 DataFrame 有 60,000 行、400 列,你该从哪里开始了解手里是什么数据?pandas 提供了便捷工具,可以快速查看 DataFrame 的总体信息以及开头/结尾的若干行。
课程使用 scikit-learn 库中与数据科学绑定的经典数据集——Iris 鸢尾花数据集(英国生物学家 Ronald Fisher 1936 年论文中的测量数据,Notebook 开篇有说明)来演示这些功能:
import pandas as pd from sklearn.datasets import load_iris iris = load_iris() iris_df = pd.DataFrame(data=iris['data'], columns=iris['feature_names'])生成的iris_df前几行如下:
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 |
配套 notebook.ipynb 在文档基础上还补充了三个属性级探查工具:
DataFrame.shape:属性而非方法(所以不带括号),返回(150, 4),即 150 行 4 列——每行一个观测点、每列一个特征。DataFrame.columns:返回列名Index,本例为sepal length (cm)、sepal width (cm)、petal length (cm)、petal width (cm)。DataFrame.describe():对数值列给出统计摘要(count、mean、std、min、25%/50%/75% 分位数、max),例如本数据集sepal length (cm)的均值为 5.843333。
文档正文重点展开的三个方法是:
3.1DataFrame.info():查看结构摘要
info()打印 DataFrame 内容摘要。对 Iris 数据集运行iris_df.info(),输出为:
RangeIndex: 150 entries, 0 to 149 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 sepal length (cm) 150 non-null float64 1 sepal width (cm) 150 non-null float64 2 petal length (cm) 150 non-null float64 3 petal width (cm) 150 non-null float64 dtypes: float64(4) memory usage: 4.8 KB由此可知:Iris 数据集共 150 条记录、4 列,无空值;所有数据均以 64 位浮点数存储。Non-Null Count一栏正是后续缺失值处理的直接入口。
3.2DataFrame.head()与DataFrame.tail():查看首尾行
head()查看开头若干行(iris_df.head()返回索引 0–4 的行,数值如 5.1/3.5/1.4/0.2 起始);tail()查看结尾若干行(iris_df.tail()返回索引 145–149 的行,如 6.7/3.0/5.2/2.3 至 5.9/3.0/5.1/1.8)。
结论:仅仅查看 DataFrame 的元数据以及开头/结尾的少数值,就能立即对数据的规模、形状与内容建立直观认知。这是每次接触新数据集的第一动作。
4. 处理缺失值
学习目标:本节结束后,应知道如何从 DataFrame 中替换或移除空值。
你希望使用(或不得不使用)的大多数数据集都含有缺失值。缺失数据的处理方式带有微妙的权衡,会最终影响分析与真实世界结论。
4.1 两种“空值”:NaN与None
pandas 用两种方式表示缺失值:
NaN(Not a Number):IEEE 浮点规范中的特殊值,仅用于表示缺失的浮点数值。None:Python 原生对象,用于非浮点类型列的缺失值。
Notebook 用实验演示了两者在底层的关键差异,值得记住:
import numpy as np # None 会把数组升级为 object 类型 example1 = np.array([2, None, 6, 8]) example1 # array([2, None, 6, 8], dtype=object) example1.sum() # TypeError: unsupported operand type(s) for +: 'int' and 'NoneType' # NaN 支持向量化运算,但参与运算的结果仍是 NaN np.nan + 1 # nan np.nan * 0 # nan example2 = np.array([2, np.nan, 6, 8]) example2.sum(), example2.min(), example2.max() # (nan, nan, nan)从 Notebook 的实现细节看:含None的数组会退化为 Python 对象级解释执行(大数组上会明显变慢),且聚合运算可能直接报错;而含NaN的数组可以正常运行聚合(不报错),但sum()/min()/max()的结果是nan。两种空值各有使用限制,需要在使用时留心。
4.2 识别空值:isnull()/notnull()
pandas中检测空数据的主要方法是isnull()与notnull(),两者都返回布尔掩码(Boolean mask):
import numpy as np example1 = pd.Series([0, np.nan, '', None]) example1.isnull()0 False 1 True 2 False 3 True dtype: bool请仔细审视这个输出——有没有意外?0虽然是算术上的零,但它是完全合法的正整数,pandas 按整数对待;''更微妙一点:尽管空字符串看起来像“没有东西”,它仍是字符串对象,pandas 不将其视为空值。
反过来用这些掩码更接近实际用法:布尔掩码可以直接作为Series或DataFrame的索引,当你想隔离缺失(或存在)的值时非常有用。
结论:
isnull()与notnull()在DataFrame上产生相似的结果:它们展示掩码结果及其索引,在处理数据时能帮你事半功倍。
4.3 删除空值:dropna()与axis/how/thresh参数
除了识别,pandas 还提供了从Series/DataFrame中移除空值的便捷方式(尤其对大数据集,直接移除缺失 [NA] 值通常比其他方式更可取)。回到example1:
example1 = example1.dropna() example10 0 2 dtype: object这与example1[example1.notnull()]的筛选输出一致,区别在于dropna直接把缺失值从Series中移除了。
DataFrame有两个维度,因此提供了更多删除选项。构造示例:
example2 = pd.DataFrame([[1, np.nan, 7], [2, 5, 8], [np.nan, 6, 9]])| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | NaN | 7 |
| 1 | 2.0 | 5.0 | 8 |
| 2 | NaN | 6.0 | 9 |
(注意:pandas 将含NaN的两列自动升级为浮点数类型。)
由于无法只删除 DataFrame 中某个孤立的值,只能删除整行或整列。数据科学中列通常代表变量、行代表观测,所以更常见的操作是删行;dropna()的默认行为就是移除所有含任一空值的行:
example2.dropna()0 1 2 1 2.0 5.0 8如需按列删除 NA 值,使用axis='columns':
example2.dropna(axis='columns')2 0 7 1 8 2 9这在小数据集上可能删掉大量本应保留的数据。若只想删除含“若干”甚至“全部”空值的行/列,可用how与thresh参数控制。dropna默认how='any';改为how='all'则只删除全为空值的行或列。扩展示例(新增全空第 3 列):
example2[3] = np.nan example2| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | NaN | 7 | NaN |
| 1 | 2.0 | 5.0 | 8 | NaN |
| 2 | NaN | 6.0 | 9 | NaN |
thresh提供更细粒度的控制:指定一行/一列至少要有多少个非空值才能被保留:
example2.dropna(axis='rows', thresh=3)0 1 2 3 1 2.0 5.0 8 NaN这里第 0、2 行因只含 2 个非空值而被删除。
4.4 填充空值:fillna()与ffill/bfill
有时用有效值填充空值比删除更合理。你可以用isnull就地实现,但值一多就很繁琐。pandas 因此提供fillna,返回一份缺失值已被替换的Series/DataFrame拷贝。新建示例:
example3 = pd.Series([1, np.nan, 2, None, 3], index=list('abcde')) example3a 1.0 b NaN c 2.0 d NaN e 3.0 dtype: float64三种常用填充方式:
# 1) 用单一值(如 0)填充所有空值 example3.fillna(0)a 1.0 b 0.0 c 2.0 d 0.0 e 3.0 dtype: float64# 2) 前向填充(forward-fill):用上一个有效值填充 example3.fillna(method='ffill')a 1.0 b 1.0 c 2.0 d 2.0 e 3.0 dtype: float64# 3) 后向填充(back-fill):把下一个有效值向前传播 example3.fillna(method='bfill')a 1.0 b 2.0 c 2.0 d 3.0 e 3.0 dtype: float64对DataFrame用法相同,但还可指定沿哪个轴(axis)填充。再次使用扩展后的example2:
example2.fillna(method='ffill', axis=1)0 1 2 3 0 1.0 1.0 7.0 7.0 1 2.0 5.0 8.0 8.0 2 NaN 6.0 9.0 9.0注意:当前一行没有可前向填充的值时(第 2 行第 0 列),空值会原样保留。
核心要点:处理数据集缺失值有多种途径。具体策略(删除、替换,乃至用什么方式替换)应由该数据本身的特点决定。与数据集打交道越多,你对缺失值处理的直觉就越成熟。
5. 去除重复数据
学习目标:本节结束后,应能自如地在 DataFrame 中识别并移除重复值。
除缺失值外,真实世界数据集中经常遇到重复数据。pandas提供了检测与移除重复条目的便捷手段。
5.1 识别重复:duplicated()
duplicated方法返回布尔掩码,标明某条DataFrame记录是否是前面某条记录的重复:
example4 = pd.DataFrame({'letters': ['A','B'] * 2 + ['B'], 'numbers': [1, 2, 1, 3, 3]})| letters | numbers | |
|---|---|---|
| 0 | A | 1 |
| 1 | B | 2 |
| 2 | A | 1 |
| 3 | B | 3 |
| 4 | B | 3 |
example4.duplicated()0 False 1 False 2 True 3 False 4 True dtype: bool5.2 移除重复:drop_duplicates()
drop_duplicates()直接返回一份所有duplicated值均为False的数据拷贝:
example4.drop_duplicates()letters numbers 0 A 1 1 B 2 3 B 3duplicated与drop_duplicates默认考虑所有列,但可以指定只检查列的子集:
example4.drop_duplicates(['letters'])letters numbers 0 A 1 1 B 2核心要点:去除重复数据几乎是每个数据科学项目的必要步骤。重复数据会改变分析结果、给出错误结论!
6. 从 Notebook 看进阶实践:真实脏数据质检
配套 notebook.ipynb 在 README 主题之外还提供了一个“Real-World Data Quality Checks”章节,把上面的技术扩展到三类更隐蔽的现实问题,值得作为进阶阅读:
- 不一致的分类值:同一类别存在多种拼写(如
USA/U.S.A/United States)。Notebook 演示了先str.lower()再用映射字典标准化的方法,也给出基于rapidfuzz模糊匹配的自动检测方案(process.extract+fuzz.ratio,相似度阈值 70)。 - 异常数值(离群点):如
age = 199、-5。Notebook 演示了两种检测手段——基于领域知识的直接过滤(age < 0 or age > 120)、IQR 四分位距法(Q1 - 1.5*IQR到Q3 + 1.5*IQR)与 Z-Score 法(scipy.stats.zscore,阈值 3);并总结了四种处理方式:删除、截断到边界、替换为NaN后再填补、保留(若为合法极值)。 - 近似重复行:先
str.strip().str.lower()归一化后用duplicated(subset=..., keep=False)找出所有重复出现项,再用模糊匹配(相似度阈值 90)识别如"John Smith"与"John Smith"这类近似重复,最后以drop_duplicates(subset=['name_normalized'], keep='first')去重。
Notebook 最终把整套流程封装成一个可复用的清洗管线clean_dataset(df):先df.copy()保护原始数据,再依次标准化分类列、将非法年龄置为NaN、去空白并按归一化名称去重,然后对比清洗前后的行数、唯一国家数与非法年龄数来验证效果。其“Key Takeaways”给出的最佳实践同样适用于任何项目:始终保留原始“脏”数据副本,绝不要覆盖源数据文件,清洗结果另存为data_cleaned.csv一类清晰命名的文件。
7. 动手练习与配套资源
- 完整交互演示:本文所有讲解内容均以 Jupyter Notebook 形式提供在 2-Working-With-Data/08-data-preparation/notebook.ipynb 中,每节之后附有练习题(Exercise),可直接在本地打开运行。
- 本课作业:assignment.md 布置了“Evaluating Data from a Form”任务——某客户用一个小表单收集客户数据,其中 data/form.csv 就是该表单的 10 条提交记录(月份、州、宠物三个字段,混杂大小写与缩写),你需要运用本课技术找出表单设计问题并给出改进建议;配套练习 Notebook 为 assignment.ipynb。
- 延伸阅读:缺失值的更多讨论可参考 4-Data-Science-Lifecycle/15-analyzing/notebook.ipynb(
NaN与None深入说明);数据探索与深度分析见 4-Data-Science-Lifecycle/15-analyzing/README.md。
小结
数据准备是数据分析与建模前的关键一步,且高度依赖实践。本篇按课程原文脉络给出了一条可复制的清洗主线:先用shape/columns/info()/describe()/head()/tail()摸清数据结构,再用isnull()/notnull()定位缺失、按axis/how/thresh参数化地dropna()、或按常量/ffill/bfill策略fillna(),最后用duplicated()/drop_duplicates()清除精确重复。在此基础上,结合课程 Notebook 的分类值标准化、离群点检测(IQR/Z-Score)与近似重复识别,就能组成覆盖绝大多数“脏数据”场景的完整质检流程。具体采用哪种缺失值或重复值策略,应由数据本身的成因决定——这也是本课题反复强调的判断力训练。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考