【Pandas学习笔记】Pandas-SettingWithCopyWarning警报问题分析

1. 问题描述

pandas读取.csv文件形成DataFrame格式数据,然后对数据每个列进行操作,但是在运行到如下代码时报错SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFram。并且在不同项目目录和相同的python虚拟环境下可以正常运行。

df[column] = fill_column_moving_average(df, column)

报错信息解释:用来提醒用户正在对一个DataFrame的副本进行操作,并未操作原始数据,可能导致原始数据并未正常修改。信息中提到请尝试使用.loc,使用 .loc.iloc 进行显式操作可以避免 SettingWithCopyWarning,确保你明确地知道是在操作视图还是副本。

2. 解决方案

解决方案:使用.loc进行显式的行列索引赋值,这样可确保原始数据可以正常被修改。

修改前:

def fill_moving_average(df: DataFrame):
    for column in df.columns:
        # 判断每列第一个数据是否为nan
        if pd.notna(df.at[0, column]):
            # 如果不是nan,不做任何操作
            pass
        else:
            # 如果是用column列的均值替换
            df.at[0, column] = df[column].mean()

        # 对每一列进行操作
        df[column] = fill_column_moving_average(df, column)

    return df

修改后:

def fill_moving_average(df: DataFrame) -> DataFrame:
    for column in df.columns:
        # 判断每列第一个数据是否为nan
        if pd.notna(df.at[0, column]):
            # 如果不是nan,不做任何操作
            pass
        else:
            # 如果是用本列的平均值替换
            df.at[0, column] = df[column].mean()

        # 对每一列进行操作
        df.loc[:, column] = fill_column_moving_average(df, column)

    return df

3. 问题分析

3.1. 视图(View)和副本(Copy)

在 Pandas 中,视图(view)和副本(copy)是数据框(DataFrame)和序列(Series)对象的两种不同表现形式,理解这两者的区别对于避免意外的数据修改和处理效率非常重要。

  • 视图(View)

    视图是指对原始数据的引用,不用创建新的数据。当对视图进行操作时,实际上是在操作原始数据。

    特点:

    • 修改视图中的数据会直接反映到原始数据中,因为它们共享相同的底层数据。
    • 视图通常更高效,因为它们不会复制数据,只是提供了对数据的另一种访问方式。
    • 视图的生成依赖于 Pandas 的具体操作和上下文,并不是所有情况下都会返回视图。
  • 副本(Copy)

    副本是指对原始数据的一个完全独立的拷贝。副本有自己独立的内存空间,修改副本不会影响原始数据。

    特点:

    • 修改副本中的数据不会影响原始数据。
    • 副本通常会占用更多的内存,因为它们复制了数据。
    • 在进行复杂的数据操作时,Pandas 有时会返回副本以避免修改原始数据。

示例:

import pandas as pd
"""
操作视图
"""
data = {
    'A': [1, 2, 3, 4],
    'B': [10, 20, 30, 40]
}
df = pd.DataFrame(data)

view = df.loc[:3]

view.loc[0, 0] = 100

print(df)
"""
     A   B
0  100  10
1    2  20
2    3  30
3    4  40
"""
import pandas as pd
"""
操作副本
"""
data = {
    'A': [1, 2, 3, 4],
    'B': [10, 20, 30, 40]
}
df = pd.DataFrame(data)

# 通过 .copy() 方法显式创建副本
copy = df[:3].copy()

# 修改副本的数据
copy.loc[0, 0] = 100

# 查看原始 DataFrame
print(df)

"""
   A   B
0  1  10
1  2  20
2  3  30
3  4  40
"""

3.2. 链式赋值的潜在问题

链式赋值指的是在一条语句中对 DataFrame 进行多次索引操作并赋值的行为。由于链式赋值在内部可能会创建中间视图或副本,Pandas 无法确定你最终是否意图修改原始 DataFrame,从而可能引发 SettingWithCopyWarning 警告。

import pandas as pd

data = {
    'A': [1, 2, 3, 4, 5],
    'B': [10, 20, 30, 40, 50]
}
df = pd.DataFrame(data)
# 链式赋值:df[df['A'] > 2]返回一个子集,然后再对子集的B列进行赋值操作
# 这种操作会引发 SettingWithCopyWarning 警告,因为 Pandas 无法确定你是否打算修改原始 DataFrame
df[df['A'] > 2]['B'] = 0

print(df)

为了避免链式赋值的潜在问题,可以使用.loc或者.iloc进行显式赋值,这些方法能够明确地告诉 Pandas 你想要修改原始 DataFrame 的哪一部分。

# 避免链式赋值,使用 .loc 显式操作
df.loc[df['A'] > 2, 'B'] = 0

如上代码,Pandas 就知道你是在直接修改原始 DataFrame 的 ‘B’ 列中满足 df['A'] > 2 条件的行。

3.3. df[column]和df.loc[:, column]的区别

  1. df[column]
    • 直接访问 DataFrame 的某一列,返回一个 Series 对象。
    • 可能会返回视图(view)或副本(copy),这取决于上下文。
    • 当使用这种方式进行链式赋值操作时(例如 df[column] = something),容易触发 SettingWithCopyWarning,因为 Pandas 无法确定你是否想修改原始 DataFrame。
  2. df.loc[:, column]
    • 使用 .loc 显式进行基于标签的索引操作。
    • 明确地返回一个视图(view)而不是副本(copy)。
    • 这种方式通常不会触发 SettingWithCopyWarning,因为 Pandas 可以确认你是要修改原始 DataFrame 的部分内容。

3.4. df.loc[]和df.at[]的区别

atloc 是 Pandas 库中用于访问 DataFrame 和 Series 中的数据的方法。

at用于快速访问和设置单个值。(主要用于单个标量值的访问和设置,在访问单个元素时性能更快,因为它专门为单个元素设计)

loc用于基于标签的访问,允许访问或修改多个元素。(用于标签索引,可以选择一行、一列或多个元素,支持更复杂的索引操作)

示例:

import pandas as pd

data = {
    'A': [1, 2, 3, 4],
    'B': [10, 20, 30, 40]
}
df = pd.DataFrame(data)
# 读取单个值
value = df.at[0, 'A']

# 设置单个值
df.at[0, 'A'] = 100
print(df)
import pandas as pd

data = {
    'A': [1, 2, 3, 4],
    'B': [10, 20, 30, 40]
}
df = pd.DataFrame(data)

print(df)

# 读取单个值
value = df.loc[0, 'A']
print(f"value: {value}")

# 读取单行或多行
row = df.loc[0]
print(f"row: {row}")
rows = df.loc[[0, 1, 2]]
print(f"rows:{rows}")

# 读取单列或多列
column = df.loc[:, 'A']
print(f"column:{column}")
columns = df.loc[:, ['A', 'B']]
print(f"columns:{columns}")

# 读取子 DataFrame
sub_df = df.loc[0:2, ['A', 'B']]
print(f"sub_df:{sub_df}")

# 设置单个值
df.loc[0, 'column_name'] = 100
print(f"df:{df}")

# 设置多行或多列
df.loc[0:2, 'column_name'] = 200
print(f"df:{df}")

3.5. df.loc[]和df.iloc[]的区别

在Pandas中,df.loc[]df.iloc[] 是两种常用的索引方法,它们分别基于标签和基于位置来进行数据选取。

df.loc[] 主要是基于行和列的标签来选取数据。它允许你通过行标签和列标签来访问DataFrame中的数据。

  • 如果只传入一个参数,那么它会被解释为行标签。
  • 如果传入两个参数,第一个被视为行标签,第二个被视为列标签。
# 选取行
df.loc['row_label']

# 选取列
df.loc[:, 'column_label']

# 选取行和列
df.loc['row_label', 'column_label']

df.iloc[] 是基于整数位置的索引方法。它根据行和列的位置(从0开始计数)来选取数据。

  • 如果只传入一个参数,那么它会被解释为行位置。
  • 如果传入两个参数,第一个被视为行位置,第二个被视为列位置。
# 选取行
df.iloc[0]

# 选取列
df.iloc[:, 0]

# 选取行和列
df.iloc[0, 0]

4. 其他问题

将代码修改成下述这样,仍是在相同虚拟环境下,两个不同的项目一个报错一个不报错。

def fill_column_moving_average(df: DataFrame, column) -> DataFrame:
    moving_avg = df[column].rolling(window=10, min_periods=1).mean()
    return df[column].fillna(moving_avg)


df = pd.read_csv(r'E:\07-code\tunnelproject\service\data\merge\0.csv')
for column in df.columns:
    # 判断每列第一个数据是否为nan df[column].iloc[0]
    if pd.notna(df.at[0, column]):
        # 如果不是nan, 不做任何操作
        pass
    else:
        # 如果是nan, 用本列的平均值替换
        df.at[0, column] = df[column].mean()

    # 对每一列进行操作
    # df[column] = fill_column_moving_average(df, column)
    moving_avg = df[column].rolling(window=10, min_periods=1).mean()
    df[column] = df[column].fillna(moving_avg)

5. 小结

对DataFrame切片数据进行操作时,最好使用atlociloc进行显式赋值,确保Pandas明确知道你要修改原始数据,若不想修改原始数据,可以采用Copy()备份的方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值