jitted函数的行为不一致

16 python jit numpy pandas numba

我有一个非常简单的功能,如下所示:

import numpy as np
from numba import jit
import pandas as pd

@jit
def f_(n, x, y, z):
    for i in range(n):
        z[i] = x[i] * y[i] 

f_(df.shape[0], df["x"].values, df["y"].values, df["z"].values)
Run Code Online (Sandbox Code Playgroud)

我通过了

df = pd.DataFrame({"x": [1, 2, 3], "y": [3, 4, 5], "z": np.NaN})
Run Code Online (Sandbox Code Playgroud)

我希望该函数将修改数据z列,如下所示:

>>> f_(df.shape[0], df["x"].values, df["y"].values, df["z"].values)
>>> df

   x  y     z
0  1  3   3.0
1  2  4   8.0
2  3  5  15.0
Run Code Online (Sandbox Code Playgroud)

这在大多数情况下工作正常,但不知何故无法修改其他数据.

我仔细检查了一下事情:

  • 我还没有确定可能导致此问题的数据点的任何问题.
  • 我看到在打印结果时,数据会按预期进行修改.
  • 如果我z从函数返回数组,它将按预期进行修改.

不幸的是,我无法将问题简化为可重复性最小的案例.例如,删除不相关的列似乎"修复"问题,使得减少不可能.

jit是否以不打算使用的方式使用?我应该注意哪些边境案件?或者它可能是一个错误?

编辑:

我找到了问题的根源.当数据包含重复的列名时会发生:

>>> df_ = pd.read_json('{"schema": {"fields":[{"name":"index","type":"integer"},{"name":"v","type":"integer"},{"name":"y","type":"integer"},
... {"name":"v","type":"integer"},{"name":"x","type":"integer"},{"name":"z","type":"number"}],"primaryKey":["index"],"pandas_version":"0.20.
... 0"}, "data": [{"index":0,"v":0,"y":3,"v":0,"x":1,"z":null}]}', orient="table")
>>> f_(df_.shape[0], df_["x"].values, df_["y"].values, df_["z"].values)
>>> df_
   v  y  v  x   z
0  0  3  0  1 NaN
Run Code Online (Sandbox Code Playgroud)

如果删除了重复,则该函数的工作方式与

>>> df_.drop("v", axis="columns", inplace=True)
>>> f_(df_.shape[0], df_["x"].values, df_["y"].values, df_["z"].values)
>>> df_
   y  x    z
0  3  1  3.0
Run Code Online (Sandbox Code Playgroud)

MSe*_*ert 8

啊,那是因为在你的"失败案例"中,它df["z"].values返回了存储在列中的内容的副本.它与numba函数无关:'z'df

>>> import pandas as pd
>>> import numpy as np
>>> df = pd.DataFrame([[0, 3, 0, 1, np.nan]], columns=['v', 'y', 'v', 'x', 'z'])
>>> np.shares_memory(df['z'].values, df['z'])
False
Run Code Online (Sandbox Code Playgroud)

在"工作案例"中,它是'z'列的视图:

>>> df = pd.DataFrame([[0, 3, 1, np.nan]], columns=['v', 'y', 'x', 'z'])
>>> np.shares_memory(df['z'].values, df['z'])
True
Run Code Online (Sandbox Code Playgroud)

注意:这实际上很有趣,因为副本是df['z']在你访问时没有制作的.values.

这里的内容是,您不能指望对DataFrame建立索引或访问.values系列将始终返回视图.因此,就地更新列可能不会更改原始值.不仅重复的列名称可能是个问题.当属性values返回一个副本时,当它返回一个视图时并不总是清晰的(除了pd.Series那时它始终是一个视图).但这些只是实施细节.因此,依靠特定行为绝不是一个好主意..values正在做的唯一保证是它返回numpy.ndarray包含相同值的a.

但是,通过简单地z从函数返回修改后的列来避免这个问题非常容易:

import numba as nb
import numpy as np
import pandas as pd

@nb.njit
def f_(n, x, y, z):
    for i in range(n):
        z[i] = x[i] * y[i] 
    return z  # this is new
Run Code Online (Sandbox Code Playgroud)

然后将函数的结果分配给列:

>>> df = pd.DataFrame([[0, 3, 0, 1, np.nan]], columns=['v', 'y', 'v', 'x', 'z'])
>>> df['z'] = f_(df.shape[0], df["x"].values, df["y"].values, df["z"].values)
>>> df
   v  y  v  x    z
0  0  3  0  1  3.0

>>> df = pd.DataFrame([[0, 3, 1, np.nan]], columns=['v', 'y', 'x', 'z'])
>>> df['z'] = f_(df.shape[0], df["x"].values, df["y"].values, df["z"].values)
>>> df
   v  y  x    z
0  0  3  1  3.0
Run Code Online (Sandbox Code Playgroud)

如果您对目前在特定情况下发生的事情感兴趣(正如我所提到的,我们在这里讨论实现细节,所以不要将其视为给定.这就是它现在实现的方式).如果您有一个DataFrame,它将dtype在多维NumPy数组中存储具有相同列的列.如果您访问该blocks属性,则可以看到这一点(不建议使用,因为内部存储可能会在不久的将来发生变化):

>>> df = pd.DataFrame([[0, 3, 0, 1, np.nan]], columns=['v', 'y', 'v', 'x', 'z'])
>>> df.blocks
{'float64':
     z
  0  NaN
 , 
 'int64':
     v  y  v  x
  0  0  3  0  1}
Run Code Online (Sandbox Code Playgroud)

通常,通过将列名转换为相应块的列索引,可以非常轻松地创建该块的视图.但是,如果您具有重复的列名称,则无法保证访问任意列是视图.例如,如果要访问,'v'则必须使用索引0和2索引Int64块:

>>> df = pd.DataFrame([[0, 3, 0, 1, np.nan]], columns=['v', 'y', 'v', 'x', 'z'])
>>> df['v']
   v  v
0  0  0
Run Code Online (Sandbox Code Playgroud)

从技术上讲,可以将非重复列索引为视图(在这种情况下,甚至对于重复列,例如通过使用,Int64Block[::2]但这是一个非常特殊的情况......).如果有重复的列名,Pandas会选择安全选项来始终返回副本(如果你考虑它就有意义.为什么索引一列返回一个视图而另一列返回一个副本).索引DataFrame具有对重复列的显式检查,并以不同方式处理它们(导致副本):

    def _getitem_column(self, key):
        """ return the actual column """

        # get column
        if self.columns.is_unique:
            return self._get_item_cache(key)

        # duplicate columns & possible reduce dimensionality
        result = self._constructor(self._data.get(key))
        if result.columns.is_unique:
            result = result[key]

    return result
Run Code Online (Sandbox Code Playgroud)

columns.is_unique是重要的路线.这是True"正常情况",但"失败案例"是"假".