C: 1970년대 등장
→ 이후 프로그래머가 아니어도 컴퓨터 프로그래밍을 업무에 적용하려는 사람이 증가
→ 하지만 그들에게 C는 너무 어렵다!
→ JAVA, Python 등장: 1990년대
1990년대 초반 웹이 등장하면서 웹 페이지를 동적으로 만들고 상호 작용할 수 있는 기술이 필요해졌고 다양한 운영체제와 하드웨어 환경에서 실행 가능한 언어의 필요성이 커져 다양한 플랫폼에서 실행될 수 있는 이식성, 웹 환경에서의 활용, 그리고 객체 지향 프로그래밍의 장점을 결합하여 JAVA 탄생
다른 라이브러리 안에 같은 이름을 가진 함수가 존재할 수 있음에 유의
sum()
numpy.sum()
pandas.sum()
→ 메서드 이름은 같아도 구현 방식은 서로 다름! 그래서 속도 차이나 메모리 효율 차이 등이 생김

import numpy as np
arr = np.array([1, 2, 3, 4, 5,])
print(arr)
print(type(arr))
# [Out]
[1 2 3 4 5]
<class 'numpy.ndarray'>
import numpy as np
# 0으로 채워진 배열
zeros_arr = np.zeros((2, 3))
print(zeros_arr)
# 1로 채워진 배열
ones_arr = np.ones((2, 3))
print(ones_arr)
# 특정 값으로 채워진 배열
full_arr = np.full((2, 3), 7)
print(full_arr)
# [Out]
[[0. 0. 0.]
[0. 0. 0.]]
[[1. 1. 1.]
[1. 1. 1.]]
[[7 7 7]
[7 7 7]]
import numpy as np
arr1 = np.arange(1, 10, 2) # 1부터 10-1까지 2씩 증가
print(arr1)
arr2 = np.linspace(0, 1, 5) # 0부터 1까지 5개 값을 균등 분할
# [Out]
[1 3 5 7 9]
[0. 0.25 0.5 0.75 1. ]
import numpy as np
random_arr = np.random.rand(3, 3) # 0~1 사이의 난수 (3x3 배열)
print(random_arr) # 실행할 때마다 출력 달라짐

import numpy as np
arr = np.array([1, 2, 3, 4, 5,])
print(arr + 10) # 배열의 모든 요소에 10을 더함
print(arr * 2) # 배열의 모든 요소를 2배
print(arr ** 2) # 배열의 모든 요소를 제곱
# [Out]
[11 12 13 14 15]
[2 4 6 8 10]
[1 4 9 16 25]
import numpy as np
arr1 = np.array([1, 2, 3,])
arr2 = np.array([4, 5, 6,])
print(arr1 + arr2) # 요소별 덧셈
print(arr1 * arr2) # 요소별 곱셈
# [Out]
[5 7 9]
[4 10 18]
for문을 사용해 하나씩 계산# 연산 속도 비교
import numpy as np
import time
# 리스트 연산
list_data = list(range(1000000))
start = time.time()
list_result = [x*2 for x in list_data]
end = time.time()
print("리스트 연산 시간:", end - start)
# NumPy 배열 연산
np_data = np.array(list_data)
start = time.time()
np_result = np_data * 2 # 벡터 연산
end = time.time()
print("NumPy 연산 시간:", end - start)
# 메모리 비교
import numpy as np
import sys
list_data = list(range(1000))
np_data = np.array(list_data)
print("리스트 크기:", sys.getsizeof(list_data), "bytes")
print("NumPy 크기:", np_data.nbytes, "bytes")
NumPy 데이터 관리 효율이나 처리 속도는 램에서만 영향이 있는 건가요? 네
하드디스크 저장은 큰 의미가 없고 런타임에서 램에 로딩되는 것이 영향을 줍니다.
처리 속도의 경우 cpu 처리 속도도 함께 영향을 미칩니다.
추가: C와 Python은 자료 구조의 근본 설계 개념이 다름 → 이로 인해 발생히는 차이점은 '클래스' 설명에서 다시 이야기
# 행렬 곱셈
import numpy as np
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
# 리스트로 행렬 곱셈 (복잡함)
result = [[sum(a*b for a, b in zip(A_row, B_col)) for B_col in zip(*B)] for A_row in A]
print("리스트 행렬 곱셈 결과:\n", result)
# NumPy로 행렬 곱셉 (간단!)
A_np = np.array(A)
B_np = np.array(B)
result_np = np.dot(A_np, B_np)
print("NumPy 행렬 곱셈 결과:\n", result_np)
따라서 데이터 분석, 머신러닝, 과학 계산에서는 NumPy가 필수
axis = 0: 열(column) 방향으로 연산 → 위에서 아래로axis = 1: 행(row) 방향으로 연산 → 왼쪽에서 오른쪽으로arr = [[10, 20, 30],
[40, 50, 60],
[70, 80, 90]]
np.sum(arr, axis = 1): 각 행(row)별로 값을 더함arr, axis = 1: 함수의 파라미터(parameter), 옵션값[10+20+30, 40+50+60, 70+80+90] = [60, 150, 240]np.sum(arr, axis = 0): 각 열(column)별로 값을 더함[10+40+70, 20+50+80, 30+60+90] = [120, 150, 180]다른 함수나 메서드에서도
axis = 1일 때 행 계산일까?
NumPy에서axis=1이 행(row) 단위 연산인 것은 다른 함수나 메서드에서도 일관되게 적용되는 기준입니다.
즉 axis=0이면 열(column) 기준, axis=1이면 행(row) 기준으로 동작하는 것은 다양한 NumPy 함수(sum, mean, max, min, std, argmax 등)에서 동일하게 적용됩니다.
| 함수 | axis=0 (열 기준) | axis=1 (행 기준) |
|---|---|---|
| np.sum() | 각 열의 합 | 각 행의 합 |
| np.mean() | 각 열의 평균 | 각 행의 평균 |
| np.max() | 각 열의 최댓값 | 각 행의 최댓값 |
| np.min() | 각 열의 최솟값 | 각 행의 최솟값 |
| np.std() | 각 열의 표준편차 | 각 행의 표준편차 |
| np.argmax() | 각 열에서 최댓값의 위치(인덱스) | 각 행에서 최댓값의 위치(인덱스) |
import numpy as np
arr = np.array([[1, 2, 3,],
[4, 5, 6,],
[7, 8, 9,],])
print(np.max(arr, axis=0)) # 각 열에서 최댓값 찾기
print(np.max(arr, axis=1)) # 각 행에서 최댓값 찾기
print(np.argmax(arr, axis=0)) # 각 열에서 최댓값의 인덱스
print(np.argmax(arr, axis=1)) # 각 행에서 최댓값의 인덱스
# [Out]
[7 8 9] # 각 열에서 최댓값 (열 기준)
[3 6 9] # 각 행에서 최댓값 (행 기준)
[2 2 2] # 각 열에서 최댓값의 인덱스 (0, 1, 2번째 행에서 각각 찾음)
[2 2 2] # 각 행에서 최댓값의 인덱스 (각 행에서 최댓값 위치)
axis=0은 열(column) 기준, axis=1은 행(row) 기준으로 동작sum(), mean(), max(), min(), std(), argmax() 등)에서 동일한 방식으로 적용됨즉, 어떤 함수를 사용하든 axis=0이면 열(column) 단위 연산, axis=1이면 행(row) 단위 연산으로 처리됨
for문을 사용해야 함조건 연산(불리언 인덱싱)을 이용하면 간단하게 해결 가능np.random.randint(0, 16, (4, 4)): 0~15 사이의 정수를 무작위로 (4, 4) 배열로 생성arr % 2 == 0)% 연산자를 사용해 짝수인지 확인arr % 2 == 0의 결과는 True/False로 이루어진 불리안 배열이 됨arr[arr % 2 == 0] = 0)arr[arr % 2 == 0]: 짝수인 원소만 선택0으로 변경
응용이 많이 되니 꼭 알아두기!
bitmap 형식의 이미지 처리 → 특정 RGB값만 제거 등
| 표현식 | 의미 |
|---|---|
arr % 2 == 0 | 짝수인 요소 찾기(True/False 배열) |
arr[arr % 2 == 0] | 짝수인 요소만 선택 |
arr[arr % 2 == 0] = 0 | 짝수를 0으로 변경 |
arr[arr % 2 == 0]: 짝수만 선택arr[arr % 2 == 0] = 0: 해당 값들을 0으로 변경import numpy as np
np.random.seed(42) # 시드값 42로 난수 고정
arr1 = np.random.randint(0, 16, (4, 4))
print("시드 42로 생성한 배열:")
print(arr1)
np.random.seed(42) # 다시 시드 42로 고정
arr2 = np.random.randint(0, 16, (4, 4))
print("\n다시 시드 42로 생성한 배열:")
print(arr2)

42로 고정하면 결과가 동일함Q. 시드값 42가 의미하는건 뭔가요?
A. 랜덤값을 만들 때 기준이 되는 값입니다.
동일한 조건에서 다음번 작업에 똑같은 요소(원소)로 구성된 배열을 랜덤으로 만들 고자 할 때 유리합니다.
Q. 난수 고정 값을 다른 경우에 42로 고정해도 결과가 똑같이 나오나요?
A.np.random.seed(42)에서42는 고정값(seed)이기 때문에, 같은 시드값을 사용하면 항상 같은 결과가 나오게 됨.
따라서 다른 값으로 시드를 고정해도 동일한 시드값을 사용하는 한 그 시드값에 대응하는 난수 시퀀스는 항상 동일한 결과가 나옴.
→ 모든 컴퓨터에서 항상 동일한 값!
np.random.seed()는 재현 가능한 결과를 얻을 때 유용 → 실험 결과를 다시 실행해야 할 때, 동일한 데이터를 재사용할 때arr.reshape(new_shape)# 1차원 배열을 2차원 배열로 변경
import numpy as np
arr = np.arange(12) # 0~11까지의 정수로 이루어진 1차원 배열
print("원본 배열:")
print(arr)
reshaped_arr = arr.reshape(3, 4) # 3행 4열의 2차원 배열로 변경
print("\nReshaped 배열:")
print(reshaped_arr)
# [Out]
원본 배열:
[ 0 1 2 3 4 5 6 7 8 9 10 11]
Reshaped 배열:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
# 2차원 배열을 1차원 배열로 변경
reshaped_arr = reshaped_arr.reshape(-1)
# -1을 사용하면 자동으로 1차원으로 변경
print("\n1차원 배열로 변경:")
print(reshaped_arr)
# [Out]
1차원 배열로 변경:
[ 0 1 2 3 4 5 6 7 8 9 10 11]
※ 주의: reshape(-1)은 자동으로 차원 크기를 계산해줍니다. (원래 배열의 크기와 일치해야만 적용 가능)
reshape에서 2차원 배열에서 1차원으로 변경할 때 -1에 대한 설명
-1을 사용하면 배열의 크기를 자동으로 계산
(-1은 자동으로 다른 차원에 맞는 크기를 계산)
즉, 어떤 차원을-1로 지정하면 나머지 차원에 맞게 자동으로 크기를 결정
→ 배열 크기를 잘 모르거나, 자동으로 계산해 주기를 원할 때 유용
→ 1차원 배열을 2차원 배열로 변환하거나 2차원 배열을 1차원 배열로 변환할 때 편리하게 사용 가능
print(arr[0])arr[0] = 99arr[index] → index는 배열의 위치를 나타냄# 1차원 배열 인덱싱
import numpy as np
arr = np.array([10, 20, 30, 40, 50,])
print(arr[0]) # 첫 번째 원소 선택
print(arr[2]) # 세 번째 원소 선택
# 2차원 배열 인덱싱
arr = np.array([[10,20,30],
[40,50,60],
[70,80,90]])
# 첫 번째 행, 두 번째 열의 값
print(arr[0, 1]) # 출력: 20
# 두 번째 행, 첫 번째 열의 값
print(arr[1, 0]) # 출력: 40

arr[start:end] → start는 포함, end는 미포함np.linspace(start, end, num)에서 end는 default가 포함임# 1차원 배열 슬라이싱
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
print(arr[1:4]) # 1번부터 3번까지(20, 30, 40)
# 2차원 배열 슬라이싱
arr = np.array([[10,20,30],
[40,50,60],
[70,80,90]])
# 첫 번째 행의 전체 값
print(arr[0, :]) # 첫 번째 행의 모든 열 선택
# 두 번째 열의 모든 값
print(arr[:, 1]) # 두 번째 열의 모든 행 선택
# 첫 번째, 두 번째 행과 첫 번째, 두 번쨰 열
print(arr[:2, :2])
# [Out]
[20 30 40]
[10 20 30]
[20 50 80]
[[10 20]
[40 50]]
# 조건 인덱싱으로 짝수만 선택
import numpy as np
arr = np.array([10, 15, 20, 25, 30])
even_arr = arr[arr % 2 == 0]
print(even_arr)
# [Out]
[10 20 30]
# 조건을 만족하는 값 변경
arr[arr % 2 == 0] = 0 # 짝수만 0으로 변경
print(arr)
# [Out]
[0 15 0 25 0]
reshape(): 배열의 모양을 변경할 때 사용indexing, slicing은 리스트와 동일!