Chỉ báo tiến độ trong quá trình hoạt động của gấu trúc


158

Tôi thường xuyên thực hiện các thao tác gấu trúc trên các khung dữ liệu vượt quá 15 triệu hàng hoặc hơn thế và tôi rất muốn có quyền truy cập vào một chỉ báo tiến trình cho các hoạt động cụ thể.

Có một chỉ báo tiến trình dựa trên văn bản cho các hoạt động phân chia-áp dụng kết hợp gấu trúc tồn tại không?

Ví dụ, trong một cái gì đó như:

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

trong đó feature_rollupmột hàm có liên quan phần nào lấy nhiều cột DF và tạo các cột người dùng mới thông qua các phương thức khác nhau. Các thao tác này có thể mất một lúc cho các khung dữ liệu lớn, vì vậy tôi muốn biết liệu có thể có đầu ra dựa trên văn bản trong sổ ghi chép iPython cập nhật cho tôi về tiến trình hay không.

Cho đến nay, tôi đã thử các chỉ số tiến trình vòng lặp chính tắc cho Python nhưng chúng không tương tác với gấu trúc theo bất kỳ cách có ý nghĩa nào.

Tôi hy vọng có một cái gì đó tôi đã bỏ qua trong thư viện / tài liệu về gấu trúc cho phép người ta biết tiến trình của sự kết hợp chia tách. Một triển khai đơn giản có thể sẽ xem xét tổng số tập hợp khung dữ liệu mà applyhàm đang hoạt động và báo cáo tiến trình là phần hoàn thành của các tập hợp con đó.

Đây có lẽ là một cái gì đó cần phải được thêm vào thư viện?


Bạn đã thực hiện% prun (hồ sơ) trên mã chưa? đôi khi bạn có thể thực hiện các thao tác trên toàn bộ khung trước khi bạn áp dụng để loại bỏ các tắc nghẽn
Jeff

@Jeff: bạn đặt cược, tôi đã làm điều đó sớm hơn để vắt kiệt mọi hiệu suất cuối cùng của nó. Vấn đề thực sự thuộc về ranh giới giảm bản đồ giả mà tôi đang làm việc vì các hàng nằm trong hàng chục triệu vì vậy tôi không mong đợi siêu tăng tốc chỉ muốn có một số phản hồi về tiến trình.
cwharland


@AndyHayden - Như tôi đã nhận xét về câu trả lời của bạn, việc thực hiện của bạn khá tốt và thêm một ít thời gian cho công việc chung. Tôi cũng đã mã hóa ba hoạt động bên trong tính năng rollup lấy lại toàn bộ thời gian hiện đang dành riêng cho tiến trình báo cáo. Vì vậy, cuối cùng tôi cá là tôi sẽ có các thanh tiến trình với việc giảm tổng thời gian xử lý nếu tôi làm theo toàn bộ chức năng của cython.
cwharland

Câu trả lời:


277

Do nhu cầu phổ biến, tqdmđã thêm hỗ trợ cho pandas. Không giống như các câu trả lời khác, điều này sẽ không làm chậm đáng chú ý gấu trúc - đây là một ví dụ cho 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)

Trong trường hợp bạn quan tâm đến cách thức hoạt động của nó (và cách sửa đổi nó cho các cuộc gọi lại của riêng bạn), hãy xem các ví dụ trên github , tài liệu đầy đủ về pypi hoặc nhập mô-đun và chạy help(tqdm).

BIÊN TẬP


Để trả lời trực tiếp câu hỏi ban đầu, thay thế:

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

với:

from tqdm import tqdm
tqdm.pandas()
df_users.groupby(['userID', 'requestDate']).progress_apply(feature_rollup)

Lưu ý: tqdm <= v4.8 : Đối với các phiên bản của tqdm dưới 4,8, thay vì tqdm.pandas()bạn phải làm:

from tqdm import tqdm, tqdm_pandas
tqdm_pandas(tqdm())

5
tqdmban đầu thực sự được tạo ra cho các iterables đơn giản: from tqdm import tqdm; for i in tqdm( range(int(1e8)) ): passHỗ trợ gấu trúc là một vụ hack gần đây tôi đã thực hiện :)
casper.dcl

6
Btw, nếu bạn sử dụng sổ ghi chép Jupyter, bạn cũng có thể sử dụng tqdm_notebooks để có được một thanh đẹp hơn. Cùng với gấu trúc bạn hiện cần phải khởi tạo nó như from tqdm import tqdm_notebook; tqdm_notebook().pandas(*args, **kwargs) xem ở đây
grinsbaeckchen

2
Kể từ phiên bản 4.8.1 - thay vào đó hãy sử dụng tqdm.pandas (). github.com/tqdm/tqdm/commit/ từ
mork

1
Cảm ơn, @mork là chính xác. Chúng tôi đang làm việc (chậm) về phía tqdmv5, điều này khiến mọi thứ được mô đun hóa nhiều hơn.
casper.dcl

1
Để biết khuyến nghị cú pháp gần đây, hãy xem tài liệu về tqdm Pandas tại đây: pypi.python.org/pypi/tqdm#pandas-integration
Manu CJ

18

Để điều chỉnh câu trả lời của Jeff (và có chức năng này có thể sử dụng lại).

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

Lưu ý: phần trăm tiến trình áp dụng cập nhật nội tuyến . Nếu chức năng của bạn xuất hiện thì điều này sẽ không hoạt động.

In [11]: g = df_users.groupby(['userID', 'requestDate'])

In [12]: f = feature_rollup

In [13]: logged_apply(g, f)
apply progress: 100%
Out[13]: 
...

Như thường lệ, bạn có thể thêm nó vào các đối tượng nhóm của mình như một phương thức:

from pandas.core.groupby import DataFrameGroupBy
DataFrameGroupBy.logged_apply = logged_apply

In [21]: g.logged_apply(f)
apply progress: 100%
Out[21]: 
...

Như đã đề cập trong các bình luận, đây không phải là một tính năng mà gấu trúc cốt lõi sẽ quan tâm thực hiện. Nhưng python cho phép bạn tạo chúng cho nhiều đối tượng / phương thức gấu trúc (làm như vậy sẽ khá tốn công ... mặc dù bạn có thể khái quát hóa phương pháp này).


Tôi nói "khá nhiều công việc", nhưng bạn có thể có thể viết lại toàn bộ chức năng này như một trang trí (tổng quát hơn).
Andy Hayden

Cảm ơn bạn đã mở rộng bài viết của Jeff. Tôi đã triển khai cả hai và việc chậm lại cho mỗi lần khá tối thiểu (đã thêm tổng cộng 1,1 phút vào một thao tác mất 27 phút để hoàn thành). Bằng cách này, tôi có thể xem tiến trình và đưa ra bản chất adhoc của các hoạt động này Tôi nghĩ rằng đây là một sự chậm lại chấp nhận được.
cwharland

Tuyệt vời, rất vui vì nó đã giúp. Tôi thực sự ngạc nhiên về sự chậm lại (khi tôi thử một ví dụ), tôi đã dự đoán nó sẽ tệ hơn rất nhiều.
Andy Hayden

1
Để tăng thêm hiệu quả của các phương thức được đăng, tôi đã lười biếng trong việc nhập dữ liệu (gấu trúc quá giỏi trong việc xử lý csv lộn xộn !!) và một vài trong số các mục của tôi (~ 1%) đã hoàn toàn loại bỏ các phần chèn thêm (nghĩ toàn bộ hồ sơ chèn vào các trường duy nhất). Loại bỏ những điều này gây ra sự tăng tốc lớn trong bản giới thiệu tính năng do không có sự mơ hồ về những việc cần làm trong các hoạt động kết hợp chia tách-áp dụng.
cwharland

1
Tôi xuống còn 8 phút ... nhưng tôi đã thêm đôi khi vào danh sách tính năng (nhiều tính năng hơn -> AUC tốt hơn!). 8 phút này là mỗi đoạn (tổng số hai khối ngay bây giờ) với mỗi đoạn trong khu phố 12 triệu hàng. Vì vậy, vâng ... 16 phút để thực hiện các thao tác khổng lồ trên 24 triệu hàng bằng HDFStore (và có nội dung nltk trong danh sách tính năng). Khá tốt. Chúng ta hãy hy vọng internet không phán xét tôi về sự thiếu hiểu biết hoặc xung quanh ban đầu đối với các phần chèn thêm lộn xộn =)
cwharland

11

Trong trường hợp bạn cần hỗ trợ về cách sử dụng tính năng này trong sổ ghi chép Jupyter / ipython, như tôi đã làm, đây là một hướng dẫn hữu ích và nguồn cho bài viết liên quan :

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)

Lưu ý dấu gạch dưới trong câu lệnh nhập cho _tqdm_notebook. Như bài viết đã đề cập, sự phát triển đang ở giai đoạn cuối beta.


8

Đối với bất cứ ai đang tìm cách áp dụng tqdm trên mã áp dụng gấu trúc song song tùy chỉnh của họ.

(Tôi đã thử một số thư viện để song song hóa trong nhiều năm, nhưng tôi chưa bao giờ tìm thấy giải pháp song song hóa 100%, chủ yếu cho chức năng áp dụng và tôi luôn phải quay lại mã "thủ công" của mình.)

df_multi_core - đây là người bạn gọi. Nó chấp nhận:

  1. Đối tượng df của bạn
  2. Tên hàm bạn muốn gọi
  3. Tập hợp con của cột có thể được thực hiện theo (giúp giảm thời gian / bộ nhớ)
  4. Số lượng công việc để chạy song song (-1 hoặc bỏ qua cho tất cả các lõi)
  5. Bất kỳ kwarg nào khác chức năng của df đều chấp nhận (như "trục")

_df_split - đây là chức năng của trình trợ giúp nội bộ phải được định vị toàn cầu cho mô-đun đang chạy (Pool.map là "phụ thuộc vị trí"), nếu không tôi sẽ xác định vị trí bên trong ..

đây là mã từ ý chính của tôi (Tôi sẽ thêm nhiều bài kiểm tra chức năng gấu trúc ở đó):

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

Bellow là một mã kiểm tra cho một ứng dụng song song với 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))

Trong đầu ra, bạn có thể thấy 1 thanh tiến trình để chạy mà không cần song song và các thanh tiến trình trên mỗi lõi khi chạy song song. Có một chút khó khăn và đôi khi các lõi còn lại xuất hiện cùng một lúc, nhưng ngay cả khi đó tôi nghĩ nó hữu ích vì bạn có được số liệu thống kê tiến độ trên mỗi lõi (ví dụ: nó / giây và tổng số hồ sơ)

nhập mô tả hình ảnh ở đây

Cảm ơn bạn @abcdaa vì thư viện tuyệt vời này!


1
Cảm ơn @mork - vui lòng thêm vào github.com/tqdm/tqdm/wiki/How-to-make-a-great-ProTHER-Bar hoặc tạo một trang mới tại github.com/tqdm/tqdm/wiki
casper. dcl

Cảm ơn, nhưng đã phải thay đổi phần này: try: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)vì ngoại lệ KeyError thay vì ValueError, thay đổi thành Ngoại lệ để xử lý tất cả các trường hợp.
Marius

Cảm ơn @mork - câu trả lời này nên cao hơn.
Andy

5

Bạn có thể dễ dàng làm điều này với một trang trí

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)

sau đó chỉ cần sử dụng đã sửa đổi (và thay đổi khi bạn muốn in)


1
Rõ ràng cảnh báo là điều này sẽ làm chậm chức năng của bạn! Bạn thậm chí có thể cập nhật nó với tiến trình stackoverflow.com/questions/5426546/, ví dụ: đếm / len theo tỷ lệ phần trăm.
Andy Hayden

vâng - bạn sẽ có thứ tự (số lượng nhóm), vì vậy tùy thuộc vào nút thắt cổ chai của bạn, điều này có thể tạo ra sự khác biệt
Jeff

có lẽ điều trực quan cần làm là bọc cái này trong một logged_apply(g, func)hàm, nơi bạn có quyền truy cập để đặt hàng và có thể đăng nhập lại từ đầu.
Andy Hayden

Tôi đã làm như trên trong câu trả lời của tôi, cũng cập nhật phần trăm táo bạo. Trên thực tế tôi không thể làm cho bạn làm việc ... Tôi nghĩ với bit kết thúc tốt đẹp. Nếu bạn sử dụng nó để áp dụng thì dù sao nó cũng không quá quan trọng.
Andy Hayden

1

Tôi đã thay đổi câu trả lời của Jeff , để bao gồm tổng số, để bạn có thể theo dõi tiến trình và một biến để chỉ in mỗi lần lặp X (điều này thực sự cải thiện hiệu suất rất nhiều, nếu "print_at" cao một cách hợp lý)

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

hàm ClearDefput () là từ

from IPython.core.display import clear_output

nếu không có câu trả lời của IPython Andy Hayden thì không có nó

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.