python - jitted 函数的不一致行为

标签 python pandas numpy jit 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)

我路过

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

我预计该函数会像这样修改数据 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

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

我仔细检查了事情并且:

  • 我还没有确定任何可能导致此问题的数据点问题。
  • 当我打印结果时,我看到数据已按预期修改。
  • 如果我从函数返回 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

如果删除重复项,该函数将按预期工作:

>>> 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

最佳答案

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

>>> 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

在“工作案例”中,它是 'z' 列的 View :

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

注意:这实际上很有趣,因为复制是在您执行 df['z'] 时创建的,而不是在您访问 .values 时创建的。

这里的要点是,您不能期望索引 DataFrame 或访问 Series 的 .values 总是会返回一个 View 。因此就地更新列可能不会更改原始值。不仅重复的列名可能是个问题。当属性 values 返回一个副本以及当它返回一个 View 时并不总是很清楚(除了 pd.Series 然后它总是一个 View )。但这些只是实现细节。因此,在这里依赖特定行为绝不是一个好主意。 .values 的唯一保证是它返回包含相同值的 numpy.ndarray

不过,通过简单地从函数返回修改后的 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

然后将函数的结果赋给列:

>>> 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

如果您对当前在您的特定案例中发生的事情感兴趣(正如我提到的,我们在这里讨论的是实现细节,所以不要把它当作给定的。它只是现在).如果您有一个 DataFrame,它将在多维 NumPy 数组中存储具有相同 dtype 的列。如果您访问 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}

通常,通过将列名转换为相应 block 的列索引,可以很容易地创建该 block 的 View 。但是,如果您有重复的列名,则不能保证访问任意列是一个 View 。例如,如果你想访问 'v',那么它必须使用索引 0 和 2 索引 Int64 block :

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

从技术上讲,可以将非重复的列索引为 View (在这种情况下,甚至对于重复的列,例如通过使用 Int64Block[::2] 但这是一个非常特殊的案子...)。如果存在重复的列名,Pandas 选择安全选项总是返回一个副本(如果你仔细想想就有意义。为什么索引一个列返回一个 View 而另一个返回一个副本)。 DataFrame 的索引有一个 explicit check对于重复的列并以不同的方式对待它们(产生副本):

    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

columns.is_unique 是这里重要的一行。对于您的“正常案例”,它是 True,但对于“失败案例”,它是“False”。

关于python - jitted 函数的不一致行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51412619/

相关文章:

python - 在 Pandas 中设置最大字符串长度

python - 为什么python中的计算机之间的强制转换规则不同?

python - 在 python 中的 byref vector 参数上使用 ctypes 进行 DLL 转换

python - 如何查找 **options 参数是什么?

python - 确保 Android SDK 的路径正确

python - Pandas 什么时候默认广播 Series 和 Dataframes?

python - python 如何有效地排序时间

python - 使用 boost::numpy 时出现 LNK2001 错误

python - 如何在 python 中 reshape 这个图像数组?

python - 尝试部署到heroku但不断被拒绝