如何高效拆分Pandas DataFrame中含转义逗号的分隔值?
问题
我从API获取CSV格式的数据并读取为Pandas DataFrame,代码如下:
import io import requests import pandas as pd response = requests.get(url, params=params, headers=headers) df = pd.read_csv(io.StringIO(response.content.decode("utf-8")))
检查后发现DataFrame中大量单元格包含长度不一的逗号分隔值,这些值未被引号包裹,部分值还包含反斜杠转义的逗号。示例数据如下:
eg = pd.DataFrame({"a": ["A single value", "Three,separate,values", "This value contains a \\,, and so does\\, this one", pd.NA], "b": ["", pd.NA, "2,3,4","12,17"]})
我需要将这些单元格拆分为单元格内的列表或单独列,目前用逐单元格处理的方式实现,代码如下:
import csv def to_list_of_strings(cell): if pd.isnull(cell): return [] elif "," in cell: return next(csv.reader(io.StringIO(cell), escapechar="\\")) else: return [cell] result = eg.a.apply(to_list_of_strings)
(使用csv模块是为了处理引号等其他可能的解析需求)
期望结果(list(result)的输出):
[['A single value'], ['Three', 'separate', 'values'], ['This value contains a ,', ' and so does, this one'], []]
但这种逐单元格处理的方式在处理多列及数千行数据时速度较慢,请问是否存在更快的矢量化实现方式?
优化方案
方法1:正则表达式结合str.split实现矢量化拆分
利用正则匹配未被反斜杠转义的逗号作为分隔符,直接调用Pandas的矢量化字符串方法,避免逐行循环:
import pandas as pd # 正则匹配:匹配不是由反斜杠前置的逗号 pattern = r'(?<!\\),' def split_with_escape(col): # 标记空值和空字符串 mask = col.isna() | (col == "") # 对非空值按正则拆分 split_col = col.str.split(pattern) # 将空值/空字符串对应位置替换为空列表 split_col = split_col.where(~mask, pd.Series([[]]*len(col), index=col.index)) # 还原转义的逗号:把\\,替换为, split_col = split_col.apply(lambda lst: [s.replace(r'\,', ',') for s in lst]) return split_col # 处理目标列 result = split_with_escape(eg['a'])
这种方式完全基于Pandas原生矢量化操作,速度比apply逐行处理快数倍,数据量越大优势越明显。
方法2:批量调用csv.reader提升解析效率
如果需要保留csv模块的完整解析能力(比如处理引号、复杂转义场景),可以将整列数据拼接成标准CSV多行文本,一次性交给csv.reader解析,减少重复IO操作的开销:
import io import csv import pandas as pd def batch_csv_parse(col): # 空值替换为空白字符串,避免csv解析报错 processed_col = col.fillna("") # 将列中每个元素转为一行,拼接成CSV格式文本 csv_text = '\n'.join(processed_col) # 一次性解析所有行 parsed_rows = list(csv.reader(io.StringIO(csv_text), escapechar='\\')) # 将解析出的[""](对应原空字符串/空值)转为空列表 parsed_rows = [row if row != [""] else [] for row in parsed_rows] return pd.Series(parsed_rows, index=col.index) # 处理目标列 result = batch_csv_parse(eg['a'])
这种方法通过批量处理减少了多次创建io.StringIO和调用csv.reader的开销,效率比逐单元格处理提升显著,同时保留csv模块的解析兼容性。
方法3:用numpy.vectorize封装原函数(折中方案)
如果不想修改原有解析逻辑,可以用numpy.vectorize对原函数进行矢量化封装,虽然底层仍是循环,但内部优化比Pandas的apply更高效,适合快速改造原有代码:
import numpy as np import csv import io import pandas as pd def to_list_of_strings(cell): if pd.isnull(cell) or cell == "": return [] elif "," in cell: return next(csv.reader(io.StringIO(cell), escapechar="\\")) else: return [cell] # 矢量化封装原函数 vectorized_parse = np.vectorize(to_list_of_strings, otypes=[object]) # 应用到目标列 result = pd.Series(vectorized_parse(eg['a']), index=eg.index)
内容的提问来源于stack exchange,提问作者Stuart
相关产品推荐
相关产品推荐

