今天,跑数据,很无聊
遇到问题:
- .gz压缩文件解压不了,太抽象
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gzip
def un_gz(file_name):
# 获取文件的名称,去掉后缀名
f_name = file_name.replace(".gz", "")
# 开始解压
g_file = gzip.GzipFile(file_name)
#读取解压后的文件,并写入去掉后缀名的同名文件(即得到解压后的文件)
open(f_name, "wb+").write(g_file.read())
g_file.close()
un_gz('D:\\python36\\config.gz')
报错:
大概意思就是,文件格式不对,解压的魔数对不上;太抽象了
网上检索了一堆信息,说是压缩文件坏掉了,可是我又可以手动解压,真的抽象
尝试办法:
-
升级python版本,从3.7 -> 3.9;因为我看了gzip api文档,不同版本的api有所不同;但是没用
-
更换api,(1)直接用python调用os ungzip命令(2)直接pd.read_csv(‘data.csv.gz’, compression=‘gzip’)等等;但是没用,还是报错,属实抽象;如何使用Python解压gz文件
最后,用了最原始的方法:
select all,extract files;简单粗暴,没有办法的办法
- 数据量太大,怎么优化跑得快一点
问题描述,从一张表里面读取user_id,根据id去查另外12种类型的数据(每种数据有多张表,共1000+张表),获取每个用户的相应数据写到一个文件夹里
这里核心来说,应该用了几个手段来处理
- 分片处理
- 多线程
- 循环优化
首先,读取user_id,从一张csv里面抽取了100万+的user_id;这步的话直接串行处理了,因为是一次性的前置操作;大概花了几分钟;如果要优化的话,可以多线程分块读取csv处理,最后再合并数组也行;
第二步是关键,遍历,是先遍历数据表,还是先遍历user_id
for user in user_list:
for table in table_list:
时间复杂度:1000的1000000次方,可以获取部分用户的完整数据;一张表近5M,频繁读取会很耗时,所以应该优先考虑,每张表只用读取一次就完成相应的操作
for table in table_list:
for user in user_list
时间复杂度:1000000的1000次方,每张表只需要读入内存一次;
最终,我结合了这两个方法
(1)先对user_list进行分片
def list_of_groups(init_list, childern_list_len):
'''
init_list为初始化的列表,childern_list_len初始化列表中的几个数据组成一个小列表
:param init_list:
:param childern_list_len:
:return:
'''
list_of_group = zip(*(iter(init_list),) * childern_list_len)
end_list = [list(i) for i in list_of_group]
count = len(init_list) % childern_list_len
end_list.append(init_list[-count:]) if count != 0 else end_list
return end_list
for table in table_list:
group_lists = list_of_groups(user_list, SIZE)
for index, children_user_list in enumerate(group_lists):
for user_id in children_user_list:
## extract data
这样实现的好处是,我们在保证每张表只读取一次的同时,也能分批次获取部分用户的完整数据;这部分数据可以先用起来;
上面的实现嵌套了三层循环,那么我们可以进一步考虑用多线程来解决问题,因为任务是cpu密集型,所以线程就开CPU core个就好啦;Windows下查看电脑的CPU个数,核心数,线程数
也可以在跑的时候看看cpu和内存的负载,两者都能拉到50%以上吧应该,充分利用起来(但是cpu负载过高会不会是在频繁切换线程?),线程数设置真是一门学问啊,还有超线程技术呢(超线程就是一个物理核模拟出多个逻辑核)
我们还是优先考虑获取小批次用户的完整数据
pool = ThreadPoolExecutor(max_workers=CPU_CORE)
for table in table_list:
group_lists = list_of_groups(user_list, SIZE)
for index, children_user_list in enumerate(group_lists):
pool.submit(self.action, user_id) # pool.submit是有返回值的
其它
python创建二维数组
_list = [[] for _ in range(length)]
pandas操作多列
df.apply(lambda row: func(row['key1'], row['key2'], other_params), axis=1)
pandas函数式筛选特定的行数据
df合并,见Pandas - Merge 函数
merge()每次只能合并两张表,但是粒度更细,concat()可以一次性合并多张表,但是粒度比较粗
我的需求:根据指定的key合并多张表,所以我用的是merge()和list作为一个队列进行操作
while len(tables) > 1:
tables.append(pd.merge(tables.pop(), tables.pop(), on=['key1', 'key2'], how='outer'))
df = tables.pop()
df = pd.DataFrame(columns=columns_name)
res = pd.concat([df, *df_list]) # df_list包含多个df, *df_list解构
合并多张表如果有列名相同,rename就好啦
df修改列名
df = df.rename(columns=lambda x: rename_columns(x, column_suffix))
# 或者直接指定
df = df.rename(columns={'a':'A'})
def rename_columns(x, column_suffix):
print(x)
if x == 'key1':
return x
if x == 'key2':
return x
return x + '(' + column_suffix + ')'
df删除空值、冗余
df = pd.read_csv(os.path.join(root, file), low_memory=False).dropna(axis=1, how='all').drop_duplicates()
drop_duplicates()默认保留第一项
dropna(axis=1, how=‘all’) 列全空删除该列
玄学问题
file_name = poor_quality_event[0] + '_' + poor_quality_event[3] + '_' + self.table_prefix_list[
index] + '.csv'
output_path = os.path.join(self.output_path, poor_quality_event[0])
if self.mkdir(output_path):
res.to_csv(os.path.join(output_path, file_name), index=False, columns=columns_name)
今天在写csv的时候遇到了一个问题,现象如下:
(1)我先mkdir再to_csv,理论上写文件的时候,文件夹肯定是存在,但是在写文件的时候报错文件不存在;而且这个现在仅针对某一个名字比较长的文件夹[Errno 2] No such file or directory: 这个报错很坑,它没有告诉你是因为创建失败才没有的,你可以试着创建一个名字很长的csv,它就是报这个错,但是它不会跟你说是因为名字过长创建失败。
(2)csv在pycharm可以打开,但是用Microsoft打开报错;
经过我苦心孤诣,终于搞明白了,是命名过长导致的;
res.to_csv(os.path.join(output_path, file_name), index=False, columns=columns_name)
这一步创建了一个名字过长的csv失败了,导致写入的时候报错不存在
对于(2),应该是pycharm和Microsoft打开csv的方式有所区别,所以Microsoft打开报错
综上,是由于我文件夹名字本来就比较长,然后我还基于文件夹生成文件名,就更长,导致出现异常
总结就是,对于Microsoft的一些文件,不要命名过长。
而且我在pycharm重命名文件夹抛出java.io异常,说明pycharm可能是基于java实现的hhh
pandas
取特定行
df.iloc[index]
Pandas DataFrame切片
pandas读取行和列
pandas遍历
for idx, item in df.iterrows():
要注意这里的idx对应的是df里面的idx不一定是从0开始的
总结
非专业数据处理,对数据不够敏感,很多时候都在踩坑,优化,一点点进步
- 不同时间粒度的同类型表,合并数据表时根据需求取某一时间粒度的就好
- 合并数据表的关键在于对齐主键,这个需要熟悉业务,或者至少清楚表与表之间键的关系;
- 这种数据表通常包含标识+数值,找主键就是从标识里面去筛
- 这个数据表的工作是很繁琐的,但是在业务中又很频繁,通常会遇到那些大宽表,取数,合表hhh
追加,写代码的时候遇到的一个小bug