数据挖掘:分享两个Pandas使用小陷阱

原文首发:这里


这里主要分享数据分析过程中两个很小的陷阱。

concat比较耗时

背景是有上万个csv文件,想把他们整合到一个文件里,推荐整合后的格式保存为大数据里的.parquet,可以节省很多空间。

整合多文件时,不要着急用concat,而是先list到一起,最后统一concat。原因是concat的时候,需要考虑每个dataframe的index对齐,所以速度很慢。

我一开始的慢速写法:

def concat_high_freq(base_dir, output_name='test_combined'):
    # 无法容忍的龟速!!!
    res = pd.DataFrame()

    for file in tqdm(os.listdir(base_dir)):
        if file.endswith('.csv'):
            file_data = pd.read_csv(os.path.join(base_dir, file))
            file_data['id'] = file.split('_')[-1].replace('.csv', "")
            #file_data['spcTime'] = file.split('_')[-2]
            res = pd.concat([res, file_data], axis=0)

上万个文件concat,一下午都没跑完。主要就是因为每一次迭代都调用concat特别耗时。所以,尽量避免循环文件时每一步都concat,就快了几百倍,改进后的写法如下:

def concat_high_freq(base_dir, output_name='test_combined'):
    '''
    将高频数据的多个文件合并到一起,综合考虑到读取速度与储存大小,保存为parquet格式
    '''
    dfs = []
    for file in tqdm(os.listdir(base_dir)):
        if file.endswith('.csv'):
            file_data = pd.read_csv(os.path.join(base_dir, file))
            file_data['id'] = int(file.split('_')[-1].replace('.csv', ""))
            #file_data['spcTime'] = file.split('_')[-2]
            dfs.append(file_data)
            # res = pd.concat([res, file_data], axis=0)
    dfs.sort(key=lambda x: x.id[0])
    res = pd.concat(dfs)

多循环注意别特征列重复

第二个的背景是生成特征时的小技巧。机器学习任务需要反复实验,尤其是尝试新特征时,因此每次灵活选择特征是很重要的。我一般在特征开始时生成一个新list,然后每次尝试新增一组特征时,都把新加的特征列名扔进这个深拷贝的list里。
不过,今天却小翻车了一下,feature_cols就是我用来保存特征名字的列表,一开始的代码长这样:

def get_lag_feature(data, lag_length, lag_features, feature_cols=[]):
    qids = sorted(np.unique(data['QUEUE_ID']))
    data_out = pd.DataFrame()
    for qid in tqdm(qids):
        data_qid = data.loc[data['QUEUE_ID'] == qid].copy(deep=True)

        for i in range(lag_length):
            for col in lag_features:
                feature_col = '{}_lag{}'.format(col, i+1)
                data_qid[feature_col] = data_qid[col].shift(-i)
                feature_cols += [feature_col]
        data_qid.drop(columns=lag_features, inplace=True)
        data_qid = data_qid.iloc[:-10]  # 去掉最后10条NA

        data_out = data_out.append(data_qid)
    data_out = data_out.reset_index(drop=True)
    print(data_out.shape)
    return data_out

这一段生成的数据保存,竟然足足有4G之多,内存直接爆炸后面都没法跑了。然后,找到问题改进以后,就只有130M了,改进后就是下面这段代码。

def get_lag_feature2(data, lag_length, lag_features, feature_cols=[]):
    temp = pd.DataFrame()
    qids = sorted(data['QUEUE_ID'].unique())

    for qid in tqdm(qids):  # 按QUEUE_ID进行处理
        queue = data[data['QUEUE_ID'] == qid].copy(deep=True)
        # 生成时间窗数据
        for i in range(lag_length):
            for sf in lag_features:
                new_f = '%s_%d' % (sf, i + 1)
                queue[new_f] = queue[sf].shift(-i)
        # 删除原来的列
        queue.drop(columns=lag_features, inplace=True)
        # 对于每个QUEUE_ID,丢弃最后10条有NAN值的数据
        queue = queue.head(queue.shape[0] - 10)
        temp = temp.append(queue)

    # 重设索引
    data = temp.reset_index(drop=True)
    return data

主要问题出在哪里呢?就是最后返回的时候,我们可以输出一下最后的维度

print(data_out[feature_cols].shape)

为了找到问题,看一下之前保存的数据维度是(495024, 1052), 之后保存的数据的维度是(495024, 40)。 所以数据巨大的原因其实出在了feature_cols上。由于这里有三层循环,最外层的循环是针对每一个ID的,由于有几百个ID,导致需要的列重复了几百遍,占有的空间也就增大了几百倍。想要解决这个问题,或者调整循环顺序,或者把feature_cols换一种数据结构:集合set。

大概这就是copy-paste代码,自己稍微一改就容易出错的原因吧。
我是YueTan,欢迎关注

上一篇:.ini文件的python处理


下一篇:office 2013 快速换KEY