파이썬을 공부하고 나서 배운 건 데이터 분석 분야에 쓰이는 파이썬 기반 라이브러리, 패키지들을 배운다. 데이터를 분석할 때 전처리- 시각화 과정을 밟는데 이 때 들어가는 프로그램들을 배우고 써보기로 했다. (이를 기반으로 팀 프로젝트를 전에 했었고 회고록이 있다.)
0. 가상환경 설정
너무 많은 프로그램을 설치하는 것도 일이고, 충돌이 나니까...
사용 방법으로 주로 두가지가 있다.
VSCode : 가상환경 생성, 연결, 패키지 관리를 한번에 할 수 있다. (코드 편집기)
Anaconda : 패키지, 가상 환경, 개발 도구를 편히 사용하도록 파이썬 환경을 제공하는 소프트웨어
파이썬처럼 설치하는 것이라서, 둘이 충돌날 수도 있고. 파이썬 외에도 배워야 할 게 있다보니 코드 편집기인 VSCode로 진행했다.
VS Code로 가상환경 만들기
만들어지면 이런 상태가 뜬다. 그러면 이제 작업하면 된다.
이번에 쓰는 파일은 .ipynb 을 사용한다.
주피터 노트의 기본 저장 파일 형식이라고 하는데, Markdown, Python 코드 실행, 데이터 결과 출력물 한꺼번에 확인하고 수정, 보수하기 위해 만들어진 대화형 파일이라 데이터 분석할 때 자주 쓰인다.
1. NumPy
- 다차원 배열을 쉽고 효율적으로 사용할 수 있도록 지원하는 파이썬 라이브러리
- 데이터 분석 라이브러리에 많이 사용됨
행렬 설명
주로 어디에 쓰는가? : 대용량 데이터 통계 분석 및 행렬, 벡터/행렬 곱, 난수 생성과 수학 함수 적용 , 이미지 데이터 처리.
1-1. ndarray
- numpy의 핵심 데이터 구조
- 동일한 자료형의 다차원 배열
둘의 자료형 차이점.
구체적인 차이점
List : 원소로 여러가지 자료형이 된다. 내부 배열의 원소 개수가 달라도 됨. / 빼기, 곱하기, 나누기 연산 불가
Ndarray : 한가지 자료형만 허용. 내부 배열 원소 개수가 같아야 함. / 같은 위치의 원소 끼리 산술 계산 가능
> 원소 개수 다르면 에러가 뜬다.
헷갈리기에 용어 이해로 붙여둔다.
먼저 시작하기 앞서 터미널을 통해
pip install numpy 로 설치한다. <이후 라이브러리, 패키지는 이 pip install 명령어를 붙이고 시작한다.
import numpy as np
Numpy를 np라는 별칭으로 임포트 한다.
# ndarray의 생성
a = np.array([[1,2,3],[4,5,6]])
b = np.array([1.0, 3.14, 1,24])
# 배열의 구조
print(f"배열의 구조: {a.shape}")
구조를 튜플로 반환한다.
# 배열의 차원 수
print(f"배열의 차원: {a.ndim}")
# 데이터 타입
print(f"배열의 데이터 타입: {a.dtype}")
print(f"배열의 데이터 타입: {b.dtype}")
# 형변환
new_a = a.astype(np.float64)
print(f"수정한 배열의 데이터 타입: {new_a.dtype}")
자료형 변환하는 ()메서드.
2차원 구조
# 3차원 행렬
a = np.array([[[1,2,3], [4,5,6]],
[[1,2,3], [4,5,6]],
[[1,2,3], [4,5,6]]])
print(f"배열의 구조: {a.shape}")
print(f"배열의 차원: {a.ndim}")
# 4차원 행렬
a = np.array([[[[1,2,3],[4,5,6]],
[[1,2,3],[4,5,6]],
[[1,2,3],[4,5,6]]],
[[[1,2,3],[4,5,6]],
[[1,2,3],[4,5,6]],
[[1,2,3],[4,5,6]]]])
print(f"배열의 구조: {a.shape}")
print(f"배열의 차원: {a.ndim}")
1-2. 배열 초기화
# 모든 요소가 0인 배열 생성
np.zeros((3,4)) #2차원
np.zeros((2,3,4),dtype=np.int64) #3차원
# 모든 요소가 1인 배열 생성
np.ones((5, 6))
5행, 6열로 모든 요소가 1인 걸 생성한다.
# (원소의 값이) 초기화 되지 않은 배열 생성
np.empty((2,3))
쓰레기 값이라고 지칭하기도 한다.
# 주어진 값으로 채운 배열
np.full((3,3), 7)
full(행, 열, 주어진 값) 구조다.
# 단위 행렬
np.eye(3, 3)
np.eye(3,5,1)
eye (n,n)은 n,n 크기 단위로 생성
eye (n,n,n)은 n,n크기로 생성하고 n 방향으로 대각선 이동한다.
3행 5열 1인덱스부터 1실행
구체적인 설명
# arange : range()와 유사한 기능 제공
# 시작 이상 끝 미만의 정수 배열을 지정한 간격으로 생성
np.arange(0, 10)
np.arange(0, 10, 2)
np .arange( 0 , 10 , 2 ) 결과
# linspace : 시작~끝까지 균일 간격으로 지정한 개수만큼 숫자를 생성
# 끝을 포함
np.linspace(10, 100, 10)
np.linspace(0.1, 1, 10)
끝 포함이므로 1도 들어간다.
# random.rand(m,n) : 0 ~ 1 사이의 난수로 초기화
np.random.rand(2, 3)
# random.randn(m, n) : 표준정규분포를 따르는 난수로 초기화
# 표준정규분포 : 평균 0, 분산 1인 정규분포
np.random.randn(2, 4, 5)
# random.randint(low, high, (size))
np.random.randint(10, 20, (2,4))
low 이상 high 미만의 숫자로 size 형태의 정수 배열을 생성
# random.seed() : 난수 생성시 시작값 제공
np.random.seed(42)
np.random.randn(2,3)
시작값을 동일하게 주면 항상 같은 결과를 얻을 수 있다고 하는데... seed()를 사실 잘 안 써봤다.
seed가 전역 상태(글로벌 스테이터스) 를 사용해서 코드 전반에 예측 어려웠기에
RNG로 독립적인 난수 시퀸스를 생성한다고 한다.
# RNG(Random Numbwer Genenrator) : 최근 Numpy 사용에서 권장되는 방식
from numpy.random import default_rng
rng = default_rng(seed=42)
rng2 = default_rng(seed=10)
print(rng.random((3,2)))
print(rng2.random((3,2)))
이하 이를 기반으로 해본 실습들
더보기
# 실습1. 배열 초기화 및 생성(1)
# 1. 0으로 채워진 크기 (3, 4) 배열을 생성한 후, 모든 값을 5로 채우는 새로운 배열을 만드세요.
a1 = np.zeros((3,4))
a1_2 = np.full((3,4), 5)
print("문제1:", a1, a1_2, a1 + a1_2, sep="\n")
# 2. 0부터 20까지 2씩 증가하는 1차원 배열을 생성하세요.
a2 = np.arange(0,21,2)
print("\n문제2:", a2)
# 3. 0~1 사이의 실수 난수를 가지는 (2, 3) 크기의 배열을 생성하세요.
a3 = np.random.rand(2,3)
# print("\n문제3:", a3)
# 4. 평균이 100, 표준편차가 20인 정규분포 난수 6개를 생성하세요.
# (평균, 표준편차, 개수)
a4 = np.random.normal(100, 20, 6)
# print("\n문제4:", a4)
# 5. 1부터 20까지의 정수를 포함하는 1차원 배열을 만들고, 이 배열을 (4, 5) 크기의 2차원 배열로 변환하세요.
a5 = np.arange(1,21).reshape(4,5)
# print("\n문제5:", a5)
# 6. 0부터 1까지 균등 간격으로 나눈 12개의 값을 가지는 배열을 생성하고, 이를 (3, 4) 크기로 변환하세요.
a6 = np.linspace(0, 1, 12).reshape(3,4)
# print("\n문제6:", a6)
# 7. 0~99 사이의 정수 난수로 이루어진 (10, 10) 배열을 생성한 뒤,
# np.eye()로 만든 단위 행렬을 더하여 대각선 요소가 1씩 증가된 배열을 만드세요.
a7 = np.random.randint(0, 100, (10, 10))
a7_eye = np.eye(10)
a7_added = a7 + a7_eye
print("\n문제7:", a7_added)
# 8. 0~9 사이의 정수 난수로 이루어진 (2, 3, 4) 3차원 배열을 생성하세요.
a8 = np.random.randint(0, 10, (2, 3, 4))
print("\n문제8:", a8)
- 다차원 배열을 다루는 편의 기능 제공
- Python의 시퀀스보다 빠름
- 인덱싱과 슬라이싱의 기본적인 문법은 동일하다.
# 인덱싱
a = np.array([10,20,30,40,50])
print(a[2])
print(a[-1])
결과값은 순서대로
30
50
# 다차원 인덱싱
# 파이썬 리스트
matrix = [[1,2,4], [4,5,6]]
print("파이썬 인덱싱:", matrix[1][1])
파이썬은 중첩 리스트로 표현해서 각 차원 따로 접근함.
[
[1, 2, 4],
[4, 5, 6]
]
이런 모양으로. 있어서 1은 두번째 리스트 [4,5,6]고 1이면 두 번째 요소 선택이니 5가 된다.
바깥 리스트 → 안쪽 리스트 → 원소 순서로 차례차례 들어가는 방식
# numpy 배열
a2 = np.array([[1,2,4], [4,5,6]])
print("numpy 인덱싱:", a2[1,1])
# 3차원 배열 인덱싱
a3 = np.arange(24).reshape(2,3,4)
print(a3)
print("3차원 인덱싱:", a3[1,1,1])
[행,열] 형식으로 접근하기 때문에
a3[1,1,1]은 두 번째 블록
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
에서 그 안에 있는 두 번째 행 선택
[16 17 18 19]
그리고 그 행 안에서 두 번째 열 선택이므로
17이 된다.
3차원이면 차원 하나 더 감싸기 때문에 1이면 3차원을 지칭한다.
a3[0,0,0] 이면 첫 번째 블록, 첫 번째 행, 첫 번째 열이니 0 되듯이.
# 슬라이싱
# arr[행_슬라이스, 열_슬라이스]
# arr[..., 4차원 슬라이스, 3차원 슬라이스, 2차원, 1차원]
a1 = np.array([10,20,30,40,50])
print(a1[1:3])
print(a1[2:])
print(a1[:2])
print(a1[:])
print(a1[::2])
print(a1[::-1])
순서대로
1인덱스~3인덱스 사이로
2인덱스 이후 값으로
2인덱스 이전 값으로
전부 호출
2 배수 인덱스만 호출 0 / 2 / 4
역순 호출
# 파이썬 리스트와의 차이
# 파이썬 리스트
py_list = [10,20,30,40,50]
sliced = py_list[1:4]
sliced[1] = 100
print("py원본:", py_list)
print("py슬라이싱:", sliced)
print()
# numpy 배열
a1 = np.array([10, 20, 30, 40, 50])
a1_sliced = a1[1:4]
a1_sliced[1] = 100
print("numpy원본:", a1)
print("numpy슬라이싱:", a1_sliced)
numpy는 a1_sliced한 게 적용되어 원본이 바뀌었다.
이하 행,열 슬라이싱 예제들
더보기
# 2차원 배열 슬라이싱
a2 = np.arange(1,21).reshape(4,5)
print(a2)
# 행 슬라이싱
# print(a2[0])
# print(a2[1])
# print(a2[1:3])
# print(a2[2:])
# print()
# 열 슬라이싱
print(a2[:, 2])
print(a2[:, -1])
print(a2[:, 1:3])
print()
# 행과 열 슬라이싱
print(a2[1:3, 2:4])
print(a2[2: , 3: ])
print(a2[::2, ::2])
# 3차원 슬라이싱
a3 = np.arange(36).reshape(3, 3, 4)
print(a3)
print(a3[1, 1, 1:3])
왜 그렇게 되는지에 대한 설명.
# 얕은 복사 : 복사본이 원본과 메모리를 공유 -> 변경사항이 서로에게 영향을 줌
a1 = np.array([1,2,3])
a1_viewd = a1.view()
a1_viewd[1] = 10
print("원본", a1)
print("복사본", a1_viewd)
print()
# 깊은 복사 : 복사본이 원본과 독립적으로 복사됨 -> 서로 영향을 주지 X
a2 = np.array([1,2,3])
a2_copied = a2.copy()
a2_copied[1] = 10
print("원본2", a2)
print("복사본2", a2_copied)
요소 선택
# Fancy Indexing
# 정수 배열을 사용하여 여러 인덱스로 여러 요소를 한번에 선택
af = np.arange(1,21)
print(af)
print(af[[4, 7, 11]])
af2 = np.arange(1,21).reshape(4,5)
print(af2)
print(af2[[1,3] , [2,4]])
# Boolean Indexing
ab = np.linspace(10, 100, 10)
print(ab)
print(ab[ab > 40])
print()
# Boolean masking
ab2 = np.arange(0, 21)
print(ab2)
mask = ab2 % 2 == 0
print(mask)
print(ab2[mask])
조건식을 사용하여 요소 선택
조건을 변수로 처리하여 인덱싱 가능 ( Boolean Masking)
이하 배운 것들로 하는 실습 예제들
더보기
# 실습2
# 1. 다음 배열에서 2, 4, 6번째 요소를 Fancy Indexing으로 선택하세요.
arr = np.arange(10, 30, 2)
print(arr)
answer = arr[[1,3,5]]
print(answer)
[10 12 14 16 18 20 22 24 26 28]
[12 16 20]
# 2. 3x3 배열에서 왼쪽 위 → 오른쪽 아래 대각선의 요소만 인덱싱으로 추출하세요.
arr = np.arange(1, 10).reshape(3, 3)
print(arr)
arr[[0,1,2],[0,1,2]]
[[1 2 3]
[4 5 6]
[7 8 9]]
array([1, 5, 9])
# 3. 3x4 배열에서 마지막 열만 선택해 모두 -1로 변경하세요.
arr = np.arange(1, 13).reshape(3, 4)
print(arr)
arr[:, -1] = -1
arr
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
array([[ 1, 2, 3, -1],
[ 5, 6, 7, -1],
[ 9, 10, 11, -1]])
# 4. 4x4 배열에서 행을 역순, 열을 역순으로 각각 슬라이싱해 출력하세요.
arr = np.arange(1, 17).reshape(4, 4)
print(arr)
print("행 역순:", arr[::-1])
print("열 역순:", arr[:,::-1])
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]]
행 역순: [[13 14 15 16]
[ 9 10 11 12]
[ 5 6 7 8]
[ 1 2 3 4]]
열 역순: [[ 4 3 2 1]
[ 8 7 6 5]
[12 11 10 9]
[16 15 14 13]]
# 5. 4x5 배열에서 가운데 2x3 부분을 슬라이싱한 뒤 copy()를 이용해 독립 배열을 만드세요.
arr = np.arange(1, 21).reshape(4, 5)
print(arr)
arr_copied = arr[1:3, 1:4].copy()
print(arr_copied)
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]]
[[ 7 8 9]
[12 13 14]]
# 6. 3x4 배열에서 짝수이면서 10 이상인 값만 선택하세요.
arr = np.array([[ 4, 9, 12, 7],
[10, 15, 18, 3],
[ 2, 14, 6, 20]])
print(arr[(arr % 2 == 0) & (arr >= 10)])
[12 10 18 14 20]
# 7. 5x5 배열에서 2, 4번째 행을 선택하고, 선택된 행에서 열 순서를 [4, 0, 2]로 재배치하세요.
arr = np.arange(1, 26).reshape(5, 5)
print(arr)
result = arr[[1,3]]
print(result)
result[:, [4, 0, 2]]
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]
[21 22 23 24 25]]
[[ 6 7 8 9 10]
[16 17 18 19 20]]
array([[10, 6, 8],
[20, 16, 18]])
# 8. 5x3 배열에서 각 행의 첫 번째 값이 50 이상인 행만 Boolean Indexing으로 선택하세요.
arr = np.array([[10, 20, 30],
[55, 65, 75],
[40, 45, 50],
[70, 80, 90],
[15, 25, 35]])
arr[arr[: ,0] >= 50 ]
array([[55, 65, 75],
[70, 80, 90]])
# 9. 4x4 배열에서 (0,1), (1,3), (2,0), (3,2) 위치의 요소를 한 번에 선택하세요.
arr = np.arange(1, 17).reshape(4, 4)
arr[[0,1,2,3],[1,3,0,2]]
array([ 2, 8, 9, 15])
# 10. 3차원 배열 (2, 3, 4)에서 모든 블록에서 두 번째 열만 추출해 새로운 2차원 배열 (2, 3)을 만드세요.
arr3d = np.arange(24).reshape(2, 3, 4)
print(arr3d)
arr3d[:, :, 1]
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
array([[ 1, 5, 9],
[13, 17, 21]])