熊猫行动中的进度指示器


157

我定期对超过1500万行的数据帧执行熊猫操作,我很乐意能够访问特定操作的进度指示器。

是否存在基于文本的熊猫拆分应用合并操作进度指示器?

例如,类似:

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

其中feature_rollup包含一些DF列并通过各种方法创建新用户列的函数。对于大型数据帧,这些操作可能需要一段时间,因此我想知道是否有可能在iPython笔记本中提供基于文本的输出,从而使我了解进度。

到目前为止,我已经尝试了Python的规范循环进度指示器,但是它们并未以任何有意义的方式与熊猫互动。

我希望pandas库/文档中有一些被我忽略的东西,它使人们知道了split-apply-combine的进度。一个简单的实现方法可能是查看apply功能在其上起作用的数据帧子集的总数,并将进度报告为这些子集的完成部分。

这是否可能需要添加到库中?


您是否对代码执行了%prun(配置文件)?有时您可以在应用整个帧之前进行操作,然后再消除瓶颈
Jeff

@Jeff:您敢打赌,我之前做过这件事,是为了从中挤出最后一点性能。这个问题实际上归结为我正在处理的伪map-reduce边界,因为行数千万,所以我不希望超速增加只是希望对进度有一些反馈。
cwharland 2013年


@AndyHayden-正如我评论您的答案一样,您的实施效果很好,并为整个工作增加了少量时间。我还囊括了功能汇总中的三个操作,这些操作重新恢复了现在一直专注于报告进度的所有时间。因此,最后我敢打赌,如果我在整个功能上都遵循cython的话,将会减少总处理时间。
cwharland

Answers:


277

由于需求旺盛,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)

如果您对它的工作方式(以及如何为自己的回调进行修改)感兴趣,请参阅github上示例pypi 完整文档或导入模块并运行help(tqdm)

编辑


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

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

与:

from tqdm import tqdm
tqdm.pandas()
df_users.groupby(['userID', 'requestDate']).progress_apply(feature_rollup)

注意:tqdm <= v4.8:对于低于4.8的tqdm版本,tqdm.pandas()您不必执行以下操作:

from tqdm import tqdm, tqdm_pandas
tqdm_pandas(tqdm())

5
tqdm最初实际上是为纯可迭代对象创建的:from tqdm import tqdm; for i in tqdm( range(int(1e8)) ): pass对熊猫的支持是我最近所做的一
件事

6
顺便说一句,如果您使用Jupyter笔记本,则还可以使用tqdm_notebooks获得更漂亮的条形。您现在需要与大熊猫一起实例化它,如此from tqdm import tqdm_notebook; tqdm_notebook().pandas(*args, **kwargs) 处所示
grinsbaeckchen

2
从4.8.1版开始-使用tqdm.pandas()代替。 github.com/tqdm/tqdm/commit/…–
mork

1
谢谢,@ mork是正确的。我们正在(缓慢地)朝tqdmv5 迈进,这使事情更加模块化。
casper.dcl

1
有关最新语法的建议,请参见此处的tqdm Pandas文档:pypi.python.org/pypi/tqdm#pandas-integration
Manu CJ

18

调整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

注意:应用进度百分比会内联更新。如果您的函数标准输出,则将无法正常工作。

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允许您为许多熊猫对象/方法创建这些(这样做将需要很多工作...尽管您应该能够概括这种方法)。


我说“很多工作”,但是您可能可以将整个功能重写为(更一般的)装饰器。
安迪·海登

感谢您扩展Jeff的帖子。我已经实现了这两者,并且每个实现的速度降低都非常小(向耗时27分钟的操作总共添加了1.1分钟)。这样,我可以查看进度,并且鉴于这些操作的特殊性,我认为这是可以接受的速度降低。
cwharland

太好了,很高兴它有所帮助。实际上,我对速度的下降感到惊讶(尝试示例时),我希望它会变得更糟。
安迪·海登

1
为了进一步提高发布方法的效率,我对数据导入比较懒惰(熊猫在处理杂乱的csv方面太擅长了!!),而我的一些条目(〜1%)已经完全破坏了插入(整体思考)记录插入单个字段)。消除这些因素会大大加快功能汇总的速度,因为在拆分应用合并操作期间没有做什么工作的模棱两可。
cwharland

1
我只有8分钟的时间...但是我在功能汇总中添加了一些功能(更多功能->更好的AUC!)。这8分钟是每个块(现在总共两个块),每个块在1200万行附近。所以,是的。。。16分钟使用HDFStore对2400万行进行大量操作(功能汇总中包含nltk内容)。很好。让我们希望互联网不会在最初的无知或对混乱的插入的模棱两可上判断我=)
cwharland

11

如果您需要了解如何在Jupyter / IPython的笔记本使用此支持,像我一样,这里是一个有益的指导和源相关文章

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)

请注意的import语句中的下划线_tqdm_notebook。正如所引用的文章所提到的,开发处于beta后期。


8

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

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

df_multi_core-这是您要呼叫的那个。它接受:

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

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

这是我的要旨中的代码(我将在其中添加更多的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

波纹管是与tqdm“ progress_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个进度条,用于在没有并行化的情况下运行,以及每核进度条,在具有并行化的情况下运行。会有一些小小的变化,有时其他核心会同时出现,但是即使如此,我仍然认为这很有用,因为您可以获得每个核心的进度统计信息(例如,每秒/秒和总记录)

在此处输入图片说明

谢谢@abcdaa提供的这个出色的库!



谢谢,但是不得不更改这些部分:try: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)由于KeyError异常而不是ValueError,请更改为Exception以处理所有情况。
马吕斯(Marius)

谢谢@mork-这个答案应该更高。
安迪

5

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

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(并在您希望打印时更改)


1
明显的警告是这会减慢您的功能!您甚至可以使用进度stackoverflow.com/questions/5426546/…更新它,例如count / len作为百分比。
安迪·海登

是的-您将拥有订单(组数),所以这取决于您的瓶颈,这可能会有所不同
Jeff

也许直观的事情是将其包装在一个logged_apply(g, func)函数中,您可以在其中访问订单,并且可以从一开始就进行记录。
安迪·海登

我在回答中做了上述操作,还更新了百分比。实际上,我无法让您的工作正常……我想一点点。如果您将其用于申请,那么它并不是那么重要。
安迪·海登

1

我更改了Jeff的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的答案就是没有它

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.