Skip to content

Commit f020419

Browse files
committed
make pandas a classmethod. TODO: Readme update, unit tests (incl subclasses)
1 parent 787f3b2 commit f020419

2 files changed

Lines changed: 95 additions & 13 deletions

File tree

tqdm/_tqdm.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,86 @@ def write(cls, s, file=sys.stdout, end="\n"):
324324
inst.refresh()
325325
# TODO: make list of all instances incl. absolutely positioned ones?
326326

327+
@classmethod
328+
def pandas(tclass, *targs, **tkwargs):
329+
"""
330+
Registers the given `tqdm` class with
331+
pandas.core.
332+
( frame.DataFrame
333+
| series.Series
334+
| groupby.DataFrameGroupBy
335+
| groupby.SeriesGroupBy
336+
).progress_apply
337+
338+
A new instance will be create every time `progress_apply` is called,
339+
and each instance will automatically close() upon completion.
340+
341+
Parameters
342+
----------
343+
targs, tkwargs : arguments for the tqdm instance
344+
345+
Examples
346+
--------
347+
>>> import pandas as pd
348+
>>> import numpy as np
349+
>>> from tqdm import tqdm, tqdm_gui
350+
>>>
351+
>>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6)))
352+
>>> tqdm.pandas(ncols=50) # can use tqdm_gui, optional kwargs, etc
353+
>>> # Now you can use `progress_apply` instead of `apply`
354+
>>> df.groupby(0).progress_apply(lambda x: x**2)
355+
356+
References
357+
----------
358+
https://stackoverflow.com/questions/18603270/
359+
progress-indicator-during-pandas-operations-python
360+
"""
361+
from pandas.core.frame import DataFrame
362+
from pandas.core.series import Series
363+
from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy
364+
365+
def inner(df, func, *args, **kwargs):
366+
"""
367+
Parameters
368+
----------
369+
df : (DataFrame|Series)[GroupBy]
370+
Data (may be grouped).
371+
func : function
372+
To be applied on the (grouped) data.
373+
*args, *kwargs : optional
374+
Transmitted to `df.apply()`.
375+
"""
376+
# Precompute total iterations
377+
total = getattr(df, 'ngroups', None)
378+
if total is None: # not grouped
379+
total = len(df) if isinstance(df, Series) \
380+
else df.size // len(df)
381+
else:
382+
total += 1 # pandas calls update once too many
383+
384+
# Init bar
385+
t = tclass(*targs, total=total, **tkwargs)
386+
387+
# Define bar updating wrapper
388+
def wrapper(*args, **kwargs):
389+
t.update()
390+
return func(*args, **kwargs)
391+
392+
# Apply the provided function (in *args and **kwargs)
393+
# on the df using our wrapper (which provides bar updating)
394+
result = df.apply(wrapper, *args, **kwargs)
395+
396+
# Close bar and return pandas calculation result
397+
t.close()
398+
return result
399+
400+
# Monkeypatch pandas to provide easy methods
401+
# Enable custom tqdm progress in pandas!
402+
DataFrame.progress_apply = inner
403+
DataFrameGroupBy.progress_apply = inner
404+
Series.progress_apply = inner
405+
SeriesGroupBy.progress_apply = inner
406+
327407
def __init__(self, iterable=None, desc=None, total=None, leave=True,
328408
file=sys.stderr, ncols=None, mininterval=0.1,
329409
maxinterval=10.0, miniters=None, ascii=None, disable=False,

tqdm/_tqdm_pandas.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ def tqdm_pandas(tclass, *targs, **tkwargs):
3434
https://stackoverflow.com/questions/18603270/
3535
progress-indicator-during-pandas-operations-python
3636
"""
37+
38+
if isinstance(tclass, type) or (getattr(tclass, '__name__', '').startswith(
39+
'tqdm_')): # delayed adapter case
40+
tclass.write("Warning: `tqdm_pandas(tqdm(...))` is deprecated,"
41+
" please use `tqdm.pandas(...)` instead.")
42+
tclass.pandas(*targs, **tkwargs)
43+
return
44+
45+
t = tclass
46+
t.write("Warning: tqdm_pandas: using a bar instance is deprecated,"
47+
" please provide a bar class instead.", file=t.fp)
48+
3749
from pandas.core.frame import DataFrame
3850
from pandas.core.series import Series
3951
from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy
@@ -49,24 +61,14 @@ def inner(df, func, *args, **kwargs):
4961
5062
*args and *kwargs are transmitted to DataFrameGroupBy.apply()
5163
"""
64+
5265
# Precompute total iterations
5366
total = getattr(df, 'ngroups', None)
5467
if total is None: # not grouped
55-
total = len(df) if isinstance(df, Series) \
68+
t.total = len(df) if isinstance(df, Series) \
5669
else df.size // len(df)
5770
else:
58-
total += 1 # pandas calls update once too many
59-
60-
# Init bar
61-
if isinstance(tclass, type) or \
62-
(hasattr(tclass, '__name__') and
63-
tclass.__name__.startswith('tqdm_')): # delayed adapter case
64-
t = tclass(*targs, total=total, **tkwargs)
65-
else:
66-
t = tclass
67-
t.total = total
68-
t.write("Warning: tqdm_pandas: using a bar instance is deprecated,"
69-
" please provide a bar class instead.", file=t.fp)
71+
t.total = total + 1 # pandas calls update once too many
7072

7173
# Define bar updating wrapper
7274
def wrapper(*args, **kwargs):

0 commit comments

Comments
 (0)