文章目录
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]的区别
- df[column]
- 直接访问 DataFrame 的某一列,返回一个 Series 对象。
- 可能会返回视图(view)或副本(copy),这取决于上下文。
- 当使用这种方式进行链式赋值操作时(例如
df[column] = something
),容易触发SettingWithCopyWarning
,因为 Pandas 无法确定你是否想修改原始 DataFrame。
- df.loc[:, column]
- 使用
.loc
显式进行基于标签的索引操作。 - 明确地返回一个视图(view)而不是副本(copy)。
- 这种方式通常不会触发
SettingWithCopyWarning
,因为 Pandas 可以确认你是要修改原始 DataFrame 的部分内容。
- 使用
3.4. df.loc[]和df.at[]的区别
at
和 loc
是 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切片数据进行操作时,最好使用at
、loc
、iloc
进行显式赋值,确保Pandas明确知道你要修改原始数据,若不想修改原始数据,可以采用Copy()
备份的方法。