10 minute read

이 글에서는 pandas 라이브러리에 대한 기초 지식만 소개합니다. pandas 라이브러리를 이용하면 다음과 같은 작업을 수행할 수 있습니다.

  • 1차원 시리즈(Series): 원소를 세로 방향으로 정렬한 1차원 자료구조이며, 데이터프레임의 원소(열벡터)로 사용됩니다.
  • 2차원 데이터프레임(DataFrame): 시리즈를 원소로 갖는 2차원 자료구조입니다. R의 데이터프레임에서 유래한 것으로 알려져 있습니다.
  • 3차원 패널(Panel): 시간의 흐름에 따라 여러 개의 데이터프레임을 중첩한 것입니다. 동일한 관찰 대상이 시간의 흐름에 따라 어떻게 바뀌어 가는지 확인하는 분석이 가능합니다.

이 글에서는 1차원 시리즈 및 2차원 데이터프레임을 생성하고, 인덱싱과 슬라이싱하는 방법을 소개합니다. 먼저 관련 라이브러리를 호출하겠습니다.

import numpy as np
import pandas as pd

지금부터는 numpypandas 라이브러리를 함께 호출하는 것이 좋습니다. 시리즈와 데이터프레임의 값(values)이 1~2차원 배열로 되어 있기도 하지만, 데이터 분석 작업에서 numpy에 포함된 함수도 많이 사용되기 때문입니다.

pandas 라이브러리를 호출하면서 마지막에 as pd를 추가함으로써 pandas에 포함된 모듈 및 함수를 사용할 때 ‘pandas.’ 대신 줄임말인 ’pd.’를 추가하게 됩니다.

아래 그림은 데이터프레임을 시각적으로 표현한 것입니다. numpy의 2차원 배열과 같은 점은 행과 열을 갖는 2차원 자료구조라는 점이지만, 2차원 배열은 모든 원소의 자료형이 같아야 하고 데이터프레임은 모든 원소의 자료형이 다를 수 있다는 점에서 다릅니다. 예를 들어, 엑셀 파일에 고객데이터가 저장되어 있다고 가정해보겠습니다. 첫 번째 열(column)인 ’고객ID’는 비록 숫자로 표현되어 있지만 문자열이어야 합니다. 두 번째와 세 번째 열도 문자열이구요. 네 번째 열인 ’나이’는 정수입니다. 다섯 번째 열인 ’키’는 소수점이 있는 실수이구요. 이렇듯 열마다 자료형이 다를 수 있어야 하므로 데이터 분석을 위한 자료구조는 데이터프레임이 됩니다. 아울러 데이터프레임의 열은 세로 방향으로 원소를 갖는 시리즈입니다.

데이터프레임의 시각적 예시 데이터프레임의 시각적 예시

데이터프레임의 각 요소를 가리키는 명칭이 분야마다 다양합니다. 행(row)은 관측값, 레코드 또는 사례 등으로 불리고, 열(column)은 변수, 특성 또는 속성으로 불립니다. 이 글에서는 행 또는 관측값, 열 또는 변수로 통일하도록 하겠습니다.

아울러 데이터프레임의 원소는 시리즈이고, 시리즈의 원소는 값(value)인데 혼동할 수 있으므로 데이터프레임에서 값을 셀(cell) 또는 셀값(cell value)이라고 하겠습니다.

데이터프레임의 명칭 데이터프레임의 명칭

데이터 분석 교과서에서 2차원 배열(행렬) 또는 데이터프레임의 크기를 일반화할 때 n행 x p열로 표현합니다. 따라서 위 그림에 행 크기를 n, 열 크기를 p로 추가한 것입니다.

이제 시리즈를 생성하고 인덱싱하는 방법에 대해 알아보겠습니다.

시리즈

시리즈는 1차원 배열과 달리 세로 방향으로 정렬된 자료구조입니다. 아울러 맨 왼쪽에 인덱스가 출력되고, 맨 아래에 자료형이 출력됩니다. 시리즈를 생성할 때 인덱스를 지정할 수 있으며, 지정하지 않으면 정수 0부터 시작합니다. 물론 생성된 시리즈의 인덱스를 나중에 변경할 수도 있습니다.

시리즈 생성

시리즈를 생성하려면 pd.Series() 함수의 data 매개변수에 배열, 리스트, 튜플 또는 딕셔너리를 지정합니다. 시리즈를 생성할 때 index 매개변수에 인덱스명을 원소로 갖는 리스트를 지정하면 인덱스명을 설정할 수 있습니다. 다만 딕셔너리의 경우, 딕셔너리의 키(key)가 인덱스명으로 자동 설정되므로 인덱스명을 따로 지정할 수 없습니다.

sr = pd.Series(data = np.arange(start = 1, stop = 10, step = 2))
sr
## 0    1
## 1    3
## 2    5
## 3    7
## 4    9
## dtype: int64
type(sr)
## <class 'pandas.core.series.Series'>

첫 번째 코드에서 data에 전달되는 인자는 1차원 배열이며, 배열의 원소는 모두 정수입니다. 인덱스를 추가하지 않았으므로 인덱스는 0부터 시작하게 됩니다.

두 번째 코드에서 sr을 출력하면 맨 왼쪽에 0부터 4까지 정수 인덱스가 세로 방향으로 출력되고, 오른쪽으로 sr의 원소인 1, 3, 5, 7, 9가 출력됩니다. 아울러 맨 아래에 시리즈의 자료형이 출력됩니다.

세 번째 코드에서 sr의 클래스를 확인해보면 ’pandas.core.series.Series’로 출력됩니다.

리스트 또는 딕셔너리로 시리즈를 생성하는 방법에 대해 차례대로 소개하겠습니다.

pd.Series(data = [1, 3, 5, 7, 9], index = ['a', 'b', 'c', 'd', 'e'])
## a    1
## b    3
## c    5
## d    7
## e    9
## dtype: int64

pd.Series() 함수의 data 매개변수에 값에 해당하는 리스트를 지정하고, index 매개변수에 인덱스명에 해당하는 리스트를 지정합니다. 맨 왼쪽에 인덱스를 유심히 살펴보시기 바랍니다.

pd.Series(data = {'a': 1, 'b': 3, 'c': 5, 'd': 7, 'e': 9})
## a    1
## b    3
## c    5
## d    7
## e    9
## dtype: int64

딕셔너리로 시리즈를 생성할 때는 딕셔너리의 키(key)가 인덱스로 자동 설정되므로 index 매개변수를 추가하면 안됩니다.

시리즈 확인

시리즈 객체에도 다양한 속성과 방식이 포함되어 있는데, 이번 절에서는 형태, 값, 인덱스명 및 자료형을 확인하는 방법을 소개합니다.

sr.shape
## (5,)

시리즈의 형태(shape)를 확인하려면 shape 속성을 사용합니다. sr은 1차원이므로, 형태를 출력하면 sr 원소의 개수인 ’(5,)’이 출력됩니다.

sr.values
## array([1, 3, 5, 7, 9])

시리즈의 값(values)을 확인하려면 values 속성을 사용합니다. 시리즈의 값은 1차원 배열이므로 맨 앞에 ’array’가 출력됩니다.

sr.index
## RangeIndex(start=0, stop=5, step=1)

1차원 배열과 다르게 시리즈는 index 속성을 사용하여 인덱스명을 출력할 수 있습니다. sr을 생성할 때 별도의 인덱스명을 지정하지 않았으므로 정수 0부터 시작하는 범위가 자동으로 설정됩니다.

sr.dtype
## dtype('int64')

시리즈의 자료형(data type)을 출력하려면 dtype 또는 dtypes 속성을 사용합니다. 시리즈의 자료형을 출력했을 때 ‘int’ 또는 ’float’로 출력되면 숫자만 포함되어 있다고 판단할 수 있으며, ’object’로 출력되면 문자열이 포함되어 있다고 판단합니다.

시리즈의 인덱스명 변경

시리즈의 index 속성을 이용하여 시리즈의 인덱스명을 변경할 수 있습니다. 주의해야 할 점은 시리즈 원소 개수와 동일한 리스트를 지정해야 한다는 점입니다.

sr.index = ['a', 'b', 'c', 'd', 'e']
sr
## a    1
## b    3
## c    5
## d    7
## e    9
## dtype: int64

sr의 인덱스명을 문자열로 변경한 다음, sr을 출력하면 맨 왼쪽에 인덱스명이 바뀌어 있음을 확인할 수 있습니다.

시리즈의 인덱싱 및 슬라이싱: iloc 인덱서

시리즈를 인덱싱하는 방법은 다양하지만 iloc 인덱서 또는 loc 인덱서를 사용하는 방법이 많이 사용됩니다. 데이터 분석 작업을 위해 매우 중요한 부분이므로 꼭 이해하고 넘어가시기 바랍니다.

먼저 iloc 인덱서를 사용한 인덱싱을 소개합니다. 시리즈 뒤에 iloc 인덱서를 추가하고 대괄호를 연 다음, 대괄호 안에 정수 인덱스를 스칼라 또는 리스트로 지정합니다. 현재 sr의 인덱스명은 ’a’부터 ’e’까지 문자열로 되어 있지만 iloc 인덱서를 사용하면 현재 인덱스명과 상관없이 정수 인덱스로 원소를 선택할 수 있습니다.

중요한 점은, 대괄호 안에 정수 스칼라를 지정하면 시리즈 원소를 반환하지만, 대괄호 안에 정수 인덱스를 원소로 갖는 리스트를 지정하면 해당 원소를 포함하는 시리즈를 반환합니다. 즉, 인덱싱 방식에 따라 반환되는 객체가 달라집니다.

sr.iloc[0]
## 1
sr.iloc[[0]]
## a    1
## dtype: int64

첫 번째 코드를 실행하면 sr의 0번 인덱스 원소를 반환합니다.

두 번째 코드를 실행하면 sr의 0번 인덱스 원소를 선택하여 시리즈로 반환합니다.

두 코드의 차이를 반드시 이해하시기 바랍니다.

iloc 인덱서를 사용하면 2개 이상의 원소를 선택하는 팬시 인덱싱이 가능합니다. 대괄호 안에 리스트를 지정해야 하므로 반환되는 결과는 시리즈입니다.

sr.iloc[[1, 3]]
## b    3
## d    7
## dtype: int64

콜론을 사용한 슬라이싱도 가능합니다.

sr.iloc[1:3]
## b    3
## c    5
## dtype: int64

위 코드를 실행하면 1~2번 인덱스 원소를 선택하여 시리즈로 반환합니다.

시리즈의 인덱싱 및 슬라이싱: loc 인덱서

이번에는 loc 인덱서를 사용한 인덱싱을 소개합니다. iloc 인덱서는 정수 인덱스를 사용하지만 loc 인덱서는 인덱스명 자체를 사용한다는 점에서 다릅니다. 현재 sr의 인덱스명은 ’a’부터 ’e’까지 문자열이므로 iloc 인덱서를 사용한 인덱싱에서와 같이 정수 인덱스를 사용하면 에러가 발생합니다.

sr.loc['a']
## 1
sr.loc[['a']]
## a    1
## dtype: int64

첫 번째 코드를 실행하면 sr의 인덱스명이 ’a’인 원소를 반환합니다.

두 번째 코드를 실행하면 sr의 인덱스명이 ’a’인 원소를 선택하여 시리즈로 반환합니다.

loc 인덱서도 2개 이상의 원소를 선택하는 팬시 인덱싱이 가능합니다.

sr.loc[['b', 'd']]
## b    3
## d    7
## dtype: int64

iloc 인덱서는 정수 인덱스를 사용해야 했으므로 결과로 반환되는 시리즈의 인덱스명과 다를 수 있었는데요. loc는 인덱스명 자체를 사용하므로 결과와 일치합니다. 이 점이 loc 인덱서의 장점이죠.

loc 인덱서로 콜론을 사용한 슬라이싱은 더 좋습니다.

sr.loc['b':'d']
## b    3
## c    5
## d    7
## dtype: int64

위 코드를 실행하면 인덱스명 ’b’부터 ’d’인 원소를 선택하여 시리즈로 반환합니다. 콜론 뒤에 지정된 인덱스명을 포함한다는 것이 장점이라고 볼 수 있습니다.

참고

프로그래밍 언어에 익숙하지 않은 비개발자들에게 Python이 어려운 이유 중 하나로 0부터 시작하는 인덱스에 있는 것 같습니다. 아울러 콜론을 슬라이싱을 할 때 콜론 뒤에 지정한 인덱스는 포함되지 않는다는 점도 혼란을 가중시키는 요인으로 판단하고 있습니다. 그래서 저는 loc 인덱서를 사용한 인덱싱과 슬라이싱이 비개발자들에게 안정감 있는 코딩 방법이라고 추천하고 있습니다.

loc 인덱서의 장점은 아직 끝나지 않았습니다. 시리즈로 비교 연산을 실행하면 True 또는 False를 원소로 갖는 시리즈가 반환되는데요.

sr >= 5
## a    False
## b    False
## c     True
## d     True
## e     True
## dtype: bool

loc 인덱서 대괄호 안에 비교 연산 결과로 반환되는 시리즈를 지정하면 True에 해당 원소를 선택합니다. 이것을 불리언 인덱싱(Boolean Indexing)이라고 합니다. 현업에서 데이터 분석 업무를 실행할 때 팬시 인덱싱 보다 불리언 인덱싱이 더 많이 사용된다는 점에서 loc 인덱서를 사용한 인덱싱이 무엇보다 중요하다고 강조할 수 있습니다. 물론 경우에 따라서 iloc 인덱서를 사용한 인덱싱도 필요하므로 두 가지 방법을 모두 숙지하는 것을 추천드립니다.

sr.loc[sr >= 5]
## c    5
## d    7
## e    9
## dtype: int64
sr.iloc[sr >= 5]
## ValueError: iLocation based boolean indexing cannot use an indexable as a mask
##   File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/pandas/core/indexing.py", line 931, in __getitem__
##     return self._getitem_axis(maybe_callable, axis=axis)
##   File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/pandas/core/indexing.py", line 1552, in _getitem_axis
##     self._validate_key(key, axis)
##   File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/pandas/core/indexing.py", line 1400, in _validate_key
##     raise ValueError(

첫 번째 코드를 실행하면 sr 원소 중 5 이상인 원소를 선택하여 시리즈로 반환하지만, 두 번째 코드를 실행하면 에러가 발생합니다.

만약 2개 이상의 비교 연산 결과를 결합하려면 비트 연산자인 &(ampersand) 또는 |(bar) 기호를 사용합니다. &는 논리곱에 해당하므로 모두 True인 원소를 선택하며, |는 논리합에 해당하므로 둘 중 하나라도 True인 원소를 선택합니다. 중요한 점은, 비트 연산자 앞 뒤 코드를 소괄호(parenthesis) 묶어주어야 한다는 것입니다.

sr.loc[(sr >= 5) & (sr <= 7)]
## c    5
## d    7
## dtype: int64

위 코드를 실행하면 sr 원소가 5 이상이고 7 이하인 조건을 만족하는 원소를 선택하여 시리즈로 반환합니다.

sr[(sr >= 5) & (sr <= 7)]
## c    5
## d    7
## dtype: int64

시리즈로 불리언 인덱싱을 실행할 때 loc 인덱서를 생략해도 된다는 점을 참고하시기 바랍니다.

데이터프레임

데이터프레임은 원소인 시리즈가 겹친 형태로 행과 열을 갖는 2차원 자료구조입니다. 데이터프레임은 2차원 배열, 리스트 및 딕셔너리로 생성하며, 행이름(인덱스명)과 열이름(컬럼명)을 가집니다.

데이터프레임 생성

데이터프레임을 생성하려면 pd.DataFrame() 함수의 data 매개변수에 배열, 리스트 또는 딕셔너리를 지정합니다. 데이터프레임을 생성할 때 indexcolumns 매개변수에 인덱스명과 컬럼명을 각각 지정할 수 있습니다. indexcolumns를 생략하면 인덱스명과 컬럼명이 정수 0부터 자동 지정됩니다.

딕셔너리로 데이터프레임을 생성하면 딕셔너리의 키(key)가 컬럼명으로 자동 설정되므로 컬럼명을 따로 지정할 수 없습니다.

리스트로 인덱스명과 컬럼명을 생략한 데이터프레임을 생성하고 출력해보겠습니다.

df = pd.DataFrame(data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]])
df
##    0  1  2
## 0  1  2  3
## 1  4  5  6
## 2  7  8  9

시리즈와 마찬가지로 맨 왼쪽에 출력되는 것은 인덱스명이고, 맨 위에 출력되는 것은 컬럼명입니다. 아울러 맨 아래에는 데이터프레임의 자료형이 추가로 출력됩니다.

이번에는 인덱스명과 컬럼명을 추가한 데이터프레임을 생성하고 출력해보겠습니다.

df = pd.DataFrame(
  data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
  index = [1, 2, 3], 
  columns = ['A반', 'B반', 'C반']
)
df
##    A반  B반  C반
## 1   1   2   3
## 2   4   5   6
## 3   7   8   9

맨 왼쪽에 출력되는 인덱스명과 맨 위에 출력되는 컬럼명이 지정한 리스트로 설정되었습니다. 이렇게 인덱스명과 컬럼명을 사용자가 원하는 값으로 지정하면 loc 인덱서를 사용하여 인덱싱할 수 있습니다.

type(df)
## <class 'pandas.core.frame.DataFrame'>

df의 클래스를 확인해보면 ’pandas.core.frame.DataFrame’으로 출력됩니다.

이번에는 딕셔너리로 데이터프레임을 생성하는 두 가지 방법을 소개합니다. 딕셔너리의 키(key)가 컬럼명으로 자동 설정되므로 columns 매개변수를 추가하면 안됩니다. index 매개변수를 추가할 수 있지만 생략하겠습니다.

pd.DataFrame(data = {'A반': [1, 2, 3], 'B반': [4, 5, 6], 'C반': [7, 8, 9]})
##    A반  B반  C반
## 0   1   4   7
## 1   2   5   8
## 2   3   6   9

위 코드는 원소가 3개인 리스트를 값(values)으로 갖는 딕셔너리로 데이터프레임을 생성합니다. 딕셔너리의 키가 컬럼명이 되고, 키에 지정된 값인 리스트는 열벡터로 입력됩니다.

pd.DataFrame(data = [{'A반': 1, 'B반': 2, 'C반': 3},
                     {'A반': 4, 'B반': 5, 'C반': 6},
                     {'A반': 7, 'B반': 8, 'C반': 9}])
##    A반  B반  C반
## 0   1   2   3
## 1   4   5   6
## 2   7   8   9

위 코드는 딕셔너리를 원소로 갖는 리스트로 데이터프레임을 생성합니다. 리스트의 원소인 딕셔너리가 가로 방향으로 입력된다는 특징이 있습니다. 이런 형태의 딕셔너리를 생성할 때가 있는지 궁금하실 텐데요. 웹 크롤링을 하면서 한 페이지마다 하나의 값을 수집하여 딕셔너리로 만들고, 딕셔너리를 리스트의 원소로 생성하면 이와 같은 형태의 리스트를 생성할 수 있습니다. 특히, JSON 데이터가 이와 유사합니다.

데이터프레임 확인

데이터프레임 객체에도 다양한 속성과 방식이 포함되어 있는지, 이번 절에서는 형태, 값, 인덱스명, 컬럼명 및 자료형을 확인하는 방법을 소개합니다. 아울러 두 가지 중요한 방식을 함께 소개합니다.

df.shape
## (3, 3)

데이터프레임의 형태(shape)를 확인하려면 shape 속성을 사용합니다. df는 2차원이므로, 형태를 출력하면 df의 ‘(행 길이, 열 길이)’ 순서로 원소 개수가 각각 출력됩니다.

df.values
## array([[1, 2, 3],
##        [4, 5, 6],
##        [7, 8, 9]])

데이터프레임의 값(vlaues)을 확인하려면 values 속성을 사용합니다. 데이터프레임의 값은 2차원 배열이므로 맨 앞에 ’array’가 출력됩니다.

df.index
## Int64Index([1, 2, 3], dtype='int64')

데이터프레임의 인덱스를 확인하려면 index 속성을 사용합니다. df를 생성할 때 인덱스명이 정수 1부터 시작하도록 지정했으므로 해당 값이 출력됩니다.

df.columns
## Index(['A반', 'B반', 'C반'], dtype='object')

시리즈와 다르게 데이터프레임은 columns 속성을 사용하여 컬럼명을 출력할 수 있습니다. df를 생성할 때 컬럼명을 지정했으므로 해당 값이 출력됩니다.

df.dtypes
## A반    int64
## B반    int64
## C반    int64
## dtype: object

데이터프레임의 열(컬럼)별 자료형을 출력하려면 dtypes 속성을 사용합니다. 데이터프레임의 열은 시리즈이므로 시리즈의 자료형을 한꺼번에 출력하는 것과 같습니다.

방금 소개한 데이터프레임에 대한 다섯 가지 정보를 한 번에 확인할 수 있는 방식(method)을 소개합니다. 바로 info()입니다.

df.info()
## <class 'pandas.core.frame.DataFrame'>
## Int64Index: 3 entries, 1 to 3
## Data columns (total 3 columns):
##  #   Column  Non-Null Count  Dtype
## ---  ------  --------------  -----
##  0   A반      3 non-null      int64
##  1   B반      3 non-null      int64
##  2   C반      3 non-null      int64
## dtypes: int64(3)
## memory usage: 96.0 bytes

위 코드를 실행하면 df의 열 길이, 행 길이, 열이름, 자료형, 열별 자료형 뿐만 아니라, 열별 결측값을 제외한 원소 개수까지 출력합니다. 따라서 데이터프레임을 생성했다면 데이터프레임의 정보를 한번에 확인함으로써 데이터프레임이 어떤 상태인지 판단할 수 있습니다. 특히 중요한 것은 열별 결측값 개수를 제외한 원소 개수와 자료형입니다. 데이터 분석의 재료는 데이터입니다. 재료의 상태가 좋아야 분석 결과도 좋습니다. 따라서 데이터프레임의 열별 자료형이 맞는지 확인하고 만약 다르다면 자료형을 변경해주어야 합니다. 예를 들어, 고객의 나이를 의미하는 열의 자료형이 문자열이라면 고객의 평균 나이를 계산할 수 없습니다. 고객의 ID가 순번을 의미하는 숫자로 작성되었다고 하더라도 ID는 문자열이어야 합니다.

이상으로 pandas 라이브러리를 이용하여 시리즈와 데이터프레임을 생성하고 다양한 정보를 확인하는 방법을 소개했습니다. 데이터 분석 작업을 할 때, 직접 데이터프레임을 만드는 경우는 그렇게 많지 않으며 대개는 엑셀이나 텍스트 파일을 읽어서 데이터프레임을 생성하게 됩니다.

Updated: