猫になりたい

コンサルのデータ分析屋、計量経済とか機械学習をやっています。pyてょnは3.7を使ってマスコレルウィンストングリーン。

python:tqdmのモダンなimport方法と便利な使い方

皆さんも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 tqdmconda intall tqdmでインストールできます。
この記事では2019/12/03時点でcondaでインストール出来る最新の4.39.0を使用します。

基本的な使い方

先づは一番普通のtqdmの使い方を見てみましょう。
但し、ここでは実行の簡便性からjupyter notebook上で実行しています。

# tqdmをインポート
from tqdm import tqdm

for i in tqdm(range(10)):
    pass  # 何もしない

結果は以下のようになります。
赤い背景に進捗を表すプログレスバーが出ているのが確認できます。

f:id:shikiponn:20191205151918p:plain:w500
tqdmの実行結果

Jupyter notebookで使う

通常のtqdmをJupyterで使うと起きる問題

続いてJupyter notebook上でのtqdmの使い方ですが、
上述したtqdmの使い方をJupyter上でしようとすると、 ちょっとした問題が発生します。確認してみましょう。

以下のコードを実行中にJupyterの停止ボタンを使って停止します。

for i in std_tqdm(range(10000000)):
    pass

そして再度実行すると……、以下のように表示がおかしくなってしまいます! f:id:shikiponn:20191231172644p:plain:w500

Jupyter上でのtqdmの(モダンな)インポート方法

この様な表示のバグを防ぐために、Jupyter上でtqdmを使うときはJupyter用のtqdmを使用しましょう。
以下の様にインポートすれば後は通常のtqdmと同じ様に使うだけです。

 from tqdm.notebook import tqdm

tqdm.notebook.tqdmを使うと前述の表示バグを回避できるだけでなく、以下のように美しい表示を得ることができます

f:id:shikiponn:20191231211426p:plain:w500
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.Daraframegroupbyしたもの、と組み合わせることがよくあると思います。 しかしgeneratorはリストと違って長さがわからないので、残り時間と総イテレーション数がわからないことが問題になります。(下図参照)

f:id:shikiponn:20191231204201p:plain:w500
generatorにtqdmを適用してfor文を実行している画面。総イテレーション回数と予測総実行時間がきちんと表示されていないのが見て取れる

これはtotal引数に総イテレーション回数を渡すことで解決できます。
下図のように、total引数をtqdmに渡すことで総イテレーション回数と予測総実行時間が表示されるようになります。
f:id:shikiponn:20191231212531p:plain:w500

pandasのapply, map, aggregate, transformと組み合わせる

重めの処理をpandas.DataFrameにapplyやmapしているときも進捗を可視化したい時でしょう。 この様なときはprogress_applyprogress_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が実行できました。 f:id:shikiponn:20191231220038p:plain:w500

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

以下のように何も表示されません。 f:id:shikiponn:20191231222759p:plain:w500

この様なときはbackend引数を'multiprocessing'に変更することで子プロセスでの進捗が可視化出来ます。 上図のプログラムのbackend引数を以下のように変更します。

Parallel(n_jobs=2, backend="multiprocessing")(delayed(do_nothing)(li, n) for li, n  in zip(li_sample, range(3)))

すると下記のように(変な空行が表示されるものの)子プロセスでの処理状況が可視化されます。 f:id:shikiponn:20191231223047p:plain:w700

勿論、以下のようにPrallelのverbose引数と併用することもできます。 f:id:shikiponn:20191231223533p:plain:w700

backend引数を変更することでtqdmが表示される様になるのは、恐らくjoblibのデフォルトのバックエンドであるlokyが標準エラーをどこかにやってしまっているからなのではないかと疑っています。 とはいえ、joblibの公式ドキュメントによるとbackend引数のデフォルトである'loky'の方がロバストであるそうなので無闇矢鱈に'multiprocessing'に変更することはお勧めしません。

余談ですが、'loky'の方がロバストであると言いつつも、windows環境下でPrallelを使用しているときに時々遭遇するエラーは、backendを'multiprocessing'に変更することで回避できる事があります。不思議。

プログレスバーに名前をつける

tqdmを複数回コード中で呼んでいる場合、以下のようにどのプログレスバーがどの処理を表しているのか解りづらくなってしまいます。 f:id:shikiponn:20191231224541p:plain:w500

次のようにして、各プログレスバーに名前をつけることでどのプログレスバーがどの処理のものなのかを分かり易く出来ます。

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

実行すると、下記のように名前付きでプログレスバーを表示できます。
f:id:shikiponn:20191231224658p:plain:w500

まとめ

以上のようにjupyte notebbok上でtqdmを使用する際の今後推奨されるインポート方法と、知っておくと便利な使い方をまとめました。 是非皆さんのpythonライフを便利にするためにご活用ください。

参考文献

github.com joblib.readthedocs.io

*1:於2019-12末