查看: 684|回复: 3

Pandas DataFrame常用操作与读取报错排查

[复制链接]
发表于 8 小时前 | 显示全部楼层 |阅读模式
Pandas DataFrame 是 Python 数据处理绕不开的核心对象,数据清洗、分析和建模基本都会围绕它展开。本文按实际开发顺序梳理 DataFrame 的创建、读取、选取、增删改、合并排序、缺失值清洗和常见报错排查,重点说明参数含义、调用方式和错误原因,方便在脚本中直接落地。

一、理解 DataFrame 与 Series 的关系
DataFrame 可以看成由多个 Series 按列拼起来的表状容器,每一列是一个 Series,所有列共享同一个行索引。索引可以是默认整数,也可以是日期、字符串或自定义标签,它不仅是行号,还会参与按标签定位、数据对齐和合并。
  1. import pandas as pd
  2. df = pd.DataFrame({
  3.     '姓名': ['张三', '李四', '王五'],
  4.     '年龄': [25, 30, 28],
  5.     '城市': ['北京', '上海', '广州']
  6. })
  7. print(type(df['姓名']))  # <class 'pandas.core.series.Series'>
  8. print(df['姓名'].shape)  # (3,)
  9. print(df[['姓名']].shape)  # (3, 1)
复制代码

从 DataFrame 取单列 df['姓名'] 得到 Series,而 df[['姓名']] 仍然得到 DataFrame,两者后续行为不同。Series 的索引与 DataFrame 行索引是同一套机制,创建、合并、计算时如果索引没有对齐,就容易出现 NaN。

二、创建 DataFrame 的常见方式
字典传列最常用,键名作为列名,值可以是列表、NumPy 数组、Series 或标量。特别注意 Series 传入时若索引与默认索引不一致,最终会按索引并集对齐,无法对齐的位置填 NaN。
  1. import numpy as np
  2. df = pd.DataFrame({
  3.     'A': [1, 2, 3],
  4.     'B': np.array([4.0, 5.0, 6.0]),
  5.     'C': pd.Series([7, 8, 9], index=['a', 'b', 'c'])
  6. })
复制代码

从列表嵌套创建时,每个内层列表代表一行,适合已经按行组织的数据:
  1. df = pd.DataFrame(
  2.     [[1, 'x', True], [2, 'y', False]],
  3.     columns=['id', 'label', 'flag']
  4. )
复制代码

从 NumPy 数组创建常用于建模前的特征矩阵:
  1. data = np.random.randn(5, 3)
  2. df = pd.DataFrame(data, columns=['f1', 'f2', 'f3'])
复制代码

创建时还可以关注 dtype、index、copy。dtype 可统一指定列类型,index 可自定义行索引,copy 控制是否复制底层数据。

三、read_csv、read_excel 与文本读写
pd.read_csv 是最常用的读取入口,但只写 pd.read_csv('data.csv') 很容易遇到列名错位、中文乱码、日期被读成字符串、编号被读成数值等问题。常用参数组合如下:
  1. df = pd.read_csv(
  2.     'data.csv',
  3.     encoding='utf-8-sig',
  4.     header=0,
  5.     sep=',',
  6.     parse_dates=['日期列'],
  7.     dtype={'编号': str},
  8.     na_values=['NA', 'NULL', '']
  9. )
复制代码

encoding 需要重点排查。Windows 上 Excel 另存的 CSV 可能是 gbk 或带 BOM 的 utf-8-sig,直接用 utf-8 读取会抛 UnicodeDecodeError。可以先试 utf-8,报错后改 gbk,再不行用 utf-8-sig。dtype={'编号': str} 能避免身份证号、银行卡号、订单号等前导零丢失。parse_dates 显式声明后,日期列会变成 datetime64[ns],后续排序、筛选、提取年月日才方便。

read_excel 底层依赖 openpyxl 处理 .xlsx,或 xlrd 处理旧版 .xls,缺少依赖会报 ImportError。读取时建议 sheet_name 写表名而不是索引,避免表顺序变化导致读错;usecols='A:F' 可减少内存占用;dtype={'客户ID': str} 可固定关键列类型。sheet_name=None 会返回字典,键是表名,值是 DataFrame,适合一个 Excel 多 sheet 的场景。
  1. df_excel = pd.read_excel(
  2.     '数据表.xlsx',
  3.     sheet_name='订单明细',
  4.     header=0,
  5.     usecols='A:F',
  6.     dtype={'客户ID': str}
  7. )
复制代码

Excel 单元格自带格式信息,pandas 读取时更尊重这些格式;CSV 没有元数据,完全依赖推断。因此读 Excel 时某列变成 Timestamp 而不是字符串,通常是单元格本身已是日期格式。

read_table、read_fwf 和 to_csv 也值得掌握。read_table 可视为 read_csv 的通用版本,read_fwf 用于固定宽度文本,例如老系统日志、银行对账单。
  1. df_fwf = pd.read_fwf('对账单.txt', widths=[8, 20, 15], names=['日期', '商户', '金额'])
  2. df.to_csv('output.csv', index=False, encoding='utf-8-sig')
复制代码

to_csv 的 index 默认会把行索引写进文件,导致输出文件多出一列无列名索引;不需要保存索引时务必加 index=False。中文环境输出 CSV 建议用 utf-8-sig,让 Windows Excel 直接打开时不乱码。

四、数据选取与条件筛选
列选取推荐统一使用 df['列名'],不要用属性访问 df.列名。列名若与 index、shape 等属性冲突,或含空格、特殊字符时,属性访问容易出错且不报错,排查隐蔽。行选取可用 head、tail、sample,定位则主要靠 loc 和 iloc。

loc 按标签定位,iloc 按位置定位。默认整数索引时两者看似一致,但索引改成日期、字符串或乱序整数后,iloc[0] 永远是第一行,loc[0] 是找标签为 0 的行。截取连续行时,loc 是闭区间,iloc 是左闭右开,df.loc[1:10] 与 df.iloc[1:10] 取到的行数不同,处理数据时要格外小心。
  1. df.loc[df['年龄'] > 28, ['姓名', '城市']]
  2. df.iloc[1:4, 0:3]
复制代码

多条件筛选不能用 and、or、not,要用按位运算符 &、|、~,并且每个条件都要加括号:
  1. df_sel = df[(df['年龄'] > 28) & (df['城市'] == '上海')]
  2. df_sel = df[(df['年龄'] < 23) | (df['年龄'] > 30)]
  3. df_sel = df[~(df['姓名'].isin(['张三', '李四']))]
复制代码

不加括号时,& 的优先级高于比较运算符,表达式会被错误解析,结果可能是报错或意外筛选结果。isin 适合列表多值匹配,~df['列'].isin(...) 表示反向筛选。字符串筛选可用 str.startswith 和 str.contains,contains 支持正则,na=False 可让空值不参与判断:
  1. df[df['手机号'].str.startswith('139')]
  2. df[df['备注'].str.contains('退款|售后', na=False)]
复制代码

五、计算、增删改与类型转换
DataFrame 计算的核心是向量化,整列一起算,而不是逐行 for 循环。
  1. df['人均消费'] = df['总金额'] / df['人数']
  2. df['年龄分组'] = np.where(df['年龄'] > 30, '中年组', '青年组')
  3. df['折扣后'] = df['原价'].round(2) * 0.8
复制代码

apply 和 map 用于逐行或逐元素处理。Series.map 适合单列元素替换或字典映射,字典中没出现的值会变成 NaN;Series.apply 对列中每个元素调用函数;DataFrame.apply 默认 axis=0 对每列应用,axis=1 对每行应用。
  1. df['订单等级'] = df['订单金额'].apply(
  2.     lambda x: '高' if x > 1000 else ('中' if x > 500 else '低')
  3. )
  4. df['综合得分'] = df.apply(
  5.     lambda row: row['评分'] * 0.6 + row['销量'] * 0.4,
  6.     axis=1
  7. )
复制代码

函数复杂且需要复用时用普通函数,简单逻辑可用 lambda。若单函数非常耗时,可考虑 pandarallel 的 parallel_apply,但一万行以下通常没必要,调度开销可能反而更大。map 和 apply 对缺失值行为略有差异,应用前可用 fillna 或 isna 过滤。

新增、修改、删除列和行:
  1. df['新列'] = 0
  2. df['新列'] = df['旧列'] * 2
  3. df['新列'] = df['列A'].astype(str) + '_' + df['列B'].astype(str)
  4. df.insert(位置索引, '列名', 数值)
  5. df['年龄'] = df['年龄'].replace({25: 26, 30: 31})
  6. df.loc[df['城市'] == '上海', '城市'] = 'Shanghai'
  7. df = df.drop(columns=['列A', '列B'])
  8. df = df.drop(index=[3, 5])
复制代码

条件修改单元格要用 df.loc[行条件, 列名] = 新值。不要写 df[条件]['列名'] = 值,这种链式赋值会触发 SettingWithCopyWarning,修改可能不生效。drop 默认返回新对象,不修改原 DataFrame;inplace=True 已有弃用趋势,建议统一用 df = df.drop(...)。

类型转换先看 df.dtypes。显式转换可用 astype,复杂格式配合 pd.to_numeric 和 pd.to_datetime。
  1. df.dtypes
  2. df['编号'] = df['编号'].astype(str)
  3. df['年龄'] = df['年龄'].astype('int32')
  4. df['价格'] = df['价格'].astype('float64')
  5. df['日期'] = pd.to_datetime(df['日期'])
  6. df['金额'] = pd.to_numeric(df['金额'], errors='coerce')
  7. df['交易时间'] = pd.to_datetime(df['交易时间'], format='%Y-%m-%d %H:%M:%S')
  8. df['列'] = df['列'].astype('Int64')
复制代码

errors='coerce' 表示转换失败时置为 NaN,适合在清洗阶段把不可解析数据显式变为缺失值。整数列一旦出现 NaN,pandas 会自动升级为 float64,可能带来精度问题;新版 pandas 的 Int64 支持 NaN,可用 astype('Int64') 保留整数语义。

六、合并与排序
concat 是拼接,axis=0 上下堆叠,axis=1 左右拼接;纵向拼接时通常要加 ignore_index=True,避免保留两张表各自索引造成重复行号。merge 是按字段匹配,对应 SQL JOIN,on 指定键列,how 控制 left、inner、right、outer 等连接方式;键列重复时结果行数会膨胀。join 基于行索引合并,可视为 merge 的简化版。多表处理优先用 merge,语义更明确。
  1. df_all = pd.concat([df1, df2], axis=0, ignore_index=True)
  2. df_col = pd.concat([df1, df2], axis=1)
  3. df_merged = pd.merge(df_orders, df_customers, on='客户ID', how='left')
  4. df_joined = df1.join(df2, how='inner')
复制代码

排序主要用 sort_values。多列排序时按优先级列出列名,ascending 可分别指定升降序。自定义顺序可先用 map 生成临时排序列,排序后删除。
  1. df_sorted = df.sort_values('年龄', ascending=False)
  2. df_sorted = df.sort_values(['城市', '年龄'], ascending=[True, False])
  3. status_order = {'已支付': 1, '待支付': 2, '已取消': 3}
  4. df['排序用'] = df['状态'].map(status_order)
  5. df_sorted = df.sort_values('排序用').drop(columns=['排序用'])
  6. df = df.sort_index()
复制代码

sort_index 适合整理时间序列的乱序日期索引。

七、缺失值与异常值清洗
缺失值不只有删除一种思路,通常分为删除、填充、标记、预测。先统计:
  1. df.isna().sum()
  2. df.isna().mean()
  3. df[df['某列'].isna()]
复制代码

删除时注意粒度,dropna() 默认删掉任意含缺失值的行;dropna(subset=['关键列1', '关键列2']) 只检查指定列;dropna(axis=1, thresh=5) 在某列非空值少于 5 个时删除整列。
  1. df.dropna()
  2. df.dropna(subset=['关键列1', '关键列2'])
  3. df.dropna(axis=1, thresh=5)
复制代码

填充要结合业务含义:价格缺失可填 0,年龄缺失可用中位数,城市缺失可填“未知”,时间序列可用 ffill 或 bfill。
  1. df['价格'].fillna(0)
  2. df['年龄'].fillna(df['年龄'].median())
  3. df['城市'].fillna('未知')
  4. df['销量'].fillna(method='ffill')
  5. df['销量'].fillna(method='bfill')
复制代码

中位数比均值更不容易被极端值带偏,适合数值列;类别列更适合众数或“未知”类别。ffill/bfill 适合采集间隔固定、偶发缺失的时间序列。更复杂的预测填充可用 sklearn 的 SimpleImputer 或 KNNImputer。

异常值识别可用描述统计和分位数。IQR 方法不假设数据服从正态分布,对偏态数据更稳健:
  1. df['销售额'].describe()
  2. Q1 = df['销售额'].quantile(0.25)
  3. Q3 = df['销售额'].quantile(0.75)
  4. IQR = Q3 - Q1
  5. lower = Q1 - 1.5 * IQR
  6. upper = Q3 + 1.5 * IQR
  7. df_outlier = df[(df['销售额'] < lower) | (df['销售额'] > upper)]
复制代码

3σ 原则对正态分布假设更严格,实际数据不一定满足。处理异常值前要先判断它是测量误差、录入错误,还是真实长尾数据,不要直接删除。

八、常见报错与排查清单
1. read_csv 报 UnicodeDecodeError:依次尝试 utf-8、gbk、utf-8-sig,Windows Excel 导出的 CSV 尤其注意 BOM。
2. 编号、身份证、银行卡前导零丢失:读取时用 dtype={'编号': str}。
3. 日期列不能做时间运算:用 parse_dates 或 pd.to_datetime 转成 datetime64[ns]。
4. read_excel 报 ImportError:安装 openpyxl 或 xlrd;新版 xlrd 只支持 .xls。
5. 多条件筛选报错或结果异常:用 &、|、~,每个条件加括号。
6. 修改单元格不生效:避免链式赋值,用 df.loc[行条件, 列名] = 新值。
7. 输出 CSV 多出索引列或中文乱码:to_csv 加 index=False 和 encoding='utf-8-sig'。
8. 合并后出现大量 NaN:检查键列、索引是否对齐,以及 how 参数是否符合预期。
9. 整数列变 float:列中存在 NaN,必要时转成 Int64。
10. 性能瓶颈:优先向量化,少写 for 循环;apply 只在必要时使用,数据量大再考虑并行方案,读取 Excel 时用 usecols 减少内存。

整体来看,Pandas DataFrame 的难点不在 API 数量,而在索引对齐、数据类型推断和筛选写法这些容易被忽略的细节。把创建、读取、选取、增删改、合并排序和缺失值处理串起来理解,遇到报错时就能从数据类型、索引、参数和赋值方式四个方向快速定位。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Pandas DataFrame常用操作与读取报错排查

楼主整理得挺系统,尤其把 DataFrame 和 Series 的关系、单列取出来是 Series 还是 DataFrame 的区别讲清楚了,这个点在后面赋值和筛选时确实容易踩坑。read_csv 那段也很实用,编码、dtype 和 parse_dates 基本就是实际读文件时最常排查的参数,身份证号、订单号前导零丢失的问题很多人都遇到过。read_excel 依赖 openpyxl 或 xlrd、用 sheet_name 写表名而不是索引,这些提醒也很到位。to_csv 的 index=False 和 utf-8-sig 也是经常被忽略但一用就见效的细节。loc 和 iloc 的闭区间与左闭右开差异值得反复强调。期待后续把合并、排序、缺失值清洗和报错排查继续展开,尤其是索引对齐导致 NaN 的场景,如果能多给几个排查思路就更好。谢谢分享。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Pandas DataFrame常用操作与读取报错排查

楼主这帖整理得挺系统,按创建、读取、选取、增删改、合并排序和缺失值清洗的顺序来,很适合当速查。几个点挺有共鸣:单列取出来是 Series、双中括号仍是 DataFrame,后面行为确实不一样;创建时 Series 索引不一致会对齐出 NaN,这个坑很常见。读取部分把 encoding、dtype、parse_dates 讲清楚了,尤其 dtype 指定编号为 str 来保前导零,订单号、身份证号场景太实用了。read_excel 推荐 sheet_name 写表名、usecols 控制列,to_csv 加 index=False 和 utf-8-sig,都是实际脚本里会反复用到的。loc 和 iloc 的闭区间、左闭右开区别也提醒得到位,同样数字范围截取的行数不同,确实容易看走眼。最后 loc 布尔筛选那段好像还没贴完,期待后续把合并、排序和缺失值处理也补上。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Pandas DataFrame常用操作与读取报错排查

楼主这篇整理得很实在,尤其是把 DataFrame 和 Series 的关系、单列和双中括号取列的区别放在开头,对刚上手 pandas 的人很有用。实际脚本里读取 CSV 的 encoding 和 dtype 确实最容易踩坑,utf-8 报错后换 gbk 或 utf-8-sig,编号类字段不加 str 前导零就丢了。parse_dates 显式声明日期列、to_csv 加 index=False 和 utf-8-sig、read_excel 指定 sheet_name 和 usecols 这些细节也都很关键。loc 和 iloc 的闭区间、左闭右开差异更是经常取错行,默认整数索引时不容易发现,换成日期或乱序索引后就明显了。最后条件筛选那段好像没贴完整,期待楼主把 loc 筛选和后续增删改、合并排序部分补上。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-15 21:57 , Processed in 0.114307 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部