パンダの操作中の進行状況インジケーター


158

1,500万行を超えるデータフレームでパンダ操作を定期的に実行しており、特定の操作の進行状況インジケーターにアクセスしたいです。

パンダのsplit-apply-combine操作のテキストベースの進行状況インジケーターはありますか?

たとえば、次のようなものです。

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

どこfeature_rollup多くのDF列を取るやや関与関数であり、様々な方法で新しいユーザー列を作成します。大きなデータフレームの場合、これらの操作にはしばらく時間がかかる可能性があるため、進行状況を更新するiPythonノートブックでテキストベースの出力が可能かどうかを知りたいです。

これまでのところ、Pythonの標準ループ進捗インジケーターを試しましたが、意味のある方法でパンダと相互作用しません。

私がパンダのライブラリ/ドキュメントで見落としてきた、split-apply-combineの進行状況を知ることができるものがあることを願っています。単純な実装では、apply関数が動作しているデータフレームサブセットの総数を調べて、それらのサブセットの完了した部分として進行状況を報告します。

これはおそらくライブラリに追加する必要があるものですか?


コードで%prun(プロファイル)を実行しましたか?ボトルネックを排除するために適用する前に、フレーム全体を操作できる場合があります
Jeff

@ジェフ:確かに、私は以前にそれを行って、パフォーマンスの最後のすべてのビットを絞りました。行が数千万にあるので、問題は本当に私が取り組んでいる疑似map-reduce境界に帰着します。したがって、超高速の増加は、進行状況に関するフィードバックが必要なだけだとは思いません。
cwharland 2013

cythonising考えてみましょう:pandas.pydata.org/pandas-docs/dev/...
アンディ・ヘイデン

@AndyHayden-私があなたの答えにコメントしたように、あなたの実装は非常に良く、全体の仕事に少しの時間を追加します。また、機能のロールアップ内で3つの操作をcythonizedしました。したがって、最終的には、関数全体でcythonを実行すると、合計処理時間を短縮するプログレスバーが表示されます。
cwharland 2013

回答:


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/...
モルク

1
ありがとう、@ morkは正しいです。tqdm物事をよりモジュール化するv5に向けて(ゆっくりと)取り組んでいます。
casper.dcl 2017

1
最近の推奨構文については、tqdm Pandasのドキュメントをこちらでご覧ください:pypi.python.org/pypi/tqdm#pandas-integration
Manu CJ

18

ジェフの答えを微調整する(そしてこれを再利用可能な関数として持つ)。

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を使用すると、多くのパンダオブジェクト/メソッドに対してこれらを作成できます(これを行うと、かなりの作業になります...このアプローチを一般化できるはずです)。


私は「やや手間がかかる」と言いますが、おそらくこの関数全体を(より一般的な)デコレータとして書き直すことができます。
アンディヘイデン

ジェフの投稿を拡張してくれてありがとう。私は両方を実装しましたが、それぞれの速度低下はごくわずかです(完了までに27分かかった操作に合計1.1分が追加されました)。このように私は進捗状況を見ることができ、これらの操作のアドホックな性質を考えると、これは許容できるスローダウンだと思います。
cwharland 2013

すばらしい、助かった。私は実際にスローダウンに驚いた(例を試してみたとき)、それははるかに悪いと予想していました。
アンディヘイデン

1
投稿されたメソッドの効率をさらに上げるために、私はデータのインポートについて怠惰でした(パンダは乱雑なcsvの処理が上手すぎます!!)いくつかのエントリ(〜1%)は挿入を完全に打ち消しました(全体を考える)単一のフィールドに挿入されたレコード)。これらを排除すると、機能のロールアップが大幅にスピードアップします。これは、split-apply-combine操作中に何をするかについてのあいまいさがなかったためです。
cwharland 2013

1
私は8分になりました...しかし、機能ロールアップに何かを追加しました(より多くの機能->より優れたAUC!)。この8分はチャンクごと(現在は合計2つのチャンク)であり、各チャンクは約1200万行にあります。ええと... HDFStoreを使用して2,400万行を処理するのに16分かかります(機能のロールアップにはnltkのものがあります)。結構いい。
乱雑な

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。参考記事にあるように、開発はベータ段階の後半です。


8

カスタムの並列pandas-applyコードにtqdmを適用したいと考えている人のために。

(私は何年にもわたっていくつかのライブラリーを並列化のために試しましたが、主に適用機能のための100%並列化ソリューションを見つけることができなかったため、常に「手動」のコードに戻らなければなりませんでした。)

df_multi_core-これはあなたが呼び出すものです。それは受け入れます:

  1. あなたのdfオブジェクト
  2. 呼び出したい関数名
  3. 関数を実行できる列のサブセット(時間/メモリの削減に役立ちます)
  4. 並行して実行するジョブの数(-1またはすべてのコアで省略)
  5. dfの関数が受け入れる他のクワーグ(「軸」など)

_df_split-これは、実行中のモジュールにグローバルに配置する必要がある内部ヘルパー関数です(Pool.mapは「配置依存」)。それ以外の場合は、内部的に配置します。

これが私の要点のコードです(ここにパンダ関数テストを追加します):

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つの進行状況バーと、並列化ありで実行する場合のコアごとの進行状況バーが表示されます。わずかな問題があり、残りのコアが同時に表示されることもありますが、それでもコアあたりの進行状況の統計(it / secと合計レコードなど)を取得できるので便利だと思います

ここに画像の説明を入力してください

この素晴らしいライブラリをありがとう@abcdaa!


1
@morkに感謝-github.com/tqdm/tqdm/wiki/How-to-make-a-great-Progress-Barに追加するか、github.com / tqdm / tqdm / wiki
。 dcl

ありがとうございます。ただし、これらの部分を変更する必要がtry: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)ありました。ValueErrorではなくKeyError例外のため、すべてのケースを処理するためにExceptionに変更してください。
マリウス

@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の回答を変更しました。これにより、進捗状況と変数を追跡して、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の答えはそれなしでそれを行います

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.