我有一个非常简单的功能,例如:
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
返回的是存储在列中的副本。它与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
在“工作案例”中,它是该'z'
列的视图:
>>> df = pd.DataFrame([[0, 3, 1, np.nan]], columns=['v', 'y', 'x', 'z'])
>>> np.shares_memory(df['z'].values, df['z'])
True
NB:实际上很有趣,因为复制是df['z']
在您访问而不是时创建的.values
。
这里的要点是,您不能期望索引DataFrame或访问.values
Series的索引将始终返回视图。因此,就地更新列可能不会更改原始值。不仅重复的列名可能是一个问题。当属性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
然后将函数的结果分配给该列:
>>> 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,它将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}
通常,通过将列名转换为相应块的列索引,可以很容易地在该块中创建视图。但是,如果列名重复,则不能保证访问任意列都是视图。例如,如果要访问,'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
从技术上讲,可以将未重复的列索引为视图(在这种情况下,甚至对于重复的列,例如通过使用,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
这columns.is_unique
是重要的一行。这是True
你的“正常情况”,但“假”的“失败案例”。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句