Làm cho Pandas DataFrame áp dụng () sử dụng tất cả các lõi?


105

Kể từ tháng 8 năm 2017, Pandas DataFame.apply () rất tiếc vẫn bị giới hạn hoạt động với một lõi đơn, có nghĩa là máy đa lõi sẽ lãng phí phần lớn thời gian tính toán khi bạn chạy df.apply(myfunc, axis=1).

Làm cách nào bạn có thể sử dụng tất cả các lõi của mình để chạy song song áp dụng trên khung dữ liệu?

Câu trả lời:


79

Bạn có thể sử dụng swiftergói:

pip install swifter

Nó hoạt động như một plugin cho gấu trúc, cho phép bạn sử dụng lại applychức năng:

import swifter

def some_function(data):
    return data * 10

data['out'] = data['in'].swifter.apply(some_function)

Nó sẽ tự động tìm ra cách hiệu quả nhất để song song hóa hàm, bất kể nó có được vector hóa (như trong ví dụ trên) hay không.

Các ví dụ khácso sánh hiệu suất có sẵn trên GitHub. Lưu ý rằng gói đang được phát triển tích cực, vì vậy API có thể thay đổi.

Cũng lưu ý rằng điều này sẽ không hoạt động tự động đối với các cột chuỗi. Khi sử dụng chuỗi, Swifter sẽ dự phòng cho một con gấu trúc “đơn giản” apply, không phải là cặp song song. Trong trường hợp này, ngay cả việc buộc nó sử dụng dasksẽ không tạo ra các cải tiến về hiệu suất và tốt hơn là bạn chỉ nên tách tập dữ liệu của mình theo cách thủ công và sử dụng song songmultiprocessing .


1
Sự tò mò thuần túy của chúng tôi, có cách nào để giới hạn số lõi nó sử dụng khi áp dụng song song không? Tôi có một máy chủ chia sẻ vì vậy nếu tôi lấy tất cả 32 lõi sẽ không ai hài lòng.
Maksim Khaitovich

1
@MaximHaytovich Tôi không biết. Swifter sử dụng dask trong nền, vì vậy có thể nó tôn trọng các cài đặt này: stackoverflow.com/a/40633117/435093 - nếu không, tôi khuyên bạn nên mở sự cố trên GitHub. Tác giả rất phản hồi.
slhck

@slhck cảm ơn! Sẽ đào sâu hơn một chút. Nó dường như không hoạt động trên windows server anyway - chỉ bị treo không làm bất cứ điều gì về công việc đồ chơi
Maksim Khaitovich

bạn có thể vui lòng giúp tôi trả lời điều này không: - stackoverflow.com/questions/53561794/…
ak3191

2
Đối với các chuỗi, chỉ cần thêm allow_dask_on_strings(enable=True)như thế này: df.swifter.allow_dask_on_strings(enable=True).apply(some_function) Nguồn: github.com/jmcarpenter2/swifter/issues/45
Sumit Sidana

104

Cách đơn giản nhất là sử dụng map_partitions của Dask . Bạn cần những lần nhập này (bạn sẽ cần pip install dask):

import pandas as pd
import dask.dataframe as dd
from dask.multiprocessing import get

và cú pháp là

data = <your_pandas_dataframe>
ddata = dd.from_pandas(data, npartitions=30)

def myfunc(x,y,z, ...): return <whatever>

res = ddata.map_partitions(lambda df: df.apply((lambda row: myfunc(*row)), axis=1)).compute(get=get)  

(Tôi tin rằng 30 là số lượng phân vùng phù hợp nếu bạn có 16 lõi). Chỉ để hoàn thiện, tôi đã tính thời gian chênh lệch trên máy của mình (16 lõi):

data = pd.DataFrame()
data['col1'] = np.random.normal(size = 1500000)
data['col2'] = np.random.normal(size = 1500000)

ddata = dd.from_pandas(data, npartitions=30)
def myfunc(x,y): return y*(x**2+1)
def apply_myfunc_to_DF(df): return df.apply((lambda row: myfunc(*row)), axis=1)
def pandas_apply(): return apply_myfunc_to_DF(data)
def dask_apply(): return ddata.map_partitions(apply_myfunc_to_DF).compute(get=get)  
def vectorized(): return myfunc(data['col1'], data['col2']  )

t_pds = timeit.Timer(lambda: pandas_apply())
print(t_pds.timeit(number=1))

28.16970546543598

t_dsk = timeit.Timer(lambda: dask_apply())
print(t_dsk.timeit(number=1))

2.708152851089835

t_vec = timeit.Timer(lambda: vectorized())
print(t_vec.timeit(number=1))

0,010668013244867325

Đưa ra hệ số 10 tốc độ đi từ gấu trúc áp dụng cho dask áp dụng trên các phân vùng. Tất nhiên, nếu bạn có một hàm mà bạn có thể vectơ hóa, thì bạn nên - trong trường hợp này, hàm ( y*(x**2+1)) được vectơ hóa tầm thường, nhưng có rất nhiều thứ không thể vectơ hóa.


2
Thật tuyệt khi biết, cảm ơn vì đã đăng bài. Bạn có thể giải thích lý do tại sao bạn chọn 30 phân vùng? Hiệu suất có thay đổi khi thay đổi giá trị này không?
Andrew L

4
@AndrewL Tôi giả sử rằng mỗi phân vùng được phục vụ bởi một quy trình riêng biệt và với 16 lõi, tôi giả định rằng 16 hoặc 32 quy trình có thể chạy đồng thời. Tôi đã dùng thử và hiệu suất dường như cải thiện lên đến 32 phân vùng, nhưng việc tăng thêm không có tác dụng có lợi. Tôi cho rằng với một máy quad-core bạn muốn 8 phân vùng, vv Lưu ý rằng tôi đã thông báo một số cải tiến từ 16 đến 32, vì vậy tôi nghĩ rằng bạn thực sự muốn $ NUM_PROCESSORS 2x
Roko Mijic

9
Chỉ có điều làThe get= keyword has been deprecated. Please use the scheduler= keyword instead with the name of the desired scheduler like 'threads' or 'processes'
wordsforthewise

6
Đối với dask v0.20.0 trở đi, hãy sử dụng ddata.map_partitions (lambda df: df.apply ((lambda row: myfunc (* row)), axis = 1)). Compute (Scheduler = 'process') hoặc một trong các các tùy chọn lập lịch khác. Mã hiện tại ném "TypeError: Từ khóa get = đã bị xóa. Vui lòng sử dụng từ khóa Scheduler = thay thế bằng tên của bộ lập lịch mong muốn như 'thread' hoặc 'process'"
mork

1
Đảm bảo rằng trước khi bạn làm điều này, khung dữ liệu không có chỉ mục trùng lặp khi nó ném ValueError: cannot reindex from a duplicate axis. Để giải quyết vấn đề đó, bạn nên xóa các chỉ mục trùng lặp df = df[~df.index.duplicated()]hoặc đặt lại các chỉ mục của mình trước df.reset_index(inplace=True).
Habib Karbasian

24

bạn có thể thử pandarallelthay vào đó: Một công cụ đơn giản và hiệu quả để song song hóa các hoạt động của gấu trúc trên tất cả các CPU của bạn (Trên Linux và macOS)

  • Song song hóa có chi phí (cài đặt các quy trình mới, gửi dữ liệu qua bộ nhớ dùng chung, v.v.), do đó, song song chỉ có hiệu quả nếu số lượng tính toán để song song hóa đủ cao. Đối với lượng dữ liệu rất ít, việc sử dụng parallezation không phải lúc nào cũng có giá trị.
  • Các hàm được áp dụng KHÔNG được là các hàm lambda.
from pandarallel import pandarallel
from math import sin

pandarallel.initialize()

# FORBIDDEN
df.parallel_apply(lambda x: sin(x**2), axis=1)

# ALLOWED
def func(x):
    return sin(x**2)

df.parallel_apply(func, axis=1)

xem https://github.com/nalepae/pandarallel


xin chào, tôi không thể giải quyết một vấn đề, bằng cách sử dụng pandarallel, có một Lỗi: AttributeError: Không thể chọn đối tượng cục bộ 'standard_worker. <locals> .closure. <locals> .wrapper'. Bạn có thể giúp tôi với điều này?
Alex Cam

@Alex Sry Tôi không phải là nhà phát triển của mô-đun đó. Mã của bạn trông như thế nào? Bạn có thể thử khai báo "các hàm bên trong" của mình là toàn cục? (chỉ đoán thôi)
G_KOBELIEF

@AlexCam Chức năng của bạn nên được xác định bên ngoài chức năng khác để python có thể chọn nó cho quá trình đa xử lý
Kenan

1
@G_KOBELIEF Với Python> 3.6, chúng ta có thể sử dụng hàm lambda với pandaparallel
user110244

18

Nếu bạn muốn ở trong trăn bản địa:

import multiprocessing as mp

with mp.Pool(mp.cpu_count()) as pool:
    df['newcol'] = pool.map(f, df['col'])

sẽ áp dụng hàm ftheo kiểu song song cho cột colkhung dữ liệudf


Theo một cách tiếp cận như thế này, tôi đã nhận được một ValueError: Length of values does not match length of indextừ __setitem__trong pandas/core/frame.py. Không chắc liệu tôi có làm sai điều gì không, hoặc nếu việc gán cho df['newcol']không phải là threadsafe.
Rattle

2
Bạn có thể ghi pool.map vào danh sách temp_result trung gian để cho phép kiểm tra xem độ dài có khớp với df hay không và sau đó thực hiện df ['newcol'] = temp_result?
Olivier Cruchant

ý bạn là tạo cột mới? bạn sẽ sử dụng cái gì?
Olivier Cruchant

có, chỉ định kết quả của bản đồ cho cột mới của khung dữ liệu. Bản đồ không trả về danh sách kết quả của từng đoạn được gửi đến hàm f? Vậy điều gì sẽ xảy ra khi bạn gán nó cho cột 'newcol? Sử dụng gấu trúc và Python 3
Mina

Nó thực sự hoạt động rất trơn tru! Bạn đã thử à? Nó tạo ra một danh sách có cùng độ dài của df, cùng thứ tự với những gì đã được gửi. Nghĩa đen là c2 = f (c1) theo kiểu song song. Không có cách nào đơn giản hơn để đa xử lý trong python. Về hiệu suất, có vẻ như Ray cũng có thể làm những điều tốt (theo hướng dẫn này, nhưng nó không hoàn thiện như vậy và quá trình cài đặt không phải lúc nào cũng suôn sẻ theo kinh nghiệm của tôi
Olivier Cruchant

2

Đây là một ví dụ về máy biến áp cơ sở sklearn, trong đó gấu trúc áp dụng được mắc song song

import multiprocessing as mp
from sklearn.base import TransformerMixin, BaseEstimator

class ParllelTransformer(BaseEstimator, TransformerMixin):
    def __init__(self,
                 n_jobs=1):
        """
        n_jobs - parallel jobs to run
        """
        self.variety = variety
        self.user_abbrevs = user_abbrevs
        self.n_jobs = n_jobs
    def fit(self, X, y=None):
        return self
    def transform(self, X, *_):
        X_copy = X.copy()
        cores = mp.cpu_count()
        partitions = 1

        if self.n_jobs <= -1:
            partitions = cores
        elif self.n_jobs <= 0:
            partitions = 1
        else:
            partitions = min(self.n_jobs, cores)

        if partitions == 1:
            # transform sequentially
            return X_copy.apply(self._transform_one)

        # splitting data into batches
        data_split = np.array_split(X_copy, partitions)

        pool = mp.Pool(cores)

        # Here reduce function - concationation of transformed batches
        data = pd.concat(
            pool.map(self._preprocess_part, data_split)
        )

        pool.close()
        pool.join()
        return data
    def _transform_part(self, df_part):
        return df_part.apply(self._transform_one)
    def _transform_one(self, line):
        # some kind of transformations here
        return line

để biết thêm thông tin, hãy xem https://towardsdatascience.com/4-easy-steps-to-improve-your-machine-learning-code-performance-88a0b0eeffa8


0

Để sử dụng tất cả các lõi (vật lý hoặc logic), bạn có thể thử mapplythay thế cho swifterpandarallel.

Bạn có thể đặt số lượng lõi (và hành vi phân khúc) dựa trên init:

import pandas as pd
import mapply

mapply.init(n_workers=-1)

...

df.mapply(myfunc, axis=1)

Theo default ( n_workers=-1), gói sử dụng tất cả các CPU vật lý có sẵn trên hệ thống. Nếu hệ thống của bạn sử dụng siêu phân luồng (thường gấp đôi số lượng CPU vật lý sẽ hiển thị), mapplysẽ sinh ra thêm một nhân viên để ưu tiên nhóm đa xử lý hơn các quy trình khác trên hệ thống.

Tùy thuộc vào định nghĩa của all your coresbạn, bạn cũng có thể sử dụng tất cả các lõi logic để thay thế (hãy cẩn thận như thế này, các quy trình liên kết với CPU sẽ đấu tranh cho các CPU vật lý, điều này có thể làm chậm hoạt động của bạn):

import multiprocessing
n_workers = multiprocessing.cpu_count()

# or more explicit
import psutil
n_workers = psutil.cpu_count(logical=True)
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.