판다스(pandas) 시작하기

판다스는 파이썬에서 자료 정제 및 분석을 빠르고 쉽게 할 수 있는 자료 구조와 관리 도구들을 가지고 있다. 판다스는 수치 해석 도구들인 NumPy, SciPy 및 자료 분석 라이브러리인 statsmodels, scikit-learn과 시각화 도구인 matplotlib들과 함께 사용된다. 판다스는 NumPy 에서 사용되는 배열 기반 연산과 for 문 없이 계산하는 벡터화 계산을 기반으로 한다. 넘파이와 판다스의 큰 차이점은 넘파이는 같은 형에 대한 배열을 주로 다루는 반면 판다스는 이질적인 자료형을 다루는 것에 더 적합하다는 것이다.

앞으로 나오는 모든 코딩에서 다음과 같은 import 관례를 따른다.

import pandas as pd

pd. 을 사용하면 pandas.를 의미하는 것이다.

판다스 자료 구조 소개

판다스를 다루기 위해서 2가지 중요한 자료 구조 시리즈(Series)와 데이터프레임(DataFrame)에 익숙해져야 한다.

시리즈(Series)

시리즈는 연속적인 값들로 이루어진 일차원 배열과 인덱스로 불리는 자료 라벨(label) 배열로 이루어진 객체이다. 리스트로부터 간단한 시리즈를 다음과 같이 만들 수 있다.

In [2]:
import pandas as pd
import numpy as np

rng = np.random.RandomState(1234)
In [3]:
obj = pd.Series([-5, 0, 10, 3])
obj
Out[3]:
0    -5
1     0
2    10
3     3
dtype: int64

출력된 시리즈에서 왼쪽은 인덱스이고 오른쪽 값이다. 인덱스를 지정하지 않으면 기본값으로 0부터 N - 1의 숫자가 설정된다. 여기서 N은 자료의 갯수이다. 시리즈 객체로부터 인덱스 객체와 값에 대한 넘파이 배열을 얻을 수 있다.

In [4]:
obj.index
Out[4]:
RangeIndex(start=0, stop=4, step=1)
In [5]:
obj.values
Out[5]:
array([-5,  0, 10,  3], dtype=int64)

종종 각각의 자료에 대응되는 자료 라벨을 직접 지정하여 시리즈를 만드는 것이 바람직할 때가 있다.

In [6]:
obj2 = pd.Series([-5, 0, 10, 3], index=['a', 'b', 'd', 'c'])
obj2
Out[6]:
a    -5
b     0
d    10
c     3
dtype: int64
In [7]:
obj2.index
Out[7]:
Index(['a', 'b', 'd', 'c'], dtype='object')

넘파이와는 다르게 하나의 값 또는 여러 값들을 선택할 때 라벨을 사용할 수 있다.

In [8]:
obj2['a']
Out[8]:
-5

인덱스를 통해서 값을 변경할 수 있다.

In [9]:
obj2['c'] = -12

인덱스 리스트를 통해서 대응되는 값들을 선택할 수 있다.

In [10]:
obj2[['a', 'd', 'c']]
Out[10]:
a    -5
d    10
c   -12
dtype: int64

수학 함수, 스칼로 곱, 논리 배열에 대한 필터링과 같은 넘파이 함수 또는 연산들을 하더라도 인덱스 - 값 연결은 계속 보존된다.

In [11]:
obj2[obj2 < 0]
Out[11]:
a    -5
c   -12
dtype: int64
In [12]:
obj2 * 2
Out[12]:
a   -10
b     0
d    20
c   -24
dtype: int64
In [13]:
import numpy as np

np.exp(obj2)
Out[13]:
a        0.006738
b        1.000000
d    22026.465795
c        0.000006
dtype: float64

시리즈를 사전 형식으로 생각하여 열쇠(key)는 인덱스에, 값은 값에 대응하여 생각할 수 있다.

In [14]:
'a' in obj2
Out[14]:
True

사전형 자료를 다음과 같이 시리즈로 만들 수 있다.

In [15]:
인구 = {'서울': 9776, '부산':3429, '대전':1531, '세종':276, '충남':2148, '대구':2465}
시리3 = pd.Series(인구)
시리3
Out[15]:
대구    2465
대전    1531
부산    3429
서울    9776
세종     276
충남    2148
dtype: int64

사전형을 시리즈로 만들 때 사전의 열쇠에 대한 정렬 순서로 저장이된다. 순서를 바꿔 저장하고 싶으면 열쇠에 대한 리스트를 만들어 인덱스에 넘겨주면 된다.

In [16]:
도시들 = ['대전', '세종', '대구', '충남', '인천']
시리4 = pd.Series(인구, index=도시들)
시리4
Out[16]:
대전    1531.0
세종     276.0
대구    2465.0
충남    2148.0
인천       NaN
dtype: float64

인천을 제외한 도시들은 인구 리스트와 적절히 연결되어 값을 설정했다. 인천은 인구 리스트에 없기 때문에 NaN을 출력했다. 이러한 값을 판다스에서는 소실값(missing values)이라고 부른다. isnullnotnull 함수들을 이용해 소실 자료들을 찾을 수 있다.

In [17]:
pd.isnull(시리4)
Out[17]:
대전    False
세종    False
대구    False
충남    False
인천     True
dtype: bool
In [18]:
pd.notnull(시리4)
Out[18]:
대전     True
세종     True
대구     True
충남     True
인천    False
dtype: bool

시리즈의 인스턴스 메소드로도 접근할 수 있다.

In [19]:
시리4.isnull()
Out[19]:
대전    False
세종    False
대구    False
충남    False
인천     True
dtype: bool

시리즈끼리의 산술 연산에서 자동으로 같은 인덱스끼리 계산을 해준다.

In [20]:
시리3
Out[20]:
대구    2465
대전    1531
부산    3429
서울    9776
세종     276
충남    2148
dtype: int64
In [21]:
시리4
Out[21]:
대전    1531.0
세종     276.0
대구    2465.0
충남    2148.0
인천       NaN
dtype: float64
In [22]:
시리3 + 시리4
Out[22]:
대구    4930.0
대전    3062.0
부산       NaN
서울       NaN
세종     552.0
인천       NaN
충남    4296.0
dtype: float64

시리즈 객체와 객체의 인덱스는 판다스의 다른 주요 영역과 통합되는 name 속성을 가지고 있다.

In [23]:
시리4.name = "인구통계"
시리4.index.name = "도시들"
시리4
Out[23]:
도시들
대전    1531.0
세종     276.0
대구    2465.0
충남    2148.0
인천       NaN
Name: 인구통계, dtype: float64

시리즈 인덱스는 다음과 같이 변경할 수 있다.

In [24]:
obj
Out[24]:
0    -5
1     0
2    10
3     3
dtype: int64
In [25]:
obj.index = ['김', '최', '윤', '홍']
obj
Out[25]:
김    -5
최     0
윤    10
홍     3
dtype: int64

데이터프레임(DataFrame)

데이터프레임은 같은 크기의 자료들로 이루어진 열들을 차례로 모아 놓은 직사각 모양의 자료형이다. 데이터프레임의 열과 행은 각각 인덱스를 가지고 있다. 데이터프레임을 만드는 방법은 여러가지고 있지만 가장 쉬운 방법은 같은 크기의 리스트 또는 넘파이 배열을 값으로 갖는 사전형을 사용하는 것이다.

In [26]:
데이터 = {'지역': ['대전', '대전', '대전', '서울', '서울', '서울'],
      '년도': [2015, 2016, 2017, 2015, 2016, 2017],
      '인구': [1542, 1535, 1531, 9941, 9852, 9776]}
프레임 = pd.DataFrame(데이터)

시리즈에서와 같이 데이터프레임도 자동으로 인덱스를 설정하고 열 인덱스 이름으로 정렬을 한다.

In [27]:
프레임
Out[27]:
년도 인구 지역
0 2015 1542 대전
1 2016 1535 대전
2 2017 1531 대전
3 2015 9941 서울
4 2016 9852 서울
5 2017 9776 서울

head 메소드를 통해 상위 5개의 자료들만 볼 수 있다.

In [28]:
프레임.head()
Out[28]:
년도 인구 지역
0 2015 1542 대전
1 2016 1535 대전
2 2017 1531 대전
3 2015 9941 서울
4 2016 9852 서울

선택 사항 columns 리스트를 지정함으로 원하는 순서로 열을 나열할 수 있다.

In [29]:
pd.DataFrame(데이터, columns=['지역', '인구', '년도'])
Out[29]:
지역 인구 년도
0 대전 1542 2015
1 대전 1535 2016
2 대전 1531 2017
3 서울 9941 2015
4 서울 9852 2016
5 서울 9776 2017

사전에 없는 열이름을 지정하면 소실값이 지정이 된다.

In [30]:
프레임2 = pd.DataFrame(데이터, columns=['지역', '인구', '년도', '자산'],
             index=['하나', '둘', '셋', '넷', '다섯', '여섯'])
프레임2
Out[30]:
지역 인구 년도 자산
하나 대전 1542 2015 NaN
대전 1535 2016 NaN
대전 1531 2017 NaN
서울 9941 2015 NaN
다섯 서울 9852 2016 NaN
여섯 서울 9776 2017 NaN
In [31]:
프레임2.columns
Out[31]:
Index(['지역', '인구', '년도', '자산'], dtype='object')

데이터프레임 열은 사전식으로 접근하거나 속성으로 접근할 수 있다. 다음은 사전식 열쇠를 통한 접근이다.

In [32]:
프레임2['지역']
Out[32]:
하나    대전
둘     대전
셋     대전
넷     서울
다섯    서울
여섯    서울
Name: 지역, dtype: object

다음은 속성을 통한 접근이다.

In [33]:
프레임2.지역
Out[33]:
하나    대전
둘     대전
셋     대전
넷     서울
다섯    서울
여섯    서울
Name: 지역, dtype: object

사전식으로 접근할 때는 열쇠 이름이 어떤 것이 와도 되지만 속성식으로 접근할 때는 파이썬 변수 이름 규칙을 따라야 한다. 행을 접근할 때는 나중에 자세히 나오겠지만 loc 속성을 통해 할 수 있다.

In [34]:
프레임2.loc['하나']
Out[34]:
지역      대전
인구    1542
년도    2015
자산     NaN
Name: 하나, dtype: object

열에 값을 지정하는 것은 스칼라 또는 배열을 통해 할 수 있다.

In [35]:
프레임2['자산'] = 20
프레임2
Out[35]:
지역 인구 년도 자산
하나 대전 1542 2015 20
대전 1535 2016 20
대전 1531 2017 20
서울 9941 2015 20
다섯 서울 9852 2016 20
여섯 서울 9776 2017 20
In [36]:
프레임2.자산 = np.arange(6)
프레임2
Out[36]:
지역 인구 년도 자산
하나 대전 1542 2015 0
대전 1535 2016 1
대전 1531 2017 2
서울 9941 2015 3
다섯 서울 9852 2016 4
여섯 서울 9776 2017 5

배열을 할당할 때는 배열의 크기가 데이터프레임의 행의 크기와 같아야 한다. 시리즈를 데이터프레임에 열로 지정할 수 있다. 이때 시리지 인덱스 이름과 데이터프레임 인덱스 이름이 같은 것만 값이 설정이 되고 나머지는 소실값이 된다.

In [37]:
 = pd.Series([20, 19, 3], index=['둘', '넷', '다섯'])
프레임2['자산'] = 
프레임2
Out[37]:
지역 인구 년도 자산
하나 대전 1542 2015 NaN
대전 1535 2016 20.0
대전 1531 2017 NaN
서울 9941 2015 19.0
다섯 서울 9852 2016 3.0
여섯 서울 9776 2017 NaN

사전형식으로 데이터프레임의 열을 추가할 때 열 이름이 존재하지 않으면 새로운 열을 추가한다. 주의해야 할 것은 속성 프레임2.덤을 이용해서는 새로운 열을 추가할 수 없다.

In [38]:
프레임2['덤'] = 프레임2.지역 == '대전'
프레임2
Out[38]:
지역 인구 년도 자산
하나 대전 1542 2015 NaN True
대전 1535 2016 20.0 True
대전 1531 2017 NaN True
서울 9941 2015 19.0 False
다섯 서울 9852 2016 3.0 False
여섯 서울 9776 2017 NaN False

del 함수를 이용해서 열을 제거할 수 있다.

In [39]:
del 프레임2['덤']
프레임2
Out[39]:
지역 인구 년도 자산
하나 대전 1542 2015 NaN
대전 1535 2016 20.0
대전 1531 2017 NaN
서울 9941 2015 19.0
다섯 서울 9852 2016 3.0
여섯 서울 9776 2017 NaN

속성을 이용해서 제거할 수는 없다.

In [40]:
del 프레임2.인구
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-40-b5d7c36d7aeb> in <module>()
----> 1 del 프레임2.인구

AttributeError: 인구

자주 사용되는 자료형으로 사전의 사전형 꼴이다.

In [41]:
인구1 = {'인구': {'하나': 1542, '여섯': 9776, '둘': 1535}, '지역': {'하나': '대전', '여섯': '서울', '둘': '대전', '셋':'대전'}}
프레임3 = pd.DataFrame(인구1)
프레임3
Out[41]:
인구 지역
1535.0 대전
NaN 대전
여섯 9776.0 서울
하나 1542.0 대전

판다스는 바깥 열쇠는 열 인덱스 이름으로 안쪽 열쇠는 행 인덱스 이름으로 해석한다.

In [42]:
프레임3.T
Out[42]:
여섯 하나
인구 1535 NaN 9776 1542
지역 대전 대전 서울 대전

데이터프레임의 인덱스와 열이 name 속성을 지정하면 그것이 표시된다.

In [43]:
프레임3.index.name = '번호'
프레임3.columns.name = '분류'
프레임3
Out[43]:
분류 인구 지역
번호
1535.0 대전
NaN 대전
여섯 9776.0 서울
하나 1542.0 대전

시리즈와 비슷하게 데이터프레임의 values 속성을 이용하면 이차원 값 배열을 얻을 수 있다.

In [44]:
프레임3.values
Out[44]:
array([[1535.0, '대전'],
       [nan, '대전'],
       [9776.0, '서울'],
       [1542.0, '대전']], dtype=object)

다음 표는 데이터프레임 첫번째 인자로 들어갈 수 있는 자료형들이다.

Type Notes
2D ndarray A matrix of data, passing optional row and column labels
dict of arrays, lists, or tuples Each sequence becomes a column in the DataFrame; all sequences must be the same length
NumPy structured /record array Treated as the “dict of arrays” case
dict of Series Each value becomes a column; indexes from each Series are unioned together to form the result’s row index if no explicit index is passed
dict of dicts Each inner dict becomes a column; keys are unioned to form the row index as in the “dict of Series” case
List of dicts or Series Each item becomes a row in the DataFrame; union of dict keys or Series indexes become the DataFrame’s column labels
List of lists or tuples Treated as the “2D ndarray” case
Another DataFrame The DataFrame’s indexes are used unless different ones are passed
NumPy MaskedArra y Like the “2D ndarray” case except masked values become NA/missing in the DataFrame result

인덱스 객체(Index Objects)

판다스 인덱스(Index) 객체는 축 라벨과 축 이름을 관리한다. 시리즈 또는 데이터프레임을 만들 때 선택 인자 index=에 라벨 리스트를 대입하면 자동으로 인덱스 객체로 만든다.

In [45]:
 = pd.Series(range(3), index=['a', 'b', 'c'])

Out[45]:
a    0
b    1
c    2
dtype: int64
In [46]:
인덱스 = .index
인덱스
Out[46]:
Index(['a', 'b', 'c'], dtype='object')

인덱스 객체는 그 자체로는 불변 객체이다.

In [47]:
인덱스[0] = 'A'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-47-ed214f2ed8a2> in <module>()
----> 1 인덱스[0] = 'A'

~\Anaconda3\lib\site-packages\pandas\core\indexes\base.py in __setitem__(self, key, value)
   1722
   1723     def __setitem__(self, key, value):
-> 1724         raise TypeError("Index does not support mutable operations")
   1725
   1726     def __getitem__(self, key):

TypeError: Index does not support mutable operations

그러나 Index.values 속성을 이용하면 값을 변경할 수 있다.

In [48]:
인덱스.values
Out[48]:
array(['a', 'b', 'c'], dtype=object)

다음과 같이 첫번째 인덱스 라벨을 소문자 a에서 대문자 A로 변경할 수 있다.

In [49]:
인덱스.values[0] = 'A'
인덱스
Out[49]:
Index(['A', 'b', 'c'], dtype='object')

그러면 인덱스 객체를 사용하고 있는 시리즈 객체의 인덱스도 다음과 같이 변경이 되는 것을 볼 수 있다.

In [50]:

Out[50]:
A    0
b    1
c    2
dtype: int64

인덱스 객체를 배열처럼 다룰 수 있을뿐만아니라 집합처럼 다룰 수도 있다.

In [51]:
프레임3
Out[51]:
분류 인구 지역
번호
1535.0 대전
NaN 대전
여섯 9776.0 서울
하나 1542.0 대전
In [52]:
프레임3.columns
Out[52]:
Index(['인구', '지역'], dtype='object', name='분류')
In [53]:
'인구' in 프레임3.columns
Out[53]:
True
In [54]:
'여섯' in 프레임3.index
Out[54]:
True

파이썬 집합과는 다르게 판다스 인덱스는 중복된 값을 가질 수 있다.

In [55]:
중복인덱 = pd.Index(['하나', '둘', '하나', '셋'])
중복인덱
Out[55]:
Index(['하나', '둘', '하나', '셋'], dtype='object')

중복된 인덱스 이름을 사용하면 같은 이름으로 된 모든 항목이 선택된다. 인덱스는 다음과 같은 집합 연산들이 가능하다.

Method Description
append Concatenate with additional Index objects, producing a new Index
difference Compute set difference as an Index
intersecti on Compute set intersection
union Compute set union
isin Compute boolean array indicating whether each value is contained in the passed collection
delete Compute new Index with element at index i deleted
drop Compute new Index by deleting passed values
insert Compute new Index by inserting element at index i
is_monoto nic Returns True if each element is greater than or equal to the previous element
is_unique Returns True if the Index has no duplicate values
unique Compute the array of unique values in the Index

핵심 기능들

인덱싱 다시 하기

새로운 인덱스에 맞게 기존의 자료를 재배열하여 새로운 객체를 만들어내는 메소드 reindex는 판다스의 중요한 메소드 중의 하나이다. 예를 들어보자.

In [56]:
 = pd.Series([3, -8, 2, 0], index=['d', 'b', 'a', 'c'])

Out[56]:
d    3
b   -8
a    2
c    0
dtype: int64

시리즈 객체에서 reindex 메소드를 부르면 인자로 넘겨준 새로운 인덱스에 맞게 자료들을 재배열하고 만일 인덱스가 없으면 소실값(missing value)을 설정한다.

In [57]:
.reindex(['a', 'b', 'c', 'd', 'e'])
Out[57]:
a    2.0
b   -8.0
c    0.0
d    3.0
e    NaN
dtype: float64

시계열 자료일 경우 reindex 할 때 소실값들을 채우는 것이 더 좋을 때가 있다. 이 때 사용할 수 있는 선택인자로 method=가 있다. method='ffill'은 앞에 있는 자료로 뒤에 있는 소실값들을 채울 수 있다.

In [58]:
시1 = pd.Series(['빨강', '파랑', '초록'], index=[0, 2, 4])
시1
Out[58]:
0    빨강
2    파랑
4    초록
dtype: object
In [59]:
시1.reindex(range(6), method='ffill')
Out[59]:
0    빨강
1    빨강
2    파랑
3    파랑
4    초록
5    초록
dtype: object

데이터프레임의 경우, reindex는 행, 열 또는 둘다를 바꿀 수 있다. 하나의 인덱스만 주어지면 행에대해서 재 인덱싱을 한다.

In [60]:
import numpy as np

 = pd.DataFrame(np.arange(6).reshape(3, 2), index=range(0, 5, 2), columns=['A', 'B'])

Out[60]:
A B
0 0 1
2 2 3
4 4 5

다음은 행에 대해서 재 인덱싱을 한 경우이다.

In [61]:
.reindex(range(5))
Out[61]:
A B
0 0.0 1.0
1 NaN NaN
2 2.0 3.0
3 NaN NaN
4 4.0 5.0

다음은 열에 대해서만 재 인덱싱을 한 경우이다.

In [62]:
.reindex(columns=['B', 'C', 'A'])
Out[62]:
B C A
0 1 NaN 0
2 3 NaN 2
4 5 NaN 4

다음은 행, 열 모두에 재인덱싱을 한 것이다.

In [63]:
.reindex(range(5), columns=['B', 'C', 'A'])
Out[63]:
B C A
0 1.0 NaN 0.0
1 NaN NaN NaN
2 3.0 NaN 2.0
3 NaN NaN NaN
4 5.0 NaN 4.0

다음은 reindex 메소드의 인자들이다.

Argument Description
index New sequence to use as index. Can be Index instance or any other sequence-like Python data structure. An Index will be used exactly as is without any copying.
method Interpolation (fill) method; ‘ffill’ fills forward, while ‘bfill’ fills backward.
fill_va lue Substitute value to use when introducing missing data by reindexing.
limit When forward- or backfilling, maximum size gap (in number of elements) to fill.
toleranc e When forward- or backfilling, maximum size gap (in absolute numeric distance) to fill for inexact matches.
level Match simple Index on level of MultiIndex; otherwise select subset of.
copy If True, always copy underlying data even if new index is equivalent to old index; if False, do not copy the data when the indexes are equivalent.

축으로부터 항목 제거

축으로부터 지정된 항목을 제거하는 것은 어렵지 않다. drop 메소드를 이용해서 지정된 축으로부터 항목들을 제거하고 새로운 객체를 반환한다.

In [64]:
 = pd.Series(range(5), index=['a', 'b', 'c', 'd', 'e'])

Out[64]:
a    0
b    1
c    2
d    3
e    4
dtype: int64
In [65]:
새시 = .drop('c')
새시
Out[65]:
a    0
b    1
d    3
e    4
dtype: int64
In [66]:
.drop(['a', 'c'])
Out[66]:
b    1
d    3
e    4
dtype: int64

데이터프레임의 경우는 두 개의 축으로부터 항목들을 제거할 수 있다.

In [67]:
 = pd.DataFrame(np.arange(3 * 3).reshape(3, 3), index=['ㄱ', 'ㄴ', 'ㄷ'], columns=['A', 'B', 'C'])

Out[67]:
A B C
0 1 2
3 4 5
6 7 8

행으로부터 항목을 제거한다.

In [68]:
.drop('ㄱ')
Out[68]:
A B C
3 4 5
6 7 8

열로부터 항목을 제거한다.

In [69]:
.drop(['A', 'B'], axis='columns')
Out[69]:
C
2
5
8

inplace= 인자를 통해서 원래 자료를 변경할 수 있다.

In [70]:
.drop(['ㄱ'], inplace=True)

Out[70]:
A B C
3 4 5
6 7 8

원래 자료가 변경되면 복구할 수 없기 때문에 inplace 인자는 주의해서 사용해야한다.

인덱싱, 선택, 여과

넘파이 인덱싱할 때와 마찬가지로 시리즈 인덱싱은 대괄호 []를 사용하는 것은 같지만 시리즈 인덱싱에서는 인덱스 항목을 사용할 수 있다는 점이 다르다.

In [71]:
 = pd.Series(range(5), index=['a', 'b', 'c', 'd', 'e'])

Out[71]:
a    0
b    1
c    2
d    3
e    4
dtype: int64
In [72]:
[1]
Out[72]:
1
In [73]:
['b']
Out[73]:
1

콜론을 이용해서도 선택할 수 있다.

In [74]:
[1:3]
Out[74]:
b    1
c    2
dtype: int64

인덱스 항목을 통해서도 선택할 수 있다. 여기서 주의해야 할 것은 슬라이싱 끝 항목에 해당하는 자료가 포함된다는 것이다.

In [75]:
['b':'c']
Out[75]:
b    1
c    2
dtype: int64

선택된 항목에 값을 할당함으로 기존 자료를 변경할 수 있다.

In [76]:
['c':'d'] = 0

Out[76]:
a    0
b    1
c    0
d    0
e    4
dtype: int64

데이터프레임의 경우는 열이름을 이용해서 선택할 수 있다.

In [77]:
 = pd.DataFrame(np.arange(3 * 4).reshape(3, 4), index=['A', 'B', 'C'], columns=['가', '나', '다', '라'])

Out[77]:
A 0 1 2 3
B 4 5 6 7
C 8 9 10 11

하나의 열이름을 통해 접근하면 반환되는 유형은 시리즈이고 리스트로 접근하면 데이터프레임이 반환된다.

In [78]:
['가']
Out[78]:
A    0
B    4
C    8
Name: 가, dtype: int32
In [79]:
type(['가'])
Out[79]:
pandas.core.series.Series
In [80]:
[['가', '다']]
Out[80]:
A 0 2
B 4 6
C 8 10

데이터프레임의 인덱스를 콜론 :을 이용해서 접근하면 행으로 접근하는 것이 된다.

In [81]:
[:2]
Out[81]:
A 0 1 2 3
B 4 5 6 7

또는 행 인덱스의 항목으로도 접근할 수 있다.

In [82]:
['A':'C']
Out[82]:
A 0 1 2 3
B 4 5 6 7
C 8 9 10 11

여기서도 마찬가지로 마지막 항목이 포함되는 것을 볼 수 있다. 논리값으로도 인덱스를 사용할 수 있다. 논리 인덱스는 기본적으로 행에 대해서 접근한다.

In [83]:
[['가'] <= 4]
Out[83]:
A 0 1 2 3
B 4 5 6 7

또는 데이터프레임 전체에 대한 논리값을 이용해서도 접근할 수 있다.

In [84]:
 < 6
Out[84]:
A True True True True
B True True False False
C False False False False

이것을 이용하면 조건을 만족하는 데이터프레임의 성분을 한꺼번에 바꿀 수 있다.

In [85]:
[ < 6] = 0

Out[85]:
A 0 0 0 0
B 0 0 6 7
C 8 9 10 11

lociloc를 이용한 선택

넘파이 인덱스 경우와 비슷하게 행과 열을 이용해서 인덱싱을 할 수 있다. 행, 열 이름을 가지고 할 수 있는 것이 loc이고 정수를 가지고 인덱싱을 하는 것이 iloc이다.

In [86]:
.loc['B', ['가', '다']]
Out[86]:
가    0
다    6
Name: B, dtype: int32
In [87]:
.iloc[:2, [3, 0, 1]]
Out[87]:
A 0 0 0
B 7 0 0

인덱스의 인수가 하나만 나오면 행이 된다.

In [88]:
.iloc[2]
Out[88]:
가     8
나     9
다    10
라    11
Name: C, dtype: int32

슬라이싱과 행, 열이름을 같이 사용할 수도 있다.

In [89]:
.loc['A':'B', ['다', '가']]
Out[89]:
A 0 0
B 6 0

다음은 데이터프레임 인덱싱 선택 사항들이다.

Type Notes
df[val] Select single column or sequence of columns from the DataFrame; special case conveniences: boolean array (filter rows), slice (slice rows), or boolean DataFrame (set values based on some criterion)
df.loc[val] Selects single row or subset of rows from the DataFrame by label
df.loc[:, val] Selects single column or subset of columns by label
df.loc[val1, val2] Select both rows and columns by label
df.iloc[where] Selects single row or subset of rows from the DataFrame by integer position
df.iloc[:, where] Selects single column or subset of columns by integer position
df.iloc[where_i, where_j] Select both rows and columns by integer position
df.at[label_i, label_j] Select a single scalar value by row and column label
df.iat[i, j] Select a single scalar value by row and column position (integers)
reindex method Select either rows or columns by labels
get_value, set_value methods Select single value by row and column label

정수 인덱스(Integer Indexes)

정수 기반 인덱스를 사용할 때는 위치 인덱스로 접근하는 것을 허용하지 않는다. 예를 들어보자. 다음은 인덱스 이름이 정수로 되어있는 시리즈이다.

In [90]:
시1 = pd.Series(range(3))
시1
Out[90]:
0    0
1    1
2    2
dtype: int64

파이썬 인덱스에서 -1은 가장 마지막 항목을 의미한다. 하지만 판다스는 정수형 인덱스를 이름으로 가지면 오류를 발생한다.

In [91]:
시1[-1]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-91-b4a177795d30> in <module>()
----> 1 시1[-1]

~\Anaconda3\lib\site-packages\pandas\core\series.py in __getitem__(self, key)
    621         key = com._apply_if_callable(key, self)
    622         try:
--> 623             result = self.index.get_value(self, key)
    624
    625             if not is_scalar(result):

~\Anaconda3\lib\site-packages\pandas\core\indexes\base.py in get_value(self, series, key)
   2558         try:
   2559             return self._engine.get_value(s, k,
-> 2560                                           tz=getattr(series.dtype, 'tz', None))
   2561         except KeyError as e1:
   2562             if len(self) > 0 and self.inferred_type in ['integer', 'boolean']:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()

KeyError: -1

하지만 인덱스 이름이 정수형이 아닐 때는 문제없이 작동한다.

In [92]:
시2 = pd.Series(range(3), index=['a', 'b', 'c'])
시2
Out[92]:
a    0
b    1
c    2
dtype: int64
In [93]:
시2[-1]
Out[93]:
2

정확한 인덱싱을 하기위해서는 loc 또는 iloc를 사용하는 것이 안전하다.

산술 연산

서로 다른 인덱스 객체들로 이루어진 객체들간의 산술 연산을 할 때 판다스는 두 인덱스 객체의 합집합에 대해서 연산을 한다. 예를 들어 보자.

In [94]:
시1 = pd.Series(range(5), index=['ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅂ'])
시1
Out[94]:
ㄱ    0
ㄴ    1
ㄷ    2
ㄹ    3
ㅂ    4
dtype: int64
In [95]:
시2 = pd.Series(range(1, 10, 2), index=['ㄱ', 'ㄴ', 'ㄷ', 'ㅁ', 'ㅅ'])
시2
Out[95]:
ㄱ    1
ㄴ    3
ㄷ    5
ㅁ    7
ㅅ    9
dtype: int64

두 객체를 더해보자.

In [96]:
시1 + 시2
Out[96]:
ㄱ    1.0
ㄴ    4.0
ㄷ    7.0
ㄹ    NaN
ㅁ    NaN
ㅂ    NaN
ㅅ    NaN
dtype: float64

인덱스 이름이 겹치지 않으면 각각의 객체는 소실값을 설정하고 연산을 할 때 소실값들에 대한 연산을 한다.

데이터프레임의 경우도 비슷하다.

In [97]:
데1 = pd.DataFrame(np.arange(3 * 3).reshape(3, 3), columns=list('ㄱㄴㄷ'), index=['가', '다', '라'])
데1
Out[97]:
0 1 2
3 4 5
6 7 8
In [98]:
데2 = pd.DataFrame(np.arange(4 * 3).reshape(4, 3), columns=list('ㄱㄷㄹ'), index=['나', '다', '라', '마'])
데2
Out[98]:
0 1 2
3 4 5
6 7 8
9 10 11

두 데이터프레임을 더하면 행, 열의 인덱스의 합집합에 대한 확장을 한 후 연산을 한다.

In [99]:
데1 + 데2
Out[99]:
NaN NaN NaN NaN
NaN NaN NaN NaN
6.0 NaN 9.0 NaN
12.0 NaN 15.0 NaN
NaN NaN NaN NaN

산술 연산 및 소실값 채우기

인덱스가 일치하지 않는 두 객체들간의 연산을 할 때 소실값을 0과 같은 값으로 채우고 연산할 필요가 있다. 이 때 fill_value= 인자를 이용해서 소실값을 채운다. 단, 두 객체의 성분이 동시에 소실값을 가지면 fill_value를 채우지 않고 np.nan을 유지한다.

In [100]:
데1 = pd.DataFrame(np.arange(3 * 4).reshape(3, 4), columns=list('abcd'))
데1.loc[0, 'c'] = np.nan
데1
Out[100]:
a b c d
0 0 1 NaN 3
1 4 5 6.0 7
2 8 9 10.0 11
In [101]:
데2 = pd.DataFrame(np.arange(4 * 5).reshape(4, 5), columns=list('abcde'))
데2.loc[1, 'b'] = np.nan
데2.loc[0, 'c'] = np.nan
데2
Out[101]:
a b c d e
0 0 1.0 NaN 3 4
1 5 NaN 7.0 8 9
2 10 11.0 12.0 13 14
3 15 16.0 17.0 18 19

두 객체가 겹쳐지지 않는 부분은 소실값(missing value)으로 대체되어 계산된다.

In [102]:
데1 + 데2
Out[102]:
a b c d e
0 0.0 2.0 NaN 6.0 NaN
1 9.0 NaN 13.0 15.0 NaN
2 18.0 20.0 22.0 24.0 NaN
3 NaN NaN NaN NaN NaN

더하기 +는 대응되는 메소드 add를 사용하여 계산할 수 있다. 그리고 fill_value= 인자를 사용하여 둘 중의 하나가 NA인 값을 fill_value값으로 채우고 연산을 한다.

In [103]:
데1.add(데2, fill_value=100)
Out[103]:
a b c d e
0 0.0 2.0 NaN 6.0 104.0
1 9.0 105.0 13.0 15.0 109.0
2 18.0 20.0 22.0 24.0 114.0
3 115.0 116.0 117.0 118.0 119.0

둘 다 NaN(0, 'c') 성분은 fill_value 값으로 대체되지 않는 것을 알 수 있다. 스칼라 나눗셈에 대한 연산도 rdiv 메소드를 이용할 수 있다.

In [104]:
1 / 데1
Out[104]:
a b c d
0 inf 1.000000 NaN 0.333333
1 0.250000 0.200000 0.166667 0.142857
2 0.125000 0.111111 0.100000 0.090909
In [105]:
데1.rdiv(1)
Out[105]:
a b c d
0 inf 1.000000 NaN 0.333333
1 0.250000 0.200000 0.166667 0.142857
2 0.125000 0.111111 0.100000 0.090909

다음은 산술 연산 메소드들이다.

Method Description
add, radd Methods for addition (+)
sub, rsub Methods for subtraction (-)
div, rdiv Methods for division (/)
floordiv, rfloordiv Methods for floor division (//)
mul, rmul Methods for multiplication (*)
pow, rpow Methods for exponentiation (**)

데이터프레임과 시리즈 연산

데이터프레임과 시리즈간의 연산이 가능하다. 기본적으로 데이터프레임의 열 이름과 시리즈의 인덱스 이름에 대해서 연산을 한다.

In [106]:
 = pd.DataFrame(np.arange(3 * 4).reshape(3, 4), columns=list('abcd'), index=list('ㄱㄴㄷ'))

Out[106]:
a b c d
0 1 2 3
4 5 6 7
8 9 10 11

주어진 데이터프레임의 첫번째 행을 시리즈 시1으로 만들어 시리즈의 인덱스와 데이터프레임의 열 이름이 같아지도록 만들었다.

In [107]:
시1 = .iloc[0]
시1
Out[107]:
a    0
b    1
c    2
d    3
Name: ㄱ, dtype: int32

두 객체의 더하기는 데이터프레임의 각 행과 시리즈의 더하기가 된다. 여기서 사용되는 방법이 브로드캐스팅이다. 즉, 차원이 다른 두 객체를 일정한 규칙을 만족하도록 낮은 차원 객체를 높은 차원 객체와 같은 크기를 만든다. 브로드캐스팅은 부록을 참조하자.

In [108]:
 + 시1
Out[108]:
a b c d
0 2 4 6
4 6 8 10
8 10 12 14

만일 시리즈의 인덱스 이름과 데이터프레임의 행이름에 대해서 연산을 하고 싶으면 axis= 인자를 사용하면 된다. 다음은 데이터프레임의 첫번째 열을 시리즈 시2로 만들었다. 따라서 시리즈의 인덱스 이름과 데이터프레임의 행이름이 같다.

In [109]:
시2 = ['a']
시2
Out[109]:
ㄱ    0
ㄴ    4
ㄷ    8
Name: a, dtype: int32

데이터프레임의 열과 시리즈와 연산을 하려면 axis='index' 또는 axis=0를 사용한다.

In [110]:
.add(시2, axis='index')
Out[110]:
a b c d
0 1 2 3
8 9 10 11
16 17 18 19

함수 사용 및 대응(Function Application and Mapping)

판다스는 넘파이 범용함수(ufunc; 성분끼리 연산)를 사용할 수 있다.

In [111]:
rng = np.random.RandomState(1234)

 = pd.DataFrame(rng.randn(3 * 4).reshape(3, 4), columns=list('abcd'), index=list('ㄱㄴㄷ'))

Out[111]:
a b c d
0.471435 -1.190976 1.432707 -0.312652
-0.720589 0.887163 0.859588 -0.636524
0.015696 -2.242685 1.150036 0.991946
In [112]:
np.abs()
Out[112]:
a b c d
0.471435 1.190976 1.432707 0.312652
0.720589 0.887163 0.859588 0.636524
0.015696 2.242685 1.150036 0.991946

데이터프레임의 행 또는 열벡터에 적용할 수 있는 apply 메소드가 있다.

In [113]:
f = lambda x : x.max() - x.min()

.apply(f)
Out[113]:
a    1.192024
b    3.129848
c    0.573119
d    1.628470
dtype: float64

함수 f는 배열 x의 최대값에서 최소값을 뺀 값을 반환한다. 기본적으로 데이터프레임.apply(함수) 하면 데이터프레임의 열 벡터에 함수를 적용한다. axis='columns'로하면 행에 대해서 함수를 적용할 수 있다.

In [114]:
.apply(f, axis='columns')
Out[114]:
ㄱ    2.623683
ㄴ    1.607752
ㄷ    3.392721
dtype: float64

sum, mean과 같은 데이터프레임 메소드들은 apply를 사용할 필요가 없다. 함수의 반환값으로 스칼라뿐 아니라 여러 값을 갖는 시리즈도 될 수 있다.

In [115]:
def g(x):
    return pd.Series([x.min(), x.max()], index=['min', 'max'])

.apply(g)
Out[115]:
a b c d
min -0.720589 -2.242685 0.859588 -0.636524
max 0.471435 0.887163 1.432707 0.991946

직접하기

  • 4x5 데이터프레임을 하나 만들고 각 행과 열에 대해서 합과 평균을 반환하는 코드를 작성하시오.
  • 각 행과 열에 대해서 합을 구하고 각 합이 전체의 몇 퍼센트인지를 구하는 코드를 작성하시오.

applymap 메소드를 이용하면 성분에 대한 연산을 하는 함수를 사용할 수 있다. 다음과 같이 각 성분 숫자를 소수점 2자리만 표시되는 함수를 만들어 보자.

In [116]:
포맷 = lambda x : '{:.2f}'.format(x)

.applymap(포맷)
Out[116]:
a b c d
0.47 -1.19 1.43 -0.31
-0.72 0.89 0.86 -0.64
0.02 -2.24 1.15 0.99

applymap이라는 이름은 시리즈에 map 메소드가 있어서 각 성분에 대해서 함수를 적용할 수 있기 때문이다. 즉, 위의 함수는 다음과 같이 각 열에 대해서 map을 적용한 것이다.

In [117]:
['a'].map(포맷)
Out[117]:
ㄱ     0.47
ㄴ    -0.72
ㄷ     0.02
Name: a, dtype: object

정렬 및 순위(Sorting and Ranking)

행 또는 열이름에 대해서 자료를 정렬하려면 sort_index 메소드를 사용하면 된다.

In [118]:
 = pd.DataFrame(np.arange(3 * 4).reshape(3, 4), columns=list('ㄴㄱㄹㄷ'), index=list('dab'))

Out[118]:
d 0 1 2 3
a 4 5 6 7
b 8 9 10 11

sort_index는 기본적으로 행 이름에 대해서 정렬을 한다.

In [119]:
.sort_index()
Out[119]:
a 4 5 6 7
b 8 9 10 11
d 0 1 2 3

열이름에 대해서 정렬을 하고 싶으면 axis='columns'를 사용한다.

In [120]:
.sort_index(axis='columns')
Out[120]:
d 1 0 3 2
a 5 4 7 6
b 9 8 11 10

자료는 기본적으로 오름차순으로 정렬이 된다. 내림차순으로 정렬을 하고 싶으면 ascending=False 인자를 사용한다.

In [121]:
.sort_index(axis=1, ascending=False)
Out[121]:
d 2 3 0 1
a 6 7 4 5
b 10 11 8 9

시리즈 값에 의한 정렬을 원하면 sort_values 메소드를 사용한다.

In [122]:
 = pd.Series([12, -2, 0, -9])

Out[122]:
0    12
1    -2
2     0
3    -9
dtype: int64
In [123]:
.sort_values()
Out[123]:
3    -9
1    -2
2     0
0    12
dtype: int64

소실값이 포함되어 있으면 기본적으로 소실값은 자료의 맨 나중에 정렬된다.

In [124]:
시1 = pd.Series([10, np.nan, -2, np.nan, -3])
시1.sort_values()
Out[124]:
4    -3.0
2    -2.0
0    10.0
1     NaN
3     NaN
dtype: float64

데이터프레임의 경우, 특정한 열에 대한 정렬을 원하면 by=열이름 인자를 사용하면 된다.

In [125]:
프1 = pd.DataFrame({'a': [-2, 3, -5, 1], 'b': [20, -29, 40, 20]})
프1
Out[125]:
a b
0 -2 20
1 3 -29
2 -5 40
3 1 20
In [126]:
프1.sort_values(by='a')
Out[126]:
a b
2 -5 40
0 -2 20
3 1 20
1 3 -29

두 개 이상의 열이름으로 정렬을 할 수 있다.

In [127]:
프1.sort_values(by=['b', 'a'])
Out[127]:
a b
1 3 -29
0 -2 20
3 1 20
2 -5 40

순위(ranking)는 자료의 순서대로 순위를 표시한다. 같은 순위가 있을 경우 method= 인자에 적용되는 방법에 의해 순위를 정한다.

In [128]:
 = pd.Series([7, -5, 7, 4, 2, 0, 4])
.rank()
Out[128]:
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

method 기본값은 average로 같은 숫자들의 순위의 평균을 구하는 것이다. 위에서 4는 순위가 4위와 5위이므로 평균은 4.5가 되는 것이다. 다음과 같이 method=first 값을 사용하면 자료의 위치가 앞에 있는 것이 앞 순위가 된다.

In [129]:
.rank(method='first')
Out[129]:
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

데이터프레임도 열 또는 행에 대해서 순위를 구할 수 있다.

In [130]:
 = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 'c': [-2, 5, 8, -2.5]})

Out[130]:
a b c
0 0 4.3 -2.0
1 1 7.0 5.0
2 0 -3.0 8.0
3 1 2.0 -2.5
In [131]:
.rank(axis='columns')
Out[131]:
a b c
0 2.0 3.0 1.0
1 1.0 3.0 2.0
2 2.0 1.0 3.0
3 2.0 3.0 1.0
Method Description
'average ' Default: assign the average rank to each entry in the equal group
'min' Use the minimum rank for the whole group
'max' Use the maximum rank for the whole group
``’first’` ` Assign ranks in the order the values appear in the data
``’dense’` ` Like method=’min’, but ranks always increase by 1 in between groups rather than the number of equal elements in a group

중복 이름을 가진 인덱스

이제까지 축 이름은 모두 유일한 예를 다루었다. reindex와 같이 대부분의 메소드들은 열, 행이름들이 유일해야하지만 필수는 아니다. 다음과 같이 시리즈의 인덱스이름으로 중복된 이름 a를 가질 수 있다.

In [132]:
 = pd.Series(np.arange(5), index=list('abcda'))

Out[132]:
a    0
b    1
c    2
d    3
a    4
dtype: int32

중복된 이름에 대한 반환값은 다음과 같이 시리즈형이고 중복되지 않은 인덱스 이름의 반환값은 스칼라임을 알 수 있다.

In [133]:
['a']
Out[133]:
a    0
a    4
dtype: int32
In [134]:
['b']
Out[134]:
1

따라서 프로그래밍할 때 주의를 해야 하는 부분이다. 인덱스가 유일한지 아닌지를 판단할 수 있는 방법 중의 하나로 인덱스의 속성인 is_unique를 이용할 수 있다.

In [135]:
.index.is_unique
Out[135]:
False

데이터프레임의 경우도 마찬가지이다.

In [136]:
 = pd.DataFrame(np.arange(3 * 4).reshape(3, 4), index=list('aba'))

Out[136]:
0 1 2 3
a 0 1 2 3
b 4 5 6 7
a 8 9 10 11
In [137]:
.loc['a']
Out[137]:
0 1 2 3
a 0 1 2 3
a 8 9 10 11

기술 통계량 계산 및 요약

판다스 객체는 공통적으로 사용되는 수학 또는 통계 메소드들을 갖추고 있다. 이것들 대부분은 시리즈 또는 데이터프레임의 행, 열로부터 평균 또는 합같은 스칼라 값을 뽑아 내는 요약 통계의 범주에 해당한다.

In [138]:
 = pd.DataFrame([[1.5, np.nan], [7.1, -4.5], [np.nan, np.nan], [0.75, -1.3]], index=list('abcd'), columns=['하나','둘'])

Out[138]:
하나
a 1.50 NaN
b 7.10 -4.5
c NaN NaN
d 0.75 -1.3

데이터프레임의 sum 메소드는 열에 대한 합을 시리즈로 반환한다.

In [139]:
.sum()
Out[139]:
하나    9.35
둘    -5.80
dtype: float64

axis='columns' 또는 axis=1 인자를 추가하면 행에 대한 합을 반환한다.

In [140]:
.sum(axis='columns')
Out[140]:
a    1.50
b    2.60
c    0.00
d   -0.55
dtype: float64

NA 값들은 계산할 때 기본적으로 제외된다. skipna= 인자를 추가함으로 계산시 적용할 수 있다.

In [141]:
.sum(axis='columns', skipna=False)
Out[141]:
a     NaN
b    2.60
c     NaN
d   -0.55
dtype: float64

요약 통계 메소드에 사용되는 선택 인자들이다.

메소드 설명
axis Axis to reduce over; 0 for DataFrame’s rows and 1 for columns
skipna Exclude missing values; True by default
level Reduce grouped by level if the axis is hierarchically indexed (MultiIndex)

idxmin 또는 idxmax 메소드들은 주어진 축의 값중 가장 큰 값 또는 가장 작은 값을 포함하는 인덱스 이름을 반환한다.

In [142]:
.idxmax()
Out[142]:
하나    b
둘     d
dtype: object

각각의 열 중에서 가장 큰 값을 포함하고 있는 행 이름을 각각 반환한 것을 볼 수 있다. 누적 통계량 메소드로는 다음과 같은 것이 있다.

In [143]:
.cumsum()
Out[143]:
하나
a 1.50 NaN
b 8.60 -4.5
c NaN NaN
d 9.35 -5.8

각 열에 대해서 각 행까지 누적해서 더한 값들을 적고 있다. 또다른 메소드로는 한번에 여러 개의 통계량을 보여주는 describe 메소드가 있다.

In [144]:
.describe()
Out[144]:
하나
count 3.000000 2.000000
mean 3.116667 -2.900000
std 3.469990 2.262742
min 0.750000 -4.500000
25% 1.125000 -3.700000
50% 1.500000 -2.900000
75% 4.300000 -2.100000
max 7.100000 -1.300000

다음은 요약 통계 및 관련된 메소드들이다.

메소드 설명
count Number of non-NA values
describe Compute set of summary statistics for Series or each DataFrame column
min, max Compute minimum and maximum values
argmin, argmax Compute index locations (integers) at which minimum or maximum value obtained, respectively
idxmin, idxmax Compute index labels at which minimum or maximum value obtained, respectively
quantile Compute sample quantile ranging from 0 to 1
sum Sum of values
mean Mean of values
median Arithmetic median (50% quantile) of values
mad Mean absolute deviation from mean value
prod Product of all values
var Sample variance of values
std Sample standard deviation of values
skew Sample skewness (third moment) of values
kurt Sample kurtosis (fourth moment) of values
cumsum Cumulative sum of values
cummin, cummax Cumulative minimum or maximum of values, respectively
cumprod Cumulative product of values
diff Compute first arithmetic difference (useful for time series)
pct_change Compute percent changes

상관계수 및 공분산

증권 자료를 pandas-datareader 패키지를 이용해서 내려받을 수 있다. 패키지가 설치되어 있지 않으면 다음과 같이 설치를 한다.

conda install pandas-datareader

pandas_datareader 모듈을 이용해서 4개 회사 거래 자료를 내려받는다.

import pandas_datareader as web
import pickle

all_data = {ticker: web.get_data_quandl(ticker) for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']}

미리 저장된 파일을 다음 사이트에서 내려받아 사용하도록 하자.

In [169]:
import pickle
from urllib import request

url = "http://compmath.korea.ac.kr/appmath/data/stock_example.dat"

with request.urlopen(url) as f:
    all_data = pickle.loads(f.read())

내려받은 자료 구조를 살펴보자. all_data의 자료형은 사전임을 알 수 있다.

In [170]:
type(all_data)
Out[170]:
dict

all_data의 열쇠(key)를 살펴보면 다음과 같다.

In [171]:
all_data.keys()
Out[171]:
dict_keys(['AAPL', 'IBM', 'MSFT', 'GOOG'])

첫번째 열쇠에 대응되는 값을 살펴보자. 다음에서 보는 바와 같이 판다스 데이터프레임임을 알 수 있다.

In [172]:
aapl = all_data['AAPL']
type(aapl)
Out[172]:
pandas.core.frame.DataFrame

데이터프레임의 구조를 보자.

In [173]:
pd.set_option('display.max_rows', 10)
aapl
Out[173]:
Open High Low Close Volume ExDividend SplitRatio AdjOpen AdjHigh AdjLow AdjClose AdjVolume
Date
2018-03-27 173.68 175.15 166.92 168.340 38962839.0 0.0 1.0 173.680000 175.150000 166.920000 168.340000 38962839.0
2018-03-26 168.07 173.10 166.44 172.770 36272617.0 0.0 1.0 168.070000 173.100000 166.440000 172.770000 36272617.0
2018-03-23 168.39 169.92 164.94 164.940 40248954.0 0.0 1.0 168.390000 169.920000 164.940000 164.940000 40248954.0
2018-03-22 170.00 172.68 168.60 168.845 41051076.0 0.0 1.0 170.000000 172.680000 168.600000 168.845000 41051076.0
2018-03-21 175.04 175.09 171.26 171.270 35247358.0 0.0 1.0 175.040000 175.090000 171.260000 171.270000 35247358.0
... ... ... ... ... ... ... ... ... ... ... ... ...
2010-01-08 210.30 212.00 209.06 211.980 15986100.0 0.0 1.0 27.026481 27.244955 26.867124 27.242385 111902700.0
2010-01-07 211.75 212.00 209.05 210.580 17040400.0 0.0 1.0 27.212826 27.244955 26.865839 27.062465 119282800.0
2010-01-06 214.38 215.23 210.75 210.970 19720000.0 0.0 1.0 27.550818 27.660055 27.084312 27.112585 138040000.0
2010-01-05 214.60 215.59 213.25 214.380 21496600.0 0.0 1.0 27.579091 27.706320 27.405597 27.550818 150476200.0
2010-01-04 213.43 214.50 212.38 214.010 17633200.0 0.0 1.0 27.428730 27.566240 27.293790 27.503268 123432400.0

2070 rows × 12 columns

수정 종가(AdjClose)와 거래량(Volume) 자료를 추출하자.

In [174]:
import pandas as pd

adj_close = pd.DataFrame({ticker: data['AdjClose'] for ticker, data in all_data.items()})
volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()})
In [175]:
adj_close
Out[175]:
AAPL GOOG IBM MSFT
Date
2010-01-04 27.503268 NaN 108.070985 25.153472
2010-01-05 27.550818 NaN 106.765484 25.161599
2010-01-06 27.112585 NaN 106.071937 25.007183
2010-01-07 27.062465 NaN 105.704765 24.748741
2010-01-08 27.242385 NaN 106.765484 24.917785
... ... ... ... ...
2018-03-21 171.270000 1090.88 156.690000 92.480000
2018-03-22 168.845000 1049.08 152.090000 89.790000
2018-03-23 164.940000 1021.57 148.890000 87.180000
2018-03-26 172.770000 1053.21 153.370000 93.780000
2018-03-27 168.340000 1005.10 151.910000 89.470000

2071 rows × 4 columns

In [176]:
volume
Out[176]:
AAPL GOOG IBM MSFT
Date
2010-01-04 17633200.0 NaN 6155300.0 38409100.0
2010-01-05 21496600.0 NaN 6841400.0 49749600.0
2010-01-06 19720000.0 NaN 5605300.0 58182400.0
2010-01-07 17040400.0 NaN 5840600.0 50559700.0
2010-01-08 15986100.0 NaN 4197200.0 51197400.0
... ... ... ... ...
2018-03-21 35247358.0 1640709.0 3240695.0 23753263.0
2018-03-22 41051076.0 2580374.0 4617371.0 37578166.0
2018-03-23 40248954.0 2113497.0 4389015.0 42159397.0
2018-03-26 36272617.0 2558385.0 4038586.0 55031149.0
2018-03-27 38962839.0 3029471.0 3810994.0 53704562.0

2071 rows × 4 columns

직접하기

  • General Motors(GM), Coca Cola(KO), AT & T(T), Intel(INTC) 주식 자료를 다운받아 data 변수에 저장하시오.(만일 다운이 안되면 ‘http://compmath.korea.ac.kr/appmath/data/stock_GM_KO_T_INTC.dat’를 이용하시오.)
  • data로부터 OpenClose 값을 각각 st_open, st_close 변수에 저장하시오.

시계열 부분에서 자세히 살펴볼 차의 백분율(percent change)을 계산하면 다음과 같다.

In [177]:
pct_ch = adj_close.pct_change()
pct_ch.tail()
Out[177]:
AAPL GOOG IBM MSFT
Date
2018-03-21 -0.022655 -0.006222 0.003137 -0.006979
2018-03-22 -0.014159 -0.038318 -0.029357 -0.029087
2018-03-23 -0.023128 -0.026223 -0.021040 -0.029068
2018-03-26 0.047472 0.030972 0.030089 0.075705
2018-03-27 -0.025641 -0.045679 -0.009519 -0.045959

시리즈 메소드 corr을 통하여 NA가 아닌 자료들에 대한 상관계수를 구할 수 있다.

In [178]:
pct_ch['MSFT'].corr(pct_ch['IBM'])
Out[178]:
0.47906169687685296

마찬가지로 cov 메소드를 이용해서 공분산을 구한다.

In [179]:
pct_ch['MSFT'].cov(pct_ch['IBM'])
Out[179]:
8.141169232110642e-05

데이터프레임의 corr, cov 메소드는 각 열에 대한 상관계수 전체를 계산한다.

In [180]:
pct_ch.corr()
Out[180]:
AAPL GOOG IBM MSFT
AAPL 1.000000 0.433070 0.365634 0.410778
GOOG 0.433070 1.000000 0.374787 0.599392
IBM 0.365634 0.374787 1.000000 0.479062
MSFT 0.410778 0.599392 0.479062 1.000000
In [157]:
pct_ch.cov()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-157-de587e26bcb4> in <module>()
----> 1 pct_ch.cov()

NameError: name 'pct_ch' is not defined

데이터프레임의 corrwith 메소드를 이용하면 데이터프레임의 열 또는 행을 다른 데이터프레임(또는 시리즈)의 짝을 찾아 상관관계를 계산할 수 있다. 인자로 시리즈를 건네면 데이터프레임의 각 열과 상관관계를 계산하여 시리즈 형으로 반환한다.

In [181]:
pct_ch.corrwith(pct_ch.IBM)
Out[181]:
AAPL    0.365634
GOOG    0.374787
IBM     1.000000
MSFT    0.479062
dtype: float64

인자로 데이터프레임을 건네면 같은 열 이름에 대해서 상관관계를 계산한다.

In [182]:
pct_ch.corrwith(volume)
Out[182]:
AAPL   -0.075738
GOOG    0.000481
IBM    -0.156549
MSFT   -0.084004
dtype: float64

선택인자 axis='columns'를 사용하여 행에 대해서 상관관계를 계산한다.

In [183]:
pct_ch.corrwith(volume, axis='columns')
Out[183]:
Date
2010-01-04         NaN
2010-01-05    0.698721
2010-01-06    0.289461
2010-01-07   -0.911877
2010-01-08   -0.658798
                ...
2018-03-21   -0.843804
2018-03-22    0.758479
2018-03-23   -0.403013
2018-03-26    0.963553
2018-03-27   -0.362487
Length: 2071, dtype: float64

유일한 값, 빈도수, 포함관계

시리즈에서 값에 대한 정보를 추출하는 메소드들에 대해서 살펴보자. 다음과 같은 시리즈를 만들자.

In [184]:
 = pd.Series(['ㄱ', 'ㄴ', 'ㄹ', 'ㄱ', 'ㄴ', 'ㄹ', 'ㄴ', 'ㄷ'])

시리즈 값들 중 유일한 값들을 unique 메소드를 이용해서 찾아내자.

In [162]:
유일 = .unique()
유일
Out[162]:
array(['ㄱ', 'ㄴ', 'ㄹ', 'ㄷ'], dtype=object)

value_counts 메소드를 이용해 값들에 대한 빈도수를 찾을 수 있다.

In [163]:
.value_counts()
Out[163]:
ㄴ    3
ㄹ    2
ㄱ    2
ㄷ    1
dtype: int64

판다스 함수를 이용해서도 빈도수를 구할 수 있다.

In [164]:
pd.value_counts(, sort=False)
Out[164]:
ㄱ    2
ㄹ    2
ㄷ    1
ㄴ    3
dtype: int64

isin 메소드를 이용해 값들이 시리즈 또는 데이터프레임의 열에 속했는지를 판단해 논리형 배열로 반환한다.

In [165]:
거름 = .isin(['ㄱ', 'ㄴ'])
거름
Out[165]:
0     True
1     True
2    False
3     True
4     True
5    False
6     True
7    False
dtype: bool
In [166]:
[거름]
Out[166]:
0    ㄱ
1    ㄴ
3    ㄱ
4    ㄴ
6    ㄴ
dtype: object

다음은 포함관계, 유일값, 빈도수 메소드들이다.

Method Description
isin Compute boolean array indicating whether each Series value is contained in the passed sequence of values
match Compute integer indices for each value in an array into another array of distinct values; helpful for data alignment and join-type operations
unique Compute array of unique values in a Series, returned in the order observed
value_cou nts Return a Series containing unique values as its index and frequencies as its values, ordered count in descending order

데이터프레임 열에 대한 빈도수를 다음과 같이 계산한다.

In [167]:
 = pd.DataFrame({'가': ['a', 'c', 'd', 'c', 'd'],
                  '나': ['b', 'c', 'a', 'b', 'c'],
                  '다': ['a', 'e', 'b', 'd', 'e']})

Out[167]:
0 a b a
1 c c e
2 d a b
3 c b d
4 d c e
In [168]:
.apply(pd.value_counts).fillna(0)
Out[168]:
a 1.0 1.0 1.0
b 0.0 2.0 1.0
c 2.0 2.0 0.0
d 2.0 0.0 1.0
e 0.0 0.0 2.0

데이터프레임 열에 a가 1개, b는 0개 c는 2개, …등을 의미한다.