一、问题背景
需要遍历文本,并将符合条件的数据写入DataFrame。
采用的方法是for循环结合if判断,并通过insert方法逐列添加,如下:
ctria3 = pd.DataFrame(index=['属性', '节点']) # 创建一个空的DataFrame
for line in data:
if line.startswith('CTRIA3'):
line = line.strip('\n') # 去除末尾的换行符
e_id = line[c2] # 单元编号
p_id = line[c3] # 单元属性编号
nodes = [line[c4], line[c5], line[c6]] # 单元的3个节点
ctria3.insert(loc=0, column=e_id, value=[p_id, nodes])
这个方法效率非常低,在电脑上运行大概需要50秒,在笔记本上则跳出警告:
PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance
二、原因分析
当时猜测的原因有如下几个:
- for if 的效率问题;
- 字符串识别时,startswith函数的效率问题;
- dataframe添加列的方式,即insert方法的效率。
通过逐一排查:
- for if if 的方式修改为for if for if的方式,速度变化不大;
- 将startswith方法修改,即将line.startswith('CTRIA3') 修改为 line[:6] == 'CTRIA3', 速度略有提升,但还是很慢;
- 将insert方法替换,改为ctria3[e_id] = [p_id, nodes],速度不变,因为两者事实上都是insert方法。
三、解决方法
最后发现最主要拖慢速度的还是原因(3),参考该文(向Pandas DataFrame添加大量列的最佳实践)的建议,采用列表和pandas的concat方法修改,相比之前的方式速度提高了约5倍,只需不到10秒了,尽管还是比较慢,,但我已经很满意了。
修改后的代码如下:
dfs = []
for line in data:
if line[:6] == 'CTRIA3':
line = line.strip('\n') # 去除末尾的换行符
e_id = line[c2] # 单元编号
p_id = line[c3] # 单元属性编号
nodes = [line[c4], line[c5], line[c6]] # 单元的3个节点
dfs.append(DataFrame(columns=[e_id], data=[p_id, nodes]))
ctria3 = concat(dfs, axis=1)
ctria3.index = ['属性', '节点']