Numpy 배열의 기본 개념 이해
# 리스트
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2 곱하기?
a = a * 2 # 각각의 요소에 2가 곱해지나 아니면 반복이 되나? 반복이 된다고 했다!
# 확인
print(a) # 요소에 2를 곱할 수가 없다. 안타깝지만 이게 리스트의 특징이다. 컴프리헨션을 쓰든지 해야 한다.
리스트가 가지는 한계이다. for 문을 하든지, list comprehension을 해야 한다. 할 수는 있는데 많이 귀찮아진다...
이 상황에서 우리가 배열을 쓴다면?
# 배열
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) # ap는 이제 리스트가 아니고, 배열이다.
# 확인
print(a) # [1 2 3 4 5 6 7 8 9]
# 2 곱하기
a = a * 2
# 확인
print(a) # [ 2 4 6 8 10 12 14 16 18] # 크기는 가장 큰 자릿수에 맞춰서 결정되기에 띄어쓰기가 생김
# 배열
# 다음 배열 요소 중에서 짝수만 표시하세요.
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 확인
print(a) # [1 2 3 4 5 6 7 8 9]
# 짝수만 표시
print( a[a % 2 == 0] ) # [2 4 6 8]
검색을 의미하는 괄호가 [ ]이다. a[a % 2 == 0]를 해버리면, 그 조건을 검색해서 찾아준다. 그 외에도 엄청나게 큰 수를 곱하고 더하고 하는 것을 엄청나게 빠르게 해주는 수치 연산 라이브러리가 바로 numpy이다.
1차원, 2차원, 3차원 배열 만들기
편의를 위해 Numpy 배열을 그냥 '배열'이라고 부르자. 이후에 데이터 처리를 할 때 배열로 변환해서 연산을 하거나, 결과가 배열로 표시되는 경우가 있기에 배우는 것이다. 배열에 대한 개념은 정확히 파악해 두자.
- axis: 배열의 각 축 (ex. 3X4 배열: axis 0[행]과 axis 1[열]을 갖는 2차원 배열. 가로와 세로가 있으면 2차원이다.)
- rank: 축의 개수 (ex. 3X4 배열: rank 2의 array. 2차원은 축이 2개이다.)
- shape: 축의 길이, 배열의 크기 (ex. 3X4 배열: shape는 (3, 4)
배열 만들기
배열은 np.array() 함수를 사용해서 만들게 된다.
대부분 리스트로부터 배열을 만들거나, 머신러닝 관련 함수의 결괏값이 배열이 된다.
1) 1차원 배열 만들기
1차원은 벡터라고 한다.
# 1차원 리스트
a1 = [1, 2, 3, 4, 5]
# 배열로 변환
b1 = np.array(a1) # numpy가 제공하는 array라는 함수를 가지고
# 확인
print(b1) # [1 2 3 4 5]
2) 2차원 배열 만들기
2차원은 행렬이라고 한다.
# 2차원 리스트
# a2 = [[1.5, 2.5, 3.2], [4.2, 5.7, 6.4]]
a2 = [[1.5, 2.5, 3.2],
[4.2, 5.7, 6]] # list of list이다.
# 배열로 변환
b2 = np.array(a2)
# 확인
print(b2)
# [[1.5 2.5 3.2]
# [4.2 5.7 6. ]]
여기에서 배열이 가지는 단점을 확인할 수 있다. 배열은 자료형을 하나만 가질 수 있다. 이게 배열의 가장 큰 특징이다. (리스트는 여러 자료형을 섞어서 가질 수 있는 것과 대비된다.) 위에서는 6만 정수형이므로 6을 아예 실수형으로 같이 바꿔버린다. 아래는 하나가 '6' 문자열인데, 나머지를 다 문자열로 만들어버렸다.
# 2차원 리스트
# a2 = [[1.5, 2.5, 3.2], [4.2, 5.7, 6.4]]
a2 = [[1.5, 2.5, 3.2],
[4.2, 5.7, '6']] # list of list이다.
# 배열로 변환
b2 = np.array(a2)
# 확인
print(b2)
# [['1.5' '2.5' '3.2']
# ['4.2' '5.7' '6']]
3) 3차원 배열 만들기
3차원은 큐브 퍼즐을 생각하면 된다. 3차원 배열은 0, 1, 2라고 할 때, '0'이 접해진 개수(몇 개가 겹쳐졌나, 중복되었나)를 가리키고, '1'이 행, '2'가 열을 가리킨다. shape로 표시한다면 (2, 3, 3)처럼 3개로 표현될 것이다. 이는 3X3 배열이 2개가 있네. 라고 해석하면 된다. (한편, 3차원 배열은 겹쳐지는 앞뒤에 대해서는 딱 떨어져야 한다. 그래야 오류가 안 난다.)
배열 정보 확인
배열 정보를 확인하는 속성들을 기억하자.
1) 차원 확인
ndim 속성으로 배열의 차원을 확인한다. (number of dimension) b1은 2차원의 속성을 갖고 있어요!
ndim은 속성으로, 뒤에 괄호가 없어 메소드와 구분된다. (메소드는 동작이라서 무언가를 해야 하지만, 속성은 그냥 특성을 가져오는 것이다. 인간의 성별이 뭐야? 이름이 뭐야? 나이가 뭐야? 하는 것은 뛰어가다. 걸어가다. 하는 동작과는 다르다.)
# 차원 확인
print(b1.ndim) # 1
2) 형태(크기) 확인
shape 속성으로 배열의 형태를 확인하는데, 결과는 튜플로 확인된다. 앞에서부터 axis 0, axis 1, axis 2의 크기를 의미하며, 1차원은 (x, ), 2차원은 (x, y), 3차원은 (x, y, z)로 표시된다.
# 형태(크기) 확인
print(b1.shape) # (5,)
print(b2.shape) # (2, 3)
print(b3.shape) # (2, 3, 3)
3) 요소 자료형 형식 확인
dtype 속성으로 배열에 포함된 요소들의 자료형을 확인하는데, 배열은 아까 한 가지 자료형만 가지는 특징이 있다고 했다. 이는 이후 배울 데이터프레임과는 다르다. 데이터프레임은 여러 개의 열을 가지고 있고, 데이터의 형식을 여러 개로도 가질 수 있다. 그러나 numpy에게는 dtypes가 용납될 수 없다. 어떻게 데이터 형식을 여러 개 가질 수 있지? 반대로 데이터프레임의 자존심은 다양한 열이다. 그래서 s를 빼고 dtype라고 하면 화가 잔뜩 난다. (values, value 이런 것들에서 s의 유무는 그 자료형의 특성을 알려주는 것이니 중요하게 기억하자!)
# 요소 자료형 형식 확인
print(b1.dtype) # int32
print(b2.dtype) # float64
print(b3.dtype) # int32
Reshape 기능을 사용해 배열 형태 바꾸기
배열을 사용할 때 다양한 형태(shape)로 변환할 필요가 있을 수 있다.
이때, 배열에 포함된 요소가 사라지지 않는 형태라면 자유롭게 변환할 수 있다. 즉, 요소의 개수만 변하지 않으면 된다.
(이를테면, 엄청난 이미지 값이 있는 것을 컬러 정보가 있는 100장의 파일로 저장할 수 있다. 그때는 2차원으로 저장했다가, 그것을 다시 가져오려면 3차원으로 변환해줘야 한다. 그럴 때 쓸 수 있겠다.)
1) 메서드 활용: 변수.reshape()
# (2, 3) 형태의 2차원 배열 만들기
a = np.array([[1, 2, 3],
[4, 5, 6]])
# 1차원 배열로 Reshape
c = a.reshape(6,)
# 확인
c # array([1, 2, 3, 4, 5, 6])
2) 함수 활용: np.reshape()
함수를 사용할 경우, 다른 자료형도 변환시킬 수 있다는 게 특징이다.
np.reshape에서 order='F'를 사용하면, 데이터를 열을 기준으로 먼저 채워서 배열을 재구성하는데,
Fortran 방식은 데이터를 열 기준으로 읽기 때문에 1, 4(첫번째 열), 2, 5(두번째 열), 3, 6(세번째 열). 이 순으로 원소들이 선택된다.
# (2, 3) 형태의 2차원 배열 만들기
a = np.array([[1, 2, 3],
[4, 5, 6]])
# 재배열
b = np.reshape(a, (3, 2), order='F')
# 확인
b
# array([[1, 5],
# [4, 3],
# [2, 6]])
3) -1의 편리성
(m, -1) 또는 (-1, n)처럼 사용해 행 또는 열 크기 한쪽만 지정 가능하다. 전체 열의 크기를 어떻게 조절해야할지 모를 때 한쪽에 -1을 해주면 자동으로 지정해준다.
# (2, 3) 형태의 2차원 배열 만들기
a = np.array([[1, 2, 3],
[4, 5, 6]])
# reshape(m, -1) 형태로 지정하여 Reshape 가능
print(a.reshape(1, -1))
print()
# [[1 2 3 4 5 6]]
print(a.reshape(2, -1))
print()
# [[1 2 3]
# [4 5 6]]
print(a.reshape(3, -1))
print()
# [[1 2]
# [3 4]
# [5 6]]
#print(a.reshape(4, ?)) # 정수가 안되기에 패스
#print(a.reshape(5, ?)) # 정수가 안되기에 패스
print(a.reshape(6, -1))
# [[1]
# [2]
# [3]
# [4]
# [5]
# [6]]
인덱싱과 슬라이싱으로 원하는 데이터 조회
지금까지 다룬 자료형들보다 배열은 인덱싱과 슬라이싱이 조금 어렵다.
인덱싱
1차원 배열은 리스트와 방법이 같아 생략한다.
- 특정 위치의 요소 조회: 배열[행, 열]
- 특정 행 조회: 배열[[행1,행2,..], :] or 배열[[행1,행2,..]]
- 특정 열 조회: 배열[:, [열1,열2,...]]
- 특정 행의 특정 열 조회: 배열[[행1,행2,...], [열1,열2,...]]
# (3, 3) 형태의 2차원 배열 만들기
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
1) 요소 조회
# 첫 번째 행, 두 번째 열 요소 조회
print(a[0, 1]) # 2
2) 행 조회
# 첫 번째, 두 번째 행 조회
print(a[[0, 1], :]) # 모든 행을 조회
# print(a[[0, 1]]) # :는 생략 가능하다.
# [[1 2 3]
# [4 5 6]]
3) 열 조회
# 첫 번째, 두 번째 열 조회
print(a[:, [0, 1]])
# 앞에 것은 생략할 수 없다. 앞의 것을 생략하면 뒤의 것을 생략했는지 앞의 것이 생략되었는지 헷갈려서 절대 안한다.
# [[1 2]
# [4 5]
# [7 8]]
4) 행, 열 조회
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 두 번째 행 두 번째 열의 요소 조회
print(a[[1], [1]]) # [5]
# 세 번째 행 두 번째 열의 요소 조회
print(a[[2], [1]]) # [8]
# 첫 번째 행 첫 번째 열, 두 번째 행 두 번째 열의 요소 조회
print(a[[0, 1], [0, 1]]) # [1 5]
# 첫 번째 행 첫 번째 열, 두 번째 행 두 번째 열, 세 번째 행 첫 번째 열의 요소 조회
print(a[[0, 1, 2], [0, 1, 0]]) # [1 5 7]
슬라이싱
- 그 위치의 요소 조회: 배열[행1:행N, 열1:열N]
조회 결과는 2차원 배열이 되며, 마지막 범위의 값은 대상에 포함되지 않는다.
즉, 배열 [1:M, 2:N]라고 하면, 1부터 M-1까지의 행, 2부터 N-1까지의 열이 조회대상이 된다.
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 첫 번째 ~ 두 번째 행 조회
# print(a[0:2, :]) # 뒤의 것 생략
print(a[0:2])
# [[1 2 3]
# [4 5 6]]
# 첫 번째 행, 첫 번째 ~ 두 번째 열 조회
print(a[0, 0:2])
# 리스트는 여러 개를 묶어서 하나로 보여주려고 하는 것이다. 슬라이싱은 하나의 원래부터 묶음이다. 그래서 또 묶으면 별로이다.
# [1 2]
# 첫 번째 ~ 세 번째 행, 두 번째 ~ 세 번째 열 조회
print(a[0:3, 1:3])
# [[2 3]
# [5 6]
# [8 9]]
# 두 번째 ~ 끝 행, 두 번째 ~ 끝 열 조회
print(a[1:, 1:])
# [[5 6]
# [8 9]]
배열 사이의 기본적 연산 수행
# 두 개의 (2, 2) 형태의 2차원 배열 만들기
x = np.array([[1, 2], [3, 4]])
y = np.array([[5, 6], [7, 8]])
# 확인
print(x)
# [[1 2]
# [3 4]]
print(y)
# [[5 6]
# [7 8]]
1) 배열 더하기
+ 또는 np.add() 함수 사용
# 배열 더하기
print(x + y)
# 또는
print(np.add(x, y))
# [[ 6 8]
# [10 12]]
2) 배열 빼기
- 또는 np.subtract() 함수 사용
# 배열 빼기
print(x - y)
# 또는
print(np.subtract(x, y))
# [[-4 -4]
# [-4 -4]]
3) 배열 곱하기
* 또는 np.multiply() 함수 사용
# 배열 곱하기
print(x * y)
# 또는
print(np.multiply(x, y))
# [[ 5 12]
# [21 32]]
4) 배열 나누기
/ 또는 np.divide() 함수 사용
# 배열 나누기
print(x / y)
# 또는
print(np.divide(x, y))
# [[0.2 0.33333333]
# [0.42857143 0.5 ]]
5) 배열 제곱
** 또는 np.power() 함수 사용
# 배열 y 제곱
print(x ** y)
# 또는
print(np.power(x, y))
# [[ 1 64]
# [ 2187 65536]]
# 배열 제곱
print(x ** 2)
# [[ 1 4]
# [ 9 16]]
# 행렬의 곱
print(x.dot(y))
# [[19 22]
# [43 50]]
'데이터 분석 > Pandas 기초' 카테고리의 다른 글
데이터프레임 변경(4): Rolling과 Shift / Pivot과 Melt (0) | 2024.09.26 |
---|---|
데이터프레임 변경(3): 합치기(Concat)와 조인(Merge) (0) | 2024.09.25 |
데이터프레임 변경(2): 결측치 처리 / 가변수(Dummy Variable) 생성 (0) | 2024.09.25 |
데이터프레임 변경(1): 열(이름변경, 추가, 삭제) / 범주값(변경, 생성) (0) | 2024.09.24 |
데이터프레임 생성, 탐색, 조회, 집계 (1) | 2024.09.23 |