原文首发:这里
这里主要分享数据分析过程中两个很小的陷阱。
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,欢迎关注