通过XML解析,对pandas的DataFrame.append()的思考及对大文件解析加速的方法

接上一篇文章,XML结构依旧,但是这次Vv的条数非常多,10万左右,按照以下第一种python代码运行,竟然需要40分钟之久,完全不可接受,需要寻找原因,改变方法!


<File xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FileFormat.xsd">
  <FileHeader>
    <DateTime>2016-01-06T03:30:10+08:00DateTime>
  FileHeader>
  <Objects>
    <ObjectType>EutranCellTddObjectType>
    <FieldName>
      <N i="1">aN>
      <N i="2">bN>
      <N i="3">cN>
      <N i="4">dN>
      <N i="5">fN>
      <N i="6">gN>
      <N i="7">hN>
      <N i="8">iN>
      <N i="9">jN>
      <N i="10">kN>
    FieldName>
    <FieldValue>
      <Vv Label="xxx">
        <V i="1">1V>
        <V i="2">汉字V>
        <V i="3">还是汉字V>
        <V i="4">一直是汉字V>
        <V i="5">1V>
        <V i="6">怎样?V>
        <V i="7">V>
        <V i="8">V>
        <V i="9">V>
        <V i="10">{0}V>
      Vv>
  Objects>
File>
import xml.etree.cElementTree as et
import numpy as np
import pandas as pd
def xml_analysis(config_file, data_file):
    # 读取配置文件,得到输入字段名(要从xml文件中提取的字段)和输出字段名(提取后转换并最终输出的字段)
    input_field, output_field = LoadConfig(config_file)
    # 初始化三个DataFrame,其中df_input_field表示以输入字段名为列,行数为1的empty DataFrame,df_output_field表示以输出字段名为列,不定义行列数的DataFrame,tmp_df_output_field表示以输出字段名为列,行数为1的empty DataFrame
    df_input_field = pd.DataFrame(np.empty([1,len(input_field)]), columns=input_field, dtype='str')
    df_output_field = pd.DataFrame(columns=output_field, dtype='str')
    tmp_df_output_field = pd.DataFrame(np.empty([1,len(df_output_field)]), columns=df_output_field, dtype='str')
    # 创建dict_i_name的字典,i为key,name为value
    fieldName, fieldValue = XmlDataParse(data_file,'Objects','FieldName', 'FieldValue')
    names = fieldName.findall('N')
    dict_i_name = GenDict(names, input_field)
    # Get FieldValue Node
    VV = fieldValue.findall('Vv')
    for vv in VV:
        # 通过上篇文章的方法,获得一个Vv标签中,所有需要的字段值,放入df_input_field中
        df_input_field = DealingSingleValue(cm,dict_i_name,'i',df_input_field)
        # 处理单条数据,解析成输出的内容
        a = df_input_field['a'][0]
        b = df_input_field['b'][0]
        d = df_input_field['d'][0]
        tmp_df_output_field['x'] = a
        tmp_df_output_field['y'] = b
        tmp_df_output_field['z'] = d
        df_output_field.append(tmp_df_output_field)
    # 数据全部处理完,放入df_output_field中后,将该DataFrame写成csv,这里WriteCsv是自己写的函数,调用了DataFrame的to_csv,'xxx'是csv的文件名,返回文件路径+文件名
    csv_file = WriteCsv('xxx', df_output_field)
    return csv_file

按照此方法运行程序,通过在VV循环中,每1000条数据打一次时间发现,刚开始时每1000条处理时间大概在6秒,越往后越慢,到最后每1000条的处理实际大概是60秒,整个xml文件处理完用了40多分钟,不可接受!
通过观察,猜测是不停的append()导致的时间越来越长。写了一个小的测试例证实了猜想。
记得C++中的vector在不停地加元素后,会出现连续空间不够的情况,于是会分配新的更大的空间给vector,然后将之前的全部复制一遍过去,多次复制粘贴这种写的操作肯定会耗时。
猜测这里速度慢的原因是这样,需要以后找文献看看。
开始考虑直接申请一个固定的超大的空间来装,测试发现速度仍然很慢,
最终选用的解决方案是再增加一个中间容器,其长度是固定的,命名为df_output_field,而最终的输出df改名为big_df_output_field。
每次tmp_df_output_field把中间容器写满后,big_df_output_field在append中间容器,于是修改代码大致如下:

def xml_analysis(config_file, data_file):
    ISOTIMEFORMAT='%Y-%m-%d %X'
    # 定长的中间容器的行数
    MAXLEN = 1000
    input_field, output_field = LoadConfig(config_file)
    # 多初始化一个中间容器,tmp_df_output_field改为Series类型
    df_input_field = pd.DataFrame(np.empty([1,len(input_field)]), columns=input_field, dtype='str')
    big_df_output_field = pd.DataFrame(columns=output_field, dtype='str')
    tmp_df_output_field = pd.Series(np.empty([len(output_field)]), index=output_field, dtype='str')
    df_output_field = pd.DataFrame(np.empty([MAXLEN,len(output_field)]), columns=output_field, dtype='str')
FieldName, FieldValue = XmlDataParse(data_file,'Objects','FieldName', 'FieldValue')
    dict_i_name = GenDict(FieldName, input_field)
    VV = FieldValue.findall('Vv')
    cnt = 0
    append_flag = False
    for vv in VV:
        # 标识是否已经append
        append_flag = False
        # 每次先清空,防止无值时被上一值覆盖
        df_input_field[:] = None
        tmp_df_output_field[:] = None
        df_input_field = DealingSingleValue(cm,dict_i_name,'i',df_input_field)
        a = df_input_field['a']
        b = df_input_field['b']
        d = df_input_field['d']
        tmp_df_output_field['x'] = a
        tmp_df_output_field['y'] = b
        tmp_df_output_field['z'] = d
        df_output_field.ix[cnt%MAXLEN] = tmp_df_output_field
        cnt += 1
        # 提前自增1,防止cnt为0时进入判断成功部分,加入很多空行,
        # 或防止加入条件cnt>0才进入判断,使得第一行数据被覆盖
        if cnt % MAXLEN == 0:
            # 打印完成条数和当前时间
            PrintMsgAndTime('Cnt'+str(cnt),ISOTIMEFORMAT)
            big_df_output_field = big_df_output_field.append(df_output_field.copy())
            df_output_field[:][:] = None
            append_flag = True
    # 循环结束后,若最后一个数不能整除,则df_output_field的数据还未append到big_df_output_field后面,需要将之加入
    if append_flag==False:
        # 注意[0:cnt%MAXLEN-1],因为cnt在最后一个循环内又自增了一次,需要减去
        big_df_output_field = big_df_output_field.append(df_output_field.ix[0:cnt%MAXLEN-1])
    csv_file = WriteCsv('xxx',big_df_output_field, output_field)
    return csv_file

以下是测试结果和思考,
1. 这种方法减小了append的次数,确实让处理速度比较匀速,
2. MAXLEN的取值会影响速度,当MAXLEN很大,如10万的时候,每千条的处理速度比第一种方法起步时的速度慢很多,具体原因不清楚,而MAXLEN过小时,如为1,与之前的相同了,当MAXLEN=1000时,每千条的处理速度基本保持匀速,且总处理时间115秒左右
3. 按照常规考虑,函数y=f(MAXLEN),其中y是每千条处理速度,应该是一个最优化问题,能找到一个MAXLEN,使得y最大,但是哪个才是最优解没有继续尝试。

你可能感兴趣的:(python学习)