앞으로는 데이터를 다루는 데 있어 자주 사용하는 Pandas.Series의 함수를 정리하도록 하겠다.
첫 번째 Series의 함수는 isnull()이다.
이 함수는 Series의 데이터 값이 NA여부를 판단하여 데이터가 True or False로 나와있는 bool로 되어있는 Series로 반환한다. 여기에서 NA는 None과 numpy.NaN과 같은 것을 의미한다.
import pandas as pd
import numpy as np
a = pd.Series([1,np.NaN,3,4,None],
index = ['a','b','c','d','e'])
a.isnull()
#결과
a False
b True
c False
d False
e True
dtype: bool
한 가지 주의해야 할 점은 numpy.inf는 Pandas.isnull()에서 NA값으로 정의하지 않는다
a = pd.Series([1,np.NaN,3,4,np.inf],
index = ['a','b','c','d','e'])
a.isnull()
#결과
a False
b True
c False
d False
e False
dtype: bool
따라서 다음과 같이 Pandas.options.mode.use_inf_as_na를 True로 바꾸어 주어야 한다.
pd.options.mode.use_inf_as_na = True
a = pd.Series([1,np.NaN,3,4,np.inf],
index = ['a','b','c','d','e'])
a.isnull()
#결과
a False
b True
c False
d False
e True
dtype: bool
이번에는 pd.Pandas를 사용할 때 index를 이용하여 데이터를 다루는 방법을 알아보겠다.
1. index를 통한 데이터 획득
앞서 우리는 다음과 같이 선언한 pd.Series의 index를 통하여 값을 가져 올 수 있는 다양한 방법이 있는 것을 알고 있다.
python의 dictionary의 key값처럼 index의 값을 입력하면 데이터를 얻을 수 있다.
a = pd.Series([1,2,3,4,5], index = ['a', 'b', 'c', 'd', 'e'])
a['a']
#결과
1
array_like형 데이터가 index로 들어가면 관련 데이터로만 이루어진 Series형 데이터를 반환한다.
b = np.array(['c', 'a', 'b'])
a[b]
#결과
c 3
a 1
b 2
dtype: int64
python의 slicing처럼 index또한 다음과 같이 사용 가능하다.
a['a':'c']
#결과
a 1
b 2
c 3
dtype: int64
2. index를 이용한 산술연산
파이썬에서 쓰는 산술 연산자는 pd.Series에서 사용이 가능하다. 다음을 보면 알수 있다.
data = [1,2,3,4,5]
a = pd.Series(data, index = ['a', 'b', 'c', 'd', 'e'])
a > 1
#결과
a False
b True
c True
d True
e True
dtype: bool
Series형 데이터인 a를 이용하여 a > 1을 사용하면 a에 대한 index를 이용하여 1보다 큰 값에는 True 작은 값에는 False인 bool형 데이터를 갖는 Series형 데이터를 반환한다. Series형 데이터 또한 array_like형 데이터 이므로 index로 들어가는게 가능하다.
이를 index값으로 사용하여 새로운 index값으로 입력한다면 우리는 Series형 데이터에서 원하는 데이터만 습득가능하다. 즉 , 원하는 Series 데이터에서 산술 연산자를 통해 원하는 값만을 습득 가능하다.
다음의 예시는 a에 들어있는 데이터에서 1보다 큰 값을 얻는 것이다.
data = [1,2,3,4,5]
a = pd.Series(data, index = ['a', 'b', 'c', 'd', 'e'])
a[a > 1]
#결과
b 2
c 3
d 4
e 5
dtype: int64
iterable = (x*x for x in range(5))
iterable
#결과
<generator object <genexpr> at 0x7f5688c02ad0>
b = pd.Series(iterable)
#결과
0 0
1 1
2 4
3 9
4 16
dtype: int64
np.array에 값을 넣을 때와는 다르게 값의 형태로 나온 것을 확인 가능하다.
data를 python의 dictionary를 넣는다면 key값이 index가 되며 value값이 data가 되어 나온다.
dic = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
a = pd.Series(dic)
#결과
a 1
b 2
c 3
d 4
e 5
dtype: int64
만약 dictionary의 key값을 사용하지 않고 직접 지정하고 싶으면 다음과 같이 index를 지정해 주면 된다.
dic = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
index = ['b', 'e', 'a', 'd', 'c']
a = pd.Series(dic, index = index)
#결과
b 2
e 5
a 1
d 4
c 3
dtype: int64
만약 index의 수가 data의 수보다 많으면 다음과 같이 NaN(not a number)가 들어가게 된다. NaN은 Pandas에서 누락된 값 또는 NA으로 취급된다.
dic = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
index = ['b', 'e', 'a', 'd', 'c', 'f']
a = pd.Series(dic, index = index)
#결과
b 2.0
e 5.0
a 1.0
d 4.0
c 3.0
f NaN
dtype: float64
scalar value와 값이 없는 Series또한 만들 수 있다.
a = pd.Series()
b = pd.Series(42)
a
#결과
Series([], dtype: float64)
b
#결과
0 42
dtype: int64
Index
index는 우리가 데이터의 이름을 붙여 구분하기 편하게 사용할 수 있을 뿐 아니라 다양한 기능을 제공한다.
pd.Series에서 index를 이용하여 단일 값을 선택 또는 여러 값을 선택할 수 있다.
index는 앞서 pd.Series를 생성하면서 보았듯이 각 데이터 값의 이름이라고 할 수 있으며 default값을 0부터 시작하는 자연수가 붙게 된다. 또한 pd.Series를 선언할 때 index 값을 선언 가능하다. 또한 index만을 따로 뽑을 수 있으며 array_like형 데이터를 넣어주면 Series에서 index 변경이 가능하다.
a = pd.Series([1,2,3,4,5], index = ['a', 'b', 'c', 'd', 'e'])
#결과
a 1
b 2
c 3
d 4
e 5
dtype: int64
a.index
# a.keys()를 써도 같은 결과가 나온다
#결과
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
a.index = ['b', 'a', 'c', 'd', 'e']
#결과
b 1
a 2
c 3
d 4
e 5
dtype: int64
우리가 선언한 index를 이용하여 Series 데이터를 다룰 수 있다.
a = pd.Series([1,2,3,4,5], index = ['a', 'b', 'c', 'd', 'e'])
a['a']
#결과
1
a[['c', 'a', 'b']]
#결과
c 3
a 1
b 2
dtype: int64
a['a':'c']
#결과
a 1
b 2
c 3
dtype: int64
다음은 index를 이용할 수 있는 다양한 결과들이다.
pd.Series는 index에 데이터 값을 매핑하고 있으므로 파이썬의 dictionary와 비슷하다.
a.keys()
#결과
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
list(a.items())
#결과
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
'b' in a
#index값이 a에 있는지 확인 가능하다.
#결과
True
Dtype
pd.Series의 data type을 설명한다. optional이기 때문에 딱히 지정해 주지 않아도 상관없다. 만약 지정하지 않는다면 우리가 집어넣어주는 data에서 data type을 추론한다.
다음과 같이 data에 int64만 넣었으므로 dtype는 int64가 나와야 하지만 dtype을 float64로 설정해 주었기 때문에 data가 float64형태로 변하였다.
a = pd.Series([1,2,3,4,5], index = ['a', 'b', 'c', 'd', 'e'], dtype = 'float64')
#결과
a 1.0
b 2.0
c 3.0
d 4.0
e 5.0
dtype: float64
Name
pd.Series에 주는 이름이다.
다음의 예시를 보면 출력값에 이름이 나오는 것을 확인 가능하다. 또한 추가 함수인 name을 이용하여 이름만 꺼내거나 바꿀 수 있다.
a = pd.Series([1,2,3,4,5], index = ['a', 'b', 'c', 'd', 'e'], name = 'a')
#출력
a 1
b 2
c 3
d 4
e 5
Name: a, dtype: int64
a.name
#출력
'a'
a.name = b
#출력
a 1
b 2
c 3
d 4
e 5
Name: b, dtype: int64
Copy
input data를 copy하며 이 parameter에 영향받는 input data는 Series와 1d ndarray만 영향을 받는다.
자세히 말하면 Series와 1d ndarray를 input data로 사용하여 copy를 True로 사용하지 않는다면 input data에서 데이터를 연결시켜 사용하기 때문에 데이터를 수정하면 기존 input data도 수정하게 된다.
다음의 예시를 보면 편하다.
data = np.array([1,2,3,4,5])
a = pd.Series(data, index = ['a', 'b', 'c', 'd', 'e'])
#data 결과
array([1, 2, 3, 4, 5])
#a 결과
a 1
b 2
c 3
d 4
e 5
dtype: int64
a['a'] = 777
#a 결과
a 777
b 2
c 3
d 4
e 5
dtype: int64
#data 결과
array([777, 2, 3, 4, 5])
이를 방지하기 위해 copy = False를 넣어준다면 input data를 사용하는 게 아닌 복사한 데이터를 사용하기 때문에 기존 input data의 값은 변하지 않는다.
data = np.array([1,2,3,4,5])
a = pd.Series(data, index = ['a', 'b', 'c', 'd', 'e'], copy = True)
#data 결과
array([1, 2, 3, 4, 5])
#a 결과
a 1
b 2
c 3
d 4
e 5
dtype: int64
a['a'] = 777
#a 결과
a 777
b 2
c 3
d 4
e 5
dtype: int64
#data 결과
array([1, 2, 3, 4, 5])
만약 input data가 Series나 1d ndarray가 아닌 다른 data라면 copy를 False로 해도 영향을 받지 않는다.
data = [1,2,3,4,5]
a = pd.Series(data, index = ['a', 'b', 'c', 'd', 'e'])
#data 결과
array([1, 2, 3, 4, 5])
#a 결과
a 1
b 2
c 3
d 4
e 5
dtype: int64
a['a'] = 777
#a 결과
a 777
b 2
c 3
d 4
e 5
dtype: int64
#data 결과
[1, 2, 3, 4, 5]
위와 같이 다양한 방법으로 처리되며 일일히 확인이 불가능하여 일반적으로 배열과 유사한 것은 모두 가능하다고 적어놓은 것 같다.
iterable은 파이썬 표준 용어로써 반복될 수 있는 모든 것을 나타낸다.
간단한 예를 들어 우리는 문자열을 변수에 넣어주면 이는 for 문을 통해 나올 수 있다.
여기서 한 가지 주의해야 할 점은 'array_like에 속해있는 대부분의 데이터 타입은 iterable 데이터 타입이니 iterable 데이터 타입이 array_like과 같나?'라는 생각을 할 수 있다.
간단한 예를 들어 반박 가능한데 스칼라 값은 array_like에 속해있지만 iterable한 데이터 타입이 아니다.
즉 a = 3은 np.array(a)에서 오류가 나오지 않지만 for문을 통해 돌리면 에러가 나온다.
또한 iterable 데이터 타입은 np.array를 통해서 Numpy array으로 만들수 없다.
iterable = (x*x for x in range(5))
iterable
#결과
<generator object <genexpr> at 0x7f6fafed06d0>
np.array(iterable)
#결과
array(<generator object <genexpr> at 0x7f6fafa93f50>, dtype=object)
np.fromiter(iterable, float)
#결과
array([ 0., 1., 4., 9., 16.])
다음의 결과를 보면 generator object인 iterable을 만들었다. 이를 np.array에 삽입하였으며 결과를 확인해 보면 object형태로 들어갔으며 배열을 형태가 나오지 않은 것을 볼 수 있다. 따라서 iterable 형태가 array_like이 아닌것을 확인 가능하다. 이러한 문제는 np.fromiter를 이용하여 해결 가능하다.
Pandas는 데이터를 다루는데 있어 자주 사용되는 패키지이다. 주로 NumPy, SciPy, scikit-learn, matplotlib와 같이 사용한다. Pandas는 주로 NumPy의 스타일을 차용하였지만, NumPy는 단일 산술 배열 데이터를 다루는데 특화되어 있는 것과는 다르게 Pandas는 표(table), 시계열(series)등 다양한 형식의 데이터를 다루는데 초점을 맞춰 설계했다.
numpy.random 모듈은 python 내장 random 함수를 보강하여 다양한 종류의 확률 분포로부터 효과적으로 표본 값을 생성하는데 사용 가능하다.
numpy.random은 매우 큰 표본을 생성하는데 파이썬 내장 모듈보다 수십 배 빠르다.
numpy.random을 엄밀하게 말하면 유사 난수라 부르는데, 이는 난수 생성기의 시드값에 따라 정해진 난수를 알고리즘으로 생성하기 때문이다. 즉, 컴퓨터 프로그램에서 무작위 수를 구할때는 어떠한 특정 시작 숫자를 기준으로 컴퓨터가 정해진 알고리즘에 의해 마치 난수처럼 보이는 수열을 생성한다. 이러한 시작 숫자를 시드(seed)라고 한다. 또한 생성된 난수는 다음번 난수 생성을 위한 시드가 된다. 시드는 보통 자동으로 정해지지만 사람이 수동으로 정할 수 있다.
수동으로 시드를 정한다면 이후의 난수들을 예측할 수 있다. 이는 프로그램을 돌릴때 결과의 재현성을 위해 사용한다. 예를 들어 딥러닝 프로그램에 parameter를 특정 시드를 사용하여 정해준다면 parameter의 초기값에 의해 성능이 바뀌는 일이 일어나지 않는다.
Numpy의 난수 생성기의 시드값은 다음과 같이 변경 가능하다.
import numpy as np
np.numpy.seed(123)
이를 이용하여 numpy에서 제공하는 시드값은 전역 난수 시드값을 이용한다.
a = np.random.randn(5)
# 결과
array([-1.0856306 , 0.99734545, 0.2829785 , -1.50629471, -0.57860025])
b = np.random.randn(5)
# 결과
array([ 1.65143654, -2.42667924, -0.42891263, 1.26593626, -0.8667404 ])
다음의 과정을 본다면 시드를 123으로 준다면 처음 난수 결과값은 항상 -1.0856306이 나올 것이다. 다음 난수 결과값은 -1.0856306을 시드로 사용하여 생성한다.
np.random.seed(123)
a = np.random.randn(1)
# 결과
array([-1.0856306])
b = np.random.randn(1)
#결과
array([0.99734545])
다음과 같이 시드 123을 사용하여 난수을 2개 만들어보면 위의 a의 5개의 난수 값의 처음 두 개와 일치하는 것을 볼 수 있다.
여기에서 문제는 np.random.seed를 사용하여 난수를 생성한다면 앞으로 numpy를 사용하여 만드는 모든 난수값이 시드의 값에 영향을 받게 된다. 이는 서로 다른 시드를 사용하고 싶을 때 고려해줄 것이 많게 된다. 따라서 난수 생성기로부터 격리된 난수 생성기를 만들고 싶다면 다음과 같은 코드를 사용하면 된다.
np.random.RandomState를 사용한다면 rns라는 object가 생기며 이를 통해 난수에 접근 가능하다. 즉 특정 시드값을 기준으로 생성되는 난수가 필요할 때만 접근 가능하다.
예를 들어 전역 시드값을 설정해주는 np.random.seed(123)을 주었으면 위에서 보았듯이 난수를 설정하는데 있어 초기값은 -1.0856306이 나와야 하며 a를 통해 난수 5개를 만들었을때 이를 확인 가능하다. 또한 random.RandomState를 통해 생성한 rns난수생성기를 통해 생성된 5개의 난수를 보면 a의 결과와 같은 것을 확인 가능하다. 이는 서로 다르게 시드값이 적용되는 것을 의미한다.