Pandas df.iterrows()并行化

时间:2016-11-01 09:39:12

标签: python pandas multiprocessing

我想并行化以下代码:

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。我也不确定这是与熊猫并行化的最佳方法。有什么帮助吗?

4 个答案:

答案 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。我们很幸运,其他人已经为我们解决了这个问题:

  1. Python multiprocessing's Pool process limit
  2. How many processes should I run in parallel?
  3. 一个好的默认设置是使用multiprocessing.cpu_count(),这是multiprocessing.Pool的默认行为。 According to the documentation“如果进程为None,则使用cpu_count()返回的数字。”这就是我将num_processes开头设置为multiprocessing.cpu_count()的原因。这样,如果您转向更强大的机器,您可以从中获益,而无需直接更改num_processes变量。

答案 1 :(得分:12)

更快的方式(在我的情况下约为10%):

接受答案的主要区别: 使用pd.concatnp.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')