팬더 작업 중 진행률 표시기


158

나는 1,500 만 개가 넘는 행에서 데이터 프레임에 대해 팬더 작업을 정기적으로 수행하며 특정 작업에 대한 진행률 표시기에 액세스하고 싶습니다.

팬더 분할 적용 조합 작업에 대한 텍스트 기반 진행률 표시기가 있습니까?

예를 들면 다음과 같습니다.

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

여기서 feature_rollup많은 DF 열을 사용하고 다양한 방법을 통해 새로운 사용자 열을 생성하는 다소 관련된 함수가 있습니다. 이 작업은 큰 데이터 프레임의 경우 시간이 걸릴 수 있으므로 진행 상황을 업데이트하는 iPython 노트북에 텍스트 기반 출력이 가능한지 알고 싶습니다.

지금까지 파이썬에 대한 표준 루프 진행률 표시기를 시도했지만 의미있는 방식으로 팬더와 상호 작용하지 않습니다.

나는 판다 라이브러리 / 문서에서 간과 된 결합 조합의 진행 상황을 알 수있는 것을 간절히 바라고 있습니다. 간단한 구현은 apply함수가 작동 하는 총 데이터 프레임 서브 세트 수를 보고 해당 서브 세트의 완료된 부분으로 진행률을보고합니다.

라이브러리에 추가해야 할 것입니까?


코드에서 % prun (프로파일)을 수행 했습니까? 때때로 병목 현상을 제거하기 위해 적용하기 전에 전체 프레임에서 작업을 수행 할 수 있습니다.
Jeff

@ Jeff : 당신은 내기, 나는 그것의 성능의 모든 마지막 비트를 쥐어 짜기 위해 일찍 했어. 문제는 실제로 행이 수천만에 있기 때문에 내가 작업하고있는 의사 맵 감소 경계로 귀착됩니다. 따라서 초고속 증가가 진행 상황에 대한 피드백을 원한다고 기대하지 않습니다.
cwharland

cythonising을 고려하십시오 : pandas.pydata.org/pandas-docs/dev/…
Andy Hayden

@AndyHayden-귀하의 답변에 댓글을 달았을 때 구현이 상당히 좋고 전체 작업에 약간의 시간이 추가됩니다. 또한 기능 롤업 내부에서 세 가지 작업을 순환 처리하여 현재 전용보고 진행 상황을 모두 회복했습니다. 결국 전체 기능에 대해 cython을 수행하면 총 처리 시간이 단축되는 진행률 표시 줄이 생깁니다.
cwharland

답변:


277

대중적인 수요로 인해에 대한 tqdm지원이 추가되었습니다 pandas. 다른 답변과 달리 팬더 속도가 눈에 띄게 느려지지는 않습니다 . 예를 들면 다음과 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)

이것이 어떻게 작동하는지 (그리고 자신의 콜백을 위해 수정하는 방법)에 관심이 있다면 github예제 , pypi에 대한 전체 문서를 참조 하거나 모듈을 가져 와서 실행하십시오 help(tqdm).

편집하다


원래 질문에 직접 대답하려면 다음을 바꾸십시오.

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

와:

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

참고 : tqdm <= v4.8 : tqdm 버전이 4.8 미만인 경우 다음 tqdm.pandas()을 수행해야합니다.

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

5
tqdm실제로 원래 그냥 일반 반복 가능 객체 만들었습니다 : from tqdm import tqdm; for i in tqdm( range(int(1e8)) ): pass팬더 지원은 최근 내가 :) 만든 해킹했다
casper.dcl을

6
Btw, Jupyter 노트북을 사용하는 경우 tqdm_notebooks를 사용하여 더 예쁜 바를 얻을 수도 있습니다. 팬더와 함께 당신은 현재 from tqdm import tqdm_notebook; tqdm_notebook().pandas(*args, **kwargs) 여기에서 보는
grinsbaeckchen

2
버전 4.8.1부터 tqdm.pandas ()를 대신 사용하십시오. github.com/tqdm/tqdm/commit/…
mork

1
감사합니다, @mork가 맞습니다. 우리는 tqdmv5를 향해 (느리게) 일을 모듈화하도록 노력하고 있습니다.
casper.dcl

1
최근 구문 추천을 보려면 여기를 tqdm 팬더 문서를 참조하십시오 : pypi.python.org/pypi/tqdm#pandas-integration
마누 CJ

18

Jeff의 대답을 조정하고 (재사용 가능한 기능으로 사용).

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

참고 : 적용 진행률 업데이트는 인라인 입니다. 함수가 stdout이면 작동하지 않습니다.

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

In [12]: f = feature_rollup

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

평소와 같이이를 방법으로 groupby 객체에 추가 할 수 있습니다.

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

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

의견에서 언급했듯이 이것은 핵심 팬더가 구현에 관심이있는 기능이 아닙니다. 그러나 파이썬을 사용하면 많은 팬더 객체 / 메소드에 대해 이것을 만들 수 있습니다 (이 방법은 일반화 할 수는 있지만 상당히 많은 작업 일 것입니다).


나는 "조금 더 작업"이라고 말하지만,이 전체 기능을 (일반적인) 데코레이터로 다시 작성할 수 있습니다.
Andy Hayden

Jeff의 게시물을 확장 해 주셔서 감사합니다. 나는 두 가지를 모두 구현했으며 각각의 속도는 매우 작습니다 (완료하는 데 27 분이 걸리는 작업에 총 1.1 분이 추가되었습니다). 이렇게하면 진행 상황을 볼 수 있으며 이러한 작업의 임시 특성을 고려할 때 이것이 느리게 진행될 것이라고 생각합니다.
cwharland

훌륭하고 도움이되었습니다. 실제로 속도가 느려지는 것에 놀랐습니다 (예제를 시도했을 때).
Andy Hayden

1
게시 된 메소드의 효율성을 더 높이기 위해 데이터 가져 오기에 대해 게으르고 (판다가 지저분한 csv를 처리하는 데 너무 뛰어납니다!) 내 항목 중 일부 (~ 1 %)가 삽입을 완전히 깨뜨 렸습니다 (전체를 생각하십시오) 단일 필드에 삽입 된 레코드). 이러한 기능을 제거하면 분할 적용 조합 작업 중에 수행 할 작업에 대한 모호성이 없으므로 기능 롤업에서 속도가 크게 향상됩니다.
cwharland

1
나는 8 분까지 ...하지만 기능 롤업에 더 많은 것을 추가했습니다 (더 많은 기능-> 더 나은 AUC!). 이 8 분은 1 천 2 백만 행 근방의 각 청크와 함께 청크 당 (현재 2 개의 청크)입니다. HDFStore를 사용하여 2,400 만 행에서 많은 작업을 수행하려면 16 분이 필요합니다 (그리고 기능 롤업에는 많은 것들이 있습니다). 꽤 좋은. 인터넷이 엉망인 삽입물에 대한 초기 무지 나
애매함을

11

Jupyter / ipython 노트북에서이 기능을 사용하는 방법에 대한 지원이 필요한 경우 다음과 같이 유용한 기사관련 기사를 제공합니다 .

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)

에 대한 import 문에서 밑줄을 확인하십시오 _tqdm_notebook. 참조 된 기사에서 언급했듯이 개발은 베타 단계에서 진행 중입니다.


8

사용자 정의 병렬 팬더 적용 코드에 tqdm을 적용하려는 모든 사람에게 적합합니다.

(수년 동안 병렬화를 위해 라이브러리 중 일부를 시도했지만 주로 적용 기능에 대한 100 % 병렬화 솔루션을 찾지 못했으며 항상 "수동"코드로 돌아와야했습니다.

df_multi_core- 이것이 당신이 부르는 것입니다. 받아들입니다 :

  1. df 객체
  2. 호출하려는 함수 이름
  3. 함수를 수행 할 수있는 열의 하위 집합 (시간 / 메모리 감소에 도움이 됨)
  4. 병렬로 실행할 작업 수 (-1 또는 모든 코어에서 생략)
  5. df의 함수가 받아들이는 다른 kwargs (예 : "axis")

_df_split- 실행중인 모듈 (Pool.map은 "배치에 따라 다름")에 전역으로 배치해야하는 내부 도우미 함수입니다. 그렇지 않으면 내부적으로 찾습니다 ..

여기 내 요지 코드가 있습니다 (팬더 기능 테스트를 더 추가 할 것입니다).

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는 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))

출력에서 병렬화없이 실행하기위한 진행률 표시 줄 1 개와 병렬화로 실행할 때 코어 당 진행률 표시 줄을 볼 수 있습니다. 약간의 hickup이 있고 때로는 나머지 코어가 한 번에 나타나지만 코어 당 진행 통계 (예 : 초당 / 총 레코드 수)를 얻었으므로 유용하다고 생각합니다.

여기에 이미지 설명을 입력하십시오

이 훌륭한 도서관에 대해 @abcdaa에게 감사합니다!



감사합니다, 그러나이 부분을 변경해야했습니다 : try: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)ValueError 대신 KeyError 예외로 인해 모든 경우를 처리하려면 Exception으로 변경하십시오.
Marius

감사합니다 @mork-이 답변은 더 높아야합니다.
앤디

5

데코레이터로 쉽게 할 수 있습니다

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)

그런 다음 modified_function을 사용하십시오 (그리고 인쇄 할 때 변경하십시오)


1
분명히 경고하면 기능이 느려질 것입니다! 진도 stackoverflow.com/questions/5426546/… ( 예 : count / len을 백분율로 업데이트)로 업데이트 할 수도 있습니다 .
Andy Hayden

그래-당신은 순서 (그룹 수)를 가질 것이므로 병목 현상이 무엇인지에 따라 이것이 달라질 수 있습니다.
Jeff

아마도 직관적 인 일은 이것을 logged_apply(g, func)주문에 액세스 할 수 있고 처음부터 로그 할 수 있는 함수로 감싸는 것입니다.
Andy Hayden

나는 대답에서 건방진 비율 업데이트로 위의 작업을 수행했습니다. 실제로 나는 당신의 일을 할 수 없었습니다 ... 나는 랩 비트로 생각합니다. 응용 프로그램에 사용하면 어쨌든 중요하지 않습니다.
Andy Hayden

1

Jeff의 답변 을 총계를 포함하도록 변경 하여 진행률과 변수를 추적하여 모든 X 반복을 인쇄 할 수 있습니다 ( "print_at"가 상당히 높은 경우 실제로 성능을 크게 향상시킵니다)

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

clear_output () 함수는

from IPython.core.display import clear_output

IPython에 없다면 Andy Hayden의 대답은 그것을하지 않고 그렇게합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.