판다스를 사용하다 보면 우리는
1. fillna()를 통해 결측값을 채우기도 하고,
2. dropna()를 통해 결측값을 지우기도 하며,
3. replace()를 통해 값을 변경하기도 합니다.
이 과정에서 데이터분석가들은 두가지 유형으로 나누어집니다.
#1.
df.fillna(method = 'ffill', inplcae = True)
#2.
df.dropna(inplace = True)
#3.
df.replace({"런던":"뉴욕"}, inplace = True)
#1.
df_fill = df.fillna(method = 'ffill')
#2.
df_dropna = df.dropna()
#3.
df_change = df.replace({"런던":"뉴욕"})
inplace = True를 쓰거나, 혹은 새로운 데이터 프레임에 저장을 합니다.
어떤것이 맞고 틀린것이 있을까요??? 이 물음에 대한 답을 "Medium"에 게시된 글을 인용해서 확인해보겠습니다.(인용한 Medium Post Address : https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4 )
Q1. 중간 결과는 필요하지 않고, 최종결과만 필요한 상황.
Q2. 메모리를 아끼기 위해서 새로운 데이터 프레임의 생성을 막기위함
Q3. 모든 중간단계마다 새로운 변수 이름을 생성 하고 싶지않음.
Q4. 그냥 습관처럼 사용한다.
네 가지, 이유들을 들었을때 모두 끄덕일 수 있는 이유들이라고 생각합니다. 데이터 분석가로 여러가지 데이터를 가공하고, 처리하고, 분석하고, 자동화하면서 쓴이도 역시 생각해봤던 것들이고, inplace를 쓸때도 있고, 새로운 변수로 저장할때도 있었습니다.
다시 생각해 보면, 뭔가 함수에서 인자로 전달해야하는 상황이 있거나, 구분을 지어서 만들고 싶을때는 새로운변수를 사용하고 그렇지 않으면 inplace = True가 코드 작성시 더 편했던것 같습니다.
새로운 변수를 계속 만들어 내는것 또한 복잡도가 올라가서 변수명을 만드는것도 힘들지만 이를 정리하는게 더 힘들어 질거라는 생각을 해본적이 있습니다.
이러한 이유들을 Medium 필자는 다음과 같이 반박을 합니다.
만약 정말 간단하고 누가봐도 문제가 생길 구간이 없다라고 생각할때는 좋은 선택지가 될수도 있지만, 개발/디버깅/테스트 중에 파이프라인의 여러 구간중 일부 구간을 검사하고 싶을때가 있다. 이때 inplace = True를 사용하게 되면 원래있던 데이터 프레임의 형태 자체에 변형이 생기기 때문에 부작용이 생길수도 있다. 다음 예시를 통해서 이해를 해보자
(도시별 판매량이 있는 테이블이 있으며, 가장 많이 팔린 도시의 순위표를 생성하고 모든 도시의 총 판매량을 계산한다고 가정해 보자.)
def create_top_city_leaderboard(df):
df.dropna(subset=["city"], inplace=True)
df.sort_values(by=["sales"], ascending=False, inplace=True)
return df
def compute_total_sales(df):
return df["sales"].sum()
df = pd.DataFrame(
{
"도시": ["런던", "암스테르담", "뉴욕", 없음],
"매출": [100, 300, 200, 400],
}
)
단순히 코드를 봤을때는 독립적인 각각의 기능이기때문에 순서는 중요하지 않다. 그렇다면 이기능을 실행해보고 어떤일이 일어나는지 알아보자
>>> df =pd.DataFrame(
{
"도시": ["런던", "암스테르담", "뉴욕", 없음],
"매출": [100, 300, 200, 400],
}
)
>>>calculate_total_sales(df)
1000
>>>create_top_city_leaderboard(df)
도시 판매
1 암스테르담 300
2 뉴욕 200
0 런던 100
이렇게 구현했을때 모두 정상적으로 잘 구현된것처럼 보이지만 새로 만들어진 데이터 프레임을 다시 한번 계산하면 어떻게 될까??
>>> calculate_total_sales(df)
600
우리는 판매량 400을 잃어버리는 상황이 생긴다.
이 경우 create_top_city_leaderboard 데이터 프레임이 손상되어 버그가 생길 수 있다.
즉, 이것을 피하면서 inplace = True를 사용하기 위해서는 여러가지 작업사이에서 데이터프레임이 어떤형태를 가지고 있는지 뿐만아니라, 로직을 이어가면서 어떤상태에 있을것인지를 모두 염두하며 안전한 코드를 작성해야하는데 사실상 어렵다.
그렇다면 동일한 상황에서 다음과 같이 작성하면 어떨까??
def create_top_city_leaderboard(df):
return (
df.dropna(subset=["city"])
.sort_values(by=["sales"], ascending=False)
)
메모리를 사용하면서 복사할 필요없이 수정하는데 더 효과적이다. 함수자체를 전달해도 되고 메모리를 사용할지라도 새로운변수에 지정한다면 훨씬더 안정적일것이다.
inplace = True를 사용하게되면 아무것도 반환이 되지 않습니다. 따라서, 이어서 코드를 쭉 쓰는것을 방해하게 됩니다.
아래의 예시를 살펴보겠습니다.
def create_country_leaderboard(df):
country_df = df.groupby("국가")[["판매", "환불"]].sum()
country_df.rename(index=str.lower, inplace=True)
country_df.reset_index(inplace =True)
country_df.sort_values(by="sales", inplace=True)
country_df 반환
이렇게 사용된것을 inplace = True없이 연결해서 만들어 보겠습니다.
def create_country_leaderboard(df):
return (
df.groupby("country")[["sales", "refunds"]]
.sum()
.rename(index=str.lower)
.reset_index()
.sort_values(by=" 판매")
)
이렇게 가독성 있게, 연결해서 쭉 로직을 만들어갈 수 있습니다.
- Pandas개발팀에서 해당 매개변수를 사용하지 말것을 권장했으며, 추후 해당기능을 삭제할 예정이라고 발표를 했습니다.
- inplace =True를 사용하는것을 깜빡한 경우 어디서 잘못되었는지 디버깅 하기 힘들다.