(python) numpy, pandas, sklearn
NumPy
- 그냥 python for 돌면 무조건 느리다. C가 아니니까… 파이썬으로 아무리 해봐야 크게 개선이 안됨.
- 그래서 Pandas나 Numpy가 제공하는 방법 대로 접근하는게 제일 효과적인 성능 개선 방법.
1
2
3
4
5
6
7
import numpy as np
x = np.array([1, 2, 3])
print(type(x))
> <class 'numpy.ndarray'>
특정 값을 가진 index들을 반환받는 방법 : where
1
2
3
4
5
>>> arr = np.array([[1,2,0],[0,1,2]])
>>> np.where(arr == 0)
(array([0, 1], dtype=int64), array([2, 0], dtype=int64))
-> 0,2와 1,0
산술 연산과 브로드캐스트
넘파이 배열에 대한 산술 연산자의 동작은 파이썬 기본 리스트와 다르다.
* 연산
1
2
3
4
5
6
print(x\*2) - 넘파이 배열 \* 2
> [2 4 6]
l = [1, 2, 3]
print(l\*2) - 파이썬 기본 리스트 \* 2
> [1, 2, 3, 1, 2, 3]
+ 연산
1
2
3
4
5
6
7
8
9
10
print(x+1)
> [2 3 4]
print(x + [3, 3, 3])
> [4 5 6]
파이썬 기본 리스트에 배열이 아닌 스칼라값(수치 하나)을 더할 수는 없다.
print(l + [3, 3, 3])
> [1, 2, 3, 3, 3, 3]
[3, 3] 같이 원소 수가 다른 배열을 더하면 넘파이는 오류,
파이썬 기본 리스트는 끝에 [3, 3]이 추가된다.
파이썬 리스트가 지원하는 연산자는 \*, +
뿐이라 -, /
는 사용할 수 없다.
넘파이는 모든 연산자를 지원하며 원소별(element-wise) 연산을 수행한다.
넘파이 배열을 스칼라값과 산술 연산 하는 경우 넘파이 배열의 원소별로 한 번씩 계산이 수행된다. 이 기능을브로드캐스트라고 한다.
브로드캐스트는 유용한 기능이지만 배열의 형상, 차원 순서를 주의깊게 맞춰주지 않으면 에러가 발생하기 쉽다.
또는 에러가 발생하지 않고 그냥 진행되어 이상하게 동작하거나.
리스트로 원소 선택하기 ( 인덱스로 리스트 넘기기 )
인덱스로 리스트나 투플을 넘겨 해당 원소들을 선택 및 조작할 수 있다.
1
2
3
4
5
6
7
nparr = np.array([10, 11, 12, 13, 14, 15])
print(nparr[[0, 2]])
> [10 12]
nparr[[0, 2]] = 0
print(nparr)
> [ 0 11 0 13 14 15]
map
map을 사용하지 않아도 배열에서 원소들이 조건을 만족하는지를 True False
로 나타내는 리스트를 반환할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
x = np.array([1, -2, 3, -2, -5, 10])
print(x > 0) x > 0 연산에 대한 결과로 브로드캐스트가 작동하여 bool 배열이 생성된다.
> [ True False True False False True]
#이를 이용하면 반복문을 사용하지 않아도 조건을 만족하는(True에 해당하는) 원소만 조작할 수 있다.
#리스트로 원소를 선택할 수 있기 때문에, 위 bool 배열을 리스트로 넘기면 True에 대응되는 원소만 반환한다.
nparr = np.array([10, 11, 12, 13, 14, 15])
mask = x >0
print(nparr[mask])
> [10 12 15]
nparr[mask] = 0
print(nparr)
> [ 0 11 0 13 14 0]
N차원 배열
파이썬 리스트와 같은 방법으로 작성한다. 행렬의 형상은shape 으로, 행렬에 담긴 원소의 자료형은dtype 으로 알아낼 수 있다. 행렬의 차수는np.ndim() 으로 알 수 있다.
1
2
3
4
5
6
7
8
x = np.array([[1, 2], [4, 5]])
print(x.shape)
> (2, 2)
print(x.dtype)
> int32
print(x.ndim())
> 2
차원이 서로 다른(형상이 다른) 넘파이 배열 간의 산술 연산 시에도 브로드캐스트가 작동한다.
x[a, b] 는 x[a][b]와 같다. 그러나 x[:a, :b]는 x[:a][:b]와 다르다.
1
2
3
4
5
print(x[0, 1])
> 2
print(x[0][1])
> 2
flatten() 를 이용해 1차원 배열로 변경할 수 있다. 이를 평탄화 라고 부른다.
1
2
3
4
x = x.flatten()
print(x)
> [1 2 4 5]
reshape() 를 이용해 형상을 변경할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x = np.array([[1, 2, 3], [4, 5, 6]])
print(x.reshape(3, 2))
> [[1 2]
[3 4]
[5 6]]
print(x.reshape(x.size, -1)) #-1을 넣으면 알아서 나머지 크기만큼 묶어준다
> [[1]
[2]
[3]
[4]
[5]
[6]]
print(x = x.reshape(2, -1))
> [[1 2 3]
[4 5 6]]
transpose() 를 사용해 다차원 배열의 차원 순서를 변경할 수 있다. 선형대수에서 말하는 ^T 변환은 보통.T 를 사용한다. 단, .T
는 1차원 배열에는 동작하지 않는다! [[1, 2, 3]]
이런 식으로 두 번 이상 묶어야 동작한다. .reshape()
는 1차원 배열에도 동작한다.
1
2
3
4
5
6
7
8
9
10
11
12
print(x.T)
> [[1 4]
[2 5]
[3 6]]
x = np.ones((1,2,3)) #형상이 (1, 2, 3)인 행렬 생성
print(x.shape)
> (1, 2, 3)
print(x.transpose(2, 0, 1).shape)
> (3, 1, 2)
행렬곱
수학에서 의미하는 일반적인 행렬곱(내적)을 수행하려면 그냥 * 연산이 아니라np.dot() 을 사용한다.
1
2
3
4
5
6
7
8
9
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 0], [0, 1]])
print(A\*B)
>[[1 0]
[0 4]]
print(np.dot(A, B))
>[[1 2]
[3 4]]
Note ) 3.5 버전부터 자체적으로 행렬곱 연산자 @
를 지원한다.
numpy.nditer 다차원 배열 순회
https://docs.scipy.org/doc/numpy/reference/generated/numpy.nditer.html
다차원 배열을 다차원 iterator로 변환할 수 있다. 반환받은 iterator에는 다양한 method가 있는데, 그 중it.multi\_index
를사용하면 다차원 배열의 인덱스를 투플로 반환 받을 수 있다. 예를 들어2x3
행렬일 경우 iterator를 반복할 때 마다 다음이 차례로 반환된다.
1
2
3
4
5
6
7
8
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
이를 이용하면 for문 중첩 등의 복잡한 방법을 사용하지 않아도 다차원 배열을 간단히 순회할 수 있다.
초기화 및 선언
1
2
3
4
5
6
np.empty()
np.zeros()
np.ones()
np.empty\_like()
...
shape이 분명 2차원이어야 하는데 (11351,) 과 같이 나올 때 해결방법
- 예를 들어 pandas Series의 원소가 리스트일 때 이런 문제를 마주치게 된다.
- https://stackoverflow.com/questions/50971123/converty-numpy-array-of-arrays-to-2d-array
- np.stack()을 사용하면 예상하는 shape (11351, 3) 으로 변환해준다.
Pandas
- Group by: split-apply-combine
- https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html
- Series에는 map이 있으나 DataFrame에는 map이 없다. 대신 apply가 있다.
- apply는 기본적으로 한 열 마다 적용되지만, axis 지정으로 한 행 마다 적용으로 바꿀 수 있다.
- 그래서 dataframe에서 한 행 을 가져와 여러 속성을 이용해 람다를 적용 하고 싶은 경우 axis=1 apply,
- 단순히 한 행에 람다를 적용하고 싶은 경우 Series.map 을 사용.
- transform은 한 열마다 적용 되는 것이므로, 두 개의 열을 묶어서 transform으로 넘기는 것은 불가능하다. (애초에 이런 경우 apply를 쓰는 것이 맞다)
1
2
3
4
## 이런게 가능.
id\_interval = csv['Timestamp'] - csv.groupby('Arbitration\_ID')['Timestamp'].shift(1)
result['IdInterval'] = id\_interval.fillna(-1)
MultiIndex
실세계의 많은 데이터가 MultiIndex 형태다. (학교 -> 학과 -> 학번)
https://data-make.tistory.com/126
1
2
3
4
5
6
7
8
train\_x\_df = pd.read\_csv(DATA\_DIR + "train\_x\_df.csv", index\_col=['coin\_index', 'sample\_id', 'time'])
## read\_csv 말고도 다양한 방법으로 MultiIndex df로 만들 수 있음.
## (coin\_index, sample\_id) 별로, time을 1차원으로 쭉 붙이는 식으로 가공하려면
train\_x\_df.unstack()
## 컬럼 이름이 중복되는 것 끼리 열 단위 MultiIndex를 추가로 생성해서 묶어줌
https://pandas.pydata.org/docs/user_guide/reshaping.html#reshaping-by-stacking-and-unstacking
scikit-learn
차원 축소 ( dimension reduction ) : PCA
https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html 몇 차원으로 줄일지 직접 n을 지정하는 것도 가능하고 설명 분산량이 몇 이상임을 만족하는 차원 개수를 알아서 선택하도록 하는 것도 가능 { sum(pca.explained_variance_ratio_) }
1
2
3
4
pca = PCA()
pca.fit(normal\_data\_df)
data\_df\_reducted = pd.DataFrame(data=pca.transform(test\_data\_df))
KernelPCA
라는 녀석도 있음. https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.KernelPCA.html
LDA
당연히 LDA도 있다. https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html