pandas链式操作与SettingWithCopyWarning详解

1.SettingWithCopyWarning问题

SettingWithCopyWarning是pandas中一个经典问题,也是pandas库中位数不多的坑之一。关于这个问题,我们先看下面的一个例子。

import pandas as pd


def t1():
    data = {
        'name': ['a', 'b', 'c', 'd', 'e', 'f'],
        'num': [1, 2, 3, 4, 5, 6],
        'ss': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
    }
    df = pd.DataFrame(data)
    print(df, "\n")
    df[df['name'] == 'a']['num'] = 10
    print(df)

上面的代码本意,是想将df中name为’a’的行中,num赋值为10。我们看一下代码运行的结果:

  name  num   ss
0    a    1  0.1
1    b    2  0.2
2    c    3  0.3
3    d    4  0.4
4    e    5  0.5
5    f    6  0.6 

/Users/wanglei/wanglei/code/python/finance-trade/p2/DfCopyCode.py:19: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  df[df['name'] == 'a']['num'] = 10
  name  num   ss
0    a    1  0.1
1    b    2  0.2
2    c    3  0.3
3    d    4  0.4
4    e    5  0.5
5    f    6  0.6

首先代码给出了SettingWithCopyWarning的告警。注意是Warning,而不是Error。
然后我们观察输出,发现赋值并没有起到预期的效果,df的值没有发生变化。

具体原因在哪里?
要了解 SettingWithCopyWarning,首先要知道,Pandas 中的某些操作会返回数据的视图(View),某些操作会返回数据的副本(Copy)。

pandas链式操作与SettingWithCopyWarning详解_第1张图片
如上所示,左侧的视图 df2 只是原始数据 df1 一个子集,而右侧的副本创建了一个新的对象 df2。

当我们尝试对数据集进行更改时,这可能会出现问题:
pandas链式操作与SettingWithCopyWarning详解_第2张图片
根据需求,我们可能想要修改原始 df1(左),也可能想要修改 df2(右)。警告提醒我们,代码可能并没有符合需求,修改到的可能并不是我们想要修改的那个数据集。
(该部分图文来自参考文献1)

如何解决上面的问题?其实在SettingWithCopyWarning中已经给出了答案,使用loc。

def t2():
    data = {
        'name': ['a', 'b', 'c', 'd', 'e', 'f'],
        'num': [1, 2, 3, 4, 5, 6],
        'ss': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
    }
    df = pd.DataFrame(data)
    df.loc[df['name'] == 'a', 'num'] = 10
    print(df)

上面方法的输出为

  name  num   ss
0    a   10  0.1
1    b    2  0.2
2    c    3  0.3
3    d    4  0.4
4    e    5  0.5
5    f    6  0.6

这样就达到了我们预期的目的。

为什么loc方法能保证达到预期?具体可以查阅参考文献1,解释得非常清楚详细。

2.跨行的SettingWithCopyWarning问题

下面我们再来看一个例子

def t3():
    data = {
        'name': ['a', 'b', 'c', 'd', 'e', 'f'],
        'num': [1, 2, 3, 4, 5, 6],
        'ss': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
    }
    df = pd.DataFrame(data)
    subdf = df.loc[df.num <= 3]
    print(subdf)

    subdf.loc[subdf['name'] == 'a', 'num'] = 10
    print(subdf)

上面代码的输出为

  name  num   ss
0    a    1  0.1
1    b    2  0.2
2    c    3  0.3
/Users/wanglei/anaconda3/anaconda3/lib/python3.7/site-packages/pandas/core/indexing.py:965: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  self.obj[item] = s
  name  num   ss
0    a   10  0.1
1    b    2  0.2
2    c    3  0.3

我们看赋值已经成功了,subdf已经进行了改变,也使用了loc的方式进行复制,为什么还会有SettingWithCopyWarning告警呢?
链式索引可能在一行代码内发生,也可能跨越两行代码。因为subdf变量是作为 Get 操作的输出创建的,它可能是原始 DataFrame 的副本,也可能不是,除非检查,否则我们不能确认。对subdf进行索引时,实际上使用的就是链式索引。

这种情况下的警告解决方案是:创建新 DataFrame 时明确告知 Pandas 创建一个副本 (来自参考文献1)

def t4():
    data = {
        'name': ['a', 'b', 'c', 'd', 'e', 'f'],
        'num': [1, 2, 3, 4, 5, 6],
        'ss': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
    }
    df = pd.DataFrame(data)
    subdf = df.loc[df.num <= 3].copy()
    print(subdf)

    subdf.loc[subdf['name'] == 'a', 'num'] = 10
    print(subdf)

代码的输出为:

  name  num   ss
0    a    1  0.1
1    b    2  0.2
2    c    3  0.3
  name  num   ss
0    a   10  0.1
1    b    2  0.2
2    c    3  0.3

这种情况下,使用copy方法,明确告诉是创建的新副本即可。

有关SettingWithCopyWarning的问题,参考文献1中解释得非常详细,强烈建议仔细阅读!

参考文献:
1.https://zhuanlan.zhihu.com/p/41202576

你可能感兴趣的:(pandas,CopyWarning,链式索引)