Pandas DataFrame uygulansın () tüm çekirdekleri kullansın mı?


105

Ağustos 2017 itibarıyla, Pandas DataFame.apply () maalesef hala tek bir çekirdekle çalışmakla sınırlıdır, bu da çok çekirdekli bir makinenin çalıştırdığınızda işlem süresinin çoğunu boşa harcayacağı anlamına gelir df.apply(myfunc, axis=1).

Uygulamayı paralel olarak bir dataframe üzerinde çalıştırmak için tüm çekirdeklerinizi nasıl kullanabilirsiniz?

Yanıtlar:


81

swifterPaketi kullanabilirsiniz :

pip install swifter

Pandalar için bir eklenti olarak çalışır ve applyişlevi yeniden kullanmanıza izin verir :

import swifter

def some_function(data):
    return data * 10

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

Vektörize edilip edilmediğine bakılmaksızın (yukarıdaki örnekte olduğu gibi) işlevi paralelleştirmenin en verimli yolunu otomatik olarak bulacaktır.

GitHub'da daha fazla örnek ve bir performans karşılaştırması mevcuttur. Paketin aktif geliştirme aşamasında olduğunu ve bu nedenle API'nin değişebileceğini unutmayın.

Ayrıca, bunun dize sütunları için otomatik olarak çalışmayacağını unutmayın . Dizeleri kullanırken, Swifter applyparalel olmayacak "basit" Pandalara geri dönecektir. Bu durumda, onu kullanmaya zorlamak bile daskperformans iyileştirmeleri yaratmaz ve veri kümenizi manuel olarak bölmek ve kullanarak paralelmultiprocessing hale getirmek daha iyi olur .


1
Asıl merakımız, paralel uygulama yaparken kullandığı çekirdek sayısını sınırlamanın bir yolu var mı? Paylaşılan bir sunucum var, bu yüzden 32 çekirdeğin hepsini alırsam kimse mutlu olmayacak.
Maksim Khaitovich

1
@MaximHaytovich bilmiyorum. Swifter arka planda dask kullanır, bu yüzden belki şu ayarlara saygı duyar: stackoverflow.com/a/40633117/435093 - aksi takdirde GitHub'da bir sorunu açmanızı öneririm. Yazar çok duyarlı.
slhck

@slhck teşekkürler! Biraz daha kazacak. Zaten Windows sunucusunda çalışmıyor gibi görünüyor - sadece oyuncak görevinde hiçbir şey yapmıyor
Maksim Khaitovich

lütfen bu soruyu cevaplamama yardım eder misin: - stackoverflow.com/questions/53561794/…
ak3191

2
Dizeler için şöyle ekleyin allow_dask_on_strings(enable=True): df.swifter.allow_dask_on_strings(enable=True).apply(some_function) Kaynak: github.com/jmcarpenter2/swifter/issues/45
Sumit Sidana

105

En basit yol, Dask'ın map_partitions'ı kullanmaktır . Bu ithalatlara ihtiyacınız var (yapmanız gerekecek pip install dask):

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

ve sözdizimi

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)  

(16 çekirdeğiniz varsa 30'un uygun sayıda bölüm olduğuna inanıyorum). Tamlık için makinemdeki farkı zamanladım (16 çekirdek):

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

Pandalardan 10 hızlandırma faktörü vermek , bölümlere dask uygulamak için geçerlidir. Elbette, vektörleştirebileceğiniz bir fonksiyonunuz varsa, yapmalısınız - bu durumda ( y*(x**2+1)) fonksiyonu önemsiz bir şekilde vektörleştirilmiştir, ancak vektörleştirilmesi imkansız birçok şey vardır.


2
Bilmek harika, yayınladığınız için teşekkürler. Neden 30 bölüm seçtiğinizi açıklayabilir misiniz? Bu değeri değiştirirken performans değişiyor mu?
Andrew L

4
@AndrewL Her bölüme ayrı bir işlemle hizmet verildiğini varsayıyorum ve 16 çekirdekli 16 veya 32 işlemin aynı anda çalışabileceğini varsayıyorum. Denedim ve performans 32 bölüme kadar yükseliyor gibi görünüyor, ancak daha fazla artışın yararlı bir etkisi yok. Dört çekirdekli bir makineyle 8 bölüm isteyeceğinizi varsayıyorum. 16 ile 32 arasında bazı gelişmeler olduğunu fark ettiğime dikkat edin, bu yüzden gerçekten 2x $ NUM_PROCESSORS
Roko Mijic

10
Tek şeyThe get= keyword has been deprecated. Please use the scheduler= keyword instead with the name of the desired scheduler like 'threads' or 'processes'
wordsforthewise

6
Dask v0.20.0 ve üzeri için, ddata.map_partitions (lambda df: df.apply ((lambda satır: işlevim (* satır)), eksen = 1)). Hesaplama (zamanlayıcı = 'süreçler') veya aşağıdakilerden birini kullanın diğer planlayıcı seçenekleri. Geçerli kod "TypeError: get = anahtar sözcüğü kaldırıldı. Lütfen bunun yerine scheduler = anahtar sözcüğünü kullanın, bunun yerine 'thread' veya 'process'ler' gibi istenen zamanlayıcının adı kullanın"
mork

1
Bunu yapmadan önce, veri çerçevesinin fırlatırken yinelenen dizinleri olmadığından emin olun ValueError: cannot reindex from a duplicate axis. Bunu aşmak için, ya yinelenen dizinleri kaldırmalı ya da dizinlerinizi df = df[~df.index.duplicated()]sıfırlamalısınız df.reset_index(inplace=True).
Habib Karbasian

24

pandarallelbunun yerine deneyebilirsiniz : Pandalarınızı tüm CPU'larınızda paralel hale getirmek için basit ve etkili bir araç (Linux ve macOS'ta)

  • Paralelleştirmenin bir maliyeti vardır (yeni süreçleri desteklemek, paylaşılan bellek yoluyla veri göndermek, vb.), Bu nedenle paralelleştirme yalnızca paralelleştirilecek hesaplama miktarı yeterince yüksekse etkilidir. Çok az miktarda veri için, parallezasyon kullanmak her zaman buna değmez.
  • Uygulanan işlevler lambda işlevleri OLMAMALIDIR.
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)

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


merhaba, bir sorunu çözemiyorum, pandarallel kullanarak bir Hata var: AttributeError: Yerel nesne 'ready_worker. <locals> .closure. <locals> .wrapper'. Bana bu konuda yardım edebilir misin?
Alex Cam

@Alex Sry Ben o modülün geliştiricisi değilim. Kodlarınız neye benziyor? "İç işlevlerinizi" global olarak ilan etmeyi deneyebilir misiniz? (sadece tahmin et)
G_KOBELIEF

@AlexCam İşleviniz başka bir işlevin dışında tanımlanmalıdır, böylece python çoklu işlem için onu seçebilir
Kenan

1
@G_KOBELIEF Python> 3.6 ile pandaparallel ile lambda fonksiyonunu kullanabiliriz
user110244

18

Yerel python'da kalmak istiyorsanız:

import multiprocessing as mp

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

işlevi f, veri çerçevesi sütununa paralel bir şekilde coluygulayacaktırdf


Bunun gibi bir yaklaşımı takiben bir got ValueError: Length of values does not match length of indexdan __setitem__içinde pandas/core/frame.py. Yanlış bir şey yapıp yapmadığımdan veya atama iş df['newcol']parçacığı güvenli olmadığından emin değilim.
Çıngırak

2
Uzunluğun df ile eşleşip eşleşmediğini kontrol etmek için pool.map'yi bir ara temp_result listesine yazabilir ve ardından bir df ['newcol'] = temp_result?
Olivier Cruchant

yeni sütunu yaratmak mı istiyorsunuz? ne kullanırdın
Olivier Cruchant

evet, haritanın sonucunu veri çerçevesinin yeni sütununa atama. Eşleme, f işlevine gönderilen her öbeğin sonucunun bir listesini döndürmüyor mu? Peki bunu 'newcol' sütununa atadığınızda ne olur? Pandalar ve Python 3'ü Kullanma
Mina

Aslında gerçekten düzgün çalışıyor! Onu denedin mi? Gönderilenle aynı sırada df'nin aynı uzunlukta bir listesini oluşturur. Paralel bir şekilde c2 = f (c1) yapar. Python'da çoklu işlem yapmanın daha basit bir yolu yoktur. Performans açısından, Ray iyi şeyler de yapabilir gibi görünüyor (doğru datascience.com/… ), ancak bu kadar olgun değil ve kurulum benim deneyimime göre her zaman sorunsuz gitmiyor
Olivier Cruchant

2

İşte pandaların uygulandığı sklearn temel transformatörünün bir örneği paralelleştirilmiştir.

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

daha fazla bilgi için bkz. https://towardsdatascience.com/4-easy-steps-to-improve-your-machine-learning-code-performance-88a0b0eeffa8


0

Tüm (fiziksel veya mantıksal) çekirdek kullanmak için deneyebilirsiniz mapplybir alternatif olarak swifterve pandarallel.

Başlangıçta çekirdek miktarını (ve öbekleme davranışını) ayarlayabilirsiniz:

import pandas as pd
import mapply

mapply.init(n_workers=-1)

...

df.mapply(myfunc, axis=1)

Varsayılan olarak ( n_workers=-1), paket sistemde bulunan tüm fiziksel CPU'ları kullanır. Sisteminiz hiper iş parçacığı kullanıyorsa (genellikle fiziksel CPU miktarının iki katı mapplyortaya çıkar ), sistemdeki diğer işlemlere göre çoklu işlem havuzuna öncelik vermek için fazladan bir çalışan oluşturur.

Tanımınıza bağlı olarak, all your coresbunun yerine tüm mantıksal çekirdekleri de kullanabilirsiniz (bunun gibi CPU'ya bağlı işlemlerin fiziksel CPU'lar için savaşacağını ve bu da işleminizi yavaşlatabileceğini unutmayın):

import multiprocessing
n_workers = multiprocessing.cpu_count()

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