데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [Pandas] Series의 구조에 대해서 알아보자1

      Teus 24.05.22
      1,285 5 1
      DEVOTEE 요약
      본 블로그는 Pandas DataFrame을 이해하기 위해 먼저 Pandas Series를 알아보는 포스팅입니다. Pandas Series는 1차원 배열 형태의 객체로서, 여러 메서드와 내부 관리 구조를 통해 데이터를 저장 및 처리합니다. Series의 데이터는 주로 Numpy 배열 형태로 저장되며, Axes 정보와 함께 다양한 메서드를 통해 효율적으로 데이터 처리가 가능합니다. Index 객체에 대한 내용은 다음 포스팅에서 다룰 예정입니다.
      DEVOTEE 추천 블로그

      안녕하세요 Teus입니다.


      이번 포스팅은 Pandas DataFrame을 이해하기 위한

      Pandas Series 알아보기 시간 입니다.


      1. Pandas Series

      Pandas Series는 Pandas의 자료구조 중 1dim의 Array형태의 Object입니다

      다들 DataFrame이 익숙 하시겠지만, DataFrame을 이해하기 위해서는 Series를 먼저 이해할 필요가 있습니다.

      그럼 Pandas Series Source Code가 어떤 형태로 되어있는지 보도록 하겠습니다.

      image.png

      (초록색은 일반 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

      1. Axes 정보와 Data는 BlockManager가 소유하고 있음

      2. NDFrame에서 _mgr(매니저) property에 BlockManager를 할당함

      3. _mgr에 접근해서 Pandas Series의 Data를 처리하는 다양한 Method를 사용하게됨

      4. Series의 경우 Data를 Numpy array형태로 저장해서 빠른 연산속도를 확보함

      여기까지 보면, Pandas Series의 반만 알게된 상태 입니다.

      BlockManager를 만들때 사용되는 Data는 확인 하였으니, 이제 Axes에 해당하는 Index Object가 무엇인지 알아야 합니다.


      해당 내용은 2편에서 이어집니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      Teus 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기