티스토리 뷰
안녕하세요 Teus입니다.
이번 포스팅은 Pandas DataFrame을 이해하기 위한
Pandas Series 알아보기 시간 입니다.
1. Pandas Series
Pandas Series는 Pandas의 자료구조 중 1dim의 Array형태의 Object입니다
다들 DataFrame이 익숙 하시겠지만, DataFrame을 이해하기 위해서는 Series를 먼저 이해할 필요가 있습니다.
그럼 Pandas Series Source Code가 어떤 형태로 되어있는지 보도록 하겠습니다.
(초록색은 일반 Cls, 주황색은Mixin Cls입니다)
Series가 NDFrame을 상속받고, 이 NDFrame이 PandasObject를 상속받는 구조 입니다.
Series : drop_duplicates
, reset_index
, unique
등 Series Object에서 사용되는 다양한 method를 정의하는 부분
NDFrame : 내부에 Manager(약어로 mgr)을 활용해서 입력받은 Data를 저장함. DataFrame과 공유하는 Class로 shape
, axes
, ndim
등 Data의 형태에 대한 정보를 저장
PandasObject : Pandas Package가 가지고있는 다양한 Object가 기본적으로 상속받는 Class입니다. __repr__과 sizeof 등 일부 built-in-method를 정의해줌
자 그러면 Pandas Series가 어떤식으로 Data를 저장하는지 살펴볼까요?
1_1. Pandas Series
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/series.py#L245
class Series(base.IndexOpsMixin, NDFrame):
...
#static variable로 나중에 중요함
_mgr: SingleManager
def __init__(
self,
data=None,
index=None,
dtype: Dtype | None = None,
name=None,
copy: bool | None = None,
fastpath: bool = False,
) -> None:
...
Series Class의경우 일반적으로 ArrayLike Object를 받고, 이 Data를 기반으로 Pandas Series를 만들어 줍니다.
import pandas as pd
my_arr = pd.Series([i for i in range(10)])
이때 별도의 매개변수를 주는 경우를 빼고, 위와같이 Data만 주는 경우를 생각해 보겠습니다.
그러면 생성자에 매개변수중 data
이외에는 모두 기본값을 갖게 됩니다.
이때 생성자 내부에는 fastpath라고 하여서 조건문 검토를 최소화 하고, 빠르게 Series를 만들 수 있는 코드가 있습니다.
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/series.py#L404C9-L420C19
# we are called internally, so short-circuit
if fastpath:
# data is a ndarray, index is defined
if not isinstance(data, (SingleBlockManager, SingleArrayManager)):
#윈도우환경에서 Series 생성 시 "block"값을 return해줌
manager = get_option("mode.data_manager")
if manager == "block":
#static method인 from_array의 결과값을 data에 저장함
data = SingleBlockManager.from_array(data, index)
elif manager == "array":
data = SingleArrayManager.from_array(data, index)
elif using_copy_on_write() and not copy:
data = data.copy(deep=False)
if copy:
data = data.copy()
# skips validation of the name
object.__setattr__(self, "_name", name)
#만들어진 data를 활용해서 부모생성자 실행
NDFrame.__init__(self, data)
return
위 코드를 살펴보면, data가 SingleBlockManager
혹은 SingleArrayManager
의 static method인 from_array를 통해서 만들어진 Object를 가지고 data를 만들고
이 data를 사용해서 부모Cls인 NDFrame의 생성자를 실행하는 모습을 볼 수 있습니다.
그러면 이 data의 정체가 무엇인지 알기 위해서 SingleBlockManager Class
를 잠시 살펴보겠습니다.
1_2. SingleBlockManager
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/managers.py#L1787
class SingleBlockManager(BaseBlockManager, SingleDataManager):
"""manage a single block with"""
...
def __init__(
self,
block: Block,
axis: Index,
verify_integrity: bool = False,
) -> None:
self.axes = [axis]
self.blocks = (block,)
...
@classmethod
def from_array(
cls, array: ArrayLike, index: Index, refs: BlockValuesRefs | None = None
) -> SingleBlockManager:
"""
Constructor for if we have an array that is not yet a Block.
"""
array = maybe_coerce_values(array)
bp = BlockPlacement(slice(0, len(index)))
block = new_block(array, placement=bp, ndim=1, refs=refs)
return cls(block, index)
from_array method의 경우 친절하게 주석으로 Block이 되지못한 Array로 Class Object를 만들 때 쓴다
고 되어있습니다.
이때 maybe_coerce_value
의 경우 ArrayLike Object의 datetime 혹은 time format을 보정해주는 역할을 합니다.
따라서 array는 그냥 입력받은 array로 아직까지 사용중 입니다.
이제 아래 new_block을 통해서 array가 block이라고 하는 Object로 만들어지고
Block Object와 Index Object를 활용해서 Class Object가 만들어지는것을 볼 수 있습니다
1_3. func new_block
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas/core/internals/blocks.py#L2388
def new_block(
values,
placement: BlockPlacement,
*,
ndim: int,
refs: BlockValuesRefs | None = None,
) -> Block:
klass = get_block_type(values.dtype)
return klass(values, ndim=ndim, placement=placement, refs=refs)
위 함수를 보면, get_block_type의 결과로 Callable 한 Object를 가지고 오고
이 Object에 받은 매개변수를 넣어주면 Block Class Object가 나오는것을 Type Hinting으로 알 수 있습니다.
그럼 결국 get_block_type이 뭔지를 알아야 합니다.
1_4. func get_block_type
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/blocks.py#L2346
def get_block_type(dtype: DtypeObj) -> type[Block]:
if isinstance(dtype, DatetimeTZDtype):
return DatetimeTZBlock
elif isinstance(dtype, PeriodDtype):
return NDArrayBackedExtensionBlock
elif isinstance(dtype, ExtensionDtype):
# Note: need to be sure NumpyExtensionArray is unwrapped before we get here
return ExtensionBlock
# We use kind checks because it is much more performant
# than is_foo_dtype
kind = dtype.kind
if kind in "Mm":
return DatetimeLikeBlock
return NumpyBlock
소스코드를 보면 알 수 있지만, 특수한 경우를 제외하고는 NumpyBlock을 사용하는것을 볼 수 있습니다.
실제 소스코드에서 NumpyBlock 이외에 일부의 Block은 하위호환성을 위해서 남겨져 있습니다
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/blocks.py#L2232
class NumericBlock(NumpyBlock):
# this Block type is kept for backwards-compatibility
# TODO(3.0): delete and remove deprecation in __init__.py.
__slots__ = ()
class ObjectBlock(NumpyBlock):
# this Block type is kept for backwards-compatibility
# TODO(3.0): delete and remove deprecation in __init__.py.
__slots__ = ()
1_5. NumpyBlock
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/blocks.py#L2232
class NumpyBlock(libinternals.NumpyBlock, Block):
values: np.ndarray
__slots__ = ()
@property
def is_view(self) -> bool:
"""return a boolean if I am possibly a view"""
return self.values.base is not None
@property
def array_values(self) -> ExtensionArray:
return NumpyExtensionArray(self.values)
def get_values(self, dtype: DtypeObj | None = None) -> np.ndarray:
if dtype == _dtype_obj:
return self.values.astype(_dtype_obj)
return self.values
@cache_readonly
def is_numeric(self) -> bool: # type: ignore[override]
dtype = self.values.dtype
kind = dtype.kind
return kind in "fciub"
NumpyBlock같은 경우 Block Class와 libinternals.NumpyBlock을 다중상속 받고 있는것을 볼 수 있습니다.
이때 Block Class를 먼저 봐볼까요?
1_6. Block
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/blocks.py#L154
class Block(PandasObject):
"""
Canonical n-dimensional unit of homogeneous dtype contained in a pandas
data structure
"""
values: np.ndarray | ExtensionArray
ndim: int
refs: BlockValuesRefs
__init__: Callable
__slots__ = ()
is_numeric = False
...
이 Block에서는 @final을 이용해서 Pandas Data처리를위해 사용되는 상당수의 method가 정의되어있습니다.
하지만 이때 Block Class와 상위 Class는 생성자가 없습니다.
Python의 경우 Class가 매개변수를 받지만, 생성자가 없을경우 부모생성자가 자동으로 실행 됩니다.
class a:
def __init__(self, value):
self.value = value
class b:
def search(self):
return self.value
class c(a, b):
value : str
temp = c("kmsdls")
temp.search()
#>>"kmsdls"
그러므로 Block Class
가 아니라, libinternals.NumpyBlock
를 살펴볼 경우 생성자를 확인할 수 있을 겁니다.
1_7. cdef NumpyBlock
#https://github.com/pandas-dev/pandas/blob/v2.1.1/pandas//_libs/internals.pyx#L703
cdef class NumpyBlock(SharedBlock):
cdef:
public ndarray values
def __cinit__(
self,
ndarray values,
BlockPlacement placement,
int ndim,
refs: BlockValuesRefs | None = None,
):
# set values here; the (implicit) call to SharedBlock.__cinit__ will
# set placement, ndim and refs
self.values = values
보는것처럼 최상위 NumpyBlock Cython Class에서 cinit을 통해서 value를 받고
self.values = values
를 통해서 self.values에 데이터를 ndarray를 저장하는것을 확인할 수가 있습니다.
자 이제 다시 SingleBlockManager로 돌아가 보겠습니다.
2_1. SingleBlockManager2
#https://github.com/pandas-dev/pandas/blob/e86ed377639948c64c429059127bcf5b359ab6be/pandas/core/internals/managers.py#L1787
class SingleBlockManager(BaseBlockManager, SingleDataManager):
"""manage a single block with"""
...
def __init__(
self,
block: Block,
axis: Index,
verify_integrity: bool = False,
) -> None:
self.axes = [axis]
self.blocks = (block,)
...
@classmethod
def from_array(
cls, array: ArrayLike, index: Index, refs: BlockValuesRefs | None = None
) -> SingleBlockManager:
array = maybe_coerce_values(array)
bp = BlockPlacement(slice(0, len(index)))
block = new_block(array, placement=bp, ndim=1, refs=refs)
return cls(block, index)
이제 SingleBlockManager.from_array로 반환되는것은 SingleBlockManager Object입니다.
self.axes = Index Object
self.blocks = NumpyBlock Object
로 property를 설정하고, 이 SingleBlockManager Object를 NDFrame의 생성자에 넣게 됩니다.
그러면 NDFrame을 봐볼까요?
2_2. NDFrame
#https://github.com/pandas-dev/pandas/blob/2a65fdd227734cbda5d8e33171e857ab4aaeb9d5/pandas/core/generic.py#L241C1-L277C47
class NDFrame(PandasObject, indexing.IndexingMixin):
"""
N-dimensional analogue of DataFrame. Store multi-dimensional in a
size-mutable, labeled data structure
Parameters
----------
data : BlockManager
axes : list
copy : bool, default False
"""
_internal_names: list[str] = [
"_mgr",
"_cacher",
"_item_cache",
"_cache",
"_is_copy",
"_name",
"_metadata",
"_flags",
]
_internal_names_set: set[str] = set(_internal_names)
_accessors: set[str] = set()
_hidden_attrs: frozenset[str] = frozenset([])
_metadata: list[str] = []
_is_copy: weakref.ReferenceType[NDFrame] | str | None = None
_mgr: Manager
_attrs: dict[Hashable, Any]
_typ: str
# ----------------------------------------------------------------------
# Constructors
def __init__(self, data: Manager) -> None:
object.__setattr__(self, "_is_copy", None)
object.__setattr__(self, "_mgr", data)
...
Series의 상위 클래스인 NDFrame에서 _mgr property에서 할당하게 됩니다.
(이때 object.__setattr__을 사용하는데, 이는 static variable을 설정할때 쓰게 됩니다)
Pandas Series 정리1
- Axes 정보와 Data는 BlockManager가 소유하고 있음
- NDFrame에서 _mgr(매니저) property에 BlockManager를 할당함
- _mgr에 접근해서 Pandas Series의 Data를 처리하는 다양한 Method를 사용하게됨
- Series의 경우 Data를 Numpy array형태로 저장해서 빠른 연산속도를 확보함
여기까지 보면, Pandas Series의 반만 알게된 상태 입니다.
BlockManager를 만들때 사용되는 Data는 확인 하였으니, 이제 Axes에 해당하는 Index Object
가 무엇인지 알아야 합니다.
해당 내용은 2편에서 이어집니다.
감사합니다!
'Python 잡지식 > 소스코드 톱아보기' 카테고리의 다른 글
[Pandas]inplace=True동작에 대해서 (0) | 2023.12.06 |
---|---|
[Pandas]Series의 구조에 대해서 알아보자2 (1) | 2023.12.06 |
[Python]Requests Library의 동작 Part2 (0) | 2022.08.12 |
[Python]Requests Library의 동작 Part1 (0) | 2022.08.12 |
[CPython]CPython의 List 구현 살펴보기 (0) | 2022.02.16 |
- Total
- Today
- Yesterday
- git
- 분할정복
- SIMD
- 이분탐색
- 동적계획법
- hash
- C++
- 사칙연산
- 코딩테스트
- GDC
- heap
- AVX
- 완전탐색 알고리즘
- Python
- Search알고리즘
- 병렬처리
- 알고리즘
- 프로그래머스
- stack
- 컴퓨터그래픽스
- prime number
- 자료구조
- Sort알고리즘
- 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 | 31 |