8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何处理 Pandas 中的 SettingWithCopyWarning

Lexi 2月前

240 0

背景我刚刚将我的 Pandas 从 0.11 升级到 0.13.0rc1。现在,应用程序弹出许多新警告。其中一个是这样的:E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A va...

背景

我刚刚将我的 Pandas 从 0.11 升级到 0.13.0rc1。现在,应用程序弹出许多新警告。其中一个是这样的:

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE

我想知道这到底是什么意思?我需要做些更改吗?

如果我坚持使用,该如何取消警告 quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE

发出警告的功能

def _decode_stock_quote(list_of_150_stk_str):
    """decode the webpage and return dataframe"""

    from cStringIO import StringIO

    str_of_all = "".join(list_of_150_stk_str)

    quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
    quote_df['TClose'] = quote_df['TPrice']
    quote_df['RT']     = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
    quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
    quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
    quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
    quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
    quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
    
    return quote_df

更多警告信息

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
帖子版权声明 1、本帖标题:如何处理 Pandas 中的 SettingWithCopyWarning
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Lexi在本站《pandas》版块原创发布, 转载请注明出处!
最新回复 (0)
  • @Asclepius df.loc[:, foo] 还给了我 SettingWithCopyWarning: 要求我使用 Try using .loc[row_indexer,col_indexer] = value 而实际上我没有任何 row_indexer 因为我想对所有行执行此分配。我该怎么做?

  • 创建 SettingWithCopyWarning 的目的是标记可能令人困惑的“链式”分配,例如以下内容,它并不总是按预期工作,特别是当第一个选择返回副本时[ 有关背景讨论, andGH5597

    df[df['A'] > 2]['B'] = new_val  # new_val not set in df
    

    该警告建议重写如下:

    df.loc[df['A'] > 2, 'B'] = new_val
    

    但是,这不符合你的用法,这相当于:

    df = df[df['A'] > 2]
    df['B'] = new_val
    

    虽然很明显您并不关心写入是否返回到原始框架(因为您正在覆盖对它的引用),但不幸的是,此模式无法与第一个链式分配示例区分开来。因此出现了(误报)警告。 索引文档 。您可以使用以下分配安全地禁用此新警告。

    import pandas as pd
    pd.options.mode.chained_assignment = None  # default='warn'
    

    其他资源

    • pandas 用户指南:索引和选择数据
    • Python 数据科学手册:数据索引和选择
    • 真正的 Python:Pandas 中的 SettingWithCopyWarning:视图与副本
    • Dataquest:SettingwithCopyWarning:如何在 Pandas 中修复此警​​告
    • 走向数据科学:解释 Pandas 中的 SettingWithCopyWarning
  • 我正在使用数据框的一个切片,在该切片中进行修改,然后出现此错误。我通过对原始数据框执行 .copy() 创建了此切片,并且成功了。

  • 我应该如何处理 df = df[df['A'].notnull()]?

  • @Garrett 请考虑将 @RishabhAgrahari 的评论添加到您的答案中。告诉 pandas 您想要通过调用 .copy() 来操作切片的副本,这比在运行时彻底禁用所有情况下的警告要好得多。

  • 在我的情况下,要修改的行上没有设置警告。我过滤了第一行的数据,然后在第二行创建了一个计算字段。第二行显示了警告,实际上解决方案是通过添加对 .copy() 的调用来修改第一行。这很容易引起误解,有时两行之间会隔开相当多的代码。

  • 在切片上使用 .copy() 是一种糟糕的解决方法,因为它会引入额外的、不需要的副本。像 df[df.a > 2] 这样的子集操作已经创建了一个新的数据框,因此算法上不需要额外的副本。

  • fafa 2月前 0 只看Ta
    引用 9

    如何处理 SettingWithCopyWarning and ChainedAssignmentError

    这篇文章适合以下读者:

    1. 想了解这个警告的含义
    2. 想要了解抑制此警告的不同方法
    3. 想要了解如何改进他们的代码并遵循良好的做法以避免将来出现此警告。

    设置

    np.random.seed(0)
    df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
    df
       A  B  C  D  E
    0  5  0  3  3  7
    1  9  3  5  2  4
    2  7  6  8  8  1
    

    什么是 SettingWithCopyWarning

    要知道如何处理此警告,首先重要的是了解它的含义以及为什么会出现此警告。

    过滤 DataFrames 时,可以对帧进行切片/索引以返回 视图 副本 ,具体取决于内部布局和各种实现细节。顾名思义,“视图”是原始数据的视图,因此修改视图可能会修改原始对象。另一方面,“副本”是对原始数据的复制,修改副本不会对原始数据产生影响。

    正如其他答案所提到的, SettingWithCopyWarning 创建它是为了标记“链式赋值”操作。考虑 df 上面的设置。假设您想要选择列“B”中的所有值,其中列“A”中的值大于 5。Pandas 允许您以不同的方式执行此操作,有些比其他方式更正确。例如,

    df[df.A > 5]['B']
    
    1    3
    2    6
    Name: B, dtype: int64
    

    和,

    df.loc[df.A > 5, 'B']
    
    1    3
    2    6
    Name: B, dtype: int64
    

    它们返回相同的结果,因此如果您仅读取这些值,则没有任何区别。那么,问题是什么?链式赋值的问题在于,通常很难预测返回的是视图还是副本, 因此当您尝试重新赋值时,这在很大程度上会成为一个问题。 基于前面的示例,请考虑解释器如何执行此代码:

    df.loc[df.A > 5, 'B'] = 4
    # becomes
    df.__setitem__((df.A > 5, 'B'), 4)
    

    只需一次 __setitem__ 调用 df 。OTOH,考虑以下代码:

    df[df.A > 5]['B'] = 4
    # becomes
    df.__getitem__(df.A > 5).__setitem__('B', 4)
    

    现在,根据 __getitem__ 返回的是视图还是副本, __setitem__ 操作 可能不起作用 .

    一般来说,你应该使用 loc 进行基于标签的分配,使用 iloc 进行基于整数/位置的分配,因为规范保证它们总是在原始位置上操作。此外,要设置单个单元格,你应该使用 at and iat .

    内容 可参见 .

    注意: 所有用 完成的布尔索引操作 loc 也可以用 完成 iloc 。唯一的区别是 iloc 需要索引的整数/位置或布尔值的 numpy 数组,以及列的整数/位置索引。

    例如,

    df.loc[df.A > 5, 'B'] = 4
    

    可以写成 nas

    df.iloc[(df.A > 5).values, 1] = 4
    

    和,

    df.loc[1, 'A'] = 100
    

    可以写成

    df.iloc[1, 0] = 100
    

    等等。

    从 pandas >= 2.0 开始,您可以启用 写时复制优化 以节省内存并避免在写入之前复制数据(如果可能)。

    可以通过以下方式启用

    pd.options.mode.copy_on_write = True
    

    在此之后,尝试进行链式赋值将导致

    ChainedAssignmentError: A value is trying to be set on a copy of a DataFrame or Series through chained assignment.
    When using the Copy-on-Write mode, such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy.
    
    Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
    

    错误出现在与 SettingWithCopyWarning .


    只要告诉我如何抑制警告!

    考虑对 的 \'A\' 列进行简单操作 df 。选择 \'A\' 并除以 2 将引发警告,但操作将有效。

    df2 = df[['A']]
    df2['A'] /= 2
    /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/__main__.py:1: 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
    
    df2
         A
    0  2.5
    1  4.5
    2  3.5
    

    有几种方法可以直接消除此警告:

    1. 第22页

       df2 = df.loc[:, ['A']] df2['A'] /= 2     # Does not raise
    2. p23

       pd.options.mode.chained_assignment = None df2['A'] /= 2
    3. p24

       df2 = df[['A']].copy(deep=True) df2['A'] /= 2

    @Peter Cotton 在评论中,想出了一个使用上下文管理器非侵入式地改变模式的好方法(从 这个要点 ),​​只在需要时设置模式,完成后将其重置回原始状态。

    class ChainedAssignent:
        def __init__(self, chained=None):
            acceptable = [None, 'warn', 'raise']
            assert chained in acceptable, "chained must be in " + str(acceptable)
            self.swcw = chained
    
        def __enter__(self):
            self.saved_swcw = pd.options.mode.chained_assignment
            pd.options.mode.chained_assignment = self.swcw
            return self
    
        def __exit__(self, *args):
            pd.options.mode.chained_assignment = self.saved_swcw
    

    使用方法如下:

    # Some code here
    with ChainedAssignent():
        df2['A'] /= 2
    # More code follows
    

    或者,引发异常

    with ChainedAssignent(chained='raise'):
        df2['A'] /= 2
    
    SettingWithCopyError:
    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
    

    “XY 问题”:我做错了什么?

    很多时候,用户试图寻找抑制此异常的方法,却没有完全理解为什么会引发此异常。这是 XY 问题 ,用户试图解决问题“Y”,而问题实际上是更深层次的问题“X”的症状。将根据遇到此警告的常见问题提出问题,然后提供解决方案。

    问题 1 我有一个 DataFrame

    df
           A  B  C  D  E
        0  5  0  3  3  7
        1  9  3  5  2  4
        2  7  6  8  8  1
    

    我想将 col \'A\' > 5 中的值分配给 1000。我的预期输出是

          A  B  C  D  E
    0     5  0  3  3  7
    1  1000  3  5  2  4
    2  1000  6  8  8  1
    

    错误的做法:

    df.A[df.A > 5] = 1000         # works, because df.A returns a view
    df[df.A > 5]['A'] = 1000      # does not work
    df.loc[df.A > 5]['A'] = 1000   # does not work
    

    正确使用方法 loc

    df.loc[df.A > 5, 'A'] = 1000
    

    Question 2 1 我试图将单元格 (1, 'D') 中的值设置为 12345。我的预期输出是

       A  B  C      D  E
    0  5  0  3      3  7
    1  9  3  5  12345  4
    2  7  6  8      8  1
    

    我尝试过不同的方法来访问此单元格,例如 df['D'][1] 。最好的方法是什么?

    1. 这个问题与警告没有特别的关系,但了解如何正确执行这个特定的操作是有好处的,这样可以避免将来可能出现警告的情况。

    您可以使用以下任一方法来执行此操作。

    df.loc[1, 'D'] = 12345
    df.iloc[1, 3] = 12345
    df.at[1, 'D'] = 12345
    df.iat[1, 3] = 12345
    

    问题 3 我尝试根据某些条件对值进行子集化。我有一个 DataFrame

       A  B  C  D  E
    1  9  3  5  2  4
    2  7  6  8  8  1
    

    我想将 \'D\' 中的值分配给 123,使得 \'C\' == 5。我试过

    df2.loc[df2.C == 5, 'D'] = 123
    

    看起来不错,但我 仍然 遇到问题 SettingWithCopyWarning !我该如何修复这个问题?

    这实际上可能是由于管道中较高层的代码所致。您是否 df2 从较大的内容创建,例如

    df2 = df[df.A > 5]
    

    ? 在这种情况下,布尔索引将返回一个视图,因此 df2 将引用原始视图。您需要做的是分配 df2 副本

    df2 = df[df.A > 5].copy()
    # Or,
    # df2 = df.loc[df.A > 5, :]
    

    问题 4 我试图从中删除列“C”

       A  B  C  D  E
    1  9  3  5  2  4
    2  7  6  8  8  1
    

    但使用

    df2.drop('C', axis=1, inplace=True)
    

    抛出 SettingWithCopyWarning 。为什么会发生这种情况?

    这是因为 df2 必须通过其他切片操作创建视图,例如

    df2 = df[df.A > 5]
    

    这里的解决方案是,要么制作 copy() df 要么使用 loc ,像以前一样。

  • 我认为问题 2 链接到一个解决 loc、iloc、at 和 iat 之间差异的问题会很有帮助。您可能比我更了解这样的问题,但如果有帮助的话,我很乐意寻找一个。

  • @cs95:您能否在尝试基于现有列的简单数学运算创建新列的情况下添加 XY 描述。例如 df['new_col'] = df['old_col']/2。其中 'new_col' 尚不存在。谢谢

  • @cs95 - 谢谢。几乎每个人都关注你的问题 1 和 2。但是,我恰好遇到了问题 3 中的子集问题。我发现在创建子网后,_is_copy 不再为 None,这导致我使用 .copy()。

  • 非常感谢!我总是在代码中使用 .loc,因为我意识到了“链式赋值”问题,但我仍然时不时地收到几乎随机的烦人的 SettingWithCopyWarning。事实证明,我的问题就是您在“问题 3”中提到的。我花了几个月的时间才意识到这一点,所以也许应该在文档中更清楚地指出这一点……无论如何,再次非常感谢!

  • 总的来说,这样做的目的 SettingWithCopyWarning 是向用户(尤其是新用户)表明他们 可能 正在操作副本,而不是他们所认为的原始副本。存在 误报 (换句话说,如果你知道自己在做什么,那就没问题。一种可能性是简单地关闭(默认情况下是 warn )警告,正如@Garrett 建议的那样。

    这是另一个选择:

    In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB'))
    
    In [2]: dfa = df.ix[:, [1, 0]]
    
    In [3]: dfa.is_copy
    Out[3]: True
    
    In [4]: dfa['A'] /= 2
    /usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_index,col_indexer] = value instead
      #!/usr/local/bin/python
    

    您可以将 is_copy 标志设置为 False ,这将有效关闭 该对象的

    In [5]: dfa.is_copy = False
    
    In [6]: dfa['A'] /= 2
    

    如果您明确复制则不会发生进一步的警告:

    In [7]: dfa = df.ix[:, [1, 0]].copy()
    
    In [8]: dfa['A'] /= 2
    

    楼主上面展示的代码虽然合法,而且我可能也会这么做,但从技术上讲,这是出现此警告的一个案例,而不是误报。另一种避免出现警告的方法通过 进行选择操作 reindex ,例如

    quote_df = quote_df.reindex(columns=['STK', ...])
    

    或者,

    quote_df = quote_df.reindex(['STK', ...], axis=1)  # v.0.21
    
  • 我认为说存在误报是轻描淡写。我认为这个警告从来没有帮助过我,它阻塞我输出的次数多得令人难以置信。这也是糟糕的编程习惯:如果你开始忽略输出中的警告,因为你知道它们纯粹是垃圾,你可能会开始错过真正的问题。总是必须关闭相同的警告也很烦人。

  • 这种情况(例如 df['a'] = df['a'] / 2)非常常见,应该进行特殊检查以免引发警告。

  • 这里我直接回答这个问题,我们该如何处理呢?

    切片后 .copy(deep=False) 制作 pandas.DataFrame.copy .

    等一下,切片不是返回一个副本吗?毕竟,这就是警告信息试图表达的意思?请阅读长答案:

    import pandas as pd
    df = pd.DataFrame({'x':[1,2,3]})
    

    这给出了一个警告:

    df0 = df[df.x>2]
    df0['foo'] = 'bar'
    

    这不会:

    df1 = df[df.x>2].copy(deep=False)
    df1['foo'] = 'bar'
    

    df0 都是 df1 DataFrame 这使得 pandas 能够打印警告。让我们来看看它是什么。

    import inspect
    slice= df[df.x>2]
    slice_copy = df[df.x>2].copy(deep=False)
    inspect.getmembers(slice)
    inspect.getmembers(slice_copy)
    

    使用您选择的差异工具,您将看到,除了几个地址之外,唯一的实质性区别是:

    |          | slice   | slice_copy |
    | _is_copy | weakref | None       |
    

    决定是否发出警告的方法是 DataFrame._check_setitem_copy 检查 _is_copy 。所以你开始吧。制作一个, copy 这样你的 DataFrame 就不会 _is_copy .

    警告建议使用 .loc ,但如果您 .loc 在 的框架上 _is_copy ,您仍会收到相同的警告。误导?是的。烦人?当然。有帮助?在使用链式赋值时可能有帮助。但它无法正确检测链式赋值并随意打印警告。

  • 侦查得不错。FWIW 我还发现 _is_copy 对于原始 df 为 None,对于切片为弱引用。此外,切片上的 _is_copy() 返回原始 df 的所有行。但 _is_copy 打印的引用与原始 df 的 id 不同。切片是否以某种方式复制?另外,我想知道浅拷贝是否会导致后续或新版 pandas 出现其他问题?

  • Pandas 数据框复制警告

    当你去做这样的事情时:

    quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
    

    pandas.ix 在这种情况下, 返回一个新的、独立的数据框。

    您决定在此数据框中更改的任何值都不会改变原始数据框。

    这就是熊猫试图警告你的。


    为什么 .ix 这是一个坏主意

    .ix 对象试图做多件事,对于任何读过有关清洁代码的人来说,这是一种强烈的气味。

    鉴于此数据框:

    df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})
    

    两种行为:

    dfcopy = df.ix[:,["a"]]
    dfcopy.a.ix[0] = 2
    

    行为一: dfcopy 现在是一个独立的数据框。更改它不会改变 df

    df.ix[0, "a"] = 3
    

    行为二:这会改变原始数据框。


    改用 .loc

    pandas 开发人员意识到这个 .ix 对象相当难闻(推测),因此创建了两个新对象,用于帮助获取和分配数据。(另一个是 .iloc )

    .loc 速度更快,因为它不会尝试创建数据的副本。

    .loc 旨在就地修改现有的数据框,以提高内存效率。

    .loc 是可以预测的,它有一种行为。


    解决方案

    您在代码示例中所做的是加载一个包含许多列的大文件,然后将其修改为较小的文件。

    pd.read_csv 功能可以帮助您解决很多问题,并使文件的加载速度更快。

    所以不要这样做

    quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
    

    执行此操作

    columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
    df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
    df.columns = columns
    

    这将仅读取您感兴趣的列并正确命名它们。无需使用邪恶的 .ix 对象来做神奇的事情。

  • 这个问题对于 Pandas 来说确实很令人困惑。幸运的是,它有一个相对简单的解决方案。

    问题在于,数据过滤操作(例如 loc)返回的是 DataFrame 的副本还是视图并不总是很清楚。因此,进一步使用这种经过过滤的 DataFrame 可能会造成混淆。

    简单的解决方案是(除非您需要处理非常大的数据集):

    无论何时需要更新任何值,请务必确保在分配之前明确复制 DataFrame。

    df  # Some DataFrame
    df = df.loc[:, 0:2]  # Some filtering (unsure whether a view or copy is returned)
    df = df.copy()  # Ensuring a copy is made
    df[df["Name"] == "John"] = "Johny"  # Assignment can be done now (no warning)
    
返回
作者最近主题: