为什么有时候应用并不比pandas数据帧中的for-loop更快?

时间:2016-08-14 01:05:51

标签: python pandas

在大多数情况下,似乎apply可以加速数据帧的操作过程。但是当我使用apply时,我找不到加速。这是我的例子,我有一个包含两列的数据框

>>>df
index col1 col2
1 10 20
2 20 30
3 30 40

我想要做的是通过在R(x)上实现一个函数col1来计算数据框中每一行的值,结果将除以col2中的值。例如,第一行的结果应为R(10)/20。 所以这是我的函数,它将在apply

中调用
def _f(input):
  return R(input['col1'])/input['col2']

然后我在_fapply

中致电df.apply(_f, axis=1)

但我发现在这种情况下,apply比循环慢得多,比如

for i in list(df.index)
  new_df.loc[i] = R(df.loc[i,'col1'])/df.loc[i,'col2']

有谁可以解释原因?

1 个答案:

答案 0 :(得分:20)

据我了解,.apply 通常比轴上的迭代更快。我相信在引擎盖下它只是一个轴上的循环,除了在这种情况下每次都会产生函数调用的开销。

如果我们查看source code,我们可以看到,基本上我们在指定的轴上迭代并应用函数,将单个结果作为序列构建到字典中,最后调用数据框构造函数字典返回一个新的DataFrame:

    if axis == 0:
        series_gen = (self._ixs(i, axis=1)
                      for i in range(len(self.columns)))
        res_index = self.columns
        res_columns = self.index
    elif axis == 1:
        res_index = self.index
        res_columns = self.columns
        values = self.values
        series_gen = (Series.from_array(arr, index=res_columns, name=name,
                                        dtype=dtype)
                      for i, (arr, name) in enumerate(zip(values,
                                                          res_index)))
    else:  # pragma : no cover
        raise AssertionError('Axis must be 0 or 1, got %s' % str(axis))

    i = None
    keys = []
    results = {}
    if ignore_failures:
        successes = []
        for i, v in enumerate(series_gen):
            try:
                results[i] = func(v)
                keys.append(v.name)
                successes.append(i)
            except Exception:
                pass
        # so will work with MultiIndex
        if len(successes) < len(res_index):
            res_index = res_index.take(successes)
    else:
        try:
            for i, v in enumerate(series_gen):
                results[i] = func(v)
                keys.append(v.name)
        except Exception as e:
            if hasattr(e, 'args'):
                # make sure i is defined
                if i is not None:
                    k = res_index[i]
                    e.args = e.args + ('occurred at index %s' %
                                       pprint_thing(k), )
            raise

    if len(results) > 0 and is_sequence(results[0]):
        if not isinstance(results[0], Series):
            index = res_columns
        else:
            index = None

        result = self._constructor(data=results, index=index)
        result.columns = res_index

        if axis == 1:
            result = result.T
        result = result._convert(datetime=True, timedelta=True, copy=False)

    else:

        result = Series(results)
        result.index = res_index

    return result

具体做法是:

for i, v in enumerate(series_gen):
                results[i] = func(v)
                keys.append(v.name)

根据请求的轴构造series_gen

要从功能中获得更多性能,您可以按照here给出的建议。

基本上,您的选择是:

  1. 写一个C扩展名
  2. 使用numba(JIT编译器)
  3. 使用pandas.eval从大型Dataframe中挤出效果