我想并行化以下代码:
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。我也不确定这是与熊猫并行化的最佳方法。有什么帮助吗?
答案 0 :(得分:37)
正如@Khris在评论中所说,你应该将你的数据框分成几个大块并且并行地遍历每个块。您可以随意将数据帧拆分为随机大小的块,但根据您计划使用的进程数将数据帧划分为相同大小的块更有意义。幸运的是其他人为我们already figured out how to do that part:
# 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
然后重新分配原始数据框中的值,并且您已成功并行化此过程。
您的最佳表现将取决于此问题的答案。虽然“所有的过程!!!!”是一个答案,更好的答案更微妙。在某一点之后,在问题上抛出更多进程实际上会产生比其值更多的开销。这称为Amdahl's Law。我们很幸运,其他人已经为我们解决了这个问题:
一个好的默认设置是使用multiprocessing.cpu_count()
,这是multiprocessing.Pool
的默认行为。 According to the documentation“如果进程为None,则使用cpu_count()返回的数字。”这就是我将num_processes
开头设置为multiprocessing.cpu_count()
的原因。这样,如果您转向更强大的机器,您可以从中获益,而无需直接更改num_processes
变量。
答案 1 :(得分:12)
更快的方式(在我的情况下约为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)
获取更多该参数。
答案 2 :(得分:1)
考虑使用dask.dataframe,例如在此示例中显示的是类似问题:https://stackoverflow.com/a/53923034/4340584
lambda:
答案 3 :(得分:0)
要在数据帧的分区上使用 Dask(而不是在 dask.apply
上运行的 axis
),您可以使用 map_partitions
:
import multiprocessing
import dask.dataframe as ddf
# get num cpu cores
num_partitions = multiprocessing.cpu_count()
# create dask DF
df_dask = ddf.from_pandas(your_dataframe, npartitions=num_partitions)
# apply func to every partition in parallel
output = df_dask.map_partitions(func, meta=('output_col1_type','output_col2_type')).compute(scheduler='multiprocessing')