ตัวบ่งชี้ความคืบหน้าในระหว่างการดำเนินการแพนด้า


158

ฉันดำเนินการแพนด้าในกรอบข้อมูลเกินกว่า 15 ล้านแถวเป็นประจำและฉันชอบที่จะเข้าถึงตัวบ่งชี้ความคืบหน้าสำหรับการดำเนินงานเฉพาะอย่าง

ตัวบ่งชี้ความคืบหน้าของข้อความที่ใช้สำหรับการดำเนินการแบบแยกส่วนร่วมใช้กับแพนด้ามีอยู่หรือไม่?

ตัวอย่างเช่นในสิ่งที่ชอบ:

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

โดยที่feature_rollupฟังก์ชั่นที่เกี่ยวข้องจะมีคอลัมน์ DF จำนวนมากและสร้างคอลัมน์ผู้ใช้ใหม่ผ่านวิธีการต่างๆ การดำเนินการเหล่านี้อาจใช้เวลาสักครู่สำหรับเฟรมข้อมูลขนาดใหญ่ดังนั้นฉันต้องการทราบว่าเป็นไปได้หรือไม่ที่จะมีเอาต์พุตแบบข้อความในโน้ตบุ๊ก iPython ที่อัปเดตฉันในความคืบหน้า

ถึงตอนนี้ฉันได้ลองใช้ตัวบ่งชี้ความคืบหน้าของ canonical สำหรับ Python แต่พวกมันไม่ได้โต้ตอบกับ pandas ในทางที่มีความหมายใด ๆ

ฉันหวังว่าจะมีบางสิ่งที่ฉันมองข้ามในห้องสมุด / เอกสารของแพนด้าที่ช่วยให้เราทราบถึงความคืบหน้าของการแยกแบบรวมใช้ การใช้งานอย่างง่ายอาจจะดูจำนวนรวมของเซ็ตย่อย data frame ตามที่applyฟังก์ชันกำลังทำงานและรายงานความคืบหน้าเป็นเศษส่วนที่สมบูรณ์ของเซตย่อยเหล่านั้น

นี่อาจเป็นสิ่งที่ต้องเพิ่มในไลบรารีหรือไม่


คุณทำ% prun (โปรไฟล์) ในรหัสหรือไม่ บางครั้งคุณสามารถดำเนินการกับทั้งเฟรมก่อนที่คุณจะสมัครเพื่อกำจัดปัญหาคอขวด
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ที่เอกสารฉบับเต็มใน pypihelp(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ถูกสร้างขึ้นจริงสำหรับ iterables ธรรมดาเพียงดั้งเดิม: 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 Pandas ที่นี่: pypi.python.org/pypi/tqdm#pandas-integration
Manu CJ

18

เพื่อปรับแต่งคำตอบของ Jeff (และใช้ฟังก์ชันนี้เป็น reuseable)

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

หมายเหตุ: นำไปใช้ร้อยละความคืบหน้าการปรับปรุงแบบอินไลน์ หากฟังก์ชันของคุณหยุดทำงานสิ่งนี้จะไม่ทำงาน

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 ฉันได้ดำเนินการทั้งสองอย่างแล้วและการชะลอตัวของแต่ละรายการนั้นค่อนข้างน้อย (เพิ่มทั้งหมด 1.1 นาทีในการดำเนินการที่ใช้เวลา 27 นาทีในการดำเนินการ) วิธีนี้ฉันสามารถดูความคืบหน้าและกำหนดลักษณะเฉพาะของการดำเนินการเหล่านี้ฉันคิดว่านี่เป็นที่ยอมรับได้ช้าลง
cwharland

ยอดเยี่ยมดีใจที่มันช่วย ฉันแปลกใจจริง ๆ ที่การชะลอตัว (เมื่อฉันลองตัวอย่าง) ฉันคาดว่ามันจะแย่กว่านี้มาก
Andy Hayden

1
เพื่อเพิ่มประสิทธิภาพของวิธีการโพสต์ต่อไปฉันรู้สึกเกียจคร้านเกี่ยวกับการนำเข้าข้อมูล (แพนด้าก็ดีเกินกว่าที่จะจัดการกับ csv ยุ่ง !!) และรายการบางส่วนของฉัน (~ 1%) ได้แทรกการแทรกออกทั้งหมด (คิดว่าทั้งหมด บันทึกแทรกลงในเขตข้อมูลเดียว) การกำจัดสิ่งเหล่านี้ทำให้เกิดการเพิ่มความเร็วอย่างมากในการยกเลิกคุณสมบัติเนื่องจากไม่มีความเคลือบแคลงเกี่ยวกับสิ่งที่ต้องทำในระหว่างการดำเนินการแบบแยกส่วน
cwharland

1
ฉันลงไปที่ 8 นาที ... แต่ฉันเพิ่มบางสิ่งลงในการยกเลิกคุณสมบัติ (คุณสมบัติอื่น ๆ -> AUC ที่ดีขึ้น!) 8 นาทีนี้ต่อหนึ่งชิ้น (รวมเป็นสองชิ้นในขณะนี้) โดยแต่ละอันมีแถวละ 12 ล้านแถว ดังนั้น ... 16 นาทีในการดำเนินการหนักกว่า 24 ล้านแถวโดยใช้ HDFStore (และมีบางสิ่งในการยกเลิกคุณสมบัติ) ค่อนข้างดี. หวังว่าอินเทอร์เน็ตจะไม่ตัดสินฉันในความไม่รู้เบื้องต้นหรือความสับสนต่อการแทรกที่ยุ่งเหยิง =)
cwharland

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)

_tqdm_notebookหมายเหตุขีดในคำสั่งนำเข้าสำหรับ ตามที่กล่าวถึงในบทความอ้างอิงการพัฒนาอยู่ในช่วงเบต้าปลาย


8

สำหรับทุกคนที่ต้องการใช้ tqdm กับรหัสการใช้แพนด้าแบบขนานที่กำหนดเอง

(ฉันลองห้องสมุดหลายแห่งเพื่อทำการขนานกันในช่วงหลายปีที่ผ่านมา แต่ฉันไม่เคยพบโซลูชันการทำให้เท่าเทียมกัน 100% ส่วนใหญ่สำหรับฟังก์ชั่นการใช้งานและฉันต้องกลับมาหารหัส "คู่มือ" ของฉันเสมอ)

df_multi_core - นี่คือสิ่งที่คุณเรียก มันยอมรับ:

  1. วัตถุ df ของคุณ
  2. ชื่อฟังก์ชันที่คุณต้องการโทร
  3. ชุดย่อยของคอลัมน์ที่สามารถใช้งานได้ (ช่วยลดเวลา / หน่วยความจำ)
  4. จำนวนของงานที่ต้องรันแบบขนาน (-1 หรือละเว้นสำหรับทุกคอร์)
  5. kwargs อื่น ๆ ที่ฟังก์ชั่นของ df ยอมรับ (เช่น "แกน")

_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

ร้องเป็นรหัสทดสอบสำหรับการใช้แบบขนานกับ 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 สำหรับห้องสมุดที่ยอดเยี่ยมนี้!


1
ขอบคุณ @mork - โปรดเพิ่มgithub.com/tqdm/tqdm/wiki/How-to-make-a-great-Progress-Barหรือสร้างหน้าใหม่ที่github.com/tqdm/tqdm/wiki
casper dcl

ขอขอบคุณ แต่ต้องเปลี่ยนส่วนเหล่านี้: try: splits = np.array_split(df[subset], njobs) except ValueError: splits = np.array_split(df, njobs)เนื่องจากข้อยกเว้น KeyError แทน ValueError ให้เปลี่ยนเป็นข้อยกเว้นเพื่อจัดการทุกกรณี
Marius

ขอบคุณ @mork - คำตอบนี้ควรสูงกว่านี้
Andy

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/…เช่นนับ / len เป็นเปอร์เซ็นต์
Andy Hayden

อ๋อ - คุณจะมีคำสั่ง (จำนวนกลุ่ม) ดังนั้นขึ้นอยู่กับสิ่งที่คอขวดของคุณคือสิ่งนี้อาจสร้างความแตกต่าง
Jeff

บางทีสิ่งที่ใช้งานง่ายที่จะทำคือห่อสิ่งนี้ไว้ในlogged_apply(g, func)ฟังก์ชั่นที่คุณสามารถเข้าถึงคำสั่งซื้อและสามารถเข้าสู่ระบบตั้งแต่ต้น
Andy Hayden

ฉันทำตามที่กล่าวไว้ในคำตอบของฉันพร้อมกับอัพเดทร้อยละหน้าด้าน ที่จริงแล้วฉันไม่สามารถทำงานให้คุณได้ ... ฉันคิดว่าด้วย wraps bit หากการใช้งานของคุณสำหรับการสมัครนั้นไม่สำคัญเลย
Andy Hayden

1

ฉันได้เปลี่ยนคำตอบของเจฟฟ์เพื่อรวมทั้งหมดเพื่อให้คุณสามารถติดตามความคืบหน้าและตัวแปรที่จะพิมพ์ซ้ำทุก ๆ 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.