Làm thế nào để đối phó với SettingWithCopyWarning
Pandas?
Bài viết này là dành cho độc giả, những người,
- Muốn hiểu cảnh báo này có nghĩa là gì
- Muốn hiểu các cách khác nhau để ngăn chặn cảnh báo này
- Muốn hiểu cách cải thiện mã của họ và tuân theo các thực tiễn tốt để tránh cảnh báo này trong tương lai.
Thiết lập
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
Là gì SettingWithCopyWarning
?
Để biết làm thế nào để đối phó với cảnh báo này, điều quan trọng là phải hiểu ý nghĩa của nó và tại sao nó được nêu ra ngay từ đầu.
Khi lọc DataFrames, có thể lát / lập chỉ mục một khung để trả về dạng xem hoặc bản sao , tùy thuộc vào bố cục bên trong và các chi tiết triển khai khác nhau. "Chế độ xem", như thuật ngữ gợi ý, chế độ xem vào dữ liệu gốc, do đó sửa đổi chế độ xem có thể sửa đổi đối tượng ban đầu. Mặt khác, "bản sao" là bản sao dữ liệu từ bản gốc và sửa đổi bản sao không có tác dụng đối với bản gốc.
Như được đề cập bởi các câu trả lời khác, SettingWithCopyWarning
đã được tạo để gắn cờ các hoạt động "gán xích". Xem xét df
trong các thiết lập ở trên. Giả sử bạn muốn chọn tất cả các giá trị trong cột "B" trong đó các giá trị trong cột "A" là> 5. Pandas cho phép bạn thực hiện điều này theo nhiều cách khác nhau, một số chính xác hơn các giá trị khác. Ví dụ,
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
Và,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
Chúng trả về cùng một kết quả, vì vậy nếu bạn chỉ đọc các giá trị này, điều đó không có gì khác biệt. Vậy, vấn đề là gì? Vấn đề với việc gán chuỗi, nói chung là rất khó để dự đoán xem một chế độ xem hoặc bản sao được trả lại, vì vậy điều này phần lớn trở thành vấn đề khi bạn đang cố gắng gán lại các giá trị. Để xây dựng trên ví dụ trước, hãy xem xét cách mã này được trình thông dịch thực thi:
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
Với một __setitem__
cuộc gọi đến df
. OTOH, xem xét mã này:
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
Bây giờ, tùy thuộc vào việc __getitem__
trả về một khung nhìn hay một bản sao, __setitem__
thao tác có thể không hoạt động .
Nói chung, bạn nên sử dụng loc
cho gán dựa trên nhãn và iloc
cho gán dựa trên số nguyên / vị trí, vì thông số kỹ thuật đảm bảo rằng chúng luôn hoạt động trên bản gốc. Ngoài ra, để thiết lập một ô duy nhất, bạn nên sử dụng at
và iat
.
Nhiều hơn có thể được tìm thấy trong các tài liệu .
Lưu ý
Tất cả các hoạt động lập chỉ mục boolean được thực hiện với loc
cũng có thể được thực hiện với iloc
. Sự khác biệt duy nhất là iloc
mong đợi số nguyên / vị trí cho chỉ mục hoặc một mảng numpy của các giá trị boolean và chỉ mục số nguyên / vị trí cho các cột.
Ví dụ,
df.loc[df.A > 5, 'B'] = 4
Có thể viết bằng mũi
df.iloc[(df.A > 5).values, 1] = 4
Và,
df.loc[1, 'A'] = 100
Có thể được viết như
df.iloc[1, 0] = 100
Và như thế.
Chỉ cần cho tôi biết làm thế nào để đàn áp cảnh báo!
Hãy xem xét một hoạt động đơn giản trên cột "A" của df
. Chọn "A" và chia cho 2 sẽ đưa ra cảnh báo, nhưng thao tác sẽ hoạt động.
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
Có một số cách để im lặng cảnh báo trực tiếp này:
Làm một deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Thay đổipd.options.mode.chained_assignment
có thể được thiết lập để None
, "warn"
hoặc "raise"
. "warn"
là mặc định None
sẽ loại bỏ hoàn toàn cảnh báo và "raise"
sẽ ném SettingWithCopyError
, ngăn không cho hoạt động đi qua.
pd.options.mode.chained_assignment = None
df2['A'] /= 2
@Peter Cotton trong các nhận xét, đã đưa ra một cách hay là không thay đổi chế độ (được sửa đổi từ ý chính này ) bằng trình quản lý bối cảnh, để đặt chế độ chỉ khi cần thiết và đặt lại chế độ đó thành trạng thái ban đầu khi hoàn thành.
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
Cách sử dụng như sau:
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
Hoặc, để nâng cao ngoại lệ
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
"Vấn đề XY": Tôi đang làm gì sai?
Rất nhiều thời gian, người dùng cố gắng tìm cách để loại bỏ ngoại lệ này mà không hiểu đầy đủ lý do tại sao nó được nêu ra ngay từ đầu. Đây là một ví dụ điển hình cho vấn đề XY , trong đó người dùng cố gắng giải quyết vấn đề "Y" thực sự là một triệu chứng của vấn đề gốc rễ sâu hơn "X". Các câu hỏi sẽ được đưa ra dựa trên các vấn đề phổ biến gặp phải cảnh báo này và các giải pháp sau đó sẽ được trình bày.
Câu hỏi 1
Tôi có 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
Tôi muốn gán giá trị trong col "A"> 5 đến 1000. Sản lượng dự kiến của tôi là
A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
Cách sai để làm điều này:
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
Đúng cách sử dụng loc
:
df.loc[df.A > 5, 'A'] = 1000
Câu hỏi 2 1
Tôi đang cố gắng đặt giá trị trong ô (1, 'D') thành 12345. Sản lượng dự kiến của tôi là
A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1
Tôi đã thử các cách khác nhau để truy cập vào tế bào này, chẳng hạn như
df['D'][1]
. Cách tốt nhất để làm việc này là gì?
1. Câu hỏi này không liên quan cụ thể đến cảnh báo, nhưng thật tốt khi hiểu cách thực hiện thao tác cụ thể này một cách chính xác để tránh các tình huống có thể xảy ra cảnh báo trong tương lai.
Bạn có thể sử dụng bất kỳ phương pháp nào sau đây để làm điều này.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
Câu hỏi 3
Tôi đang cố gắng tập hợp các giá trị dựa trên một số điều kiện. Tôi có một DataFrame
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Tôi muốn gán giá trị trong "D" cho 123 sao cho "C" == 5. Tôi đã thử
df2.loc[df2.C == 5, 'D'] = 123
Có vẻ tốt nhưng tôi vẫn nhận được
SettingWithCopyWarning
! Làm thế nào để tôi sửa lỗi này?
Điều này thực sự có thể là do mã cao hơn trong đường ống của bạn. Bạn đã tạo ra df2
từ một cái gì đó lớn hơn, như
df2 = df[df.A > 5]
? Trong trường hợp này, lập chỉ mục boolean sẽ trả về một khung nhìn, do đó df2
sẽ tham chiếu bản gốc. Những gì bạn cần làm là gán df2
cho một bản sao :
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
Câu hỏi 4
Tôi đang cố gắng thả cột "C" tại chỗ từ
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Nhưng sử dụng
df2.drop('C', axis=1, inplace=True)
Ném SettingWithCopyWarning
. Tại sao chuyện này đang xảy ra?
Điều này là do df2
phải được tạo như một khung nhìn từ một số thao tác cắt khác, chẳng hạn như
df2 = df[df.A > 5]
Giải pháp ở đây là một trong hai làm cho một copy()
số df
, hoặc sử dụng loc
, như trước đây.