ใช้ฟังก์ชันกับ DataFrame แพนด้าที่จัดกลุ่มแบบขนานอย่างมีประสิทธิภาพ


89

ฉันมักจะต้องใช้ฟังก์ชันกับกลุ่มที่มีขนาดใหญ่มากDataFrame(ของประเภทข้อมูลผสม) และต้องการใช้ประโยชน์จากหลายคอร์

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

มีวิธีใดบ้างที่จะหลีกเลี่ยงการดองหรือหลีกเลี่ยงการคัดลอกโดยDataFrameสิ้นเชิง? ดูเหมือนว่าฟังก์ชันหน่วยความจำแบบแบ่งใช้ของโมดูลมัลติโพรเซสซิ่งจะถูก จำกัด ไว้ที่numpyอาร์เรย์ มีทางเลือกอื่นอีกไหม?


เท่าที่ฉันรู้ไม่มีวิธีที่จะแบ่งปันวัตถุโดยพลการ ฉันสงสัยว่าการดองกินเวลามากกว่าการได้รับจากการประมวลผลหลายขั้นตอนหรือไม่ บางทีคุณควรมองหาความเป็นไปได้ในการสร้างแพ็คเกจงานที่ใหญ่ขึ้นสำหรับแต่ละกระบวนการเพื่อลดเวลาในการดองญาติ ความเป็นไปได้อีกประการหนึ่งคือการใช้การประมวลผลหลายขั้นตอนเมื่อคุณสร้างกลุ่ม
Sebastian Werk

3
ฉันทำอะไรแบบนั้น แต่ใช้ UWSGI, Flask และ preforking: ฉันโหลดแพนด้าดาต้าเฟรมลงในกระบวนการแยกมัน x ครั้ง (ทำให้เป็นวัตถุหน่วยความจำที่ใช้ร่วมกัน) แล้วเรียกกระบวนการเหล่านั้นจากกระบวนการไพ ธ อนอื่นที่ฉันเชื่อมต่อผลลัพธ์ atm ฉันใช้ JSON เป็นกระบวนการสื่อสาร แต่สิ่งนี้กำลังจะมา (แต่ยังอยู่ในการทดลองขั้นสูง): pandas.pydata.org/pandas-docs/dev/io.html#msgpack-experimental
Carst

ยังไงก็ตามคุณเคยดู HDF5 แบบ chunking หรือไม่? (HDF5 ไม่ได้บันทึกสำหรับการเขียนพร้อมกัน แต่คุณยังสามารถบันทึกลงในไฟล์ที่แยกจากกันและในท้ายที่สุดสิ่งที่เชื่อมต่อกัน)
Carst

7
สิ่งนี้จะกำหนดเป้าหมายไว้ที่ 0.14 โปรดดูปัญหานี้: github.com/pydata/pandas/issues/5751
Jeff

4
@ เจฟฟ์ถูกผลักไปที่ 0.15 = (
pyCthon

คำตอบ:


12

จากความคิดเห็นด้านบนดูเหมือนว่าจะมีการวางแผนไว้pandasระยะหนึ่งแล้ว (นอกจากนี้ยังมีrosettaโครงการที่น่าสนใจซึ่งฉันเพิ่งสังเกตเห็น)

อย่างไรก็ตามจนกว่าทุกฟังก์ชันการทำงานแบบขนานจะรวมอยู่ในpandasฉันสังเกตเห็นว่ามันง่ายมากที่จะเขียนการขยายแบบขนานที่มีประสิทธิภาพและไม่ใช่หน่วยความจำเพื่อคัดลอกpandasโดยตรงโดยใช้cython+ OpenMPและ C ++

นี่คือตัวอย่างสั้น ๆ ของการเขียน groupby-sum คู่ขนานซึ่งการใช้งานมีลักษณะดังนี้:

import pandas as pd
import para_group_demo

df = pd.DataFrame({'a': [1, 2, 1, 2, 1, 1, 0], 'b': range(7)})
print para_group_demo.sum(df.a, df.b)

และผลลัพธ์คือ:

     sum
key     
0      6
1      11
2      4

หมายเหตุไม่ต้องสงสัยเลยว่าฟังก์ชันการทำงานของตัวอย่างง่ายๆนี้จะเป็นส่วนหนึ่งของในpandasที่สุด อย่างไรก็ตามบางสิ่งจะเป็นธรรมชาติมากขึ้นในการขนานใน C ++ ในบางครั้งและสิ่งสำคัญคือต้องตระหนักว่าการรวมสิ่งนี้เข้าpandasด้วยกันนั้นง่ายเพียงใด


ในการทำเช่นนี้ฉันเขียนนามสกุลไฟล์แหล่งเดียวที่เรียบง่ายซึ่งมีรหัสตามหลัง

เริ่มต้นด้วยการนำเข้าและคำจำกัดความประเภท

from libc.stdint cimport int64_t, uint64_t
from libcpp.vector cimport vector
from libcpp.unordered_map cimport unordered_map

cimport cython
from cython.operator cimport dereference as deref, preincrement as inc
from cython.parallel import prange

import pandas as pd

ctypedef unordered_map[int64_t, uint64_t] counts_t
ctypedef unordered_map[int64_t, uint64_t].iterator counts_it_t
ctypedef vector[counts_t] counts_vec_t

unordered_mapประเภทC ++ ใช้สำหรับการรวมโดยเธรดเดียวและvectorสำหรับการรวมโดยเธรดทั้งหมด

sumตอนนี้ฟังก์ชั่น เริ่มต้นด้วยมุมมองหน่วยความจำที่พิมพ์เพื่อการเข้าถึงที่รวดเร็ว:

def sum(crit, vals):
    cdef int64_t[:] crit_view = crit.values
    cdef int64_t[:] vals_view = vals.values

ฟังก์ชันจะดำเนินต่อไปโดยการหารกึ่งเท่ากันกับเธรด (ที่นี่ฮาร์ดโค้ดเป็น 4) และให้แต่ละเธรดรวมรายการในช่วง

    cdef uint64_t num_threads = 4
    cdef uint64_t l = len(crit)
    cdef uint64_t s = l / num_threads + 1
    cdef uint64_t i, j, e
    cdef counts_vec_t counts
    counts = counts_vec_t(num_threads)
    counts.resize(num_threads)
    with cython.boundscheck(False):
        for i in prange(num_threads, nogil=True): 
            j = i * s
            e = j + s
            if e > l:
                e = l
            while j < e:
                counts[i][crit_view[j]] += vals_view[j]
                inc(j)

เมื่อเธรดเสร็จสมบูรณ์ฟังก์ชันจะรวมผลลัพธ์ทั้งหมด (จากช่วงที่แตกต่างกัน) เข้าเป็นหนึ่งเดียวunordered_map:

    cdef counts_t total
    cdef counts_it_t it, e_it
    for i in range(num_threads):
        it = counts[i].begin()
        e_it = counts[i].end()
        while it != e_it:
            total[deref(it).first] += deref(it).second
            inc(it)        

สิ่งที่เหลือคือการสร้างDataFrameและส่งคืนผลลัพธ์:

    key, sum_ = [], []
    it = total.begin()
    e_it = total.end()
    while it != e_it:
        key.append(deref(it).first)
        sum_.append(deref(it).second)
        inc(it)

    df = pd.DataFrame({'key': key, 'sum': sum_})
    df.set_index('key', inplace=True)
    return df
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.