Pandas operasyonları sırasında ilerleme göstergesi


178

15 milyon veya daha fazla satırı aşan veri çerçeveleri üzerinde düzenli olarak panda işlemleri gerçekleştiriyorum ve belirli işlemler için bir ilerleme göstergesine erişmeyi çok isterim.

Pandalar bölme-uygulama-birleştirme işlemleri için metin tabanlı bir ilerleme göstergesi var mı?

Örneğin, aşağıdaki gibi bir şeyde:

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

burada feature_rollup, birçok DF sütununu alan ve çeşitli yöntemlerle yeni kullanıcı sütunları oluşturan, biraz ilgili bir işlevdir. Bu işlemler, büyük veri çerçeveleri için biraz zaman alabilir, bu nedenle, ilerleme hakkında beni güncelleyen bir iPython dizüstü bilgisayarda metin tabanlı çıktı almanın mümkün olup olmadığını bilmek istiyorum.

Şimdiye kadar, Python için kanonik döngü ilerleme göstergelerini denedim, ancak pandalarla anlamlı bir şekilde etkileşime girmiyorlar.

Pandalar kitaplığında / belgelerinde, bölünmüş-uygula-birleştirmenin ilerlemesini bilmesine izin veren bir şey olduğunu umuyorum. Basit bir uygulama, applyfonksiyonun üzerinde çalıştığı veri çerçevesi alt kümelerinin toplam sayısına bakabilir ve ilerlemeyi bu alt kümelerin tamamlanmış fraksiyonu olarak rapor edebilir.

Bu belki de kütüphaneye eklenmesi gereken bir şey mi?


kodda% prun (profil) yaptınız mı? bazen darboğazları ortadan kaldırmak için başvurmadan önce tüm çerçeve üzerinde işlemler yapabilirsiniz
Jeff

@Jeff: Bahse girerim, bunu daha önce performansın her bir parçasını çıkarmak için yaptım. Sorun gerçekten de üzerinde çalıştığım sözde harita azaltma sınırına iniyor, çünkü satırlar on milyonları buluyor, bu yüzden süper hız artışları beklemiyorum sadece ilerleme hakkında biraz geri bildirim istiyorum.
cwharland


@AndyHayden - Cevabınız üzerine yorumladığım gibi, uygulamanız oldukça iyi ve genel işe az miktarda zaman katıyor. Ayrıca, artık raporlama ilerlemesine adanmış olan tüm zamanı geri kazanan özellik toplaması içinde üç işlemi de şifreledim. Sonuçta, eğer tüm işlev üzerinde cython ile takip edersem, toplam işlem süresinde bir azalma olan ilerleme çubuklarına sahip olacağıma bahse girerim.
cwharland

Yanıtlar:


312

Yoğun talep nedeniyle, tqdmiçin destek ekledi pandas. Diğer cevaplardan farklı olarak, bu pandaları fark edilir derecede yavaşlatmaz - işte bir örnek 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)

Eğer bu eserler (ve nasıl kendi geri aramalar için değiştirmek için), nasıl gördüğünü ilgilendiğiniz github örnekler , pypi hakkındaki belgelere veya modülü ve çalıştırmak içe help(tqdm). Desteklenen diğer fonksiyonlar map, applymap, aggregate, ve transform.

DÜZENLE


Orijinal soruyu doğrudan cevaplamak için, değiştirin:

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

ile:

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

Not: tqdm <= v4.8: tqdm'nin 4.8'in altındaki sürümleri tqdm.pandas()için yapmanız gereken yerine :

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

6
tqdmaslında sadece basit yinelemeler için yaratıldı: from tqdm import tqdm; for i in tqdm( range(int(1e8)) ): passPandalar desteği yakın zamanda yaptığım bir
hack'ti

7
Btw, Jupyter not defterleri kullanıyorsanız, daha güzel bir çubuk elde etmek için tqdm_notebooks'u da kullanabilirsiniz. Pandalarla birlikte, şu anda from tqdm import tqdm_notebook; tqdm_notebook().pandas(*args, **kwargs) burada
grinsbaeckchen

2
4.8.1 sürümünden itibaren - bunun yerine tqdm.pandas () kullanın. github.com/tqdm/tqdm/commit/…
mork

1
Teşekkürler, @mork doğru. tqdmİşleri daha modüler hale getiren v5'e doğru (yavaşça) çalışıyoruz .
casper.dcl

1
Son sözdizimi önerileri için, tqdm Pandas belgelerine buradan bakın: pypi.python.org/pypi/tqdm#pandas-integration
Manu CJ

18

Jeff'in cevabını değiştirmek (ve bunu yeniden kullanılabilir bir işlev olarak kullanmak).

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

Not: uygulama ilerleme yüzdesi satır içi olarak güncellenir . İşleviniz standartlarsa, bu işe yaramaz.

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

In [12]: f = feature_rollup

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

Her zamanki gibi, bunu grup nesnelerinize yöntem olarak ekleyebilirsiniz:

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

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

Yorumlarda belirtildiği gibi, bu, çekirdek pandaların uygulamak isteyeceği bir özellik değil. Ancak python, bunları birçok panda nesnesi / yöntemi için oluşturmanıza izin verir (bunu yapmak oldukça fazla iş olacaktır ... ancak bu yaklaşımı genelleştirebilmelisiniz).


"Oldukça fazla iş" diyorum, ama muhtemelen tüm bu işlevi (daha genel) bir dekoratör olarak yeniden yazabilirsiniz.
Andy Hayden

Jeff'in gönderisini genişlettiğiniz için teşekkürler. İkisini de uyguladım ve her biri için yavaşlama oldukça az (tamamlanması 27 dakika süren bir işleme toplam 1,1 dakika eklendi). Bu şekilde ilerlemeyi görebilirim ve bu operasyonların anlık doğası göz önüne alındığında, bunun kabul edilebilir bir yavaşlama olduğunu düşünüyorum.
cwharland

Mükemmel, yardımcı olmasına sevindim. Aslında yavaşlamaya şaşırmıştım (bir örnek denediğimde), çok daha kötü olmasını bekliyordum.
Andy Hayden

1
Gönderilen yöntemlerin verimliliğini daha da artırmak için, veri içe aktarma konusunda tembel davrandım (pandalar dağınık csv'yi işlemede çok iyi !!) ve girişlerimden birkaçı (~% 1) eklemeleri tamamen çarptı (tamamını düşünün tek alanlara eklenen kayıtlar). Bunların ortadan kaldırılması, ayırma-uygulama-birleştirme işlemleri sırasında ne yapılacağı konusunda bir belirsizlik olmadığı için özellik toplamasında büyük bir hızlanmaya neden olur.
cwharland

1
8 dakikaya düştüm ... ancak özellik toplamasına bir şeyler ekledim (daha fazla özellik -> daha iyi AUC!). Bu 8 dakika parça başına (şu anda toplam iki parça), 12 milyon satırlık komşuluktaki her yığınla. Yani evet ... HDFStore kullanarak 24 milyon satırda ağır işlemler yapmak için 16 dakika (ve özellik toplamasında nltk şeyler var). Oldukça iyi. Umarım internet beni ilk bilgisizlik veya karışıklıklara karşı kararsızlık konusunda yargılamaz =)
cwharland

11

Bunu bir Jupyter / ipython not defterinde nasıl kullanacağınız konusunda desteğe ihtiyacınız olursa, benim yaptığım gibi, işte yararlı bir kılavuz ve ilgili makale için kaynak :

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)

İçin import deyimindeki alt çizgiye dikkat edin _tqdm_notebook. Başvurulan makalede belirtildiği gibi, geliştirme geç beta aşamasındadır.


10

Tqdm'yi özel paralel pandalar-uygulama kodlarına uygulamak isteyen herkes için.

(Yıllar boyunca bazı kütüphaneleri paralelleştirme için denedim, ancak esas olarak uygulama işlevi için% 100 paralelleştirme çözümü bulamadım ve her zaman "manuel" kodum için geri gelmek zorunda kaldım.)

df_multi_core - aradığınız kişi budur. Kabul eder:

  1. Df nesneniz
  2. Çağırmak istediğiniz işlev adı
  3. İşlevin gerçekleştirilebileceği sütunların alt kümesi (zamanın / belleğin azaltılmasına yardımcı olur)
  4. Paralel olarak çalıştırılacak iş sayısı (-1 veya tüm çekirdekler için ihmal)
  5. Df'nin işlevinin kabul ettiği herhangi bir diğer kwarg ("eksen" gibi)

_df_split - bu, çalışan modüle global olarak konumlandırılması gereken dahili bir yardımcı işlevdir (Pool.map "yerleşime bağlıdır"), aksi takdirde onu dahili olarak bulurdum ..

İşte özümdeki kod (oraya daha fazla panda fonksiyon testi ekleyeceğim):

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

Körük, tqdm "progress_apply" ile paralelleştirilmiş bir uygulama için bir test kodudur .

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

Çıktıda, paralelleştirme olmadan çalıştırmak için 1 ilerleme çubuğu ve paralelleştirmeyle çalışırken çekirdek başına ilerleme çubuğu görebilirsiniz. Hafif bir kesinti var ve bazen çekirdeklerin geri kalanı aynı anda görünüyor, ancak o zaman bile çekirdek başına ilerleme istatistiklerini aldığınız için yararlı olduğunu düşünüyorum (örneğin, o / sn ve toplam kayıtlar)

görüntü açıklamasını buraya girin

Bu harika kütüphane için @abcdaa'ya teşekkür ederiz!


1
Teşekkürler @mork - github.com/tqdm/tqdm/wiki/How-to-make-a-great-Progress-Bar'a eklemekten çekinmeyin veya github.com/tqdm/tqdm/wiki
casper

Teşekkürler, ancak bu bölümü değiştirmek zorundaydınız: try: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)ValueError yerine KeyError istisnası nedeniyle, tüm vakaları işlemek için İstisna olarak değiştirin.
Marius

1
Teşekkürler @mork - bu cevap daha yüksek olmalı.
Andy

5

Bunu bir dekoratörle kolayca yapabilirsiniz

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)

sonra sadece değiştirilmiş_işlevi kullanın (ve yazdırmasını istediğinizde değiştirin)


1
Apaçık bir uyarı bu, işlevinizi yavaşlatacaktır! İlerleme stackoverflow.com/questions/5426546/… ile güncellenmesini bile sağlayabilirsiniz, örneğin yüzde olarak say / len.
Andy Hayden

evet - siparişiniz olacak (grup sayısı), bu nedenle darboğazınızın ne olduğuna bağlı olarak bu bir fark yaratabilir
Jeff

belki de yapılacak sezgisel şey, bunu logged_apply(g, func)siparişe erişiminizin olduğu ve en baştan kayıt yapabileceğiniz bir işleve sarmaktır.
Andy Hayden

Yukarıdakileri cevabımda yaptım, ayrıca arsız yüzde güncellemesi. Aslında seninkini çalıştıramadım ... Sanırım biraz da olsa. Uygulama için kullanıyorsanız, zaten o kadar önemli değil.
Andy Hayden

1

Jeff'in cevabını bir toplam içerecek şekilde değiştirdim , böylece ilerlemeyi ve sadece her X yinelemesini yazdıracak bir değişkeni takip edebilirsiniz ("print_at" makul derecede yüksekse bu aslında performansı çok artırır)

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 () işlevi

from IPython.core.display import clear_output

IPython'da değilse Andy Hayden'ın cevabı bunu onsuz yapıyor

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.