固定功能的行为不一致

时间:2018-07-19 00:15:52

标签: 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

1 个答案:

答案 0 :(得分:8)

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

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 列的视图:

'z'

注意:这样做确实很有趣,因为复制是在您执行>>> df = pd.DataFrame([[0, 3, 1, np.nan]], columns=['v', 'y', 'x', 'z']) >>> np.shares_memory(df['z'].values, df['z']) True 时创建的,而不是在您访问df['z']时创建的。

这里的要点是,您不能期望索引DataFrame或访问Series的.values总是返回视图。因此,就地更新列可能不会更改原始值。不仅重复的列名可能是一个问题。当属性.values返回一个副本并且返回一个视图时,它并不总是被清除(除了values之外,它始终是一个视图)。但这只是实现细节。因此,在这里依赖特定行为绝不是一个好主意。 pd.Series所做的唯一保证是它返回包含相同值的.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

如果您对当前特定情况下发生的事情感兴趣(如我所提到的,我们在这里谈论实现细节,请不要按照给出的那样进行。这只是实现它的方式现在 >)。如果您有DataFrame,它将在多维NumPy数组中存储具有相同>>> 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 的列。如果您访问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} ,则它必须使用索引0和2索引Int64块:

'v'

从技术上讲,可以将未重复的列索引为视图(在这种情况下,甚至对于重复的列,例如,使用>>> df = pd.DataFrame([[0, 3, 0, 1, np.nan]], columns=['v', 'y', 'v', 'x', 'z']) >>> df['v'] v v 0 0 0 都可以,但这是非常特殊的情况...)。如果有重复的列名,Pandas选择安全选项总是返回一个副本(考虑一下就很有意义。为什么索引一个列返回一个视图,而另一个索引则返回一个副本)。 Int64Block[::2]的索引中有一个explicit check用于重复的列,并对其进行不同的处理(产生副本):

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 是重要的一行。对于您的“正常情况”为columns.is_unique,对于“失败情况”为“ False”。