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)。

在这里插入图片描述
如上所示,左侧的视图 df2 只是原始数据 df1 一个子集,而右侧的副本创建了一个新的对象 df2。

当我们尝试对数据集进行更改时,这可能会出现问题:
在这里插入图片描述
根据需求,我们可能想要修改原始 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值