第 14 章 Pandas 数据处理文章目录第 14 章 Pandas 数据处理 关于《AI 零基础 36 讲》给一列与一张表分别起个名字先读原始文件别急着算平均值选行选列时区分标签与位置清理格式要保留字段原本的含义完全重复与内容冲突分别处理转换失败后明确记录未知完成状态也要先映射保存清洗结果也保存数量变化清洗后再筛选和排序常见错误要保留修复结果留一道独立练习资料来源上一章的数组把数字排得很整齐。真正从表格拿到数据时数字列里可能出现“二十”完成状态可能同时写着 TRUE 与 false同一条记录也可能重复出现。先把这些情况处理清楚后面的平均值和图表才有明确含义。这一章会清洗一份学习记录表把原来的八行整理成五行可继续分析的记录并保留两行重复记录与一行非法记录。你需要先完成第 13 章能理解数组的形状也能运行第 12 章的函数和文件示例。数据全部是本项目原创的虚构教学样例。 关于《AI 零基础 36 讲》零基础也能开始学 AI《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始逐步学习Python、数据处理、机器学习与深度学习再进入大模型应用开发最后完成个人知识库助手的综合实践。本篇是正式讲解文章通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码方便你边读边试。读完以后动手检验建议按专栏顺序学习并完成对应的课后训练检查自己能否独立运用本章知识。博客专栏《AI 零基础 36 讲》给一列与一张表分别起个名字Pandas 常用的 Series 表示带索引的一维数据DataFrame 表示带行索引和列名的二维表格。一个 DataFrame 的不同列可以使用不同的数据类型。importpandasaspd minutespd.Series([20,15,40],nameminutes)framepd.DataFrame({title:[变量,循环,集合],minutes:minutes})print(type(frame[minutes]).__name__)print(type(frame[[minutes]]).__name__)print(frame.shape)结果分别是 Series、DataFrame 和(3, 2)。一个列名用单层方括号取出来是一列 Series一组列名放在列表里则返回 DataFrame即使只选了一列它仍然保留二维表结构。索引 index 是行的标签默认常从 0 开始。表格还可能使用记录 ID 作为索引标签就不一定与位置相同。筛选后也会保留原来的索引不能看到标签 5 就直接当成第六条当前记录。先读原始文件别急着算平均值CSV 是用分隔符保存表格的文本文件本章用逗号分隔。Excel 文件还能包含工作表、格式等内容读取 xlsx 使用 openpyxl 引擎。两种原始样例保存相同记录。importpandasaspd rawpd.read_csv(data/raw_records.csv,dtypestring,keep_default_naFalse)excelpd.read_excel(data/raw_records.xlsx,dtypestring,keep_default_naFalse,engineopenpyxl)print(raw.shape)print(raw.columns.to_list())print(raw.equals(excel))结果是(8, 5)、五个列名和 True。dtype“string” 让本次读取把各列作为字符串保存keep_default_naFalse 保留原来的空字符串避免读取阶段自动把它们变成缺失标记。这样清洗前还能分清“原来就是空白”和“转换时才发现格式错误”。使用 head 查看前几行使用 dtypes 查看各列类型。这份表的分钟数暂时也是文字。若直接把字符串相加含义与数字求和不同先把列转换成数值再谈总时长。路径找不到时回到第 12 章检查工作目录。Excel 读取提示缺少 openpyxl 时按本章 requirements.txt 安装到正在运行的解释器中。CSV 打开乱码时也先确认编码交付文件采用 UTF-8。选行选列时区分标签与位置loc 按标签或布尔条件选择iloc 按整数位置选择。下面使用独立小表看差异。importpandasaspd framepd.DataFrame({title:[变量,循环,集合],minutes:[20,15,40]},index[10,20,30])print(frame.loc[20,title])print(frame.iloc[1,0])print(frame.loc[frame[minutes]20,[title,minutes]].to_dict(records))前两行都得到“循环”第三行得到变量 20 分钟和集合 40 分钟两条记录。loc[20] 使用行标签 20iloc[1] 使用第二行的位置编号两套数字在这张表里明显不同。标签切片与位置切片还有边界差异。对本例按标签取 10 到 20会包括这两个标签按位置取 0 到 2只包括位置 0 与 1。先记住自己正在用哪个选择方法别把第 11 章所有切片的习惯直接套到标签选择上。筛选条件写在 loc 的第一个位置列名列表写在第二个位置。组合两个按行条件时用或|并分别给比较表达式加括号。Python 的 and 与 or 会尝试判断整列的真假不能替代逐行组合。清理格式要保留字段原本的含义完整程序先复制原表再清理各列两端空格。记录 ID 转大写完成状态和标签转小写标题保留原来的文字。importpandasaspd framepd.DataFrame({title:[ 变量 ],done:[ TRUE ],tag:[ Python ]})frame[title]frame[title].str.strip()frame[done]frame[done].str.strip().str.lower()frame[tag]frame[tag].str.strip().str.lower()print(frame.to_dict(records))结果里的 title 是变量done 是 truetag 是 python。str 提供整列字符串操作把它看成“对这一列每个文本做同一种动作”就容易与前面的数组运算接起来。程序统一大小写是当前教学记录的约定。真实表格的标签若区分大小写就不能直接照搬这个规则。标题内部的空格也可能有意义我们只删两端没有把整段文字的所有空格都去掉。完全重复与内容冲突分别处理标准化后原始表的第 5 行与第 9 行分别重复了先前记录。我们比较 record_id、title、done、minutes、tag 五列完全一致时保留第一次出现的行把后来重复的行存入 duplicate_records.csv。importpandasaspd framepd.DataFrame({record_id:[L01,L01,L02],minutes:[20,20,15]})duplicateframe.duplicated(subset[record_id,minutes],keepfirst)print(duplicate.to_list())print(frame.loc[~duplicate].to_dict(records))重复判断是[False, True, False]保留下来的是 L01 的 20 和 L02 的 15。~把每一项真假取反帮助选出非重复行。drop_duplicates 也能直接返回去重结果本章保留布尔判断是为了把丢掉的记录另存下来供核对。同一个 ID 如果分别记着 20 和 30 分钟不能只保留第一个再宣布清洗完成。完整程序会把两条都转入拒绝表注明“同一 ID 存在冲突记录”等待人工确认。重复和冲突的处理政策不同自动去重必须说明比较了哪些列。转换失败后明确记录未知缺失值表示某一项暂时没有可用内容。Pandas 可以用 NaN 或可空类型里的 NA 表示它检查时使用 isna不能简单把所有缺失值与普通数字直接比较。importpandasaspd sourcepd.Series([20,,二十],dtypestring)numericpd.to_numeric(source,errorscoerce).astype(Float64)print(numeric.isna().to_list())print(numeric.sum())print(numeric.count())输出分别是[False, True, True]、20.0 和 1。errors“coerce” 把无法转换的内容变成缺失Float64 是能容纳缺失值的浮点列类型。它与 NumPy 的 float64 名字大小写不同使用时按代码写完整。本章把原本空白标为 missing把“二十”这样的非空转换失败标为 invalid_text。两条记录仍有标题和完成状态所以保留用于查看完成数量分钟数未知不能参加已知时长求和。我们同时保存质量标记方便以后补录。把这两项直接填成 0会让读者以为已经明确记录“没花时间”。删除整行又会丢掉可用的完成状态。本章选择保留缺失没有执行插补。其他项目可以按需要填补或删除但要说明根据什么规则并记录影响范围。负数、无穷值和大于 1440 的分钟数另作非法值处理。1440 是本章对一次日内学习记录采用的一天上限属于教学项目政策。程序不会把负五分钟转成零也不会把“二十”自行猜成 20。完成状态也要先映射原表的 true 与 false 是文字布尔列应当保存真正的真假值。直接把字符串转成 bool 会遇到第 11 章的同一个问题。importpandasaspd sourcepd.Series([true,false])print(source.astype(bool).to_list())fixedsource.map({true:True,false:False}).astype(boolean)print(fixed.to_list())第一行错误结果是[True, True]修复后是[True, False]。两段都能运行错误发生在结果含义上。map 明确规定哪些文字对应哪种状态未被映射的文字会成为缺失随后由程序拒绝这条状态不明的记录。保存清洗结果也保存数量变化clean_records.py 返回四份结果分别是保留表、重复表、拒绝表和清洗记录字典。完整流程中的 copy 用来保护输入source_row 帮助回到原文件定位记录reset_index 为最终表重新提供连续行编号。在本章目录运行下面的命令。.venv\Scripts\python.exe examples/clean_records.py实际结果包括下面四行。原始 8 行重复 2 行拒绝 1 行保留 5 行 分钟数原始缺失 1 个清洗后缺失 2 个 已知分钟数合计 75不含缺失值 CSV 与 Excel 输入一致 True行数满足 8 2 1 5缺失从一个变成两个是因为“二十”转换失败后也被记为未知。已知分钟数只来自 20、15、40 三项75 代表已经知道的时长之和不能称为全部五条记录的完整总时长。结果目录同时保存 clean_records.csv、duplicate_records.csv、rejected_records.csv 和 audit.json。审计 audit 在这里就是把每步处理数量和规则留下来让结果可以回头查。你可以先打开 audit.json 对数字再打开拒绝表查看第 8 行为什么被拒绝。CSV 不保存 Pandas 的列类型读回 clean_records.csv 时要再指定或转换 done 与 minutes。保存使用 indexFalse避免把临时行索引写成一列没有业务含义的编号。列名、单位和质量标记定义见 字段说明。清洗后再筛选和排序下面查看已完成且分钟数已知的记录按分钟数从高到低排列。importpandasaspd cleanpd.read_csv(results/clean_records.csv,dtype{done:boolean,minutes:Float64})selectedclean.loc[clean[done]clean[minutes].notna(),[title,minutes]]selectedselected.sort_values(minutes,ascendingFalse)print(selected.to_dict(records))结果是集合 40 分钟、变量 20 分钟。列表虽然已经完成但时长未知不会混入这个筛选结果。sort_values 按列值排序ascendingFalse 让较大的值排在前面。全部值都缺失时默认 sum 可能返回 0。做时长汇总要同时看 count 或本章的 known_minutes_count想让零项已知数据返回缺失可以使用 sum(min_count1)。先确认参与计算的记录数再解释总和可以避免把“还没拿到数据”读成“总量确实为零”。常见错误要保留修复结果字段名写成 minute实际列名是 minutes会产生 KeyError。先打印 columns再修正列名修复样例返回[20]。把“二十”直接交给 astype(float)会产生 ValueError修复样例使用 to_numeric 并列出无法转换的原内容避免悄悄忽略它。两组完整报错与修复输出见 error-samples.md。如果筛选后要改原表某些单元格直接写frame.loc[条件, 列名] 值不要反复连续索引后猜测修改是否传回原表。独立处理选中的数据时明确取一份 copy。留一道独立练习对清洗后的表分别统计 python 与 ai 标签的记录数量以及两组已知分钟数的合计。预期数量是 3 与 2已知时长是 35 与 40两个分组各有一条未知时长记录。先筛选标签再核对缺失数量答案见 【零基础学AI】第 14 章课后练习与答案。下一章会继续使用这份清洗结果用统计数字和图表回答具体问题。资料来源【零基础学AI】第 14 章课后练习与答案Pandas 数据结构与选择Pandas 读取 CSVPandas 读取 ExcelPandas 数值转换Pandas 缺失值处理
阅读完成 · 觉得有帮助?