2026.01.20
2025.12.30 - 2026.01.08 Python
2026.01.09 - 2026.01.13 MySQL
2026.01.14 - 2026.01.15 Web Crawling
2026.01.16 - 2026.01.19 1차 단위 프로젝트 관련 준비 및 발표 (2days)
2026.01.20 - 2026.01.23 Data Analysis
데이터란, 존재하는 어떠한 측정된 값을 말한다.
관계형 데이터 베이스, 스프레드시트 파일 (Excel, CSV 등) 처럼 형태가 정해져 있다.
정형 데이터 외 모든 데이터, 즉 고정된 구조가 없는 데이터로, 다양한 형태와 형식을 가진다. 텍스트, 이미지 동영상, 오디오, LLM, 멀티모달 등..
데이터 분석은 단순히 숫자를 계산하는 것이 아니라, 데이터라는 원석에서 '의미있는 가치(insight)'를 캐내는 모든 과정을 말한다.
: 데이터 분석과 데이터 마이닝 프로젝트를 체계적으로 수행하기 위해 널리 사용되는 표준 프로세스로, CRISP-DM은 6단계로 구성되어 있으며, 각 단계는 순차적이면서도 반복적인 특성을 가진다. 또한 CRISP-DM은 어떤 산업이나 문제 유형에도 적용 가능한 유연성을 가지며 명확한 가이드를 제공하므로 협업에 유리하다.
1. 비즈니스 이해 (Business Understanding): "이 분석을 왜 하는가?"에 대한 비즈니스 문제를 정의하고 목적 설정 및 가설 수립. (프로젝트의 목표와 요구사항을 명확히 정의)
2. 데이터 이해 (Data Understanding): 도메인에 대한 이해, 데이터를 수집하고 초기 상태를 살펴봄 (EDA) (프로젝트에서 사용할 데이터를 이해)
1. 데이터 수집
2. 데이터의 구조적인 특성에 대한 이해
3. 이상치와 누락값 파악
3. 데이터 준비 (Data Preparation): Data Preprocessing 단계 (데이터 정제 & 전처리), 분석에 적합한 형태로 데이터를 가공
1. 데이터를 정리하고 변환
2. 필요한 데이터를 선택하고 통합
3. 분석을 위한 데이터셋을 생성
4. 모델링 (Modeling): 데이터에 적합한 모델(알고리즘)을 선택하고 학습시킨다. (LogisticRegression 등)
1. 모델링 기법 선택
2. 데이터를 모델에 적합하게 변환
3. 모델을 학습시키고 평가
5. 평가 (Evaluation): 이 모델이 잘 학습이 되었는지(비즈니스 목적에 맞는지) 실질적 성능(정확도 등)을 검증하는 단계 (모델의 성능과 비즈니스 목표 적합성을 평가)
1. 모델의 정확도와 성능 검증
2. 모델이 비즈니스 목표에 부합하는지 확인
3. 분석 결과를 검토하고 다음 단계 결정
6. 배포 (Deployment): 분석 결과를 비즈니스 환경에 적용
1. 분석 마친 결과를 시각화하거나 보고서로 작성
2. 모델을 시스템에 통합
3. 프로젝트의 최종 성과룰 공유하고 운영에 반영
Python 이라는 프로그래밍 언어를 배웠으므로 아래의 라이브러리를 설치하여 사용.
NumPy는 수학적 연산을 빠르고 효율적으로 수행할 수 있도록 설계되어 있어, 데이터 분석, 머신러닝, 인공지능, 이미지 처리, 신호 처리, 통계 분석 등 여러 분야에서 활용된다.
NumPy 설치방법:
Anaconda Prompt에서 아래 명령어 입력을 실행
conda create -n da_env python=3.12 + enter
conda activate da_env
pip install jupyter notebook ipykernel
프롬프트에서 위 가상환경 설치 이후 VS code 셀에서 Data_analysis 신규폴더 및 NumPy 주피터노트북 파일 생성함.
NumPy 설치 명령어
!pip install numpy
를 실행시키면 되고,
이후 코드셀에서 사용할 시
import numpy as np
라고 치고 불러오기하면 됨.
FYI - NumPy든 Pandas든..라이브러리 최초 설치 이후에는 #을 붙여서 주석처리해주기
Python 리스트의 데이터 형식은 혼합(int, str, float, bool)이 가능하지만,
NumPy 배열: 무조건 똑같은 형식의 데이터 (동일한 자료형)만 담아야 함.
e.g) 전부 정수
[10, 20, 30, 40]
만약 숫자와 문자를 섞어 넣으면, NumPy는 강제로 모든 숫자를 문자로 바꿔버림.
예를 들어 [1, "사탕"]을 넣으면 ["1", "사탕"]으로 숫자 1이 문자화됨.
pros: 모든 데이터의 크기와 형식이 같기 대문에 컴퓨터가 한꺼번에 초고속으로 계산이 가능. 머신러닝이나 대용량 데이터분석 (대량의 숫자계산)에 사용되어짐.
cons: 제한적 (동일한 자료형/한 종류만 가능)
NumPy 배열
속도: 빠름
메모리 사용: 덜 사용
데이터 형식: 동일한 자료형
연산 방식: 벡터화된 연산 지원
Python의 리스트와 유사하나, 고정된 크기와 단일 데이터 타입을 가진다. 배열의 각 차원을 축(axis)이라고 하며, 각 축의 크기는 배열의 모양(shape).
import numpy as np
arr = np.array([1, 2, 3, 4])
print(arr) # [1 2 3 4]
np.zeros())arr = np.zeros((2, 3)) # 2x3 배열
print(arr)
# [[0. 0. 0.]
# [0. 0. 0.]]
np.ones())arr = np.ones((3, 2)) # 3x2 배열
print(arr)
# [[1. 1.]
# [1. 1.]
# [1. 1.]]
np.arange())arr = np.arange(0, 10, 2) # 0부터 9까지 2씩 증가하는 값
print(arr)
# [0 2 4 6 8]
np.linspace())arr = np.linspace(0, 1, 5) # 0에서 1까지 5개의 등간격 값
print(arr)
# [0. 0.25 0.5 0.75 1. ]
np.random.randn())arr = np.random.randn(2, 3) # 2x3 배열
print(arr)
# [[-0.16437183 -0.80572477 -0.59256217]
# [ 0.46449378 1.13526154 0.04380413]]
print(arr.shape)
print(arr.ndim)
print(arr.size)
print(arr.dtype)
arr = np.array([2026, 1, 20, 11, 21]) # -> 1차원 형태
print(arr.shape)
# 형태 : 튜플형태 -> 배열의 형태를 나타냄. 현재 데이터가 5개 들어있는 1차원 배열이라는 뜻
# (5,) 쉼표뒤가 비어있는 이유는 2차원이 아닌 1차원임을 명시하기 위한 파이썬 튜플의 표기법임.
print(arr.ndim)
# 깊이 (차원) -> 차원의 수(Number of Dimensions). 대괄호[ ] 가 한겹이므로 1차원 배열(벡터)임을 의미
print(arr.size)
# 크기 - 전체 요소의 개수 -> 배열 안에 총 5개의 숫자가 들어있으므로 5가 출력됨.
print(arr.dtype)
# 요소의 자료형 -> int64 또는 운영체제에 따라 int32
import numpy as np
# python 리스트를 활용한 ndarray 생성
today_arr = [2026, 1, 20, 11, 21]
arr = np.array([2026, 1, 20, 11, 21])
print(today_arr, type(today_arr))
print(arr, type(arr))
# [2026, 1, 20, 11, 21] <class 'list'>
# [2026 1 20 11 21] <class 'numpy.ndarray'>
# python 튜플을 활용한 ndarray 생성
np.array((1, 2, 3, 4, 5))
print(arr, type(arr))
arr_2d = np.array([[1, 2, 3], [4, 5, 6]]) # -> 2차원배열(Matrix, 행렬)
# 1차원 배열과 차이점: 행(row)과 열(column)의 개념이 생긴다는 점.
print(arr_2d)
print([[1, 2, 3], [4, 5, 6]]) # [[1, 2, 3], [4, 5, 6]] (파이썬 기본 리스트 형태)
print(arr_2d.shape) # (2, 3) 1차원에 들어있는게 2개 ([1,2,3], [4,5,6]), 2차원에 들어있는게 3개 -> 2개의 행과 3개의 열로 이루어진 구조
print(arr_2d.ndim) # 2 - 가로와 세로가 있는 2차원 배열임을 의미
print(arr_2d.size) # 6 - 전체 요소의 개수
print(arr_2d.dtype) # int64 (숫자만 있으니까) 64 = byte / 배열안의 값이 모두 정수이므로 정수형으로 저장
arr_int = np.array([2026, 1, 20])
arr_float = np.array([1.234, 3.14, 9.876, 10]) # ndarray는 하나의 타입만 저장 가능하기 때문에 정수 10도 마찬가지로 float로 저장됨 (정수와 실수가 섞여있으면 데이터손실을 막기 위해 더 포괄적인 범위인 실수형(float)으로 자동변환됨)
arr_bool = np.array([True, False, False, True])
arr_str = np.array(['hello', 'world', 'python-is-nice', 'numpy'])
print(arr_int.dtype) # int64 (int64, int32) / 64=8byte - 64비트 정수형. 모든 요소가 정수일때 부여
print(arr_float.dtype) # float64 (float64, floast32) - 64비트 실수형. 정수 10이 섞여있지만 다른 값들이 실수이므로 numpy는 모든 데이터를 실수로 통일하여 저장함
print(arr_bool.dtype) # bool - 불리언 (논리형)
print(arr_str.dtype) # <U14 : 유니코드 14글자까지 저장 가능한 문자열 - 배열 내 가장 긴 문자열(python-is-nice)의 글자수 의미
# 형 변환
arr = np.array([1.234, 5.678, 9, 10])
# (1)dtype 인자를 사용한 생성 시 형변환
# ndarray 생성할 때 dtype을 지정
arr = np.array([1.234, 5.678, 9, 10], dtype=int) # 시퀀스 자료 옆에 dtype 키워드인자로 타입 지정을 하면 형변환 가능
# (2) astype() 메서드를 이용한 사후 형변환
# 생성된 ndarray의 astype() 메서드를 이용해 dtype 변환
arr = arr.astype(int)
print(arr, arr.dtype)
# 소수점 아래자리가 반올림 아닌 절삭(truncation)처리되어 저장.
Python list 와 ndarray의 차이
my_list = [2026, 1, 20, '화요일', True]
print(my_list)
my_list = [[1, 2, 3], [4, 5], [6]]
print(my_list)
print(len(my_list)) # length 나
print(len(my_list[0])) # index 로도 접근 가능
# ndarray는 동일한 자료형만 저장 가능
my_ndarr = np.array([2026, 1, 20, '화요일', True])
print(my_ndarr)
# ndarry는 고정 길이를 가짐
# my_ndarr = np.array([[1, 2, 3], [4, 5], [6]])
print(my_ndarr)
print(my_ndarr.shape)
print(my_ndarr.ndim)
print(my_ndarr.size)
arr = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
print(arr)
# zeros(): 0으로 초기화된 ndarray 생성
zeros_arr = np.zeros((3, 3)) # 인자로 튜플을 넘겨줌
print(zeros_arr)
# ones(): 1로 초기화된 ndarray 생성
ones_arr = np.ones((5, 2))
print(ones_arr)
# full(): 지정한 임의의 값으로 초기화된 ndarray 생성
full_arr = np.full((4, 1), 7) # 4행1열의 2차원 배열
print(full_arr)
full_arr = np.full((4,), 7) #요소 4개의 1차원 배열
print(full_arr)
origin_arr = np.array([[10, 20], [30, 40]])
print(origin_arr.shape)
# zero_like(): 0으로 초기화된 원본 배열의 구조와 동일한 구조의 ndarray
print(np.zeros_like(origin_arr))
# ones_like(): 1으로 초기화된 원본 배열의 구조와 동일한 구조의 ndarray
print(np.ones_like(origin_arr))
# full_like(): 임의의 값으로 초기화된 원본 배열의 구조와 동일한 구조의 ndarray
print(np.full_like(origin_arr, 7))
np.arange(start, end, step) - 간격을 기준으로 생성/실수(소수점)간격 사용할수있고 배열을 반환print(np.arange(10))
print(np.arange(1, 10))
print(np.arange(1, 10, .1))
np.linspace(start, end, num) - 개수를 기준으로 생성. 시작과 끝 범위를 정해두고 그 사이를 등간격으로 몇개를 채울지 결정# print(np.linspace(10)) # 하나의 인자 가지고만으로는 쓸 수 없다.
print(np.linspace(1, 10))
# 1부터 10까지의 범위를 기본값인 50개의 숫자로 균등하게 나눔. num 매개변수(생성할 숫자의 개수)를 지정하지 않으면 기본적으로 50개의 숫자가 생성됨 ->> 1.0으로 시작하여 10.0 으로 끝나는 50개의 실수 출력됨
print(np.linspace(1, 10, 5))
# 1부터 10까지의 범위를 정확히 5개의 숫자로 균등하게 나눔 / (끝값 - 시작값) / (개수 -1) 간격으로 숫자 생성 ->> 1부터 시작해 2.25씩 더해진 5개의 숫자 출력
# * 1부터 10까지의 전체 거리(10 - 1 = 9)를 4개의 구간으로 나눕니다.
# ($5-1=4$)한 칸의 간격은 $9 % 4 = 2.25가 됩니다.
# 따라서 1, 1+2.25(3.25), 3.25+2.25(5.5), 5.5+2.25(7.75), 7.75+2.25(10) 순으로 5개가 생성됩니다.
np.logspace(start_exp, end_exp, num, base)print(np.logspace(1, 3, 4))
# 첫 번째 인자 (1): 시작값의 지수입니다. (즉, 10의 1제곱 = 10)
# 두 번째 인자 (3): 끝값의 지수입니다. (즉, 10의 3제곱 = 1000)
# 세 번째 인자 (4): 생성할 숫자의 개수 (그 사이를 로그상에서 동일한 간격으로 4개 뽑음)
linspace: 더하기 간격
logspace: 곱하기 간격 (배수)
np.linspace
값 사이의 차이가 일정함 (덧셈 기준) - 일반적인 수치 계산이나 그래프 그리기에 사용됨
np.logspace
값 사이의 비율이 일정함 (곱셈/지수 기준) - 주파수 분석, 기하급수적 성장 모델링 용도로 사용됨
NumPy배열은 0부터 시작하는 인덱스를 사용하며, 다차원 배열에서도 각 차원별로 인덱스 지정할 수 있음
기본 인덱싱
arr = np.arange(1, 11)
print(arr) # [1 2 3 4 5 6 7 8 9 10]
print(arr[6]) # 7
print(arr[-4]) # 7
arr_2d = np.array([[10, 20, 30], [40, 50, 60]])
print(arr_2d.shape) # (2, 3)
print(arr_2d[0]) # [10 20 30] -> 0번째 행 통째로 가져옴
print(arr_2d[0][2]) # 30 -> 인덱스 0의 2번째 요소 / 특정요소접근 (리스트방식)
print(arr_2d[0, 2]) # 30 -> [행 인덱스, 열 인덱스] 특정요소접근(Numpy방식)
print(arr_2d[(0, 2)]) # 30 -> 특정요소접근, 좌표를 튜플형태로 전달
arr_2d = np.array([[11, 22, 33], [44, 55, 66], [77, 88, 99]])
print(arr_2d[1, 0]) # 44 -> 1번째 행, 0번째 열의 요소 출력
print(arr_2d[2, 1]) # 88 -> 2번째 행, 1번째 열의 요소 출력
print(arr_2d[-1, -2]) # 88 (음수 인덱스 활용)
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(arr[1, 0, 2]) # 1번째 차원, 0번째 행, 2번째 열의 요소 출력 (9)
기본 슬라이싱
arr = np.arange(1, 11)
print(arr) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(arr[2:7]) # [3 4 5 6 7] 인덱스 2부터 6까지
print(arr[::2]) # [1 3 5 7 9] 처음부터 끝까지 2칸 간격으로
print(arr[4:]) # [5 6 7 8 9 10] 인덱스 4부터 끝까지
print(arr[:-3]) # [1 2 3 4 5 6 7] 처음부터 뒤에서 3번째 직전까지
print(arr[::-1]) # [10 9 8 7 6 5 4 3 2 1] 역순으로 출력
arr_2d = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])
print(arr_2d)
[[10 20 30]
[40 50 60]
[70 80 90]]
print(arr_2d[0:2, 1:2]) # [행 슬라이싱, 열 슬라이싱]
[[20]
[50]]
print(arr_2d[0:2, :]) # 특정 행 범위의 모든 열 선택 / 설명: 행은 0, 1번 / 열은 ':'(전체) 선택
[[10 20 30]
[40 50 60]]
print(arr_2d[0:2]) # 행 슬라이싱만 쓰면 열 전체를 슬라이싱. 콤마 뒤를 생략하면 자동으로 '모든 열'을 가져옴.
[[10 20 30]
[40 50 60]]
print(arr_2d[:, 1:]) # 행 전체를 슬라이싱하고 싶으면 :를 써야함 / 모든 행의 특정 열 범위 선택
# 설명: 행은 ':'(전체) / 열은 1번부터 끝까지 선택
[[20 30]
[50 60]
[80 90]]
print(arr[:, 1, :2]) # 각 차원에서 1번째 행의 처음 두 열 출력
**슬라이싱 = shape 유지 (차원 유지)
print(arr_2d[:-1, 0:1]) # 마지막 행을 제외한 0,1번 행 선택, 0번부터 1번 열 직전까지의 범위 선택 (즉 0번열만)
[[10]
[40]]
print(arr_2d[:-1, 0:1].shape) # (2, 1) -> 2행1열 (2차원)
**인덱스 접근 = 인덱싱 -> 값을 꺼내서 반환 (차원 제거)
print(arr_2d[:-1, 0]) # 마지막 행을 제외한 0,1번 행 선택, 0번 열을 특정 인덱스로 지목
[10 40]
print(arr_2d[:-1, 0].shape) # (2,) -> (1차원)
arr = np.arange(5, 31, 5) # 5부터 30까지 5씩 증가하는 배열 생성하라는 뜻
print(arr) # [5 10 15 20 25 30]
indices = [1, 3, 5] # 1, 3, 5번째 인덱스 요소를 리스트로 정의
print(arr[indices]) # [10 20 30] <- 리스트 변수를 넣어 인덱싱
print(arr[[1, 3, 5]]) # [10 20 30] <- 리스트를 직접 대괄호안에 넣어 인덱싱
arr_2d = np.array([
[5,10,15,20],
[25,30,35,40],
[45,40,55,60]
])
indices1 = [0,1]
indices2 = [1,2]
print(arr_2d[indices1, indices2]) # [[행 인덱스1, 행 인덱스2, ...], [열 인덱스1, 열 인덱스2, ...]]
print(arr_2d[[0,1], [1,2]]) # 요소의 개수 같아야 함.
# [10 35]
조건을 만족하는 배열의 요소를 선택하는 방법
arr = np.arange(1, 6)
bools = [True, False, False, False, True]
print(arr[bools]) # [1 5]
arr_2d = np.array([
[3, 6, 9],
[12, 15, 18],
[21, 24, 27]
])
print(arr_2d[arr_2d > 10]) # [12 15 18 21 24 27] -> 조건식 -> boolean 배열 반환
print(arr_2d > 10)
# [[False False False]
# [ True True True]
# [ True True True]]
print(arr_2d[arr_2d % 2 == 0]) # [6 12 18 24] -> 짝수만 출력
[참고] np.all() & np.any()
arr = np.array([10, -20, -30, -40, -50])
result_arr = arr[arr > 0]
if arr.size == result_arr.size:
print('모든 수는 양수입니다.')
else:
print('모든 수가 양수는 아닙니다')
# 모든 수가 양수는 아닙니다
arr = np.array([10, -20, -30, -40, -50])
if np.all(arr > 0):
print('모든 수는 양수입니다.')
else:
print('모든 수가 양수는 아닙니다')
# print(np.any(arr > 0))
np.all(arr > 0) # np.True_ or np.False
# 모든 수가 양수는 아닙니다
# np.False_
arr = np.array([-10, -20, -30, -40, -50])
if np.any(arr > 0):
print('양수가 포함되어 있습니다.')
else:
print('모든 수는 음수입니다')
np.any(arr > 0) # np.True_ or np.False_
# 모든 수는 음수입니다
# np.False_
기본 연산
arr_a = np.arange(1, 10).reshape(3, 3)
arr_b = np.full_like(arr_a, 7)
print(arr_a)
# [[1 2 3]
# [4 5 6]
# [7 8 9]]
print(arr_b)
# [[7 7 7]
# [7 7 7]
# [7 7 7]]
# 덧셈
print(arr_a + arr_b)
print(np.add(arr_a, arr_b))
# 뺄셈
print(arr_a - arr_b)
print(np.subtract(arr_a, arr_b))
# 곱셈
print(arr_a * arr_b)
print(np.multiply(arr_a, arr_b))
# 나눗셈
print(arr_a / arr_b)
print(np.divide(arr_a, arr_b))
특수연산
# 거듭제곱
print(arr_a ** arr_b)
print(np.power(arr_a, arr_b))
# 제곱을 구해줄 수 있는 함수
print(np.square(arr_a))
# 제곱근: square root - 어떤 수를 제곱했을 때 원래의 수가 되게 하는 수
print(np.sqrt(arr_a))
# 몫 연산
print(arr_a // arr_b)
print(np.floor_divide(arr_a, arr_b))
# 나머지 연산
print(arr_a % arr_b)
print(np.mod(arr_a, arr_b))
NumPy는 배열의 각 요소에 대해 집계(aggregation) 연산을 수행할 수 있는 다양한 함수를 제공함.
arr_negative = np.array([[-1, -2], [-100, -200]])
arr_float = np.array([[1.234, 5.678], [3.14, 2.81]])
arr = np.arange(1, 7).reshape(2, 3)
# 앞서 만든 6개의 요소를 가진 1차원 배열을 2행 3열의 2차원 배열로 모양을 재구성(reshape)하란 뜻
print(np.arange(1, 7)) # np.arange(1, 7) 출력
print(arr) # arr (reshape 후) 출력
# 절대값 (Absolute Value)
# np.abs(): 배열의 각 요소에 저장된 숫자의 부호를 제거하고 양수로 변환 (np.absolute()와 동일)
print(np.abs(arr_negative))
# 올림
# np.ceil(): 배열의 각 요소를 소수점 첫째 자리에서 올림
print(np.ceil(arr_float))
# 반올림
print(np.round(arr_float))
print(np.round(arr_float, 2))
# 두번째 인자인 2는 소수점 둘째자리까지 남기겠다는 의미. 즉 소수점 셋째자리에서 반올림 일어남
arr_float = np.array ([[1.234, -5.678], [-7.89, 10.123]])
print(np.trunc(arr_float)) # 버림 (Truncation, 절삭)-> 소수점 아래 숫자 삭제
print(np.floor(arr_float)) # 내림 (지정된 수보다 작은 최대 정수) -> 값보다 작거나 같은 가장 큰 정수
# 최대값 / 최소값 (max/min)
print(arr)
print(np.max(arr))
print(np.min(arr))
print(np.max(arr, axis=0)) # 행 기준 (행을 고정하고, 같은 열에서 가장 큰 값)
print(np.min(arr, axis=1)) # 열 기준 (열을 고정하고, 같은 행에서 가장 작은 값)
# 합계 (sum)
print(np.sum(arr))
print(arr.sum())
print(arr.sum(axis=0)) # 행 기준 (행 방향 총합 덧셈)
print(arr.sum(axis=1)) # 열 기준 (열 방향 총합 덧셈)
# 평균 (mean)
print(np.mean(arr))
print(arr.mean()) # axis = None
print(arr.mean(axis=0))
print(arr.mean(axis=1))
# 표준편차/분산 (std/var)
print(arr.std()) # 표준편차
print(arr.var()) # 분산
# 누계 (cumsum)
print(arr.cumsum())
축(axis)을 기준으로 한 연산
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d.sum(axis=0)) # [5, 7, 9] (각 열의 합)
print(arr2d.sum(axis=1)) # [6, 15] (각 행의 합)
서로 다른 크기의 배열 간 연산을 가능하게 하는 기능 -> 작은 배열을 큰 배열의 크기에 맞춰서 연산을 수행할 수 있게 함
arr = np.array([1, 2, 3])
print(arr + 10) # [11, 12, 13]
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
arr1d = np.array([1, 2, 3])
print(arr2d + arr1d)
# [[2, 4, 6],
# [5, 7, 9]]
arr3d = np.random.rand(3, 1, 4) # 3x1x4 배열
arr2d = np.random.rand(4, 4) # 4x4 배열
# 브로드캐스팅을 통해 3차원 배열과 2차원 배열 간 연산 수행
result = arr3d + arr2d
print(result.shape) # 결과: 3x4x4 배열 (3, 4, 4)
arr_a = np.arange(1, 10).reshape(3, 3) # (3, 3)
arr_b = np.full_like(arr_a, 5) # (3, 3) -> arr_a와 똑같은 (3, 3)모양에 5를 채운다
arr_b = np.array([5, 5, 5]) # (3,) 모양의 1차원 배열. 곱셈 시 각 생에 [5, 5, 5]가 반복되어 적용
arr_b = np.array([5]) # (1,) -> 요소가 하나인 1차원 배열. 모든 요소에 5가 곱해짐
arr_b = 5 # 스칼라값: 단순 숫자
result = arr_a * arr_b
# 요소별 곱셈 (element-wise multiplication), arr_b가 어떤 형태의 '5'든 상관없이 arr_a 내부의 모든 숫자 9개에 각각 5를 곱한 결과를 반환
print(result)
print(result.shape)
arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
arr2 = np.array([5, 10, 100]) # shape: (3,) -> (2, 3)
print(arr1 * arr2)
arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
arr2 = np.array([[100], [200]]) # (2, 1)
print(arr1 * arr2)
arr1 = np.array([1, 2, 3]) # (3,)
arr2 = np.array([[4], [5], [6]]) #(3, 1)
print(arr1 * arr2)
arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
arr2 = np.array([1, 2]) #(2,)
# print(arr1 * arr2) # ValueError
내적연산: 두 배열의 각 원소를 곱한 후 그 결과를 모두 더한 값을 계산하는 연산
벡터와 벡터 간, 또는 행렬과 벡터 간에 내적 연산을 사용할 수 있음
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
print(np.dot(arr1, arr2)) # 1*4 + 2*5 + 3*6 = 32
arr2d = np.array([[1, 2], [3, 4]])
arr1d = np.array([5, 6])
print(np.dot(arr2d, arr1d))
# [1*5 + 2*6, 3*5 + 4*6]
# [17, 39]
arr2d_1 = np.array([[1, 2], [3, 4]])
arr2d_2 = np.array([[5, 6], [7, 8]])
print(np.dot(arr2d_1, arr2d_2))
# [[1*5 + 2*7, 1*6 + 2*8],
# [3*5 + 4*7, 3*6 + 4*8]]
# [[19, 22],
# [43, 50]]
[행렬 A @ 행렬 B]
arr_a = np.array([[1, 2], [3, 4]])
arr_b = np.array([[1, 2], [3, 4]])
# 기본 산술연산
print(arr_a * arr_b)
print(arr_a.shape, arr_b.shape, (arr_a * arr_b).shape)
# 내적
print(np.dot(arr_a, arr_b))
# 1*1 + 2*3 , 1*2 + 2*4
# 3*1 + 4*3 , 3*2 + 4*4
print(np.dot(arr_a, arr_b).shape)
arr3 = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
arr4 = np.array([[7, 8], [9, 10], [11, 12]]) # (3, 2)
print(np.dot(arr3, arr4)) # (2, 3) * (3, 2) = (2, 2)
# [[(1*7+2*9+3*11), (1*8+2*10+3*12)],
# [(4*7+5*9+6*11), (4*8+5*10+6*12)]]
# [[ 58 64]
# [139 154]]
print(np.dot(arr4, arr3)) # (3, 2) * (2, 3) = (3, 3)
# [[(7*1+8*4), (7*2+8*5), (7*3+8*6)],
# [(9*1+10*4), (9*2+10*5), (9*3+10*6)],
# [(11*1+12*4), (11*2+12*5), (11*3+12*6)]]
# [[39 54 69]
# [49 68 87]
# [59 82 105]]
arr = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
# @ = matmul() 연산자
# print(arr @ arr) # (2, 3) * (2, 3)
print(arr)
[[1 2 3]
[4 5 6]]
# arr.T = arr의 전치행렬 (행-열을 교환)
print(arr.T)
[[1 4]
[2 5]
[3 6]]
# print(arr @ arr.T)
print(np.matmul(arr, arr.T))
#matmul() - 내적구하는 함수
[[14 32]
32 77]]
비교 연산
1. 크다/작다
arr = np.array([10, 20, 30])
print(arr > 15) # [False, True, True]
print(arr == 20) # [False, True, False]
print(arr != 20) # [True, False, True]
arr1 = np.array([1, 2, 3])
arr2 = np.array([2, 2, 2])
print(arr1 > arr2) # [False, False, True]