
做数据处理这些年我电脑里最常见的文件格式就是 csv。不管是从业务系统导出的流水、网上下载的公开数据集还是传感器记录大家好像商量好了一样最后都会交出一堆 csv 文件。面对这种局面pandas 几乎成了我处理 csv 的第一选择没有之一。这个组合看起来简单用好了之后从数据导入、清洗、分析到结果存储一整条链路全都能在同一个脚本里跑完。这一篇就围绕 pandas 和 csv 这个组合把我实际工作中踩过的坑、总结出来的参数用法以及遇到大文件时的处理思路完整捋一遍。不管你是刚接触 pandas 的新手还是已经在用但经常被编码、类型转换、内存问题折磨的老手这篇文章应该都能给你一些可以直接抄走的东西。1. 为什么偏偏是 Pandas 加 CSV 这对组合1.1 CSV 这种格式为什么一直没被淘汰先说一个很多人忽略的事实CSV 全称是“逗号分隔值”但它其实并没有一个全球统一的标准不同系统对分隔符、换行符、转义规则都有自己的理解。可即便如此它依然在数据交换领域存在了几十年原因很简单——它是纯文本。纯文本意味着什么意味着任何系统都能打开它记事本可以、手机备忘录可以、一台很旧的电脑也可以。CSV 没有复杂二进制文件的解析门槛也没有数据库那种服务端依赖一个文本文件本身就可以承载一张表。我在一个项目里接过甲方发来的数据号称“千万级公交刷卡记录”解压之后就是十来个 CSV。当时我心里就想如果对方给的是别的二进制格式光打开文件可能就得等半天。另一个原因是携带方便。区县级手机信令数据、全球地震目录这类公开数据集发布方通常都会提供 CSV 版本。因为 CSV 可以被任意语言读取用户拿到之后想转成 Excel、导入数据库还是用 Python 做分析都不受限制。这正是它作为“交换格式”最难得的品质。1.2 Pandas 恰好补上了 CSV 的短板CSV 的弱点也很明显它只负责“存”不负责“处理”。拿记事本打开一个 CSV看到的全是密密麻麻的逗号和引号根本谈不上数据分析。Excel 倒是能处理但行数一多就吃力文件到了几十 GB 更是直接退场。Pandas 解决的核心问题是把 CSV 从“能看见”变成“能分析”。它把一张 CSV 读成一个 DataFrame这是 pandas 最核心的数据结构你可以把它理解成一张“内存里的表格”。每一列可以单独设置数据类型每一行可以按条件筛选缺失值、重复值、异常值都有对应的处理函数。有了这一层CSV 就从死文件变成了活数据。这个组合之所以高效还有一个很实际的原因DataFrame 的读写函数和数据分析函数属于同一个生态读进来之后直接做清洗、聚合、计算最后再写回 CSV全程不用切换工具。比起“Excel 导出来、SQL 导进去、再导出来”的老流程脚本化处理在效率和可重复性上高太多了。我粗略估计了一下一个 pandas 用户的日常流程十有八九是这样pd.read_csv把文件读进来然后dropna、astype、groupby一路操作最后to_csv存出去。这四步看着简单但每一步都有很多参数和细节值得认真抠一抠下面按顺序展开说。2. 环境准备与最基础的 CSV 读取2.1 PyCharm 下安装 pandas 包的三种方式很多新手在 PyCharm 里遇到的第一道坎就是import pandas的时候报ModuleNotFoundError。这个问题本身不难解决关键是得理解包管理器是怎么工作的。最省事的方式是在项目底部打开 Terminal直接执行pip install pandas如果电脑上有多个 Python 版本最好先确认当前解释器python --version pip --version装完之后到 File - Settings - Project - Python Interpreter 里看一眼 pandas 有没有列出来。如果你之前已经在其他虚拟环境里装过别的包这次pip install走的可能是系统 Python而不是项目里选中的那个解释器。所以我给新手的建议很简单先确认解释器再执行安装别凭感觉。除了命令行PyCharm 的设置界面里也能图形化安装Settings - Python Interpreter - 点加号 - 搜索 pandas - Install Package。这种方式的好处是不会装错环境对新手更友好。如果用的是 Anaconda还可以绕开 PyCharm 直接装conda install pandas三种方式装出来的 pandas 本身差别不大唯一需要注意的是版本。一些老教程还在用 pandas 0.x 的写法而最近几年 pandas 已经到 2.x 了接口和默认行为有变化。我自己的习惯是装最新稳定版但如果你是在维护公司老项目建议先查一下目标机器的 pandas 版本避免本地能用、上线报错。2.2 最该吃透的几个 read_csv 参数pd.read_csv是 pandas 里出场率最高的函数之一它有一百多个参数但日常真正常用的就那么几个。把这些吃透比背完所有参数有用得多。第一个是文件路径。这本身没什么好说的但要注意 Windows 路径里的反斜杠。很多人写pd.read_csv(C:\\Users\\me\\data.csv)我更推荐用原始字符串pd.read_csv(rC:\Users\me\data.csv)少写杠少报错。第二个是sep它决定 pandas 怎么把一行文本切成列。CSV 默认是逗号但国内很多系统导出的文件其实用制表符分隔或者用分号甚至没有统一分隔符。这种情况下你得手动指定sep。比如我处理“近十年全球地震发震情况”这类 CSV 时遇到过文本字段中的逗号跟分隔符混淆直接读就会出现列错位。这时就要用到第三个参数quotechar。默认情况下pandas 会把双引号作为字符串的引用符号字段里如果出现分隔符只要被引号包住就不会被误切。如果你读的文件列数对不上先别怀疑数据用记事本打开看一眼确认引号和分隔符的配合方式再决定要不要调整quotechar或engine。第四个是encoding这个参数值得单独拿出来说。CSV 没有强制编码有的来自 Windows 的是 GBK 或者 GB18030有的是 UTF-8还有带 BOM 的 UTF-8。用默认的 UTF-8 去读 GBK 文件轻则中文全是乱码重则直接抛UnicodeDecodeError。我的经验是先加errors参数做一次试探性读取pd.read_csv(data.csv, encodinggbk, errorsignore)如果读出来的中文正常就能确认是 GBK如果还是不对劲再试utf-8-sig。这种带 BOM 的编码在 Excel 场景非常常见后面我会专门讲。第五个是dtype它直接影响数据类型的推断。pandas 默认会自己猜每列的类型列里有整数就猜 int64有小数就猜 float64有字符串就猜 object。问题在于“猜”不仅费内存而且容易猜错。比如身份证号、手机号、带前导零的编码你希望它保持字符串但 pandas 看到纯数字会自动当成 int64前导零直接消失。要避免这种情况读取时就显式指定dtype_dict {id_card: str, phone: str, district_code: str} pd.read_csv(data.csv, dtypedtype_dict)2.3 大体量 CSV 怎么读才不卡很多人第一次拿 pandas 读大文件都是直接read_csv一把梭结果内存直接爆掉。这里分享我处理“2023 年全国区县级手机信令数据”这类文件时的做法。这类文件动辄几百 MB用默认参数读pandas 会把所有数据一次性加载到内存如果电脑只有 8G 内存很容易卡死。一个基础技巧是分批读取用chunksize把read_csv变成迭代器chunk_iter pd.read_csv(big_data.csv, chunksize100000) for chunk in chunk_iter: process(chunk)每十万行算一个 chunk处理完就释放内存占用会稳定很多。另一个技巧是只读需要的列。如果你的分析只需要其中三列完全可以用usecols把其他列全砍掉pd.read_csv(big_data.csv, usecols[col_a, col_b, col_c])文件原本有五十列但这一步之后内存里的 DataFrame 只有三列读取速度提升非常明显。还有一个轻度作弊的办法如果文件太大、分析要求又没那么高可以先抽样读一部分做验证。nrows参数让你先读五万行看看数据结构等逻辑跑通了再上全量。我在清洗大体量 CSV 时几乎每次都会先用nrows探路避免因为一个列分割错误导致整段脚本白跑。3. 数据清洗从原始 CSV 到能用的表3.1 先搞清楚数据结构再动手拿到一份 CSV我一般不会急着写清洗代码。先用df.info()看每一列的类型和非空数量用df.head()看前几行内容再用df.describe()看数值列的分布。这三板斧做完对数据整体的印象就有了。这里有个容易忽略的风险数据量很大的时候head()返回的是前几行但万一前几行恰好全是空值你很可能误判整列都是空的。我会随机抽几段来看比如用df.sample(10)代替只盯着头部。顺便说说数据结构本身。很多平台课程里都有“pandas 数据结构创建”的练习核心就两个Series 和 DataFrame。Series 是一维的可以看作一列数据DataFrame 是二维表格是 Series 的集合。实际工作时临时构造一个小表来调试清洗逻辑再常见不过demo pd.DataFrame({城市: [北京, 上海, 广州], 交易量: [100, 200, 150]})不用每次都从文件里读掌握这种直接构造方式能让调试速度提升不少。列名同样值得处理。中文列名能看懂但写代码时总免不了切换输入法列名里带空格、带括号更容易出错。我的习惯是读取之后统一改成英文小写下划线风格df.columns [col.strip().lower().replace( , _) for col in df.columns]3.2 数据类型转换的正确姿势CSV 里的所有内容本质上都是字符串即使 pandas 能自动推断也不代表它推断得准。最常见的场景就是日期。手机信令数据里通常会有“时间戳”字段读取之后默认可能是 object要转成 datetime 才能参与时间序列分析。正确写法是df[time] pd.to_datetime(df[time], format%Y-%m-%d %H:%M:%S)这里format参数很重要。不写 formatpandas 会自己猜但猜的过程很慢而且遇到多种日期格式混在一起时容易报错或产生 NaT。pandas 文档里有一个规律凡是能指定格式的地方尽量指定格式性能和准确性都会好很多。数值列的转换同样常见。地震数据里的“震级”字段原始 CSV 可能存成文本比如 “5.6”pd.to_numeric可以一把转过来df[mag] pd.to_numeric(df[mag], errorscoerce)errorscoerce的意思是遇到转换不了的值就置为 NaN。这个参数是我用得最多的一个因为脏数据不可避免与其让脚本崩掉不如先把坏值变成缺失值后续再按缺失值逻辑处理。有一个新手容易踩的坑df.astype(str)在转换时不会报错但当你把数值列转成字符串后再想拿回来做运算就麻烦了。所以我的建议是能不转字符串就不转尤其是整数 ID 一类的字段保持 int64 反倒更省内存。3.3 正则表达式和数据清洗的配合看到热搜词里有一条“pandas 正在表达式”其实想说的应该就是正则表达式。正则表达式在数据清洗里真的能顶半边天。比如手机信令数据里的行政区划代码有时候导出之后会带上类似 “110101-01” 的后缀查询起来很别扭用正则就能把它清掉df[district_code_clean] df[district_code].str.replace(r\D, , regexTrue)\D匹配所有非数字字符replace 成空得到的就是纯数字。另一个常见场景是从地址文本里提取城市名虽然更复杂的提取交给专业算法更靠谱但在字段格式相对规整的情况下正则的性价比极高。用正则之前有个前提是保证这一列是字符串类型。之前有人问我为什么str.contains会报错点开一看那列是 int64字符串方法当然用不了。先df[col] df[col].astype(str)再操作或者读取时就在 dtype 里指定为 str能省很多事。4. 多文件合并与文本文件读写4.1 把几十个 CSV 合并成一个的完整流程“怎么把多个 csv 格式的文件合并在一起”这个问题我几乎每隔一阵就会在群里看到一次。核心逻辑其实就一步批量读取再拼接。先把目录下所有 CSV 路径列出来最方便是用 globimport glob paths glob.glob(./data/*.csv)然后逐个读取进列表frames [] for p in paths: frames.append(pd.read_csv(p)) df pd.concat(frames, ignore_indexTrue)pd.concat的默认行为是纵向堆叠即把行接在一起。ignore_indexTrue能让合并后的索引重新从 0 排避免每段都带着自己的行号后续筛选时逻辑更清晰。这个方法看着简单但实际操作有两个注意点。第一个是列名一致性。假设你有 5 月份的 CSV 和 6 月份的 CSV5 月份列名写的是“日期,成交量”6 月份写成了“日期,成交量(手)”直接合并就会产生两列而不是一列。合并前最好先对比所有文件的列名all_cols set() for p in paths: all_cols all_cols | set(pd.read_csv(p, nrows1).columns)提前发现自己犯的错比最后分析出错误结果再返工舒服太多。第二个是编码不一致。同一个目录里的 CSV有些来自 Linux 系统UTF-8 编码有些在 Windows 上用老软件导出GBK 编码。合并时如果统一用 UTF-8 读GBK 文件必然出错。我的一个临时方案是写个灵活读取函数先试 utf-8再回退到 gbkdef read_flexible(path): try: return pd.read_csv(path, encodingutf-8) except UnicodeDecodeError: return pd.read_csv(path, encodinggbk)这方法不优雅但应付“杂牌导出”的数据特别好用。4.2 文本文件读写的细节CSV 本质上是文本文件的一种所以 pandas 里read_csv、read_table、read_fwf都属于文本文件读取的范畴。有些开源数据集的原始格式并不是规范 CSV而是固定宽度文件每条记录的每个字段占固定字节数比如20230101 北京 朝阳 20230102 上海 浦东这时候用read_fwf比read_csv合适。pandas 会自动根据空白切分字段省去手动解析的麻烦。写入文本文件看起来更简单但不少软件导出的“假 CSV”有一个坏习惯字段用制表符或空格拼接文件名却叫 .csv。这类文件读进 pandas 时需要设置sep\t或者sepr\s。我碰到这种文件时第一反应都是先打开看分隔符到底是空格、制表符还是中文逗号再决定参数而不是盲目靠猜。另外一个很多人踩过的坑是to_csv导出时默认带了索引列。导出之后打开文件会发现最前面多了一列无名的 0、1、2……那就是 DataFrame 自带的索引。如果这个文件要交给别人导出时记得写df.to_csv(output.csv, indexFalse)我建议把这个参数当成刻板习惯来记影响非常直观你导出的是干净的表格还是一个带默认索引的“怪表”。5. 高效写入 CSV 与存储选型5.1 to_csv 值得认真调的参数写入 CSV 不只是把 DataFrame 存盘它同样有性能问题。数据量大时默认参数会一次性把所有转好的字符串写进文件内存和磁盘 IO 都有压力。第一个要提的还是index刚才说了默认要改成 False。如果是带层级索引的 DataFrame还会涉及index_label使用频率不高。第二个是encoding。如果这个 CSV 要交给国内业务系统或 Excel 用户我一般用encodingutf-8-sig。utf-8-sig会在文件开头写入 BOMExcel 打开时能正确识别为 UTF-8。如果直接用标准 utf-8 写出Excel 很可能会把中文显示成乱码尤其是老版本 Excel。第三个是压缩。pandas 可以直接写出压缩格式df.to_csv(output.csv.gz, indexFalse, compressiongzip)压缩后体积通常能小一个数量级传输和解压都省事。读取时 pandas 认得后缀照样能直接读pd.read_csv(output.csv.gz)这对组合我几乎天天用特别是做数据归档时。第四个是chunksize。如果数据有几百万行单次直接 to_csv 也不是不行但写入过程会把大量字符串对象堆积在内存里机器差一点就可能卡死。分段写的方式df.to_csv(output.csv, indexFalse, chunksize100000)pandas 会按 chunk 循环写内存占用稳定很多。其实这个参数也回答了一个很多人想问的问题既然读大文件可以用 chunksize为什么写不行pandas 早就考虑到了。5.2 CSV 和 Excel、数据库、Parquet 到底怎么选CSV 不是唯一的选择甚至在很多场景下不是最优选。我给自己整理过一张选择表这里分享出来场景推荐格式原因对外交换、交付数据CSV兼容性最好谁都能打开带格式、带公式的报表xlsx能保存样式和公式大型分析数据集、要快速读取parquet列式存储读写效率高需要并发查询、后端存储数据库表权限、索引、事务更完善pandas 里读写 Excel 也很常用但我个人只有在对方坚持要 xlsx 时才用。xlsx 相比 CSV文件体积更大、读写更慢、还要依赖 openpyxl 之类的库它适合的是“给人看的表格”不是“给程序处理的数据”。Parquet 是我比较推荐的分析存储格式。同样是几百万行的表CSV 可能需要几百 MBParquet 往往只有几十 MB且 pandas 读取速度明显更快因为它按列存储还带 schema。如果你的下游也是 Python 分析完全可以考虑把中间结果存成 Parquet只在最终交付时导出 CSV。数据库则是另一个方向。遇到几十 GB 甚至更大的数据内存计算本身就不合适了应该考虑导入数据库再用 SQL 查询这也是很多业务系统的常规做法。5.3 CSV 作为中转格式的数据库导入我经常碰到的一类需求是先用 pandas 清洗 CSV再把清洗结果写进数据库。很多人会用 pandas 的to_sql接口但这里有个容易踩的坑to_sql依赖 SQLAlchemy而且数据量大时逐条 insert 效率很低。更常见也更稳妥的路线是pandas 负责清洗输出干净的 CSV再用数据库自带的导入命令加载。比如 MySQL 里LOAD DATA INFILE /path/clean_data.csv INTO TABLE my_table FIELDS TERMINATED BY , IGNORE 1 LINES;PostgreSQL 里对应的是 COPY 命令通常比逐行 INSERT 快很多。这个流程的分工很清晰pandas 负责清洗、类型转换、异常过滤数据库负责存储和查询两边各干各擅长的活。如果你不想在 pandas 和数据库之间导来导去也可以用 pandas 分块写入 SQLAlchemy但那个方案对网络、事务边界、内存控制要求更高。我一般只在数据量不大时才让 pandas 直接写库千万级以上的数据还是走专用导入工具更稳。6. 跨工具协作CSV 与 Excel、其他语言、调试工具6.1 WPS 表格保存 CSV 的编码坑很多非程序员会在 WPS 或 Excel 里把表格“另存为 CSV”这个操作本身没问题但保存出来的 CSV 往往带着两个特征一是默认编码可能是 ANSI 或 GBK二是分隔符可能因为系统区域设置变成了分号或者制表符。如果这份 CSV 要交给 pandas 处理读取时别直接用默认参数先看第一行有没有乱码再决定编码。更理想的路径是在 WPS 里另存为的时候手动选一下编码比如选择“UTF-8 CSV”。不同版本 WPS 的菜单位置不一样如果找不到导出之后再用 pandas 处理也得承担编码猜错的风险。我印象很深的一次是同事从 WPS 导出一个“地震发震情况.csv”他以为文件是标准 UTF-8结果我用 pandas 读出来震级列全是 object日期列全是 NaT。打开一看文件开头有 BOM分隔符又是制表符。解决方案反而简单pd.read_csv(seismic.csv, sep\t, encodingutf-8-sig)这种“一步到位”的经验只有踩过坑之后才体会得到。6.2 给其他工具准备 CSV 时的兼容性问题如果 pandas 生成 CSV 是为了给 C#、LabVIEW 之类的程序使用那编码和换行符这两个问题要额外注意。C# 这类 .NET 程序读取 CSV 时如果文件是 UTF-8 但没有 BOM某些旧组件会按系统默认编码去读中文直接乱码。所以给这类下游生成文件时我一般会加encodingutf-8-sig。如果对方反馈“解析出来乱码”第一反应是确认我生成的 CSV 编码而不是一上来就改他们的代码。LabVIEW 处理 CSV 时也有个经典问题它期望的字段分隔符可能与 pandas 默认的逗号不一致而且文本字段里的引号转义规则各家实现不同。我的建议是给这类工具生成 CSV 时尽量不要在字段里保留逗号、换行符等特殊字符先在 pandas 里统一替换掉。举个例子df[raw_text] df[raw_text].str.replace(,, ).replace(\n, )这样可以大幅减少下游解析工具的翻车概率。顺便说一个“同文件同时读写”的场景。如果 C# 那边正以独占方式打开某个 CSV 文件同一时刻 pandas 再往同一个文件写入多半会抛权限异常。同时读写这件事别指望单靠一个库解决正确做法是让一方完整释放之后另一方再执行操作。序列化地访问文件才是稳定的方案。另外如果需要把 CSV 转成 HTML 放到网页上展示pandas 里一行df.to_html()就能生成表格代码。不过默认输出是裸表带着一个默认 class样式很素要好看还得自己套一层 CSS。6.3 不要忽略的高频函数ewm热搜词里有一个“pandas 中 ewm 函数参数”。ewm 是指数加权移动平均常用于金融序列、传感器数据的平滑处理。主要参数有 alpha、span、min_periods、adjust 等。简单理解span 是窗口长度的替代品alpha 是平滑系数两者互为倒数关系alpha 2 / (span 1)比如把传感器测出来的数据从 CSV 导入之后用 ewm 快速看趋势df[smooth] df[value].ewm(span10, adjustFalse).mean()这里的关键点是adjustFalse它代表从序列开头就递归平滑对实时数据流的处理更直观。很多教程默认讲的是adjustTrue两者前几个点的结果会有差异如果你对数据敏感一定要搞清楚自己到底需要哪一种。7. 常见问题与排查技巧实录7.1 读出来的列对不上这是出现频率最高的疑难杂症。表象是文件里明明只有六列pandas 读出来却有七列、八列或者某些行的数据串到了后面。排查步骤我建议固定成一套先用记事本打开原始 CSV看真实的分隔符是什么。不是所有 CSV 都用逗号。看字段里有没有包含分隔符的情况如果有是不是被引号括起来了。看文件开头有没有 BOM 或其他不可见字符。BOM 会让第一列列名多出一个可见字符前缀。对应方案分别是指定sep、指定quotechar、encodingutf-8-sig或者读完以后手动清掉列名里的 BOM。这三个原因基本覆盖了百分之九十的“列数不对”问题。7.2 内存爆掉和速度慢如果读 CSV 报 MemoryError说明文件大小已经明显超过了可用内存。处理办法不是去加虚拟内存而是从数据访问方式上优化。第一优先级是usecols只读必要列其次是dtype指定类型。比如 CSV 里有一列状态码本来 int8 就够pandas 默认读了 int64内存直接扩大八倍。长期处理大数据的人甚至会手动指定低精度dtype_dict {status: int8}第二优先级是chunksize分批处理把“全量参与计算”改成“每批算一个统计量再汇总”。这个方法适合求和、计数这些可以分段的操作。如果你要做的是全表排序、全表去重分批处理效果有限这时就该认真考虑换工具或者准备更大内存的机器。还有一个很实用的小优化字符串列在 pandas 里是内存占用最高的一种类型把重复度高的字符串转成 category 类型可以大幅瘦身df[city] df[city].astype(category)7.3 编码与乱码速查关于乱码我整理了一个简易速查表几乎可以直接拿去用现象可能原因解决办法中文全是问号或乱码GBK 文件被按 UTF-8 读取encodinggbk列名前多出未知字符带 BOM 的 UTF-8encodingutf-8-sig读文件直接抛 UnicodeDecodeError编码猜错了errorsignore 先探路Excel 打开 UTF-8 文件乱码文件缺 BOMto_csv 时用 encodingutf-8-sig一列数据被拆到多列分隔符不是逗号指定 sep或先打开文件确认这个表的核心思想是编码问题不是靠猜而是先看文件字节再对症下药。7.4 缺失值表示的差异CSV 里的缺失值有各种表示方式空字符串、英文单词 null 或 NA或者一个具体占位符 “-9999”。pandas 默认只识别部分缺失值标记如果某列里是 “-9999”它会被当成正常数值。正确做法是读取时显式指定pd.read_csv(data.csv, na_values[-9999, null, NA, ])这个参数在我处理气象、地震等科研数据时几乎每次都加因为这类数据习惯用特殊值表示缺失不加的话统计结果会非常离谱。还有个小提醒读出来的 DataFrame 里出现 None 和 NaN在 pandas 中多数函数会统一处理但如果要写数据库最好先做一步统一转换避免类型问题。8. 一点实际操作体会绕了这么大一圈其实想表达的就一件事pandas 和 CSV 这个组合看起来基础但真正高效地用起来靠的是对参数细节的把握和对数据本身的尊重。很多报错表面上是 pandas 的问题往深处看往往是文件里藏着不可见字符或者编码约定不清。我自己现在拿到一份新的 CSV流程已经固化成几个标准动作先看文件头和尾部、用 nrows 探读、检查列名编码、确认 dtype、再开始清洗。这套动作虽然朴实但能帮我避开绝大多数返工。如果你也在和数据打交道建议也沉淀一套属于自己的固定动作它会比你临时翻文档高效太多。最后分享一个小习惯处理重要的 CSV 文件之前先复制一份原文到备份目录任何清洗脚本都不直接覆盖原文件。这个习惯我坚持了好几年救过我很多次。数据这东西删起来容易找回来难备份永远不嫌多。