翼度科技»论坛 编程开发 python 查看内容

pandas高效读取大文件的探索之路

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
使用 pandas 进行数据分析时,第一步就是读取文件。
在平时学习和练习的过程中,用到的数据量不会太大,所以读取文件的步骤往往会被我们忽视。
然而,在实际场景中,面对十万,百万级别的数据量是家常便饭,即使千万,上亿级别的数据,单机处理也问题不大。
不过,当数据量和数据属性多了之后,读取文件的性能瓶颈就开始浮现出来。
当我们第一次拿到数据时,经常会反反复复的读取文件,尝试各种分析数据的方法。
如果每次读取文件都要等一段时间,不仅会影响工作效率,还影响心情。
下面记录了我自己优化pandas读取大文件效率的探索过程。
1. 准备部分

首先,准备数据。
下面的测试用的数据是一些虚拟币的交易数据,除了常用的K线数据之外,还包含很多分析因子的值。
  1. import pandas as pd
  2. fp = "all_coin_factor_data_12H.csv"
  3. df = pd.read_csv(fp, encoding="gbk")
  4. df.shape
  5. # 运行结果
  6. (398070, 224)
复制代码
总数据量接近40万,每条数据有224个属性。
然后,封装一个简单的装饰器来计时函数运行时间。
  1. from time import time
  2. def timeit(func):
  3.     def func_wrapper(*args, **kwargs):
  4.         start = time()
  5.         ret = func(*args, **kwargs)
  6.         end = time()
  7.         spend = end - start
  8.         print("{} cost time: {:.3f} s".format(func.__name__, spend))
  9.         return ret
  10.     return func_wrapper
复制代码
2. 正常读取

先看看读取这样规模的数据,需要多少时间。
下面的示例中,循环读取10次上面准备的数据all_coin_factor_data_12H.csv。
  1. import pandas as pd
  2. @timeit
  3. def read(fp):
  4.     df = pd.read_csv(
  5.         fp,
  6.         encoding="gbk",
  7.         parse_dates=["time"],
  8.     )
  9.     return df
  10. if __name__ == "__main__":
  11.     fp = "./all_coin_factor_data_12H.csv"
  12.     for i in range(10):
  13.         read(fp)
复制代码
运行结果如下:

读取一次大概27秒左右。
3. 压缩读取

读取的文件all_coin_factor_data_12H.csv大概1.5GB左右,
pandas是可以直接读取压缩文件的,尝试压缩之后读取性能是否能够提高。
压缩之后,大约 615MB 左右,压缩前大小的一半不到点。
  1. import pandas as pd
  2. @timeit
  3. def read_zip(fp):
  4.     df = pd.read_csv(
  5.         fp,
  6.         encoding="gbk",
  7.         parse_dates=["time"],
  8.         compression="zip",
  9.     )
  10.     return df
  11. if __name__ == "__main__":
  12.     fp = "./all_coin_factor_data_12H.zip"
  13.     for i in range(10):
  14.         read_zip(fp)
复制代码
运行结果如下:

读取一次大概34秒左右,还不如直接读取来得快。
4. 分批读取

接下来试试分批读取能不能提高速度,分批读取的方式是针对数据量特别大的情况,
单机处理过亿数据量的时候,经常会用到这个方法,防止内存溢出。
先试试每次读取1万条
  1. import pandas as pd
  2. @timeit
  3. def read_chunk(fp, chunksize=1000):
  4.     df = pd.DataFrame()
  5.     reader = pd.read_csv(
  6.         fp,
  7.         encoding="gbk",
  8.         parse_dates=["time"],
  9.         chunksize=chunksize,
  10.     )
  11.     for chunk in reader:
  12.         df = pd.concat([df, chunk])
  13.     df = df.reset_index()
  14.     return df
  15. if __name__ == "__main__":
  16.     fp = "./all_coin_factor_data_12H.csv"
  17.     for i in range(10):
  18.         read_chunk(fp, 10000)
复制代码
运行结果如下:

和读取压缩文件的性能差不多。
如果调整成每次读取10万条,性能会有一些微提高。

分批读取时,一次读取的越多(只要内存够用),速度越快。
其实我也试了一次读取1千条的性能,非常慢,这里就不截图了。
5. 使用polars读取

前面尝试的方法,效果都不太好,下面引入一个和pandas兼容的库Polars。
Polars是一个高性能的DataFrame库,它主要用于操作结构化数据。
它是用Rust写的,主打就是高性能
使用Polars读取文件之后返回的Dataframe虽然和pandas的DataFrame不完全一样,
当可以通过一个简单的to_pandas方法来完成转换。
下面看看使用Polars读取文件的性能:
  1. import polars as pl
  2. @timeit
  3. def read_pl(fp):
  4.     df = pl.read_csv(
  5.         fp,
  6.         encoding="gbk",
  7.         try_parse_dates=True,
  8.     )
  9.     return df.to_pandas()
  10. if __name__ == "__main__":
  11.     fp = "./all_coin_factor_data_12H.csv"
  12.     for i in range(10):
  13.         read_pl(fp)
复制代码
运行结果如下:

使用Polars后性能提高非常明显,看来,混合使用Polars和pandas是一个不错的方案。
6. 序列化后读取

最后这个方法,其实不是直接读取原始数据,而是将原始数据转换为python自己的序列化格式(pickle)之后,再去读取。
这个方法多了一个转换的步骤:
  1. fp = "./all_coin_factor_data_12H.csv"
  2. df = read(fp)
  3. df.to_pickle("./all_coin_factor_data_12H.pkl")
复制代码
生成一个 序列化文件:all_coin_factor_data_12H.pkl。
然后,测试下读取这个序列化文件的性能。
  1. @timeit
  2. def read_pkl(fp):
  3.     df = pd.read_pickle(fp)
  4.     return df
  5. if __name__ == "__main__":
  6.     fp = "./all_coin_factor_data_12H.pkl"
  7.     for i in range(10):
  8.         read_pkl(fp)
复制代码
运行结果如下:

这个性能出乎意料之外的好,而且csv文件序列化成pkl文件之后,占用磁盘的大小也只有原来的一半。
csv文件1.5GB左右,pkl文件只有690MB。
这个方案虽然性能惊人,但也有一些局限,
首先是原始文件不能是那种实时变化的数据,因为原始csv文件转换为pkl文件也是要花时间的(上面的测试没有算这个时间)。
其次,序列化之后的pkl文件python专用的,不像csv文件那样通用,不利于其他非python的系统使用。
7. 总结

本文探讨了一些pandas读取大文件的优化方案,最后比较好的就是Polars方案pickle序列化方案。
如果我们的项目是分析固定的数据,比如历史的交易数据,历史天气数据,历史销售数据等等,
那么,就可以考虑pickle序列化方案,先花时间讲原始数据序列化,
后续的分析中不担心读取文件浪费时间,可以更高效的尝试各种分析思路。
除此之外的情况,建议使用Polars方案
最后补充一点,如果读取文件的性能对你影响不大,那就用原来的方式,千万不要画蛇添足的去优化,
把精力花在数据分析的业务上。

来源:https://www.cnblogs.com/wang_yb/p/17993809
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具