pandas遇到SettingWithCopyWarning警告出现的原因和解决

在使用pandas时有很多赋值操作,并且出现了很多SettingWithCopyWarning警告。
首先先搞清楚视图和副本:
在这里插入图片描述
  对于 df2 = df.head(3) 来说,df2 为视图,可以理解为截取 df1 的一部分来显示,实际上原数据都是指向 df1。当对视图 df2 进行修改时,会对原数据进行修改。

  而对于 df2 = df.head(3).copy()来说,df2 为 df1 截取的一个副本,此处df1 和 df2是两个完全不同的 DataFrame,当对副本 df2 进行修改时,不会修改到 df1。即用 .copy() 实现了数据隔离。
在这里插入图片描述

第一种情况:如果不想在原df上进行修改,即对由原df得到的新df进行修改希望的同时不影响到原来df的数据,一般使用 .copy(),但是考虑数据量和性能,可以考虑使用 双loc即后面加.loc[:] 从原df获得新df,再对新df进行修改,并且同时不影响原df,实际上是隐式创建副本。

import pandas as pd

ids = [1, 2]
info = {'C': 0.5}
df = pd.DataFrame({
    'id': [1, 2, 3],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})

# 一、对于添加或修改df的列复现SettingWithCopyWarning
# 出现原因:操作DataFrame时,不小心创建了DataFrame的一个视图(view)而不是副本(copy)。这可能导致数据的修改未按预期生效。
# 一般使用 .loc 或 .iloc 进行明确的数据选择来避免
# 但是对于链式反应即使使用 .loc 或 .iloc 还是会出现SettingWithCopyWarning
exp_df = df.loc[df['id'].isin(ids)]
if not exp_df.empty:
    exp_df.loc[:, 'C'] = info['C']
print(exp_df)
print('-------------')
print(df)


OUT:
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
-------------
   id  A  B
0   1  4  7
1   2  5  8
2   3  6  9
.../3558177221.py:14: 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
  exp_df.loc[:, 'C'] = info['C']

但是如果用.loc[:]

import pandas as pd
ids = [1, 2]
info = {'C': 0.5}
df = pd.DataFrame({
    'id': [1, 2, 3],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})

# 方法1、使用.copy()显式创建一个新df副本
# 优点:
# 明确创建副本,意图清晰。
# 确保不会发生视图(view)的问题。
# 缺点:
# 需要额外的内存,特别是对于大数据集。
df = pd.DataFrame({
    'id': [1, 2, 3],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})
exp_df = df.loc[df['id'].isin(ids)].copy()
if not exp_df.empty:
    exp_df.loc[:, 'C'] = info['C']
print(exp_df)
print('-------------')
print(df)


OUT:
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
-------------
   id  A  B
0   1  4  7
1   2  5  8
2   3  6  9


# 方法2、使用.loc[:]隐式创建一个新df副本
# 使用两次 .loc 操作符(例如 df.loc[condition].loc[:])可以确保创建一个新的 DataFrame 副本。
# 优点:
# 代码简洁。
# 通常可以避免视图问题。
# 缺点:
# 行为可能依赖于具体的 Pandas 版本和实现细节。
exp_df = df.loc[df['id'].isin(ids)].loc[:]
if not exp_df.empty:
    exp_df.loc[:, 'C'] = info['C']
print(exp_df)
print('-------------')
print(df)


OUT:
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
-------------
   id  A  B
0   1  4  7
1   2  5  8
2   3  6  9


# 方法3、使用.assign 添加或修改列,并返回一个新的 DataFrame 副本。
# 优点:
# 清晰地添加或修改列。
# 避免了链式赋值和视图问题。
# 代码更加链式,适合与其他 Pandas 方法组合使用。
# 缺点:
# 也会消耗额外的内存,因为它创建了一个新的副本。
exp_df = df.loc[df['id'].isin(ids)].loc[:]
if not exp_df.empty:
    exp_df = exp_df.assign(C=info['C'])
print(exp_df)
print('-------------')
print(df)


OUT:
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
-------------
   id  A  B
0   1  4  7
1   2  5  8
2   3  6  9

第二种情况:**如果想在原df上进行修改**(例如使用 drop 或 concat 后重新赋值),即对由原df 得到的新df 进行修改希望同时修改原来df 的数据,则可以使用修改后的df覆盖原df来避免SettingWithCopyWarning警告。
  比如删除某列,一般会在 df.drop() 里使用 inplace=True 参数,但是有些情况可能会出现SettingWithCopyWarning警告,尤其是在 df 是从另一个 DataFrame 切片而来的情况下。此时的解决方法是去掉 inplace=True 参数,用 df = df.drop() 返回一个新的 DataFrame 再覆盖原来的 df,达到一样的目的。

```python
df = pd.DataFrame({
    'id':[1, 2, 3],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})
df = df.loc[df['id'].isin(ids)]  # 进行覆盖
print(df)
print('---------------')
if not df.empty:
    df.loc[:, 'C'] = 10
print(df)

OUT:
   id  A  B
0   1  4  7
1   2  5  8
---------------
   id  A  B   C
0   1  4  7  10
1   2  5  8  10


# 或者
import pandas as pd
ids = [1, 2]
info = {'C': 0.5}
df = pd.DataFrame({
    'id': [1, 2, 3],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})

# 确保在原DataFrame中直接进行修改,直接覆盖原df,避免链式反应
mask = df['id'].isin(ids)
if mask.any():
    df.loc[mask, 'C'] = info['C']
exp_kpi_df = df.loc[mask, :]
print(exp_kpi_df)
print('-------------')
print(df)


OUT:
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
-------------
   id  A  B    C
0   1  4  7  0.5
1   2  5  8  0.5
2   3  6  9  NaN
import pandas as pd

id = '1'
size = 1
info = {'C': 0.5}
df = pd.DataFrame({
    'id': ['11', '21', '3'],
    'A': [4, 5, 6],
    'B': [7, 8, 9]
})

# 二、对于添加或修改df的行复现SettingWithCopyWarning
result = df.loc[df['id'].str.contains(id)]
if result.shape[0] > size:
    result.loc['new'] = info['C']
print(result)
print('-------------')
print(df)


OUT:
           id    A    B
1           2  5.0  8.0
new       0.5  0.5  0.5
-------------
  id  A  B
0  1  4  7
1  2  5  8
2  3  6  9
.../2172312340.py:13: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  result.loc['new'] = info['C']
  

# 新建一个df,再添加,避免链式反应,但是在concat过程中包含了copy()
result = df.loc[df['id'].str.contains(id)]
if result.shape[0] < size:
    new_row = pd.DataFrame([info['C']] * len(df.columns), index=df.columns).T
    new_row.index = ['new']
    result = pd.concat([result, new_row])
print(result)
print('-------------')
print(df)


OUT:
           id    A    B
1           2  5.0  8.0
new       0.5  0.5  0.5
-------------
  id  A  B
0  1  4  7
1  2  5  8
2  3  6  9

总结:

1、.copy() 方法用于显式地创建一个 DataFrame 或 Series 的副本,确保数据的独立性,以确保不会在原始数据的副本上进行修改时触发 SettingWithCopyWarning 警告。由于创建完全独立的副本,这意味着所有数据都会被复制一遍,因此在数据量较大的情况下,性能开销(时间、内存消耗)可能会比较大。

2、重新赋值操作(例如使用 drop 删除列后重新赋值,或使用 concat 拼接数据后重新赋值)则主要用于数据的更新和变更。这些操作通常会创建一个新的 DataFrame,并将其赋值给原始变量,从而实现数据的变更。这个过程涉及数据的重新构建,时间和内存对消耗依赖于具体对操作和数据量。常用于需要进行数据变更并更新原始 DataFrame 时。

3、.loc[:] 时,实际上是创建了一个新的 DataFrame 视图,不复制数据,包含所有原始 DataFrame 数据的引用,虽然不像 .copy() 那样创建一个完全独立的副本,但在大多数情况下,对这个新视图的修改也不会影响原始数据,不会触发 SettingWithCopyWarning 警告,但在某些情况(如链式索引可能导致不确定行为)可能会导致意外的修改。常用于需要对筛选后的数据进行修改,但不想显式创建副本时。

总的来说解决SettingWithCopyWarning 警告只有两种方法,第一是创建新df,第二是在原df上修改然后再覆盖原df,只有这两种方式在后续对df修改时才不会报SettingWithCopyWarning。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值