23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요. Teus입니다.
이번 포스팅에서는 Pandas Object의 inplace=True 동작에 대해서 다룹니다.
inplace = True동작이 어떻게 동작하는지, 그리고 왜 사용을 지양하는지에 대해서 알아 볼겁니다.
inplace는 Object의 불변성과 관련된 중요한 키워드 입니다.
기본적으로 불변성 Data는 Data자신이 바뀌는일 없이
Data에 변경이 생길경우 변경이 적용된 새로운 Data를 만들어 줍니다.
아래 Series Object를 정렬시켜주는 sort_values()를 보시겠습니다.
import pandas as pd
dt = pd.Series([1, 8, 6, 7, 9])
print(dt.sort_values())
#>>[1, 6, 7, 8, 9]
print(dt)
#>>[1, 8, 6, 7, 9]위처럼 정렬이된 Series Object를 반환하지만
기존 dt변수에는 정렬되지 않은 Series가 그대로 남아있습니다.
하지만 매개번수에서 inplace = True로 설정할 경우 전혀 다른동작을 보여주게 됩니다.
import pandas as pd
dt = pd.Series([1, 8, 6, 7, 9])
print(dt.sort_values(inplace = True))
#>>None
print(dt)
#>>[1, 6, 7, 8, 9]보는것처럼 Object가 가지고있던 Data가 바뀌어 바뀌어 버립니다!
실제 Pandas Source Code 내부에서 어떤일이 일어나는지 보시겠습니다.
일단 Series.sort_values 코드로 가 보겠습니다.
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/series.py#L3545
class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc]
...
def sort_values(
self,
*,
axis: Axis = 0,
ascending: bool | Sequence[bool] = True,
inplace: bool = False,
kind: SortKind = "quicksort",
na_position: NaPosition = "last",
ignore_index: bool = False,
key: ValueKeyFunc | None = None,
) -> Series | None:
inplace = validate_bool_kwarg(inplace, "inplace")
# Validate the axis parameter
self._get_axis_number(axis)
# inplace를 실행하기 위해서는 copy본을 꼭 만들어줘야됨
# 단순하게 view를 통해 접근한 data에는 불가
if inplace and self._is_cached:
raise ValueError(
"This Series is a view of some other array, to "
"sort in-place you must create a copy"
)먼저 inplace일 경우 View상태에서는 실행이 불가능 합니다.
때문에 위처럼 Error처리가 되어있는것을 볼 수 있습니다.
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/series.py#L3725
class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc]
...
else:
#특정 값 기준으로 sorting하기 위해서
#sorting을 할 target data를 설정
values_to_sort = self._values
#value를 기반으로 fancy indexing을 진행할 fancy index를 생성함
sorted_index = nargsort(values_to_sort, kind, bool(ascending), na_position)
#비교결과 정렬할 필요가 없을 경우
if is_range_indexer(sorted_index, len(sorted_index)):
if inplace:
#self._update_inplace(self)를 실행하고
#None을 반환하게 되어있음.
#self._update_inplace(self)
#return None
#과 동일
return self._update_inplace(self)
return self.copy(deep=None)그리고 sorting을 할 결과에 따라 행동이 나뉘게 되는데
sorting을 할 필요가 없는 경우 자신의 복사본을 반환하거나
self._update_inplace method를 통해서 무언가는 업데이트 시키는것을 볼 수 있습니다.
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/series.py#L3734
#중요!
#정렬이 필요한 경우 정렬 후 value와 index를 fancy indexing한 뒤
#새로운 object를 생성해줌
result = self._constructor(
self._values[sorted_index], index=self.index[sorted_index], copy=False
)
#아래 문장과 동일함
#result = Series(
# self._values[sorted_index], index=self.index[sorted_index], copy=False
#)
#이때 copy = False를 사용해서 기존 Data를 완전 복제하지 않음
...
if not inplace:
return result.__finalize__(self, method="sort_values")
self._update_inplace(result)
return None그리고 실제 정렬이 필요한 경우를 보면
Series가 가지고있던 value와 index를 가지고오고
numpy의 fancy indexing을 활용해서 정렬된 상태의 Array를 만들어 줍니다.
[주의]
Slicing과 달리 Fancy indexing은 View가 아닌 데이터의 Copy본을 만들어줌.
View는 메모리를 복사하는 것이 아니라, 해당 Data를 볼 수 있는 통로라고 할 수 있음
import numpy as np
arr = np.arange(100)
arr_copy = arr[0:10]
arr[0] = 100
print(arr[0])
#>>100
print(arr_copy[0])
#>>100
arr = np.arange(100)
arr_copy = arr[[i for i in range(100)]]
arr[0] = 100
print(arr[0])
#>>100
print(arr_copy[0])
#>>0그리고 이 데이터 가지고 새로운 Object를 만들어 주는데
이때 copy = False를 통해서 데이터가 2배가 되는것을 막아주는것을 볼 수 있습니다.
import pandas as pd
import numpy as np
import psutil
cnt = 100000000
dt = np.arange(cnt)
p = psutil.Process()
rss = p.memory_info().rss/2**20
temp = pd.Series(dt, copy = False)
print(f'mem usage : {p.memory_info().rss/2**20-rss:10.5f}MB')
#copy = False일 경우 메모리가 증가하지 않음
#>>mem usage : 0.06250MB
p = psutil.Process()
rss = p.memory_info().rss/2**20
temp = pd.Series(dt, copy = True)
print(f'mem usage : {p.memory_info().rss/2**20-rss:10.5f}MB')
#copy = True일 경우 메모리가 증가함
#>>mem usage : 381.47266MB이렇게 copy = False로 반환된 data를 Pandas에서는 View라고 합니다.
이런 View는 사용한 원본Data가 바뀔경우 Series의 값 역시 변경됩니다.
import pandas as pd
import numpy as np
arr = np.arange(10)
tg = pd.Series(arr, copy = False)
print(tg[0])
#>> 0
#Series Object가 아니라 Series Object를 만들때 사용된 Data를 변경
arr[0] = 200
print(tg[0])
#>> 200이제 마지막으로 inplace 여부에따라 분기가 갈립니다.
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/series.py#L3741
...
if not inplace:
return result.__finalize__(self, method="sort_values")
self._update_inplace(result)
return Noneinplace=False일 경우 NDFrame.__finalize__ method가 실행되게 됩니다.
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/generic.py#L6148
class NDFrame(PandasObject, indexing.IndexingMixin):
...
def __finalize__(self, other, method: str | None = None, **kwargs) -> Self:
if isinstance(other, NDFrame):
for name in other.attrs:
self.attrs[name] = other.attrs[name]
self.flags.allows_duplicate_labels = other.flags.allows_duplicate_labels
# For subclasses using _metadata.
for name in set(self._metadata) & set(other._metadata):
assert isinstance(name, str)
object.__setattr__(self, name, getattr(other, name, None))
if method == "concat":
...
return selfNDFrame.__finzlize__()가 실행되면서
result에 해당하는 Series Object에 Original(기존)의 모든 properties를 전송하는 모습을 볼 수 있습니다.
하지만 attrs같은 경우 pandas에서 실험적으로 metadata를 관리하는 방법으로
일반적은 Series를 사용할때는 name이외에 다른 metadata는 없습니다.
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/generic.py#L238
class NDFrame(PandasObject, indexing.IndexingMixin):
...
@property
def attrs(self) -> dict[Hashable, Any]:
"""
Dictionary of global attributes of this dataset.
.. warning::
attrs is experimental and may change without warning.
"""
...그럼 self._update_inplace()를 확인해 볼까요?
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/generic.py#L4807
class NDFrame(PandasObject, indexing.IndexingMixin):
...
@final
def _update_inplace(self, result, verify_is_copy: bool_t = True) -> None:
self._reset_cache()
self._clear_item_cache()
self._mgr = result._mgr
self._maybe_update_cacher(verify_is_copy=verify_is_copy, inplace=True)다른 method는 모두 cache와 연관되어있고
중요한 부분은 바로 self._mgr = result._mgr 이 부분 입니다.
_mgr에는 data와 index정보가 담겨있다고 했던거 내용이 기억 나시나요?
(이전 pandas Series의 구조에서 봤었죠?)
inplace로 실행할 경우 이처럼 Class의 _mgr만 갈아끼어 넣어주는 방식으로 method가 동작합니다.
self.dropna도 살펴보고 가기#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/series.py#L5525C1-L5618C26
class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc]
...
def dropna(
self,
*,
axis: Axis = 0,
inplace: bool = False,
how: AnyAll | None = None,
ignore_index: bool = False,
) -> Series | None:
...
#매개변수의 String Literal 확인
inplace = validate_bool_kwarg(inplace, "inplace")
ignore_index = validate_bool_kwarg(ignore_index, "ignore_index")
# Validate the axis parameter
self._get_axis_number(axis or 0)
#na를 제거한 새로운 Series를 반환받음
if self._can_hold_na:
result = remove_na_arraylike(self)
...
#inplace여부에따라 분기를 나눠서 result로 덮어씌움
if inplace:
return self._update_inplace(result)
else:
return resultself.dropna() 역시 보는것처럼
조건에따라 na가 제거된 새로운 Array를 만들고
이 Array를 가지고 inplace 매개변수의 값에 따라서 분기를 나눠 수정하는것을 볼 수가 있습니다.
결국 self.sort_values()와 self.dropna()를 통해서 본 소스코드를 정리해보면.
data의 값 기준으로 정렬된 복사본을 만든다.
이 복사본을 가지고 copy = False로 새로운 Series Object를 만들어준다.
새로운 Object에서 inplace 여부에 따라
-> inplace = False : 값을 바꾼 result에 기존 object의 meta data를 덮어씌어서 반환한다.
-> inplace = True : 값을 바꾼 result의 _mgr을 기존 object의 _mgr에 덮어씌운다.
inplace = True를 쓰면 되냐, 안되냐에 대해서 말이 많습니다.
결론부터 말하면 Pandas Dev Society에서는 inplace의 사용을 추천하지 않고 있습니다.
출처 : https://github.com/pandas-dev/pandas/issues/16529
위 코드 정리본을 보면 알 수 있지만
결국 inplace를 사용하나, 사용하지 않나 result라는 새로운 Series Object가 순간적으로 만들어지게 됩니다.
때문에 inplace를 사용하는 여부에 따라 크게 달라지는것이 없다는것을 알 수가 있습니다.
inplace구현을 위해서 _mgrObject가 무겁게 됩니다.
마지막으로 이걸 구현하기 위해서 개발 Resource가 낭비됩니다!
그리고 다른 프래임워크 등에서 Data의 불변성을 추구하면서 전체적인 패러다임이 데이터의 불변성을 확보하는 방식으로 가고 있습니다.(React라던가 React라던가 React라던가...)
이러한 이유들 때문에 사용하지 않는것이 바람직하다고 볼 수 있겠습니다.😖
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.