尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Pandas数据分析入门:从核心数据结构到数据清洗实战

Pandas数据分析入门:从核心数据结构到数据清洗实战 1. 项目概述为什么从Pandas开始你的数据分析之旅如果你刚开始接触Python数据分析或者已经写了几行代码但总觉得数据处理起来磕磕绊绊那么把Pandas作为第一个深入学习的库绝对是一个明智到不能再明智的选择。我见过太多新手一上来就想用纯Python列表、字典去折腾Excel里导出的几万行数据结果光是处理一个简单的“查找某个日期之后的销售记录”就能写出一屏幕又慢又容易出错的循环判断。Pandas的出现就是为了终结这种“石器时代”的数据处理方式。它不是一个普通的库而是一套完整的、基于DataFrame的数据操作“世界观”。简单来说它让你能用处理Excel表格的思维去写代码但效率和能力却是指数级的提升。这个学习笔记系列就是我结合自己多年在数据清洗、分析和自动化报表中的实战经验为你梳理的一条从零到精通的路径。我不会只给你罗列API文档那样太枯燥了。我会带你像搭积木一样从最核心的“数据容器”——Series和DataFrame——开始理解它们的设计哲学然后逐步解锁数据查看、筛选、清洗、转换、聚合等一系列必备技能。你会发现之前那些让你头疼的“数据透视表”、“多表合并”、“缺失值处理”在Pandas里可能就是一行代码的事。更重要的是我会分享那些官方文档里不会写的“坑”和“骚操作”比如为什么有时候.loc和.iloc结果会不一样如何避免在链式操作中掉进SettingWithCopyWarning的陷阱以及怎么让Pandas处理百万行数据时依然保持流畅。我们的目标很明确让你不仅会用Pandas更能理解其内在逻辑从而在面对任何杂乱无章的数据时都能从容地掏出Pandas这把“瑞士军刀”干净利落地解决问题。2. 核心基石深入理解Series与DataFrame在真正动手写代码之前我们必须花时间把Pandas最基础、也最重要的两个数据结构——Series和DataFrame——彻底吃透。这就像学武功要先扎马步马步不稳后面的招式都是花架子。2.1 Series带标签的一维数组你可以把Series理解为一个加强版的Python列表或者字典。它和一维数组如NumPy的ndarray最大的区别在于它拥有一个可自定义的“索引”Index。这个索引可以是数字默认0, 1, 2...也可以是字符串、日期甚至是多层级的。import pandas as pd # 从一个列表创建Series使用默认整数索引 s1 pd.Series([10, 20, 30, 40]) print(s1) # 输出 # 0 10 # 1 20 # 2 30 # 3 40 # dtype: int64 # 创建时指定自定义索引标签 s2 pd.Series([100, 200, 300], index[苹果, 香蕉, 橙子]) print(s2) # 输出 # 苹果 100 # 香蕉 200 # 橙子 300 # dtype: int64核心价值索引的存在让数据访问从“位置依赖”变成了“语义化访问”。我不需要记住“香蕉”在第几个位置直接用s2[香蕉]就能拿到值200。这在处理非数值型ID如用户ID、商品SKU时极其方便。Series的运算会自动对齐索引这是Pandas高效处理数据的基石之一。实操心得索引不可变创建后索引对象本身s.index是不可修改的immutable。这意味着你不能直接对s.index进行赋值修改但可以整体替换为一个新的索引对象s.index new_index。设计成不可变是为了保证数据对齐时的哈希性能和安全性。数据类型dtype很重要Pandas会为Series推断一个数据类型如int64,float64,object通常是字符串或混合类型等。在创建大型Series时如果明确知道数据类型最好用dtype参数指定如dtypefloat32可以显著节省内存。2.2 DataFrame二维表格的终极形态DataFrame是Pandas的绝对核心你可以把它想象成Excel里的一张工作表或者SQL数据库里的一张表。它是由多个共用同一个索引的Series按列排列组成的。# 从一个字典创建DataFrame字典的键成为列名值列表成为列数据 data { 姓名: [张三, 李四, 王五], 年龄: [28, 34, 23], 城市: [北京, 上海, 广州] } df pd.DataFrame(data) print(df) # 输出 # 姓名 年龄 城市 # 0 张三 28 北京 # 1 李四 34 上海 # 2 王五 23 广州结构解剖一个DataFrame有三个核心属性df.index: 行索引。上例中是RangeIndex(start0, stop3, step1)。df.columns: 列索引。上例中是Index([姓名, 年龄, 城市], dtypeobject)。df.values: 一个二维的NumPy数组存储着表格中的原始数据。为什么DataFrame如此强大列式操作你可以像访问字典一样访问某一列df[年龄]返回的是一个Series。对整列进行数学运算、函数应用异常高效。灵活的索引行和列都有索引支持基于标签.loc和基于整数位置.iloc的精确切片与选择。自动对齐在进行运算如df1 df2时Pandas会自动根据行和列的标签对齐数据对不上的位置会引入缺失值NaN。这避免了手动循环对齐的繁琐和错误。注意事项创建DataFrame时务必确保字典中每个键对应的列表长度一致否则会引发ValueError。这是新手常犯的错误尤其是在动态构建数据时。3. 数据IO与初步探查把数据“请进来”并“看明白”学会了构造数据下一步就是把外部数据“请”进Pandas的世界并快速了解它的全貌。这是所有数据分析项目的第一步也是最关键的一步。3.1 从多种源头读取数据Pandas支持读取几乎任何格式的数据最常用的是CSV和Excel。# 从CSV文件读取 df_csv pd.read_csv(sales_data.csv, encodingutf-8) # 指定编码防止中文乱码 # 从Excel文件读取 df_excel pd.read_excel(财务报告.xlsx, sheet_name2023年) # 指定工作表 # 从剪贴板读取非常实用的技巧 # 先在Excel中选中一个区域并复制(CtrlC)然后运行 df_clipboard pd.read_clipboard() # 从字典列表创建常用于API接口返回的JSON数据 json_like_data [{a: 1, b: 2}, {a: 3, b: 4}] df_from_json pd.DataFrame(json_like_data)关键参数解析read_csv的encoding参数处理中文文件时常设为utf-8或gbk。如果遇到UnicodeDecodeError可以尝试encodinggb18030它的兼容性更好。header参数指定哪一行作为列名。header0默认表示第一行headerNone表示文件没有表头Pandas会自动生成整数列名。usecols参数一个列表指定只读取哪些列。对于列数很多的大文件这个参数能极大减少内存占用和读取时间。例如usecols[0, 2, 5]或usecols[列名A, 列名C]。3.2 数据快速探查与信息概览数据读进来后不要急着分析先用以下几组方法快速“扫描”一遍建立初步认知。# 1. 查看头部和尾部数据了解数据样式 print(df.head()) # 默认前5行 print(df.tail(3)) # 查看最后3行 # 2. 获取数据形状行列数 print(df.shape) # 输出 (行数, 列数) # 3. 获取列名、索引信息和数据类型 print(df.columns.tolist()) # 列名列表 print(df.index) # 索引信息 print(df.dtypes) # 每列的数据类型 # 4. 获取数值型列的快速统计摘要 print(df.describe()) # 输出计数(count)、均值(mean)、标准差(std)、最小值(min)、四分位数(25%, 50%, 75%)、最大值(max) # 注意describe()默认只针对数值列int, float对于非数值列可以使用 df.describe(includeall) # 5. 查看整体信息包括内存占用 df.info() # 这是一个极其重要的方法它会显示 # - 行索引范围和数据总行数 # - 每一列的非空值数量NaN数量一目了然 # - 每一列的数据类型 # - 数据占用的内存大小实操心得df.info()是你的第一道安检在开始任何清洗和分析前务必先运行df.info()。它能立刻告诉你有没有列读错了数据类型比如日期读成了字符串缺失值多不多内存占用是否异常我无数次靠它提前发现了数据源的问题。小心object类型如果一列被识别为object类型通常意味着里面是字符串或者混合了多种类型如数字和字符串。混合类型会严重拖慢运算速度并可能导致意外错误。你需要检查并统一数据类型。df.describe()的局限性它只统计数值列。对于分类数据如城市、产品类别你可以用df[列名].value_counts()来查看分布用df[列名].unique()查看唯一值。4. 数据选择与索引操作精准定位你的目标数据从庞大的DataFrame中高效、准确地提取出你需要的那部分数据是数据分析的基本功。Pandas提供了多种索引器功能强大但各有区别用错了地方可能会得到错误结果或性能警告。4.1 基于标签的索引.loc.loc主要通过行和列的标签名来进行选择。它的基本语法是df.loc[行选择器, 列选择器]。# 假设df的索引是默认整数列名为[A, B, C] # 选择单行返回一个Series row_1 df.loc[1] # 选择索引标签为1的行 # 选择多行使用列表 rows df.loc[[0, 2]] # 选择索引标签为0和2的行 # 选择行和列返回DataFrame subset df.loc[0:2, [A, C]] # 选择索引0到2包含2的行以及A,C列 # 注意这里的切片0:2是包含结束端2的与Python原生列表切片不同 # 使用布尔条件选择 condition df[年龄] 30 elder df.loc[condition] # 选择年龄大于30的所有行 elder_specific df.loc[condition, [姓名, 城市]] # 选择满足条件的行并只取姓名和城市列核心要点.loc的切片是闭区间且选择依据是索引的标签值而不是位置。如果索引不是连续的整数df.loc[1:5]会选择所有标签在1到5之间的行。4.2 基于整数位置的索引.iloc.iloc完全基于行和列的**整数位置从0开始**进行选择其行为与Python的列表、NumPy数组切片完全一致。# 选择第三行位置2 row_2 df.iloc[2] # 选择前两行 first_two_rows df.iloc[:2] # 切片不包含结束位置2 # 选择第1到第3行位置0,1,2第0和第2列位置0,2 subset df.iloc[0:3, [0, 2]] # 行切片0:3不包含3列选择位置0和2.locvs.iloc黄金法则当你的索引是有意义的标签如日期、姓名、ID时用.loc。当你只关心数据的物理行号/列号时用.iloc。简单记忆“标签用loc位置用iloc”。混用是初学者最常见的错误来源之一。4.3 直接索引与条件筛选除了.loc和.iloc还有一些更简洁的筛选方式。# 1. 直接选择单列返回Series age_series df[年龄] # 2. 选择多列返回DataFrame name_city_df df[[姓名, 城市]] # 注意传入的是列表 # 3. 布尔索引条件筛选 # 这是最常用、最强大的筛选方式之一 beijing_people df[df[城市] 北京] # 筛选城市为北京的行 young_in_beijing df[(df[城市] 北京) (df[年龄] 30)] # 多重条件北京且年龄30 # 注意多个条件必须用括号括起来逻辑运算符用 (与), | (或), ~ (非) # 4. isin() 方法判断是否在某个集合中 target_cities [北京, 上海] people_in_mega_city df[df[城市].isin(target_cities)]注意事项与避坑指南警惕SettingWithCopyWarning这是Pandas新手遇到最多的警告没有之一。它通常在你尝试修改一个可能是“视图”而非“副本”的数据子集时出现。# 危险操作示例 subset df[df[年龄] 25] subset[新列] 100 # 可能会触发SettingWithCopyWarning且修改可能不生效到原df安全做法如果你明确要修改筛选出的数据并希望影响原DataFrame使用.loc进行链式赋值。df.loc[df[年龄] 25, 新列] 100 # 安全明确指定了修改的位置如果你只是想基于子集进行后续计算不修改原数据那么显式地创建副本是更清晰的做法。subset df[df[年龄] 25].copy() # 创建副本后续操作与原df无关 subset[新列] 100 # 安全操作5. 数据清洗与预处理从杂乱到规整真实世界的数据几乎没有“干净”的。缺失值、重复行、格式不一的数据类型是常态。数据清洗通常占据一个数据分析项目80%的时间而Pandas提供了全套工具。5.1 处理缺失值缺失值在Pandas中用NaNNot a Number表示它是浮点类型。# 1. 检测缺失值 print(df.isna()) # 返回布尔型DataFrameTrue表示缺失 print(df.isna().sum()) # 统计每列的缺失值数量非常实用 # 2. 删除缺失值 # axis0 删除包含缺失值的行 howany默认该行有任何NaN就删除all则全部为NaN才删除 df_dropped_rows df.dropna(axis0, howany) df_dropped_cols df.dropna(axis1, howall) # 删除全部为NaN的列 # 3. 填充缺失值 # 用固定值填充 df_filled df.fillna(0) # 所有NaN填充为0 # 用前向填充用上一个有效值填充 df_ffill df.fillna(methodffill) # 对于时间序列数据很常用 # 用后向填充 df_bfill df.fillna(methodbfill) # 用列的平均值/中位数填充 df[年龄].fillna(df[年龄].mean(), inplaceTrue) # inplaceTrue表示直接修改原df选择策略删除当缺失数据量很少如5%且缺失是随机的时候。填充当数据有内在规律如时间序列或者缺失值有业务含义如用0填充“未填写”的金额时。用均值/中位数填充要小心它可能会扭曲数据的分布和方差。5.2 处理重复数据# 检测重复行基于所有列的值 print(df.duplicated()) # 返回布尔Series标记出重复行除第一次出现外 print(df.duplicated().sum()) # 统计重复行数 # 删除重复行 df_dedup df.drop_duplicates() # 默认保留第一次出现的行 df_dedup_last df.drop_duplicates(keeplast) # 保留最后一次出现的行 df_dedup_none df.drop_duplicates(keepFalse) # 删除所有重复行 # 基于特定列判断重复 df_dedup_subset df.drop_duplicates(subset[姓名, 城市]) # 只要姓名和城市相同就视为重复5.3 数据类型转换正确的数据类型是高效运算和正确分析的前提。# 查看当前数据类型 print(df.dtypes) # 转换数据类型 df[年龄] df[年龄].astype(int32) # 转换为32位整数节省内存 df[订单日期] pd.to_datetime(df[订单日期]) # 将字符串转换为datetime类型这是关键操作 # 处理数值型字符串如带有逗号或货币符号 df[销售额] df[销售额].str.replace(,, ).str.replace($, ).astype(float64) # 使用errors参数处理转换错误 # 例如将一列转换为数值无法转换的设为NaN df[数量] pd.to_numeric(df[数量], errorscoerce)关于日期转换的特别提醒pd.to_datetime功能非常强大能自动解析多种日期字符串格式。但如果你的日期格式比较特殊或者数据质量差最好指定格式以提高速度和准确性。df[日期列] pd.to_datetime(df[日期列], format%Y/%m/%d, errorscoerce) # format参数指定精确格式errorscoerce将解析失败的设为NaT时间戳的缺失值转换成功后你就可以方便地提取年月日等信息了df[日期列].dt.year,df[日期列].dt.month。6. 数据变形与基础运算让数据“动起来”清洗干净的数据就可以开始进行各种计算和变形以提取信息了。6.1 列的基本运算与创建新列在Pandas中对整列进行向量化运算非常高效。# 1. 直接算术运算 df[销售额翻倍] df[销售额] * 2 df[利润率] (df[利润] / df[销售额]).round(4) # 计算并保留4位小数 # 2. 使用apply函数应用自定义函数当运算复杂时 def categorize_age(age): if age 20: return 少年 elif age 40: return 青年 else: return 中年 df[年龄分组] df[年龄].apply(categorize_age) # 对于简单的lambda函数更简洁 df[姓名长度] df[姓名].apply(lambda x: len(x)) # 3. 使用assign方法链式创建新列不修改原df返回新df df_new df.assign( 销售额万元 df[销售额] / 10000, 是否高利润 df[利润率] 0.3 )性能提示尽可能使用Pandas内置的向量化函数如.str方法处理字符串.dt方法处理日期或NumPy函数避免在apply中使用复杂的Python循环尤其是在数据量大时性能差异可达数百倍。6.2 数据排序# 按单列排序 df_sorted df.sort_values(销售额, ascendingFalse) # 按销售额降序 # 按多列排序 df_sorted_multi df.sort_values([城市, 销售额], ascending[True, False]) # 先按城市升序同一城市内按销售额降序6.3 简单的数据聚合分组聚合是Pandas的精华这里先介绍最简单的汇总。# 对整个DataFrame或Series的汇总 total_sales df[销售额].sum() average_age df[年龄].mean() max_profit df[利润].max() unique_cities df[城市].nunique() # 唯一值个数 # 使用agg进行多种聚合 summary df[销售额].agg([sum, mean, std, min, max])7. 常见问题排查与性能优化技巧在实际使用中你一定会遇到各种报错和性能问题。这里记录几个最高频的“坑”和解决思路。7.1 高频错误与警告排查表问题现象可能原因解决方案KeyError: ‘列名’列名拼写错误、大小写不一致、列不存在。用df.columns.tolist()打印所有列名仔细核对。使用df.get(列名, defaultNone)安全访问。SettingWithCopyWarning试图修改一个可能是数据视图view的子集。明确使用.loc[row_indexer, col_indexer] value进行赋值或使用.copy()创建副本。布尔索引结果为空条件表达式逻辑错误或数据类型不匹配。例如字符串比较时有多余空格。检查条件使用df[列名].unique()查看实际值。尝试df[列名].str.strip() ‘目标值’。pd.read_csv编码错误文件编码非UTF-8。尝试encoding‘gbk’,‘gb18030’,‘latin1’或‘ISO-8859-1’。数值计算得到NaN数据中存在缺失值NaN任何与NaN的运算结果都是NaN。先处理缺失值fillna或dropna或使用跳过NaN的聚合函数如df[‘列’].sum(skipnaTrue)。内存使用激增1. 读取了不必要的列2. 对象类型列过多3. 中间变量未释放。1. 用usecols参数读取所需列。2. 将object类型转为category分类或更具体的类型。3. 使用del删除大变量或分块处理。7.2 初阶性能优化建议当处理几万行以上的数据时性能开始变得重要。指定数据类型在read_csv时使用dtype参数或在读取后用astype转换。将字符串列转为‘category’类型对于重复值多的列如性别、省份能极大节省内存和加速分组操作。dtype_dict {城市: category, 产品ID: int32} df pd.read_csv(data.csv, dtypedtype_dict)只取所需用usecols读取必要的列用布尔索引或查询df.query()过滤不必要的行尽早减少数据量。避免链式索引df[df[‘a’] 5][‘b’] 10这种写法既低效又容易触发SettingWithCopyWarning。应使用.loc一次性完成df.loc[df[‘a’] 5, ‘b’] 10。慎用apply如果操作可以用向量化方法内置函数、np.where、np.select实现绝对不要用apply。apply本质是Python级循环。使用高效查询对于复杂筛选df.query(‘年龄 30 and 城市 “北京”’)在语法上更清晰且在某些情况下比布尔索引更快。数据处理本身就是一个不断迭代和打磨的过程。第一次写出来的清洗代码往往很粗糙随着你对数据理解的深入你会不断回头优化它。记住在Pandas里通常有多种方法可以达到同一个目的选择最清晰、最符合你思维习惯的那一种同时兼顾效率。在接下来的笔记中我们会深入到分组聚合、数据合并、时间序列等更高级但同样核心的主题。掌握了这些基础你已经能解决工作中大部分基础的数据处理任务了。最好的学习方式就是立刻找一个自己的数据集从头到尾操作一遍遇到问题就查文档或搜索你会发现进步飞快。
返回列表