为啥 iloc() 的一种使用会给出 SettingWithCopyWarning,而另一种则不会?
Posted
技术标签:
【中文标题】为啥 iloc() 的一种使用会给出 SettingWithCopyWarning,而另一种则不会?【英文标题】:Why does one use of iloc() give a SettingWithCopyWarning, but the other doesn't?为什么 iloc() 的一种使用会给出 SettingWithCopyWarning,而另一种则不会? 【发布时间】:2019-05-17 07:25:15 【问题描述】:在一个类的方法中我使用这个语句:
self.__datacontainer.iloc[-1]['c'] = value
这样做我得到一个 “设置与复制警告: 正在尝试在 DataFrame 中的切片副本上设置值"
现在我尝试重现此错误并编写以下简单代码:
import pandas, numpy
df = pandas.DataFrame(numpy.random.randn(5,3),columns=list('ABC'))
df.iloc[-1]['C'] = 3
我没有错误。为什么第一条语句出错,第二条语句没有?
【问题讨论】:
看起来self.__datacontainer
是另一个数据帧的一部分。您需要明确地制作副本。例如,df.copy()
这对我没有帮助:df = self.__datacontainer.copy()
df.iloc[-1]['c'] = value
并得到相同的警告
【参考方案1】:
链索引
正如documentation 和本网站上的其他几个答案([1]、[2])所建议的那样,链索引被认为是不好的做法,应该避免。
由于似乎没有一种优雅的方式来使用基于整数位置的索引(即.iloc
)而不违反链索引规则(从熊猫v0.23.4
开始),建议尽可能使用基于标签的索引(即.loc
)进行分配。
但是,如果您绝对需要按行号访问数据,您可以
df.iloc[-1, df.columns.get_loc('c')] = 42
或
df.iloc[[-1, 1], df.columns.get_indexer(['a', 'c'])] = 42
熊猫行为古怪
据我了解,您在尝试人为地重现错误时期待警告是绝对正确的。
到目前为止,我发现它取决于数据框的构造方式
df = pd.DataFrame('a': [4, 5, 6], 'c': [3, 2, 1])
df.iloc[-1]['c'] = 42 # no warning
df = pd.DataFrame('a': ['x', 'y', 'z'], 'c': ['t', 'u', 'v'])
df.iloc[-1]['c'] = 'f' # no warning
df = pd.DataFrame('a': ['x', 'y', 'z'], 'c': [3, 2, 1])
df.iloc[-1]['c'] = 42 # SettingWithCopyWarning: ...
似乎pandas(至少v0.23.4
)在链分配[3]时处理混合类型和单一类型数据帧的方式不同
def _check_is_chained_assignment_possible(self): """ Check if we are a view, have a cacher, and are of mixed type. If so, then force a setitem_copy check. Should be called just near setting a value Will return a boolean if it we are a view and are cached, but a single-dtype meaning that the cacher should be updated following setting. """ if self._is_view and self._is_cached: ref = self._get_cacher() if ref is not None and ref._is_mixed_type: self._check_setitem_copy(stacklevel=4, t='referant', force=True) return True elif self._is_copy: self._check_setitem_copy(stacklevel=4, t='referant') return False
这对我来说真的很奇怪,虽然我不确定它是否不是预期的。
但是,有一个旧的bug 具有类似的行为。
更新
根据developers,上述行为是预期的。
【讨论】:
【参考方案2】:因此,如果没有围绕您的问题操作的上下文,很难回答这个问题,但the pandas documentation 很好地涵盖了这一点。
>>> df[['C']].iloc[0] = 2 # This is a problem
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
基本上它归结为 - 如果您可以使用单个操作来完成索引操作,请不要将索引操作链接在一起。
>>> df.loc[0, 'C'] = 2 # This is ok
您收到的警告是因为您未能在您可能试图修改的原始数据框中设置一个值 - 相反,您复制了它并在副本中设置了一些内容(通常在发生这种情况时对我来说,我什至没有对副本的引用,它只是被垃圾收集,所以警告很有帮助)
【讨论】:
【参考方案3】:不要专注于警告。警告只是一个指示,有时它甚至不会出现when you expect it should。有时你会注意到它occurs inconsistently。相反,请避免 chained indexing 或通常使用可能是副本的内容。
您希望按行整数位置和列标签进行索引。这是一个不自然的组合,因为 Pandas 具有按整数位置或标签进行索引的功能,但不能同时进行。
在这种情况下,您可以通过单个 iat
调用对行和列使用 整数位置索引:
df.iat[-1, df.columns.get_loc('C')] = 3
或者,如果您的索引标签保证是唯一的,您可以使用at
:
df.at[df.index[-1], 'C'] = 3
【讨论】:
这是一个非常好的答案。我被困了几个小时,因为一些似乎没有问题但有时有时会收到警告,有时不会:df.loc['filename', 'prediction score'] = 0.3
我发现我创建的没有链接的输出 CSV 文件包含问题行非常有趣。几乎所有的 iat 用法都有效,但似乎并非没有副作用。它打破了一些界限。如果我将 iloc 与链接一起使用,我会得到完美的结果。以上是关于为啥 iloc() 的一种使用会给出 SettingWithCopyWarning,而另一种则不会?的主要内容,如果未能解决你的问题,请参考以下文章
即使在 Pandas 中使用 .iloc 也会出现索引越界错误
为啥使用 filter() 给出了我想要的,但使用 get() 会引发错误