SettingWithCopyWarning เรื่องเล็ก ๆ ที่อาจโดนมองข้าม
ขอเขียน Note ไว้หน่อยเถอะ ปัญหานี้แก้นานมาก มันเป็น Warning ที่เจออยู่บ่อย ๆ แต่ก็มองข้ามมันไปตลอด เพราะที่ผ่านมามันไม่มีผลกระทบอะไรกับการทำงาน หมายถึงว่าค่าต่าง ๆ ตัวเลขต่าง ๆ หรือ Table ที่เราต้องการให้มันเป็น ยังคงถูกต้องอยู่ แต่วันนี้ค้นพบว่า เวลาที่โปรแกรมมันเตือนว่า SettingWithCopyWarning มันก็มีความหมายของมัน
ไม่รู้ว่าจะเรียกมันว่าบั๊คหรือฟีเจอร์ดี ? หรือว่าเราแหละ… ที่เป็นบั๊ค
Problem
คือเรื่องมันเป็นแบบนี้… พอดีทำ Time series model อยู่ มาถึง Step ก่อนที่จะเริ่มสร้างโมเดล เราต้องมีการแบ่ง Train, Test เป็นปกติ ซึ่งมันก็ไม่ได้ยากเย็นอะไร เลยไม่ได้อยากใช้ Library ไง มันเขียนเองก็ได้ เลยเขียนโค้ดแบ่งเป็น 80/20
train = df.iloc[0:int(df.shape[0] * 0.8)]
test = df.iloc[int(df.shape[0] * 0.8):]
แล้วคราวนี้ก็ทำโมเดลต่อ ก็มีจังหวะที่ต้องเปลี่ยนข้อมูลในรูปแบบ Percent change ก็ไม่ได้คิดอะไรมาก ใช้ .pct_change()
ปกติ
train['Close'] = train['Close'].pct_change()
มันก็มี Warning เตือนขึ้นมาว่า
__main__: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
คือเคยเห็น Warning แบบนี้มาก่อนหน้านี้แล้ว เกิดขึ้นบ่อย ๆ จังหวะที่ตัดต่อ Table ก็ไม่ได้ใส่ใจอะไร แต่ว่าอะไรไม่รู้กลับไปเปิดตัวแปร df
ขึ้นมาอีกรอบ
อ่าว งง !
ทำไม 2 Dataframes มันหน้าตาเหมือนกันอ่ะ มันต้องไม่เหมือนสิ เพราะว่าเรารัน .pct_change()
บนเครื่องตัวแปร train
เริ่มงงละ ทำใหม่อีกกี่รอบก็ไม่หาย
Reason
จนได้มาเจอกับกระทู้นี้ใน Stackoverflow มันเป็นการทำงานรูปแบบนึงของ Pandas dataframe ที่มันจะสร้าง “โซ่” ในการ Operation บางคำสั่ง ดังนั้นถึงแม้ว่าเราสร้างตัวแปรใหม่ยังไง ตัวแปรเดิมก็ได้รับผลกระทบไปด้วย
As mentioned by other answers, the
SettingWithCopyWarning
was created to flag "chained assignment" operations.
Solution
คำถามคือมันจะแก้ยังไง? ได้คำตอบจากในกระทู้นั้นแหละว่า ทางแก้หลัก ๆ มี 2 ทางด้วยกันคือ
- ใช้ Function
.copy(deep = True)
ผ่านเข้าไปในตัวแปรที่เอามารับใหม่ เพื่อที่จะทำให้ Operate แค่ตัวแปรที่เราใช้งาน - เปลี่ยนที่
pd.options.mode.chained_assignment
ที่ค่า Default มันจะเป็นwarn
เราก็เปลี่ยนให้เป็นNone
train = df.iloc[0:int(df.shape[0] * 0.8)].copy(deep = True)
แล้วลองมารัน .pct_change()
อีกรอบนึงบน Training dataset
train['Close'] = train['Close'].pct_change()
ตัวเลขใน df
เดิมไม่เปลี่ยนไปแล้ววววว
เรื่องเล็ก ๆ น้อย ๆ แบบนี้บางทีก็ไม่ควรมองข้ามนะ เนี่ยวันนี้เลยได้ความรู้ใหม่เลย ขอจดไว้ในนี้หน่อยละกัน