web-dev-qa-db-ja.com

Pandas df.iterrows()並列化

次のコードを並列化したい:

_for row in df.iterrows():
    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)]))
_

各行を個別に処理できるため、multiprocessing.Pool()を使用しようとしましたが、DataFrameを共有する方法がわかりません。また、これがパンダとの並列化を行うための最良のアプローチであるかどうかもわかりません。助けがありますか?

19
alec_djinn

@Khrisがコメントで述べたように、データフレームをいくつかの大きなチャンクに分割し、各チャンクを並行して繰り返す必要があります。データフレームをランダムなサイズのチャンクに任意に分割できますが、使用する予定のプロセスの数に基づいて、データフレームを同じサイズのチャンクに分割する方が合理的です。幸いなことに、他の誰かが すでにその部分を行う方法を考え出しています を持っています:

_# 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)]
_

これにより、データフレームをチャンクで含むリストが作成されます。ここで、データを操作する関数とともにプールに渡す必要があります。

_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)
_

この時点で、resultは、操作後の各チャンクを保持するリストになります。この場合、すべての値が二乗されています。現在の問題は、元のデータフレームが変更されていないため、既存の値をすべてプールの結果で置き換える必要があることです。

_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]
_

現在、データフレームを操作する機能はベクトル化されており、チャンクに分割するのではなく、データフレーム全体に単純に適用した場合、おそらくより高速になります。ただし、あなたの場合、関数は各チャンクの各行を反復処理してからチャンクを返します。これにより、_num_process_行を一度に処理できます。

_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
_

次に、元のデータフレームの値を再割り当てし、このプロセスを正常に並列化しました。

使用するプロセスの数

最適なパフォーマンスは、この質問に対する答えに依存します。 「すべてのプロセス!!!!」 1つの答えであり、より良い答えははるかに微妙です。特定のポイントの後、問題でより多くのプロセスをスローすると、実際にはそれ以上のオーバーヘッドが生じます。これは Amdahlの法則 として知られています。繰り返しますが、幸いなことに、他の人がすでにこの質問に取り組んでいます。

  1. Pythonマルチプロセッシングのプールプロセス制限
  2. 並行して実行するプロセスの数は?

適切なデフォルトは、multiprocessing.cpu_count()を使用することです。これは_multiprocessing.Pool_のデフォルトの動作です。 ドキュメントによると 「プロセスがNoneの場合、cpu_count()によって返される数値が使用されます。」だからこそ、最初に_num_processes_をmultiprocessing.cpu_count()に設定します。このように、より強力なマシンに移動する場合、_num_processes_変数を直接変更することなく、マシンからメリットを得ることができます。

41
TheF1rstPancake

より速い方法(私の場合は約10%):

受け入れられた答えとの主な違い:_pd.concat_および_np.array_split_を使用して、dataframreを分割および結合します。

_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
_

ここで、funcは、dfに適用する関数です。複数の引数に対してpartial(func, arg=arg_val)を使用します。

14
ic_fl2

たとえば、dask.dataframeの使用を検討してください。同様の質問に対するこの例で示されている: 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')
2
Robert