티스토리 뷰
안녕하세요. Teus입니다.
이번 포스팅에서는 Pandas Object의 inplace=True 동작에 대해서 다룹니다.
inplace = True동작이 어떻게 동작하는지, 그리고 왜 사용을 지양하는지에 대해서 알아 볼겁니다.
1. inplace = True ? False
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 내부에서 어떤일이 일어나는지 보시겠습니다.
2. 소스코드 톱아보기
일단 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 None
inplace=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 self
NDFrame.__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정보가 담겨있다고 했던거 내용이 기억 나시나요?
(이전 포스팅1, 포스팅2)
inplace로 실행할 경우 이처럼 Class의 _mgr
만 갈아끼어 넣어주는 방식으로 method가 동작합니다.
3. 아쉬우니깐 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 result
self.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
에 덮어씌운다.
4. inplace. 써요 말아요?
inplace = True를 쓰면 되냐, 안되냐에 대해서 말이 많습니다.
결론부터 말하면 Pandas Dev Society에서는 inplace의 사용을 추천하지 않고 있습니다.
출처 : https://github.com/pandas-dev/pandas/issues/16529
위 코드 정리본을 보면 알 수 있지만
결국 inplace를 사용하나, 사용하지 않나 result라는 새로운 Series Object
가 순간적으로 만들어지게 됩니다.
때문에 inplace를 사용하는 여부에 따라 크게 달라지는것이 없다는것을 알 수가 있습니다.
- inplace구현을 위해서
_mgr
Object가 무겁게 됩니다. - 마지막으로 이걸 구현하기 위해서 개발 Resource가 낭비됩니다!
- 그리고 다른 프래임워크 등에서 Data의 불변성을 추구하면서 전체적인 패더다임이 데이터의 불변성을 확보하는 방식으로 가고 있습니다.
이러한 이유들 때문에 사용하지 않는것이 바람직하다고 볼 수 있겠습니다.
'Python 잡지식 > 소스코드 톱아보기' 카테고리의 다른 글
[Python] 문자열 Encoding과 Python이 문자열을 처리하는 방법 (0) | 2023.12.06 |
---|---|
[Pandas] Pandas의 apply동작에 대해서 (0) | 2023.12.06 |
[Pandas] Series의 구조에 대해서 알아보자2 (1) | 2023.12.06 |
[Pandas] Series의 구조에 대해서 알아보자1 (0) | 2023.12.06 |
[Python]Requests Library의 동작 Part2 (0) | 2022.08.12 |
- Total
- Today
- Yesterday
- GDC
- Search알고리즘
- C++
- 자료구조
- AVX
- hash
- 완전탐색 알고리즘
- SIMD
- 동적계획법
- Python
- 코딩테스트
- 병렬처리
- git
- prime number
- 분할정복
- 알고리즘
- 컴퓨터그래픽스
- stack
- Sort알고리즘
- 프로그래머스
- heap
- 이분탐색
- 사칙연산
- Greedy알고리즘
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |