我经常对超过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
函数正在工作的数据帧子集的总数,并将进度报告为这些子集的已完成部分。
这可能需要添加到库中吗?
答案 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 github,full 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 -这是您要拨打的电话。它接受:
_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个进度条,用于在不进行并行化的情况下运行,以及每核进度条(在进行并行化时)。 会有一些小小的变化,有时其他核心会同时出现,但是即使如此,我仍然认为它很有用,因为您可以获得每个核心的进度统计信息(例如,它的每秒/秒和总记录)
感谢@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的回答就是没有它