解决熊猫索引操作不一致的解决方案
#python #datascience #pandas

摆脱烦人的设置withcopywarning消息

介绍

熊猫中的索引操作非常灵活,因此,许多情况可能会大不相同,因此产生意外的结果。此外,很难预测何时提出了SettingWithCopyWarningis,这是什么意思。我将显示几个不同的方案,以及每个操作如何影响您的代码。之后,我们将查看一个名为Copy on Write的新功能,该功能可帮助您摆脱不一致之处和SettingWithCopyWarnings。我们还将研究这可能如何影响性能和其他方法。

索引操作

让我们看一下索引操作当前如何在熊猫中工作。如果您已经熟悉索引操作,则可以跳到下一节。但是请注意,有很多情况具有不同形式的行为。确切的遗迹很难预测。

当父型数据框和新数据框的基础数据未共享时,熊猫中的操作会产生副本。视图是与父对象共享数据的对象。对视图的修改可能会影响父对象。

截至目前,一些索引操作返回副本,而另一些索引操作返回视图。即使对于经验丰富的用户,确切的行为也很难预测。过去,这对我来说是一个很大的烦恼。

让我们从具有两个列的数据框开始:

df = pd.DataFrame({"user_id": [1, 2, 3], "score": [10, 15, 20]})

a getItem 在数据框架或系列上的操作返回初始对象的子集。该子集可能由一组或一组列,一组或一组行或两者的混合物组成。 setItem 在数据框架或系列上的操作更新初始对象的子集。子集本身由呼叫的参数定义。

常规 getItem 在大多数情况下都提供了视图:

view = df["user_id"]

因此,新对象view仍然引用父对象df及其数据。因此,写入视图也将修改父对象。

view.iloc[0] = 10

setItem 操作不仅会更新我们的view,还将更新df。发生这种情况是因为两个对象之间共享了基础数据。

这是正确的,如果列user_id仅在df中出现一次。 user_id重复后, getItem 操作将返回数据框架。这意味着返回的对象是副本而不是视图:

df = pd.DataFrame(
    [[1, 10, 2], [3, 15, 4]], 
    columns=["user_id", "score", "user_id"],
)
not_a_view = df["user_id"]
not_a_view.iloc[0] = 10

setItem 操作不会更新df。即使这是一个完全可以接受的操作,我们也获得了第一个SettingWithCopyWarning getItem 操作本身具有更多的案例,例如列表键,例如df[["user_id"]],多索引 - 列等。我将在后续帖子中详细介绍,以查看执行索引操作及其行为的不同形式。

让我们看一个比单个 getItem 操作更复杂的情况:链接索引。链式索引意味着用布尔面膜进行过滤,然后是 getItem 操作或相反的操作。这是一步完成的。我们没有创建一个新变量来存储第一个操作的结果。

我们再次以常规数据帧开始:

df = pd.DataFrame({"user_id": [1, 2, 3], "score": [10, 15, 20]})

我们可以更新所有的分数大于15至15的user_ids

df["user_id"][df["score"] > 15] = 5

我们采用列user_id并之后应用过滤器。这很好,因为列选择会创建视图和 setItem 操作更新所述视图。我们也可以切换两个操作:

df[df["score"] > 15]["user_id"] = 5

此执行顺序产生另一个SettingWithCopyWarning。与我们之前的例子相反,什么也没有发生。 dataFrame df未修改。这是一个无声的不合适。布尔掩码总是创建初始数据框架的副本。因此,初始 getItem 操作返回副本。返回值未分配给任何变量,只是临时结果。 SetItem操作更新此临时副本。结果,修改丢失。当列选择返回视图时,蒙版返回副本的事实是实现细节。理想情况下,此类实施细节不应可见。

这样做的另一种方法如下:

new_df = df[df["score"] > 15]
new_df["user_id"] = 10

此操作按预期更新new_df,但无论如何都会显示SettingWithCopyWarning,因为我们无法更新df。在这种情况下,我们大多数人可能永远不想更新初始对象(例如df),但是无论如何我们都会收到警告。根据我的经验,这导致不必要的副本语句散布在代码基础上。

这只是索引操作中当前不一致和烦恼的一小部分。

由于很难预测实际行为,因此这会迫使其他方法中的许多防御副本。例如,

  • 列的下降
  • 设置新索引
  • 重置索引

所有复制基础数据。从实施的角度来看,这些副本是不需要的。这些方法可以很容易地返回视图,但是返回的视图将导致以后无法预测的行为。从理论上讲,一个 setItem 操作可以通过整个呼叫链传播,一次更新许多数据范围。

复制写

让我们看一下如何在Write(Cow)上使用新功能来帮助我们摆脱代码库中的这些不一致之处。牛意味着以任何方式源自另一个的数据框架或系列总是表现为副本。结果,我们只能通过修改对象本身来更改对象的值。牛取消更新数据框或与另一个数据框架或系列对象Inplope共享数据的系列。有了这些信息,我们可以再次查看我们的最初示例:

df = pd.DataFrame({"user_id": [1, 2, 3], "score": [10, 15, 20]})
view = df["user_id"]
view.iloc[0] = 10

getItem 操作提供了对df及其数据的视图。 setItem 操作在将10写入第一行之前会触发基础数据的副本。因此,操作不会修改df。这种行为的一个优点是,我们不必担心user_id可能被重复或使用df[["user_id"]]而不是df["user_id"]。所有这些案件的行为完全相同,没有显示令人讨厌的警告。

在更新对象的值之前触发副本具有性能含义。对于某些操作,这肯定会导致较小的放缓。另一方面,许多其他操作可以避免防御副本,从而极大地提高性能。以下操作都可以用牛返回视图:

  • 删除列
  • 设置新索引
  • 重置索引
  • 以及更多。

让我们考虑以下数据帧:

na = np.array(np.random.rand(1_000_000, 100))
cols = [f"col_{i}" for i in range(100)]
df = pd.DataFrame(na, columns=cols)

使用add_prefix将给定的字符串(例如test)添加到每个列名的开头:

df.add_prefix("test")

无母牛,这将在内部复制数据。仅查看操作时,这不是必需的。但是,由于返回视图可能会产生副作用,因此该方法返回副本。结果,操作本身非常慢:

482 ms ± 3.43 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

这需要很长时间。实际上,我们仅修改100个字符串文字,而无需触摸数据。在这种情况下,返回视图提供了显着的加速:

46.4 µs ± 1.04 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

同一操作运行多个数量级。更重要的是,使用牛时,add_prefix的运行时间是常数,并且不取决于您的数据框架的大小。此操作是在熊猫的主要分支上进行的。

仅当两个不同的对象共享相同的基础数据时,副本才有必要。在上面的示例中,viewdf都引用相同的数据。如果数据是一个DataFrame对象的独有的,则不需要副本,我们可以继续修改数据INPOPH:

df = pd.DataFrame({"user_id": [1, 2, 3], "score": [10, 15, 20]})
df.iloc[0] = 10

在这种情况下, setItem 操作将继续在不触发副本的情况下继续操作。

因此,我们最初看到的所有不同场景现在都具有完全相同的行为。我们不必再担心细微的矛盾了。

当前有奇怪且难以预测行为的另一种情况是链接索引。在牛下链接索引从不工作。这是牛机制的直接结果。列的初始选择可能会返回视图,但是当我们执行后续setItem操作时会触发副本。幸运的是,我们可以轻松地修改代码以避免链接索引:

df["user_id"][df["score"] > 15] = 10

我们可以使用loc立即进行这两个操作:

df.loc[df["score"] > 15, "user_id"] = 10

总结,我们创建的每个对象的行为就像父对象的副本。除了我们当前正在使用的对象外,我们不能意外地更新一个对象。

如何尝试

您可以从PANDAS 1.5.0开始尝试牛功能。开发仍在进行中,但是总体机制已经起作用。

您可以通过以下陈述在全球范围内设置牛标志:

pd.set_option("mode.copy_on_write", True)
pd.options.mode.copy_on_write = True

另外,您可以在本地启用:

with pd.option_context("mode.copy_on_write", True):
    ...

结论

我们已经看到,熊猫中的索引操作具有许多边缘病例和行为上的细微差异,难以预测。牛是一项旨在解决这些差异的新功能。它可能会根据我们试图使用数据来积极或负面影响性能。可以找到牛的完整建议。

感谢您的阅读。随时接触以分享您的想法和有关索引和复制的反馈。我将写下以下内容。专注于此主题的帖子和大熊猫一般。