SKN Family AI Bootcamp 24기 / 3주차 - 데이터분석 (Data Analysis) (1)

A.·2026년 1월 31일

SKN 24

목록 보기
11/15

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


Data Science

  • 방대한 양의 데이터를 수집, 분석 시각화 처리하여 유의미한 정보를 추출하는 것
  • 파이썬 패키지로는 NumPy, Pandas, Matplotlib, Seaborn등이 주로 사용됨

데이터의 종류

데이터란, 존재하는 어떠한 측정된 값을 말한다.

정형 데이터

관계형 데이터 베이스, 스프레드시트 파일 (Excel, CSV 등) 처럼 형태가 정해져 있다.

비정형 데이터

정형 데이터 외 모든 데이터, 즉 고정된 구조가 없는 데이터로, 다양한 형태와 형식을 가진다. 텍스트, 이미지 동영상, 오디오, LLM, 멀티모달 등..

데이터 분석의 개요

데이터 분석은 단순히 숫자를 계산하는 것이 아니라, 데이터라는 원석에서 '의미있는 가치(insight)'를 캐내는 모든 과정을 말한다.


데이터 분석 프로세스

CRISP-DM (Cross Industry Standard Process for Data Mining)

: 데이터 분석과 데이터 마이닝 프로젝트를 체계적으로 수행하기 위해 널리 사용되는 표준 프로세스로, 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. 프로젝트의 최종 성과룰 공유하고 운영에 반영


데이터분석에 사용될 도구(toolkit): NumPy / Pandas**

Python 이라는 프로그래밍 언어를 배웠으므로 아래의 라이브러리를 설치하여 사용.

  • Pandas : Series (1차원) / DataFrame (2차원) - 인덱싱과 슬라이싱 개념을 활용
  • NumPy : ndarray (다차원 배열로, 파이썬의 리스트를 확장하여 수치 계산에 최적화된 형태)
  • Matplotlib/Seaborn : 데이터 시각화 (그래프그리기)
  • Scikit-learn : 머신러닝 알고리즘

NumPy (Numerical 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 배열
속도: 빠름
메모리 사용: 덜 사용
데이터 형식: 동일한 자료형
연산 방식: 벡터화된 연산 지원


ndarray : NumPy의 핵심자료구조로, n차원 배열을 의미.

Python의 리스트와 유사하나, 고정된 크기와 단일 데이터 타입을 가진다. 배열의 각 차원을 축(axis)이라고 하며, 각 축의 크기는 배열의 모양(shape).

ndarray의 특징:

  • 다차원 배열 생성할 수 있다
  • 배열 내의 모든 원소는 동일한 데이터 타입이어야 함
  • 벡터화된 연산으로 빠르고 효율적인 수치 계산이 가능

ndarray 생성

  1. 리스트로부터 생성
import numpy as np
arr = np.array([1, 2, 3, 4])
print(arr)   # [1 2 3 4]
  1. 0으로 채운 배열 생성(np.zeros())
    모든 값이 0인 배열을 생성
arr = np.zeros((2, 3))  # 2x3 배열
print(arr)
# [[0. 0. 0.]
#  [0. 0. 0.]]
  1. 1로 채운 배열 생성(np.ones())
    모든 값이 1인 배열을 생성
arr = np.ones((3, 2))  # 3x2 배열
print(arr)
# [[1. 1.]
# [1. 1.]
# [1. 1.]]
  1. 연속적인 값으로 배열 생성 (np.arange())
    주어진 범위 내에서 연속적인 값으로 구성된 배열을 생성
arr = np.arange(0, 10, 2)  # 0부터 9까지 2씩 증가하는 값
print(arr)
# [0 2 4 6 8]
  1. 등간격으로 배열 생성 (np.linspace())
    시작과 끝을 포함하여 지정한 개수만큼의 값으로 배열 생성
arr = np.linspace(0, 1, 5)  # 0에서 1까지 5개의 등간격 값
print(arr)
# [0.   0.25 0.5  0.75 1.  ]
  1. 정규분포로 난수 배열 생성 (np.random.randn())
    평균 0, 표준편차 1인 정규 분포로 난수를 생성
arr = np.random.randn(2, 3)  # 2x3 배열
print(arr)
# [[-0.16437183 -0.80572477 -0.59256217]
# [ 0.46449378  1.13526154  0.04380413]]

ndarray속성(구조)

  1. shape - 배열의 모양
print(arr.shape)
  1. dimension - 배열의 차원 (깊이)
print(arr.ndim)
  1. size - 배열의 크기 (전체 요소의 개수)
print(arr.size)
  1. 배열의 데이터타입 확인 (요소의 자료형)
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의 차이

  • ndarray 는 동일한 자료형만 저장 가능
  • 형태/길이를 확인하는 방법의 차이
    - python list: len()
    • ndarray: ndarray.shape, ndarray.ndim, ndarray.size
  • ndarray는 다차원인 경우, 중첩 배열을 동일한 크기만 허용
    - ndarray는 고정길이를 가짐
    • 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)

특정 수로 초기화된 ndarray 생성

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) - 간격을 기준으로 생성/실수(소수점)간격 사용할수있고 배열을 반환
    • start: 시작하는 숫자
    • end: 끝나는 숫자 + 1 (끝값은 포함되지 않음)
    • step: start~end 증가하는 간격
    • 특징: "1부터 10까지 2씩 띄어서 만들어줘" (1, 11, 2)
print(np.arange(10))
print(np.arange(1, 10))
print(np.arange(1, 10, .1))
  • np.linspace(start, end, num) - 개수를 기준으로 생성. 시작과 끝 범위를 정해두고 그 사이를 등간격으로 몇개를 채울지 결정
    • start: 시작하는 숫자
    • end: 끝나는 숫자 (끝값이 기본적으로 포함됨)
    • num: 결과로 만들어진 수열의 요소 개수
    • 특징: "0부터 100까지 딱 5등분해줘"
# 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)
    • start_exp: 시작 지수
    • end_exp: 끝 지수
    • num: 결과로 만들어진 수열의 요소 개수
    • base: 밑 (기본값 = 10)
print(np.logspace(1, 3, 4))
# 첫 번째 인자 (1): 시작값의 지수입니다. (즉, 10의 1제곱 = 10)
# 두 번째 인자 (3): 끝값의 지수입니다. (즉, 10의 3제곱 = 1000)
# 세 번째 인자 (4): 생성할 숫자의 개수 (그 사이를 로그상에서 동일한 간격으로 4개 뽑음)

linspace: 더하기 간격
logspace: 곱하기 간격 (배수)

np.linspace

값 사이의 차이가 일정함 (덧셈 기준) - 일반적인 수치 계산이나 그래프 그리기에 사용됨

np.logspace

값 사이의 비율이 일정함 (곱셈/지수 기준) - 주파수 분석, 기하급수적 성장 모델링 용도로 사용됨

ndarray Indexing & Slicing

NumPy배열은 0부터 시작하는 인덱스를 사용하며, 다차원 배열에서도 각 차원별로 인덱스 지정할 수 있음

기본 인덱싱

  1. 1차원 배열 인덱싱
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
  1. 2차원 배열 인덱싱 - 2차원 배열에서 요소에 접근할 때는 행과 열의 인덱스를 모두 지정
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 (음수 인덱스 활용)
  1. 3차원 배열 인덱싱 - 3차원 배열의 경우, 각 차원에 대해 인덱스를 지정한다.
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

print(arr[1, 0, 2])  # 1번째 차원, 0번째 행, 2번째 열의 요소 출력 (9)

기본 슬라이싱

  1. 1차원 배열 슬라이싱
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]  역순으로 출력
  1. 2차원 배열 슬라이싱
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]]
  1. 3차원 배열 슬라이싱
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차원)

Fancy Indexing

  1. 1차원 배열의 fancy indexing
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] <- 리스트를 직접 대괄호안에 넣어 인덱싱
  1. 2차원 배열의 fancy indexing - 행과 열을 각각 지정해서 주는거
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]

Boolean indexing

조건을 만족하는 배열의 요소를 선택하는 방법

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()

  • np.all(): ndarray의 모든 요소가 조건을 만족할 때 True 반환
  • np.any(): ndarray의 요소 중 하나라도 조건을 만족할 때 True 반환
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_

ndarray 연산

기본 연산

  • 피연산자 두 배열의 같은 위치의 요소끼리 연산을 수행한 결과를 반환
  • 피연산자가 되는 두개의 배열은 같은 shape을 가져야 함
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)을 기준으로 한 연산

  • 2차원 이상의 배열에서는 축(axis)을 기준으로 연산을 수행할 수 있음
  • 축 0은 행(row)을 따라 연산을 수행하고, 축 1은 열(column)을 따라 연산을 수행한다.
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d.sum(axis=0))  # [5, 7, 9] (각 열의 합)
print(arr2d.sum(axis=1))  # [6, 15] (각 행의 합)

Broadcasting 연산

서로 다른 크기의 배열 간 연산을 가능하게 하는 기능 -> 작은 배열을 큰 배열의 크기에 맞춰서 연산을 수행할 수 있게 함

  • 두 피연산자(배열) shape이 다르면 broadcasting 연산을 수행한다.
  • shape이 작은 쪽이 큰 쪽에 맞춰 확장한다.
  • 크기가 1인 경우 무조건 확장 가능하다.
  • shape이 다른 경우, 마지막 축부터 차원이 동일한지 비교한다.
  1. 스칼라값과의 연산 - 배열 전체에 스칼라 값을 적용하여 연산할 수 있음
arr = np.array([1, 2, 3])
print(arr + 10)  # [11, 12, 13]
  1. 다차원 배열과 1차원 배열 간의 브로드캐스팅 - 다차원 배열에 1차월 배열을 브로드캐스팅하여 연산
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
arr1d = np.array([1, 2, 3])
print(arr2d + arr1d)
# [[2, 4, 6],
#  [5, 7, 9]]
  1. 3차원 배열과 2차원 배열 간의 브로드캐스팅
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

행렬 곱셈(점곱, 내적연산, Dot Product)

내적연산: 두 배열의 각 원소를 곱한 후 그 결과를 모두 더한 값을 계산하는 연산
벡터와 벡터 간, 또는 행렬과 벡터 간에 내적 연산을 사용할 수 있음

  1. 1차원 배열(벡터)간 Dot Product
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
print(np.dot(arr1, arr2))  # 1*4 + 2*5 + 3*6 = 32
  1. 2차원 배열(행렬)과 1차원 배열(벡터)간 Dot Product
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]
  1. 2차원 배열(행렬)간의 Dot Product
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]

  • 두 행렬 A, B의 곱셈은 첫번째 행렬 A의 행과 두번째 행렬 B의 열간의 곱셈을 수행한다.
  • 행렬 A의 열의 수와 행렬 B의 행의 수가 같아야 내적 연산이 가능
  • 연산 결과의 shape은 (행렬 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]
  1. 같다/다르다
print(arr == 20)  # [False, True, False]
print(arr != 20)  # [True, False, True]
  1. 배열 간 비교연산
arr1 = np.array([1, 2, 3])
arr2 = np.array([2, 2, 2])
print(arr1 > arr2)  # [False, False, True]
profile
코린이

0개의 댓글