使用dask和ThreadPoolExecutor多线程处理pandas的DataFrame数据并用tqdm显示处理进度

在pandas数据较大(千万行)时,使用groupby函数并对聚合的数据进行一系列操作需要耗费非常多的时间,但是查看cpu性能时发现cpu占用率非常低。于是萌生使用多线程加速的想法,至少让cpu满载工作。

  • 原始代码:
for user, user_hist in reviews_df.groupby(by=['user_id']):
  ...

这里用的操作比较简单,也有需要进行的操作很复杂不能用lambda操作的情况。

===========================

  • =更新,找到了更好用的方法:dask加速=
  • 使用以下代码确认是否确实使用了多线程:
"""确认是否使用到了多线程"""
import threading
def count_active_threads():
    return threading.active_count()

print("活动线程数:", count_active_threads())
return df
  • dask加速groupby计算,使用coupute方法转回pandas的DataFrame格式:
# 将pandas的DataFrame数据转为dask的dataframe
df = dd.from_pandas(df, npartitions=20)

# 写法1
df = df.map_partitions(lambda x: x.groupby('user_id', group_keys=False).apply(process_user_hist).sort_index()).compute()    # map_partitions切分的时候可能导致需要groupby的数据被切分到多个partition从而导致结果出现错误

# 写法2(推荐写法)
df = df.groupby('user_id', group_keys=False).apply(process_user_hist).compute().sort_index()

=========================

  • 使用ThreadPoolExecutor多线程加速处理:
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm

start = time.time()
executor = ThreadPoolExecutor(96)
group_list = []
def process_user_hist(x):
    user, user_reviews_df = x
    user_reviews_df = user_reviews_df.sort_values(by=['t_dat'])
    hist = user_reviews_df[['article_id', 'product_type_no']].values
    group_list.append(user, hist)
user_iter = reviews_df.groupby(by=['customer_id'])
resutl = list(executor.map(process_user_hist, tqdm(user_iter)))

这里使用ThreadPoolExecutor而非ProcessPoolExecutor,因为不同的线程需要访问同一个内存资源group_list。python存在全局解释器锁(GIL)理论上能防止不同线程同时写一个内存对象,也就是说python里面对象是线程安全的,这里我也跑了两次,验证了跑出来结果长度是正确的。(至少这里的list的append操作是安全的)

  • 另外pandas还可以通过swifter来加速apply的处理速度。例如:
import swifter
df.swifter.apply(...)

不过swifter不能对groupby对象使用,这里对我没什么帮助。

  • 另外注:多线程方式下比原始方法快很多,cpu占用也是满的。但后续在进行其他的逻辑操作时发现直接用df.groupby(['column_name']).apply(method_name)竟比多线程方式要快,不知道pandas的apply是否有多线程的优化。
posted @ 2022-05-02 23:59  风和雨滴  阅读(1357)  评论(0编辑  收藏  举报