次のコードを並列化したい:
_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を共有する方法がわかりません。また、これがパンダとの並列化を行うための最良のアプローチであるかどうかもわかりません。助けがありますか?
@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の法則 として知られています。繰り返しますが、幸いなことに、他の人がすでにこの質問に取り組んでいます。
適切なデフォルトは、multiprocessing.cpu_count()
を使用することです。これは_multiprocessing.Pool
_のデフォルトの動作です。 ドキュメントによると 「プロセスがNoneの場合、cpu_count()によって返される数値が使用されます。」だからこそ、最初に_num_processes
_をmultiprocessing.cpu_count()
に設定します。このように、より強力なマシンに移動する場合、_num_processes
_変数を直接変更することなく、マシンからメリットを得ることができます。
より速い方法(私の場合は約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)
を使用します。
たとえば、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')