大熊猫运营期间的进度指标(python)

时间:2013-09-03 23:55:02

标签: python pandas ipython

我经常对超过1500万行的数据帧执行pandas操作,我很乐意访问特定操作的进度指示器。

是否存在基于文本的pandas split-apply-combine操作的进度指示器?

例如,在:

df_users.groupby(['userID', 'requestDate']).apply(feature_rollup)

其中feature_rollup是一个涉及功能较多的函数,它接受许多DF列并通过各种方法创建新的用户列。对于大型数据帧,这些操作可能需要一段时间,因此我想知道是否可以在iPython笔记本中使用基于文本的输出来更新我的进度。

到目前为止,我已经尝试过Python的规范循环进度指示器,但它们不会以任何有意义的方式与pandas交互。

我希望在pandas库/文档中我忽略了一些让人们知道split-apply-combine进度的东西。一个简单的实现可能会查看apply函数正在工作的数据帧子集的总数,并将进度报告为这些子集的已完成部分。

这可能需要添加到库中吗?

6 个答案:

答案 0 :(得分:153)

由于受欢迎的需求,tqdm增加了对pandas的支持。与其他答案不同,这个不会明显放慢熊猫的速度 - 这里是DataFrameGroupBy.progress_apply的一个例子:

import pandas as pd
import numpy as np
from tqdm import tqdm
# from tqdm.auto import tqdm  # for notebooks

df = pd.DataFrame(np.random.randint(0, int(1e8), (10000, 1000)))

# Create and register a new `tqdm` instance with `pandas`
# (can use tqdm_gui, optional kwargs, etc.)
tqdm.pandas()

# Now you can use `progress_apply` instead of `apply`
df.groupby(0).progress_apply(lambda x: x**2)

如果您对其工作原理(以及如何根据自己的回调进行修改)感兴趣,请参阅examples on githubfull documentation on pypi或导入模块并运行{{ 1}}。

修改

要直接回答原始问题,请替换:

help(tqdm)

使用:

df_users.groupby(['userID', 'requestDate']).apply(feature_rollup)

注意:tqdm< = v4.8 : 对于低于4.8的tqdm版本,您必须执行以下操作而不是from tqdm import tqdm tqdm.pandas() df_users.groupby(['userID', 'requestDate']).progress_apply(feature_rollup)

tqdm.pandas()

答案 1 :(得分:13)

调整Jeff的答案(并将其作为可重复使用的函数)。

def logged_apply(g, func, *args, **kwargs):
    step_percentage = 100. / len(g)
    import sys
    sys.stdout.write('apply progress:   0%')
    sys.stdout.flush()

    def logging_decorator(func):
        def wrapper(*args, **kwargs):
            progress = wrapper.count * step_percentage
            sys.stdout.write('\033[D \033[D' * 4 + format(progress, '3.0f') + '%')
            sys.stdout.flush()
            wrapper.count += 1
            return func(*args, **kwargs)
        wrapper.count = 0
        return wrapper

    logged_func = logging_decorator(func)
    res = g.apply(logged_func, *args, **kwargs)
    sys.stdout.write('\033[D \033[D' * 4 + format(100., '3.0f') + '%' + '\n')
    sys.stdout.flush()
    return res

注意:应用进度百分比updates inline。如果您的功能stdouts然后这将无效。

In [11]: g = df_users.groupby(['userID', 'requestDate'])

In [12]: f = feature_rollup

In [13]: logged_apply(g, f)
apply progress: 100%
Out[13]: 
...

像往常一样,您可以将此作为方法添加到groupby对象中:

from pandas.core.groupby import DataFrameGroupBy
DataFrameGroupBy.logged_apply = logged_apply

In [21]: g.logged_apply(f)
apply progress: 100%
Out[21]: 
...

正如评论中所提到的,这不是核心熊猫有兴趣实现的功能。但是python允许你为许多pandas对象/方法创建这些(这样做会有很多工作......虽然你应该能够概括这种方法)。

答案 2 :(得分:5)

如果你需要支持如何在Jupyter / ipython笔记本中使用它,就像我一样,这里是relevant article的有用指南和来源:

from tqdm._tqdm_notebook import tqdm_notebook
import pandas as pd
tqdm_notebook.pandas()
df = pd.DataFrame(np.random.randint(0, int(1e8), (10000, 1000)))
df.groupby(0).progress_apply(lambda x: x**2)

请注意_tqdm_notebook的import语句中的下划线。正如参考文章所述,开发处于后期阶段。

答案 3 :(得分:4)

您可以使用装饰器轻松完成此操作

from functools import wraps 

def logging_decorator(func):

    @wraps
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        print "The function I modify has been called {0} times(s).".format(
              wrapper.count)
        func(*args, **kwargs)
    wrapper.count = 0
    return wrapper

modified_function = logging_decorator(feature_rollup)

然后只使用modified_function(并在需要打印时更改)

答案 4 :(得分:1)

对于任何希望在其自定义并行熊猫应用代码上应用tqdm的人。

(多年来,我尝试了一些用于并行化的库,但是我从来没有找到100%并行化解决方案,主要是针对apply函数,我总是不得不返回自己的“手动”代码。)

df_multi_core -这是您要拨打的电话。它接受:

  1. 您的df对象
  2. 您要调用的函数名称
  3. 可以执行该功能的列子集(有助于减少时间/内存)
  4. 要并行运行的作业数(所有内核为-1或忽略)
  5. df函数接受的其他任何变形(例如“轴”)

_df_split -这是一个内部帮助器函数,必须全局定位到正在运行的模块(Pool.map是“与位置相关的”),否则我将在内部定位它。 >

这是我的gist中的代码(我将在其中添加更多的pandas函数测试):

import pandas as pd
import numpy as np
import multiprocessing
from functools import partial

def _df_split(tup_arg, **kwargs):
    split_ind, df_split, df_f_name = tup_arg
    return (split_ind, getattr(df_split, df_f_name)(**kwargs))

def df_multi_core(df, df_f_name, subset=None, njobs=-1, **kwargs):
    if njobs == -1:
        njobs = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(processes=njobs)

    try:
        splits = np.array_split(df[subset], njobs)
    except ValueError:
        splits = np.array_split(df, njobs)

    pool_data = [(split_ind, df_split, df_f_name) for split_ind, df_split in enumerate(splits)]
    results = pool.map(partial(_df_split, **kwargs), pool_data)
    pool.close()
    pool.join()
    results = sorted(results, key=lambda x:x[0])
    results = pd.concat([split[1] for split in results])
    return results

Bellow是带有tqtm“ progress_apply”的并行化 apply 的测试代码。

from time import time
from tqdm import tqdm
tqdm.pandas()

if __name__ == '__main__': 
    sep = '-' * 50

    # tqdm progress_apply test      
    def apply_f(row):
        return row['c1'] + 0.1
    N = 1000000
    np.random.seed(0)
    df = pd.DataFrame({'c1': np.arange(N), 'c2': np.arange(N)})

    print('testing pandas apply on {}\n{}'.format(df.shape, sep))
    t1 = time()
    res = df.progress_apply(apply_f, axis=1)
    t2 = time()
    print('result random sample\n{}'.format(res.sample(n=3, random_state=0)))
    print('time for native implementation {}\n{}'.format(round(t2 - t1, 2), sep))

    t3 = time()
    # res = df_multi_core(df=df, df_f_name='apply', subset=['c1'], njobs=-1, func=apply_f, axis=1)
    res = df_multi_core(df=df, df_f_name='progress_apply', subset=['c1'], njobs=-1, func=apply_f, axis=1)
    t4 = time()
    print('result random sample\n{}'.format(res.sample(n=3, random_state=0)))
    print('time for multi core implementation {}\n{}'.format(round(t4 - t3, 2), sep))

在输出中,您可以看到1个进度条,用于在不进行并行化的情况下运行,以及每核进度条(在进行并行化时)。 会有一些小小的变化,有时其他核心会同时出现,但是即使如此,我仍然认为它很有用,因为您可以获得每个核心的进度统计信息(例如,它的每秒/秒和总记录)

enter image description here

感谢@abcdaa这个伟大的图书馆!

答案 5 :(得分:0)

我已经更改Jeff's answer,包含总计,以便您可以跟踪进度和变量以仅打印每X次迭代(如果“print_at”合理,这实际上会大大提高性能高)

def count_wrapper(func,total, print_at):

    def wrapper(*args):
        wrapper.count += 1
        if wrapper.count % wrapper.print_at == 0:
            clear_output()
            sys.stdout.write( "%d / %d"%(calc_time.count,calc_time.total) )
            sys.stdout.flush()
        return func(*args)
    wrapper.count = 0
    wrapper.total = total
    wrapper.print_at = print_at

    return wrapper

clear_output()函数来自

from IPython.core.display import clear_output

如果没有在IPython上,Andy Hayden的回答就是没有它