Back to Blog
Python

Python tqdm pandas Integration for Progress Bars

Integrate tqdm with pandas to add progress bars to apply, map, and manual iteration. Covers setup, usage, overhead, limitations, and parallel processing.

tqdmpandasprogress barsdataframepython
A progress bar overlaying a pandas DataFrame with a function being applied to rows.

When you run a long-running apply or map operation on a large DataFrame, pandas does not show how far the computation has progressed. The tqdm library adds progress bars to pandas operations through tqdm.pandas(), giving you a live percentage, elapsed time, and estimated remaining time as each row or column is processed. The integration is small: call tqdm.pandas() once, then use progress_apply or progress_map in place of the original method.

Enabling tqdm for pandas

Before you can use progress_apply or progress_map, you must call tqdm.pandas(). This function monkey-patches the pandas DataFrame and Series classes to add the progress-aware methods.

import pandas as pd from tqdm import tqdm # Enable tqdm for pandas tqdm.pandas()

After this call, every DataFrame and Series object in your session gains the new methods. The patch is global, so you only need to call it once per script or notebook.

Using progress_apply for DataFrames and Series

The most common use case is replacing apply with progress_apply. The method signature is identical to apply, so you can usually change the method name without touching the rest of the code.

df = pd.DataFrame({'a': range(1000), 'b': range(1000, 2000)}) # Without progress bar df['sum'] = df.apply(lambda row: row['a'] + row['b'], axis=1) # With progress bar df['sum'] = df.progress_apply(lambda row: row['a'] + row['b'], axis=1)

The progress bar appears in the console or notebook and updates as each row is processed. For a Series, the same method works:

s = pd.Series(range(1000)) result = s.progress_apply(lambda x: x ** 2)

progress_apply accepts the same arguments as apply, including axis, raw, and result_type. Note that progress_apply is not part of the pandas API; it is added by tqdm. If you run the code without calling tqdm.pandas(), you will get an AttributeError.

Using progress_map for Series transformations

For Series.map, tqdm provides progress_map. This is useful when you are mapping a dictionary or a function over a series.

s = pd.Series(['a', 'b', 'c'] * 1000) mapping = {'a': 1, 'b': 2, 'c': 3} result = s.progress_map(mapping)

You can also use a function:

def classify(x): if x < 10: return 'low' elif x < 100: return 'medium' else: return 'high' s = pd.Series(range(1000)) result = s.progress_map(classify)

progress_map is a direct replacement for map and behaves identically otherwise.

Progress bars for manual iteration with iterrows and itertuples

Sometimes you need to iterate manually over a DataFrame, for example when the logic cannot be expressed with apply. In that case, you can wrap the iterator with tqdm directly.

from tqdm import tqdm for index, row in tqdm(df.iterrows(), total=len(df)): # process row pass

The total argument is important because iterrows() does not expose its length. For itertuples, the same pattern works:

for row in tqdm(df.itertuples(), total=len(df)): # row is a named tuple pass

This approach gives you a progress bar without modifying pandas itself. It is useful when you need to keep the loop structure for readability or when you are already iterating and want feedback.

Understanding the performance overhead

Adding a progress bar is not free. Each iteration updates the bar, which involves I/O and formatting. For fast operations, the overhead can be significant. The overhead is acceptable when the function you are applying is slow, such as a network call, a database query, or a complex computation. In those cases, the progress bar provides useful feedback and the update cost is small relative to the work per row.

If you are applying a fast function to a large dataset, consider using tqdm only during development or debugging, and remove it for production runs. To reduce refresh overhead, tqdm also supports options such as mininterval and miniters.

Limitations and common mistakes

tqdm.pandas() does not add progress bars to every pandas method. For example, groupby.apply and vectorized operations do not get a bar automatically; wrap manual iteration with tqdm instead.

A common mistake is forgetting to call tqdm.pandas() before using progress_apply. The method will not exist, and you will see an AttributeError. Another mistake is calling tqdm.pandas() multiple times, which is harmless but unnecessary.

The patch is global and affects all pandas objects in the session. If you run multiple pandas operations concurrently, their progress updates can be interleaved or confusing. For concurrent workloads, prefer wrapping explicit loops with tqdm so you control each bar, or avoid the pandas patch.

Using tqdm with parallel processing

progress_apply does not parallelize anything; it only adds a progress bar to the existing sequential apply. If you need parallel execution, you can use concurrent.futures or multiprocessing and wrap the iterable with tqdm to show progress.

from concurrent.futures import ProcessPoolExecutor from tqdm import tqdm def process_row(row): # heavy computation return row['a'] + row['b'] with ProcessPoolExecutor() as executor: results = list(tqdm(executor.map(process_row, df.to_dict('records')), total=len(df)))

On Windows, put the ProcessPoolExecutor block inside an if __name__ == '__main__': guard to avoid recursive process creation.

This pattern gives you a progress bar as results are returned from the parallel mapping. It works with any parallel mapping function that returns an iterable. The total argument is required because executor.map does not expose the length.

Because progress_apply is sequential, this parallel wrapper is the main way to combine tqdm with parallel execution.

Python tqdm pandas Integration: Practical Usage and Code Examples | RYUSLOG DEV