web-dev-qa-db-de.com

Pandas df.iterrow () Parallelisierung

Ich möchte den folgenden Code parallelisieren:

for row in df.iterrow():
    idx = row[0]
    k = row[1]['Chromosome']
    start,end = row[1]['Bin'].split('-')

    sequence = sequence_from_coordinates(k,1,start,end) #slow download form http

    df.set_value(idx,'GC%',gc_content(sequence,percent=False,verbose=False))
    df.set_value(idx,'G4 repeats', sum([len(list(i)) for i in g4_scanner(sequence)]))
    df.set_value(idx,'max flexibility',max([item[1] for item in dna_flex(sequence,verbose=False)]))

Ich habe versucht, multiprocessing.Pool() zu verwenden, da jede Zeile unabhängig verarbeitet werden kann, aber ich kann nicht herausfinden, wie der Datenrahmen gemeinsam genutzt wird. Ich bin auch nicht sicher, ob dies der beste Ansatz ist, um eine Parallelisierung mit Pandas zu erreichen. Irgendeine Hilfe?

12
alec_djinn

Wie @Khris in seinem Kommentar sagte, sollten Sie Ihren Datenrahmen in ein paar große Brocken aufteilen und über jeden Block parallel iterieren. Sie können den Datenrahmen willkürlich in zufällig große Blöcke aufteilen, es ist jedoch sinnvoller, den Datenrahmen basierend auf der Anzahl der Prozesse, die Sie verwenden möchten, in gleich große Blöcke aufzuteilen. Zum Glück hat jemand anderes bereits herausgefunden, wie man diesen Teil erledigt für uns:

# don't forget to import
import pandas as pd
import multiprocessing

# create as many processes as there are CPUs on your machine
num_processes = multiprocessing.cpu_count()

# calculate the chunk size as an integer
chunk_size = int(df.shape[0]/num_processes)

# this solution was reworked from the above link.
# will work even if the length of the dataframe is not evenly divisible by num_processes
chunks = [df.ix[df.index[i:i + chunk_size]] for i in range(0, df.shape[0], chunk_size)]

Dadurch wird eine Liste erstellt, die unseren Datenrahmen in Abschnitten enthält. Nun müssen wir sie zusammen mit einer Funktion, die die Daten manipuliert, in unseren Pool übergeben.

def func(d):
   # let's create a function that squares every value in the dataframe
   return d * d

# create our pool with `num_processes` processes
pool = multiprocessing.Pool(processes=num_processes)

# apply our function to each chunk in the list
result = pool.map(func, chunks)

An diesem Punkt wird result eine Liste sein, die jeden Abschnitt nach der Bearbeitung enthält. In diesem Fall wurden alle Werte quadriert. Das Problem ist jetzt, dass der ursprüngliche Datenrahmen nicht geändert wurde. Daher müssen wir alle vorhandenen Werte durch die Ergebnisse aus unserem Pool ersetzen.

for i in range(len(result)):
   # since result[i] is just a dataframe
   # we can reassign the original dataframe based on the index of each chunk
   df.ix[result[i].index] = result[i]

Nun ist meine Funktion zum Manipulieren meines Datenrahmens vektorisiert und wäre wahrscheinlich schneller gewesen, wenn ich ihn einfach auf den gesamten Datenrahmen angewendet hätte, anstatt ihn in Stücke zu teilen. In Ihrem Fall würde Ihre Funktion jedoch jede Zeile der einzelnen Blöcke durchlaufen und dann den Abschnitt zurückgeben. Dadurch können Sie num_process-Zeilen gleichzeitig bearbeiten.

def func(d):
   for row in d.iterrow():
      idx = row[0]
      k = row[1]['Chromosome']
      start,end = row[1]['Bin'].split('-')

      sequence = sequence_from_coordinates(k,1,start,end) #slow download form http
      d.set_value(idx,'GC%',gc_content(sequence,percent=False,verbose=False))
      d.set_value(idx,'G4 repeats', sum([len(list(i)) for i in g4_scanner(sequence)]))
      d.set_value(idx,'max flexibility',max([item[1] for item in dna_flex(sequence,verbose=False)]))
   # return the chunk!
   return d

Anschließend weisen Sie die Werte im ursprünglichen Datenrahmen erneut zu und haben diesen Vorgang erfolgreich parallelisiert.

Wie viele Prozesse sollte ich verwenden?

Ihre optimale Leistung hängt von der Antwort auf diese Frage ab. Während "alle Prozesse !!!!" ist eine Antwort, eine bessere Antwort ist viel nuancierter. Nach einem bestimmten Punkt verursacht das Aufwerfen von mehr Prozessen auf ein Problem tatsächlich mehr Aufwand als es wert ist. Dies ist bekannt als Amdahls Gesetz . Wir haben das Glück, dass sich andere bereits mit dieser Frage beschäftigt haben: 

  1. Pool-Prozesslimit für Python-Multiprozessoren
  2. Wie viele Prozesse sollte ich parallel ausführen?

Eine gute Standardeinstellung ist multiprocessing.cpu_count(), das Standardverhalten von multiprocessing.Pool. Laut Dokumentation "Wenn prozessiert Keine ist, wird die von cpu_count () zurückgegebene Nummer verwendet." Deshalb habe ich num_processes am Anfang auf multiprocessing.cpu_count() gesetzt. Auf diese Weise profitieren Sie davon, wenn Sie zu einer beefier-Maschine wechseln, ohne die Variable num_processes direkt ändern zu müssen.

30
TheF1rstPancake

Ein schneller Weg (ca. 10% in meinem Fall):

Hauptunterschiede zur akzeptierten Antwort: Verwenden Sie pd.concat und np.array_split, um das Datenbild zu teilen und zu verbinden.

import multiprocessing
import numpy as np


def parallelize_dataframe(df, func):
    num_cores = multiprocessing.cpu_count()-1  #leave one free to not freeze machine
    num_partitions = num_cores #number of partitions to split dataframe
    df_split = np.array_split(df, num_partitions)
    pool = multiprocessing.Pool(num_cores)
    df = pd.concat(pool.map(func, df_split))
    pool.close()
    pool.join()
    return df

dabei ist func die Funktion, die Sie auf df anwenden möchten. Verwenden Sie partial(func, arg=arg_val) für mehr als ein Argument. 

10
ic_fl2

Ziehen Sie die Verwendung von dask.dataframe in Betracht, z. In diesem Beispiel für eine ähnliche Frage gezeigt: https://stackoverflow.com/a/53923034/4340584

import dask.dataframe as ddf
df_dask = ddf.from_pandas(df, npartitions=4)   # where the number of partitions is the number of cores you want to use
df_dask['output'] = df_dask.apply(lambda x: your_function(x), meta=('str')).compute(scheduler='multiprocessing')
1
Robert