皆さんもpythonでプログレスバーを表示するために使っているであろうtqdm(tqdm_notebook)ですが、実は近い将来from tqdm import tqdm_notebook
でのインポートが廃止されるのはご存知でしょうか!?
この記事では令和*1に於けるJupyter上でのモダンなtqdmのimport方法と、知っておくと便利なtqdmの使い方を紹介します!
TL;DR
jupyter上でのモダンなtqdmのimport方法だけを見たい方はここから
tqdmとは
tqdmはプログレスバーを表示するためのpythonパッケージです。 重めの処理をforで回しているときなど、いつ終わるか分からない繰り返し計算の進捗状況を可視化することで計算時間に検討をつけることができます。 https://github.com/tqdm/tqdm:embed:cite:w500
意外と発音の仕方が知られていないように思われますが、tqdmは「タカドゥム」のように発音します。 (下のyoutubeの動画参照) アラビア語では母音を省略するようなので、それがどう発音すればいのか解らなくしているのかもしれません。 www.youtube.com
tqdmの使い方
以下では自分がよく使うtqdmの使い方をまとめています。
尚、インストールは通常通りpip install tqdm
かconda intall tqdm
でインストールできます。
この記事では2019/12/03時点でcondaでインストール出来る最新の4.39.0を使用します。
基本的な使い方
先づは一番普通のtqdmの使い方を見てみましょう。
但し、ここでは実行の簡便性からjupyter notebook上で実行しています。
# tqdmをインポート from tqdm import tqdm for i in tqdm(range(10)): pass # 何もしない
結果は以下のようになります。
赤い背景に進捗を表すプログレスバーが出ているのが確認できます。
Jupyter notebookで使う
通常のtqdmをJupyterで使うと起きる問題
続いてJupyter notebook上でのtqdmの使い方ですが、
上述したtqdmの使い方をJupyter上でしようとすると、
ちょっとした問題が発生します。確認してみましょう。
以下のコードを実行中にJupyterの停止ボタンを使って停止します。
for i in std_tqdm(range(10000000)): pass
そして再度実行すると……、以下のように表示がおかしくなってしまいます!
Jupyter上でのtqdmの(モダンな)インポート方法
この様な表示のバグを防ぐために、Jupyter上でtqdmを使うときはJupyter用のtqdmを使用しましょう。
以下の様にインポートすれば後は通常のtqdmと同じ様に使うだけです。
from tqdm.notebook import tqdm
tqdm.notebook.tqdm
を使うと前述の表示バグを回避できるだけでなく、以下のように美しい表示を得ることができます
現在googleでtqdmの使い方を検索するとよく出てくるのは
from tqdm import tqdm_notebook as tqdm
というインポート方法ですが、これはtqdm 5.0.0で削除されます。お気をつけください。
参考:https://github.com/tqdm/tqdm/blob/master/tqdm/init.py#L25
スポンサーリンク
generatorと組み合わせる
tqdmをgeneretor、例えばpandas.Daraframe
をgroupby
したもの、と組み合わせることがよくあると思います。
しかしgeneratorはリストと違って長さがわからないので、残り時間と総イテレーション数がわからないことが問題になります。(下図参照)
これはtotal
引数に総イテレーション回数を渡すことで解決できます。
下図のように、total引数をtqdmに渡すことで総イテレーション回数と予測総実行時間が表示されるようになります。
pandasのapply, map, aggregate, transformと組み合わせる
重めの処理をpandas.DataFrameにapplyやmapしているときも進捗を可視化したい時でしょう。
この様なときはprogress_apply
やprogress_map
を使うことでプログレスバーを出すことが出来ます。
この機能を使用する際はtqdmをインポートした後に、
tqdm.pandas()
を実行する必要があります。
試してみましょう。
以下のデータフレームと関数を例として用います。
# tqdmをインポートしたら一回だけ実行すればよい tqdm.pandas() # サンプルで使用するデータフレーム df = pd.DataFrame({ 'a' : np.arange(10000000) }) # 変数を10倍して返す関数 def times_10(x): return x * 10
これに対して、
df['a'].progress_apply(times_10)
を実行すると以下のようにプログレスバー付きでapplyが実行できました。
progress_apply
の他にもprogress_map
, progress_aggregate
, progress_transform
があるので必要に応じて活用しましょう!
並列処理(joblib)と組み合わせる
jobilibで並列処理をしているときに、それぞれの子プロセスでの進捗を可視化したいときはどうすればいいのでしょうか。 ただ単にtqdmを含む関数をjoblibに投げるだけでは上手く行きません。
実際、以下のデータフレームに以下の関数を適用してみると、
import numpy as np from joblib import Parallel, delayed # サンプルデータ li_sample = [np.arange(0, 10), np.arange(10, 20), np.arange(20, 30)] print(li_sample) # 要素を二乗する関数 def do_nothing(li, n): """just iter over the sublist""" for i in std_tqdm(li, position=n): pass return n**2
以下のように何も表示されません。
この様なときはbackend
引数を'multiprocessing'
に変更することで子プロセスでの進捗が可視化出来ます。
上図のプログラムのbackend
引数を以下のように変更します。
Parallel(n_jobs=2, backend="multiprocessing")(delayed(do_nothing)(li, n) for li, n in zip(li_sample, range(3)))
すると下記のように(変な空行が表示されるものの)子プロセスでの処理状況が可視化されます。
勿論、以下のようにPrallelのverbose
引数と併用することもできます。
backend
引数を変更することでtqdmが表示される様になるのは、恐らくjoblibのデフォルトのバックエンドであるloky
が標準エラーをどこかにやってしまっているからなのではないかと疑っています。
とはいえ、joblibの公式ドキュメントによるとbackend
引数のデフォルトである'loky'の方がロバストであるそうなので無闇矢鱈に'multiprocessing'に変更することはお勧めしません。
余談ですが、'loky'の方がロバストであると言いつつも、windows環境下でPrallelを使用しているときに時々遭遇するエラーは、backend
を'multiprocessing'に変更することで回避できる事があります。不思議。
プログレスバーに名前をつける
tqdmを複数回コード中で呼んでいる場合、以下のようにどのプログレスバーがどの処理を表しているのか解りづらくなってしまいます。
次のようにして、各プログレスバーに名前をつけることでどのプログレスバーがどの処理のものなのかを分かり易く出来ます。
pbar = tqdm(range(100)) for i in pbar: pbar.set_description('1つ目を処理中') pass pbar = tqdm(range(100)) for i in pbar: pbar.set_description('2つ目を処理中') pass
実行すると、下記のように名前付きでプログレスバーを表示できます。
まとめ
以上のようにjupyte notebbok上でtqdmを使用する際の今後推奨されるインポート方法と、知っておくと便利な使い方をまとめました。 是非皆さんのpythonライフを便利にするためにご活用ください。
参考文献
github.com joblib.readthedocs.io
*1:於2019-12末