콘텐츠로 건너뛰기
Home » Pandas 기초 사용법 – Series

Pandas 기초 사용법 – Series

파이썬에서 통계 및 데이터 분석을 위해 사용하는 대표적인 패키지로 pandas를 꼽을 수 있다. 사실 pandas말고 다른 통계용 패키지는 뭐가 있나 잘 모르겠… 통계 분석을 위해서 pandas를 사용하기 위해 파이썬을 배우는 입장이 아니라면 기존의 파이썬 자료 구조와는 사용법이 살짝 다르기 때문에 약간 위화감이 들 수 있다. 이 글에서는 기존 파이썬 자료 구조에 익숙한 사용자가 pandas를 익히기 위해 필요한 내용과 그 사용법을 정리하고자 한다.

Pandas에서는 Series와 DataFrame이라는 두 가지 형식의 데이터 타입이 존재한다. Series는 특정한 기준에 따른 계열 데이터로 파이썬의 리스트와 비슷하다. DataFrame은 Series를 합친 형태로 2차원의 표 형식의 데이터를 다룰 때 사용한다.

Series

Series는 배열과 유사한 1차원 데이터를 위한 자료 구조이다. 파이썬의 리스트와 여러 모로 유사하다고 생각할 수 있지만 몇 가지 차이점이 존재한다. 파이썬 리스트가 그 원소의 타입에 제한을 두지 않는 것과 달리, pd.Series는 단일한 타입의 연속적인 데이터를 사용한다. 또 파이썬 리스트는 0부터 시작하는 정수 인덱스를 사용하여 원소를 참조하지만, pd.Series는 기본적으로 0부터 시작하는 정수 인덱스를 사용할 수 있는 것 외에 datetime 같은 별도의 인덱스를 사용할 수 있다. (이때, datetime은 numpy 에서 정의된 np.datetime64 타입의 값이다.)

또 파이썬 리스트가 xs[i], xs[i:j] 와 같은 subscription 방식을 사용하는 것과 달리, pandas의 데이터 타입들은 인덱서라는 슬라이스를 확장한 문법을 사용하여 특정 값 및 부분 집합을 액세스한다. 인덱서의 사용 방법에 대해서는 뒤에서 다시 자세히 다뤄보도록 하겠다.

https://pandas.pydata.org/docs/reference/api/pandas.Series.html

Series 생성

Series 타입은 pd.Series 로 정의되어 있다. 클래스 생성자의 모양은 아래와 같이 생겼다.

class pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False)
  • data : Series의 각각의 원소값을 전달한다. 리스트나 튜플, numpy array 같은 배열과 비슷한 값이나, 반복가능 객체 및 “사전”을 전달할 수 있다. 사전을 전달하는 경우 사전의 키가 각 값의 인덱스가 된다.
  • index: 각 값을 가리키는 인덱스들을 1차원 배열 형태로 전달한다. 값을 지정하지 않으면 0부터 시작하는 정수값으로 채워진다.
  • dtype : 각 값의 데이터 타입을 명시한다. 생략하면 pandas가 값의 타입을 추론하여 지정한다. 정수나 실수값인 경우 기본적으로 numpy 타입을 우선시하여 지정한다.
  • name : 계열의 이름이다. Series를 단독으로 사용할 때에는 크게 의미가 없지만, 여러 Series들을 하나의 데이터 프레임으로 횡방향으로 연결한다면 각 계열의 이름이 데이터 프레임에서의 칼럼의 이름이 된다.
  • copy: data로 주어진 값을 복사한다. 파이썬 연속열의 경우 이 옵션이 없어도 기본적으로 얕은 복사로 생성하는데, numpy 배열인 경우에는 사본이 아닌 원본을 그대로 참조한다. 만약 numpy 배열을 Series로 만들 때 copy=False 인 채로 만들었다면, Series에서 값을 변경하면 원본 배열에도 바뀐 값이 적용되니 주의가 필요하다.
> pd.Series([1,2,3, 4])
> pd.Series([1,2,3,4], index=list('ABCD')

파이썬의 정수 리스트를 데이터로 주면 이는 int64 타입으로 변환되어 저장된다. index= 옵션을 주면 해당 리스트/배열을 인덱스로 사용할 수 있다. pd.date_range()를 사용하면 시계열 인덱스를 생성할 수 있어서 다음과 같은 식으로 시계열 난수 데이터를 생성해볼 수 있다.

> pd.Series(np.random.randn(100),
            index=pd.date_range('2020-01-01 00:00:00', freq='1S', periods=100))

원소 액세스

Series 내의 특정 원소나 부분집합을 참조하는 방식은 파이썬 리스트와 비슷하다. 기본적으로 sr[0] 과 같이 정수 인덱스를 사용해서 특정한 원소를 액세스하거나, sr[2:8] 과 같이 슬라이스를 사용해서 부분열을 만드는 것이 가능하다. 만약 Series를 생성할 때, 인덱스를 별도로 정의해서 생성했다면 sr['a'] 와 같이 특정 값에 대한 인덱스를 사용해서 액세스하게 된다. (이렇게 별도의 인덱스를 지정했더라도, 인덱스가 정수가 아니라면 sr[0] 과 같이 정수 인덱스로도 여전히 액세스 가능하다.)

pandas의 데이터들은 여기에 추가로 .loc,.at 이라는 두 개의 인덱서를 지원한다. 이들은 미리 약속된 인덱스를 받아서 그에 해당하는 원소 혹은 부분열을 액세스할 수 있도록 해준다. .at 의 경우 단일 원소값을 참조하는데 사용하며, .loc[index] 은 단일 원소 및 부분열을 액세스할 수 있다. 이 인덱서들은 2차원 데이터인 데이터프레임에서도 같은 방식으로 사용할 수 있다. 참고로 .loc, .at 이 레이블(인덱스)를 사용하는데 비해 파이썬 내장 타입처럼 정수를 사용하여 위치를 기준으로 액세스하는데에는 앞에 i 를 붙인 .iloc, .iat 을 사용할 수 있다.

먼저 i가 붙는 인덱서를 사용하는 방법이다.

  • s.iat[i] : i 번째 원소를 리턴한다. 실질적으로 s[i] 와 같아 보이지만, 인덱스가 정수값이 아니라면 s[i]는 쓸 수 없다.
  • s.iloc[start:end] : start ~ end 앞까지 순번의 값을 얻는다. 리스트나 튜플의 슬라이스와 동일하다.
  • s.iloc[[n1, n2, n3]] : 정수 배열을 인덱스로 사용하면 n1, n2, n3 번에 해당하는 값으로 부분열을 만들어준다. s.iloc[[4, 2, 0]] 으로 원하는 위치의 값들을 찍어서 부분열을 만들 수 있다.
  • s.iloc[[True, False, ...]] : True, False 의 리스트를 전달하는데, 이 때 리스트의 길이는 계열의 길이와 같아야 한다. True 에 해당하는 같은 위치의 원소들이 필터링 된다.

s.loc[]s.iloc[] 과 유사하지만 순번이 아닌 인덱스/레이블에만 의존하여 값을 찾는다. 문자나 다른 타입의 값으로 인덱를 지정했다면 s.loc[9]s.at[0] 은 에러를 일으킨다. 사용법은 거의 같다.

  • s.loc['key'] 로 인덱스를 사용하여 특정 값을 얻을 수 있다.
  • s.loc['A':'D'] 와 같이 인덱스가 연속하는 성질이 있을 때에는 정수가 아니어도 슬라이스할 수 있다. 단, 이 경우에 끝 인덱스가 범위에 포함된다.
  • s.loc[[True, False, True, True,...]] : 불리언 값의 리스트를 넘겨주면, True 에 해당하는 순번의 값만 얻을 수 있다. s.loc[s > 0] 과 같은 식으로 0보다 큰 값만 필터링하는 것이 가능하다. 이는 Series가 numpy 배열처럼 간단한 연산에 대해서 벡터화가 적용되기 때문이다.
# 난수를 사용하여 Series 생성하고 알파벳으로 인덱스를 부여함
xr = pd.Series(np.random.randn(12), index=list('abcdefghijkl'))

# 리스트처럼 액세스하기
xr[0]
xr[1:3]

xr['a']
# 인덱스가 정수가 아닌 경우에는 인덱스를 속성처럼 사용할 수 있다.
xr.a
# 인덱스가 문자이지만, 연속되는 경우에는 슬라이스 문법을 사용할 수 있다.
# 이 경우에는 end 위치의 값도 포함된다.
xr['a':'d'] 

# .loc[] 로케이터를 사용하기
xr.loc['a']
xr.loc['a':'d']
xr.loc[['a','e','i']]
xr.loc[xr >= 0]

연결, 합치기

2개 이상의 계열 객체는 하나의 계열로 합치는 것이 가능하다. 여기서 합친다는 말의 의미는 ‘연결한다’라고 볼 수 있다. 예를 들어 길이가 각각 10인 두 개의 계열을 합치면 길이가 20인 계열을 하나 만들거나, 혹은 2 * 10 짜리 테이블(데이터프레임)을 만들 수 있다. 이 두 경우는 연결하는 방향에 따라 결정된다.

데이터프레임과 달리 계열 객체에는 join(), merge(), concat() 과 같이 연결을 의미하는 이름의 메소드가 없다. 1실제로는 append() 라는 메소드가 있어서 길이 방향으로 연결은 가능했지만, 현재 해당 메소드는 deprecated 상태이다. 따라서 pd.concat() 이라는 함수를 사용하여 두 개의 계열을 연결할 수 있다. axis= 파라미터는 연결할 방향을 결정한다. 0이면 종방향(길이방향), 1이면 횡방향이다.

두 개의 계열을 길이 방향으로 연결했을 때, 인덱스가 같은 항목이 여러 개 있더라도 상관없이 연결된다. 심지어 인덱스가 정수값이라 하더라도 재정렬은 일어나지 않는다. 인덱스 구성이 다른 두 계열을 횡방향으로 연결한 경우에는 새로운 데이터프레임이 생성되며, 이 때 인덱스를 별도로 명시적으로 지정하지 않았다면 정수 인덱스가 새롭게 매겨진다. (만약 인덱스 구성이 일치한다면 원래의 인덱스가 유지된다.)

인덱스가 중복된 원소들이 존재한다면 .loc[] 인덱서를 사용하여 특정한 인덱스에 대한 값을 참조하면 2개 이상의 값이 있을 때에는 계열 데이터가 리턴된다.

그룹바이

인덱스가 중복된 원소들이 존재할 때, 인덱스가 같은 값끼리 묶어서, 묶은 그룹에 대한 연산 결과를 알아 볼 수 있다. s.groupby()가 그 방법인데, 호출하는 방법은 두 가지이다.

  • s.groupby(by=foo)
  • s.groupby(level=0)

.groupby() 메소드는 by=level= 인자를 반드시 전달해야 한다. by= 인자에는 일반적으로 함수를 줄 수 있는데, 그룹핑을 할 키를 생성하는 함수이다. level= 은 레벨값이나 레벨 이름을 주는데, 계열 객체는 항상 1차원 데이터이므로 level=0 으로 전달하면 된다.

s = pd.Series(np.random.randn(10))
t = pd.Series(np.arange(10), index=(x*2+3 for x in range(10)))
w = pd.concat([s, t], axis=0)
w.groupby(level=0).sum()  # 같은 인덱스값들의 합

그룹바이의 결과는 GroupBy 객체를 생성하며, 그룹바이 객체는 REPL 상에서 출력했을 때 그 내용을 보여주지 않는다. 그래서 .sum(), .mean() 등의 집합 함수 메소드를 사용하여 그룹별 데이터의 모양을 얻어서 사용한다.

간단한 통계 기능

pandas는 numpy와 긴밀하게 연동되어 있어서 numpy의 기능을 잘 활용하고 있다. (실제로 내부에 저장되는 데이터는 numpy 타입의 배열2C배열과 동일한 것이라고 생각하면 된다.로 데이터를 저장한다.) 따라서 Series는 numpy 배열의 계산 동작과 비슷한 기능(벡터화 같은) 기능을 제공하며, 여기에 더해 간단한 통계도 즉시 계산할 수 있게 해준다.

통계와 관련하여 numpy 와 차이가 있는데, 분산 및 표준편차에 대해 numpy 배열은 디폴트로 모분산, 모표준편차를 계산하는데, pandas Series와 DataFrame은 통계적 추정등에 사용되는 경우가 더 많다보니, 표본분산, 표본표준편차를 계산해준다.

  • s.mean()
  • s.median()
  • s.quantile([.25, .5, .75]) : 4분위수를 찾아준다. 지정한 위치 (0~1)의 리스트를 넘겨주면 다른 분위에 해당하는 값도 찾을 수 있다.
  • s.std(), s.std(ddof=0) : 표본표준편차를 계산해준다. ddof=0 인자를 주면 모표준편차로 계산한다.
  • s.var(), s.var(ddof=0) : 표본분산을 계산해준다. ddof=0 인자를 주면 모분산으로 계산한다.
  • s.cumsum() : 누적합
  • s.describe() : 기본 통계량을 한 번에 표시하여 간단히 데이터의 모양을 가늠해볼 수 있게 한다.

그래프 기능

pandas는 제공하는 데이터 타입의 하부구조의 상당부분을 numpy의 의존하고 있기에 pandas를 설치하기 위해서는 시스템에 numpy가 먼저 설치되는 것을 필요로 한다. pandas에는 필수적인 의존성은 아니지만, 시스템에 함께 설치되어 있다면 활용할 수 있는 기능들도 제공하고 있다. 파이썬에서 그래프를 그리는데 사용하는 대표적인 시각화 라이브러리인 matplotlib 이 있다면, 데이터 프레임이나 시리즈의 값을 사용해서 간단한 차트를 그려볼 수 있다. s.plot() 를 호출해주면 matplotlib의 현재 컨텍스트(pyplot)에 플롯 차트가 그려지므로, plt.show() 함수를 사용해서 그려진 그래프를 표시해서 볼 수 있다.

from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
sr = pd.Series(np.random.randn(100),
        index=pd.date_range('2020-08-01', freq='D', periods=100))
plt.figure()
sr.plot()
plt.show()

정리

원래는 데이터프레임까지 다뤄보려고 했는데, 분량 조절에 실패해서 데이터프레임은 다음 기회를 기약하도록 하겠다.

태그: