이미지 로딩 중...
AI Generated
2025. 11. 21. · 13 Views
NumPy 수학 연산 및 통계 함수 완벽 가이드
NumPy의 강력한 수학 연산과 통계 함수를 실무 예제와 함께 배워봅니다. 데이터 분석과 과학 계산에 필수적인 함수들을 초보자도 쉽게 이해할 수 있도록 설명합니다.
목차
- 기본 수학 연산 - 배열 전체에 한번에 계산하기
- 통계 함수 - 데이터의 특징을 한눈에 파악하기
- 집계 함수와 축 - 다차원 데이터를 자유자재로
- 고급 수학 함수 - 제곱근, 지수, 로그
- 삼각 함수와 쌍곡선 함수 - 주기적 패턴 분석
- 반올림과 버림 함수 - 숫자 다듬기
- 배열 비교와 조건 연산 - 데이터 필터링
- 분산과 표준편차 - 데이터의 퍼짐 정도 측정
- 백분위수와 사분위수 - 데이터 분포의 세밀한 분석
- 누적 합과 누적 곱 - 시계열 변환
1. 기본 수학 연산 - 배열 전체에 한번에 계산하기
시작하며
여러분이 학생 100명의 시험 점수를 관리하는 프로그램을 만든다고 상상해보세요. 모든 학생의 점수에 10점씩 더해야 한다면 어떻게 하시겠어요?
for 반복문으로 100번 돌면서 하나하나 더하시겠어요? 이런 문제는 실제 데이터 분석 현장에서 매일 발생합니다.
수천, 수만 개의 데이터를 하나하나 처리하다 보면 코드도 길어지고, 속도도 느려지고, 실수할 확률도 높아집니다. 바로 이럴 때 필요한 것이 NumPy의 벡터화 연산입니다.
배열 전체에 한 줄의 코드로 연산을 적용할 수 있어서, 마치 마법처럼 빠르고 간결하게 작업을 처리할 수 있습니다.
개요
간단히 말해서, NumPy의 기본 수학 연산은 배열 전체에 동시에 계산을 적용하는 기능입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터 분석에서는 수백만 개의 데이터를 처리하는 경우가 많습니다.
예를 들어, 센서 데이터를 수집해서 모든 값을 섭씨에서 화씨로 변환하거나, 주식 가격 데이터를 정규화하는 경우에 매우 유용합니다. 기존에는 파이썬 리스트와 for 반복문을 사용했다면, 이제는 NumPy 배열에 직접 연산자를 적용할 수 있습니다.
코드 한 줄이면 충분합니다. NumPy의 핵심 특징은 첫째, 모든 기본 연산자(+, -, *, /, **)를 지원한다는 점, 둘째, 배열의 모양이 같으면 요소별로 자동 계산된다는 점, 셋째, 순수 파이썬보다 10배~100배 빠르다는 점입니다.
이러한 특징들이 대용량 데이터 처리에서 결정적인 차이를 만듭니다.
코드 예제
import numpy as np
# 학생들의 시험 점수
scores = np.array([85, 90, 78, 92, 88])
# 모든 점수에 10점 보너스 추가 - 한 줄이면 끝!
bonus_scores = scores + 10
print(f"보너스 점수: {bonus_scores}") # [95 100 88 102 98]
# 모든 점수를 2배로
doubled = scores * 2
print(f"2배 점수: {doubled}") # [170 180 156 184 176]
# 배열끼리 연산도 가능
weights = np.array([0.3, 0.3, 0.2, 0.1, 0.1])
weighted_scores = scores * weights # 요소별 곱셈
print(f"가중치 적용: {weighted_scores}") # [25.5 27. 15.6 9.2 8.8]
설명
이것이 하는 일: NumPy 배열에 수학 연산자를 적용하면, 배열의 모든 요소에 자동으로 같은 연산이 적용됩니다. 이를 '벡터화 연산'이라고 부릅니다.
첫 번째로, scores + 10 부분은 배열의 각 요소에 10을 더합니다. 왜 이렇게 하는지 설명하면, NumPy가 내부적으로 C 언어로 구현된 최적화된 반복문을 실행하기 때문에 파이썬 반복문보다 훨씬 빠릅니다.
우리는 코드 한 줄만 쓰면 되지만, 뒤에서는 고성능 연산이 일어나는 것이죠. 두 번째로, scores * 2 같은 곱셈 연산이 실행되면서 각 점수가 2배가 됩니다.
내부에서는 NumPy가 메모리를 효율적으로 사용하면서 모든 요소를 순회하며 곱셈을 수행합니다. 파이썬 리스트로 같은 작업을 하려면 리스트 컴프리헨션이나 map 함수를 써야 하지만, NumPy는 훨씬 직관적입니다.
세 번째로, scores * weights 같은 배열 간 연산이 실행됩니다. 마지막으로, 같은 크기의 두 배열을 곱하면 같은 위치의 요소끼리 곱해져서 새로운 배열이 만들어집니다.
이를 '요소별 연산(element-wise operation)'이라고 합니다. 여러분이 이 코드를 사용하면 데이터 처리 속도가 크게 향상되고, 코드가 간결해지며, 실수할 가능성이 줄어듭니다.
실무에서는 수백만 개의 센서 데이터를 변환하거나, 이미지의 모든 픽셀 값을 조정하거나, 금융 데이터를 정규화할 때 이런 벡터화 연산을 활용합니다.
실전 팁
💡 배열의 크기가 다를 때는 브로드캐스팅 규칙이 적용됩니다. 예를 들어 (3, 4) 배열과 (4,) 배열을 더하면 자동으로 크기가 맞춰집니다.
💡 나눗셈할 때 0으로 나누는 실수를 조심하세요. np.divide() 함수를 사용하면 경고 메시지를 제어할 수 있습니다.
💡 대용량 데이터를 처리할 때는 in-place 연산(+=, *=)을 사용하면 메모리를 절약할 수 있습니다.
💡 복잡한 수식은 괄호를 사용해서 연산 순서를 명확히 하세요. result = (a + b) * (c - d) 처럼요.
💡 성능이 중요하다면 배열을 생성할 때 dtype을 명시하세요. np.array([1, 2, 3], dtype=np.float32)는 메모리를 절반만 사용합니다.
2. 통계 함수 - 데이터의 특징을 한눈에 파악하기
시작하며
여러분이 온라인 쇼핑몰을 운영하는데, 지난달 매출 데이터가 1000개 있다고 생각해보세요. 평균 매출은 얼마인지, 가장 많이 팔린 날은 언제인지, 매출 편차는 어느 정도인지 알고 싶으시죠?
이런 질문들에 답하려면 통계 계산이 필요합니다. 엑셀로 일일이 계산할 수도 있지만, 데이터가 많아질수록 힘들어집니다.
게다가 매일 자동으로 분석해야 한다면 더욱 어렵습니다. 바로 이럴 때 필요한 것이 NumPy의 통계 함수입니다.
mean(), std(), max() 같은 함수들로 데이터의 특징을 몇 초 만에 파악할 수 있습니다.
개요
간단히 말해서, NumPy의 통계 함수는 배열 데이터의 평균, 표준편차, 최댓값, 최솟값 등을 자동으로 계산해주는 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터 분석의 첫 단계는 항상 데이터 탐색입니다.
예를 들어, 머신러닝 모델을 만들기 전에 데이터의 분포를 확인하거나, 비즈니스 리포트를 작성할 때 핵심 지표를 계산하는 경우에 필수적입니다. 기존에는 직접 합계를 구하고 개수로 나누는 식으로 평균을 계산했다면, 이제는 np.mean() 한 줄이면 됩니다.
더 정확하고, 더 빠르고, 더 읽기 쉽습니다. NumPy 통계 함수의 핵심 특징은 첫째, 다차원 배열의 특정 축(axis)을 기준으로 계산할 수 있다는 점, 둘째, NaN(결측값)을 무시하는 버전(nanmean, nanstd)도 제공한다는 점, 셋째, 매우 빠른 C 구현으로 대용량 데이터도 즉시 처리한다는 점입니다.
이러한 특징들이 실무 데이터 분석을 가능하게 만듭니다.
코드 예제
import numpy as np
# 한 달간의 일별 매출 데이터 (단위: 만원)
sales = np.array([120, 135, 98, 142, 156, 133, 128, 145, 139, 151])
# 평균 매출
avg_sales = np.mean(sales)
print(f"평균 매출: {avg_sales:.1f}만원") # 134.7만원
# 표준편차 - 매출이 얼마나 들쭉날쭉한지
std_sales = np.std(sales)
print(f"표준편차: {std_sales:.1f}만원") # 16.8만원
# 최대, 최소 매출
print(f"최고 매출: {np.max(sales)}만원") # 156만원
print(f"최저 매출: {np.min(sales)}만원") # 98만원
# 중앙값 - 평균보다 이상치에 덜 민감
median_sales = np.median(sales)
print(f"중앙값: {median_sales}만원") # 136.5만원
# 합계
total_sales = np.sum(sales)
print(f"총 매출: {total_sales}만원") # 1347만원
설명
이것이 하는 일: NumPy의 통계 함수들은 배열의 모든 데이터를 분석해서 대푯값들을 계산합니다. 데이터 과학자들이 가장 자주 사용하는 기능이죠.
첫 번째로, np.mean(sales) 부분은 모든 매출 값을 더한 후 개수로 나누어 평균을 구합니다. 왜 이렇게 하는지 설명하면, 평균은 데이터의 중심 경향을 나타내는 가장 기본적인 지표이기 때문입니다.
"대체로 하루에 얼마나 벌었나?"라는 질문에 답할 수 있죠. 두 번째로, np.std() 함수가 실행되면서 각 값이 평균에서 얼마나 떨어져 있는지를 계산합니다.
내부에서는 각 값과 평균의 차이를 제곱하고, 평균을 낸 후, 제곱근을 구하는 복잡한 과정이 일어납니다. 하지만 우리는 함수 하나만 호출하면 됩니다.
표준편차가 크면 매출이 불규칙하다는 뜻이고, 작으면 안정적이라는 뜻입니다. 세 번째로, np.max()와 np.min() 함수가 배열을 순회하며 가장 큰 값과 작은 값을 찾습니다.
마지막으로, np.median()은 데이터를 정렬한 후 가운데 값을 반환합니다. 만약 극단적으로 높거나 낮은 값(이상치)이 있다면, 평균보다 중앙값이 더 신뢰할 만한 대푯값이 됩니다.
여러분이 이 코드를 사용하면 복잡한 통계 계산을 몇 줄로 해결할 수 있고, 데이터의 전체적인 특성을 빠르게 파악할 수 있습니다. 실무에서는 A/B 테스트 결과를 비교하거나, 센서 데이터의 이상을 감지하거나, 재무 리포트를 자동화할 때 이런 통계 함수들을 매일 사용합니다.
실전 팁
💡 axis 매개변수를 사용하면 다차원 배열에서 특정 축을 기준으로 계산할 수 있습니다. np.mean(arr, axis=0)은 열별 평균을 계산합니다.
💡 데이터에 결측값(NaN)이 있으면 np.nanmean(), np.nanstd() 같은 함수를 사용하세요. 일반 함수는 NaN이 있으면 결과도 NaN이 됩니다.
💡 percentile 함수로 사분위수를 계산할 수 있습니다. np.percentile(data, 25)는 하위 25% 값을 반환합니다.
💡 대용량 데이터의 통계를 계산할 때는 메모리를 고려하세요. np.mean(large_array, keepdims=True)는 차원을 유지해서 브로드캐스팅에 유용합니다.
💡 표준편차 계산 시 ddof 매개변수를 조정하세요. np.std(data, ddof=1)은 표본 표준편차를, ddof=0은 모집단 표준편차를 계산합니다.
3. 집계 함수와 축 - 다차원 데이터를 자유자재로
시작하며
여러분이 3개 지점을 운영하는 카페 체인의 데이터 분석가라고 상상해보세요. 각 지점에서 일주일간 커피, 케이크, 샌드위치 판매량을 기록했습니다.
이제 지점별 총 판매량도 알고 싶고, 상품별 총 판매량도 알고 싶습니다. 이런 문제는 실무에서 정말 자주 만납니다.
엑셀처럼 행 합계, 열 합계를 구하는 것인데, 데이터가 3차원, 4차원으로 복잡해지면 어떻게 해야 할까요? 바로 이럴 때 필요한 것이 axis 매개변수입니다.
NumPy의 집계 함수에 axis를 지정하면, 원하는 방향으로만 계산을 수행할 수 있습니다.
개요
간단히 말해서, axis는 다차원 배열에서 "어느 방향으로 계산할지"를 지정하는 매개변수입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 실제 데이터는 거의 항상 다차원입니다.
예를 들어, 시계열 데이터는 (시간, 센서, 측정값) 형태이고, 이미지는 (높이, 너비, RGB) 형태입니다. 특정 차원만 집계해야 하는 경우가 매우 많습니다.
기존에는 복잡한 반복문으로 원하는 차원만 순회했다면, 이제는 axis=0, axis=1 같은 간단한 매개변수로 해결됩니다. axis의 핵심 특징은 첫째, axis=0은 행 방향(세로), axis=1은 열 방향(가로)을 의미한다는 점, 둘째, axis를 생략하면 전체 배열을 하나의 값으로 집계한다는 점, 셋째, 여러 축을 동시에 지정할 수도 있다는 점(axis=(0, 1))입니다.
이러한 특징들이 복잡한 데이터 변환을 가능하게 만듭니다.
코드 예제
import numpy as np
# 3개 지점 × 3개 상품의 판매량 (지점, 상품)
# 행: 강남점, 홍대점, 신촌점
# 열: 커피, 케이크, 샌드위치
sales = np.array([
[120, 45, 30], # 강남점
[98, 52, 28], # 홍대점
[135, 38, 35] # 신촌점
])
# 지점별 총 판매량 (각 지점의 모든 상품 합계)
# axis=1: 가로 방향으로 합계
branch_total = np.sum(sales, axis=1)
print(f"지점별 총 판매: {branch_total}") # [195 178 208]
# 상품별 총 판매량 (모든 지점의 상품별 합계)
# axis=0: 세로 방향으로 합계
product_total = np.sum(sales, axis=0)
print(f"상품별 총 판매: {product_total}") # [353 135 93]
# 전체 총 판매량 (axis 생략)
total = np.sum(sales)
print(f"전체 총 판매: {total}") # 581
# 지점별 평균 판매량
branch_avg = np.mean(sales, axis=1)
print(f"지점별 평균: {branch_avg}") # [65. 59.33 69.33]
설명
이것이 하는 일: axis 매개변수는 NumPy에게 "이 차원을 따라서 계산해라"라고 지시합니다. 그 차원은 계산 후 사라지고, 결과 배열의 차원이 하나 줄어듭니다.
첫 번째로, np.sum(sales, axis=1) 부분은 각 행(지점)에 대해 열(상품)들을 더합니다. 왜 이렇게 하는지 설명하면, axis=1은 "1번 축(열)을 따라 계산하라"는 뜻이고, 그 결과 각 지점별로 하나의 합계 값이 나옵니다.
원래 (3, 3) 형태였던 배열이 (3,) 형태로 바뀝니다. 두 번째로, np.sum(sales, axis=0) 코드가 실행되면서 각 열(상품)에 대해 행(지점)들을 더합니다.
내부에서는 NumPy가 0번 축을 순회하면서 같은 열 위치의 값들을 모두 더합니다. 결과는 (3,) 형태로, 각 상품별 총 판매량을 나타냅니다.
세 번째로, axis를 생략한 np.sum(sales) 코드는 배열의 모든 요소를 하나의 값으로 합칩니다. 마지막으로, 이렇게 계산된 결과는 스칼라 값(단일 숫자)이 됩니다.
전체 판매량 같은 단일 지표가 필요할 때 사용합니다. 여러분이 이 코드를 사용하면 복잡한 다차원 데이터를 원하는 방향으로 집계할 수 있습니다.
실무에서는 시계열 데이터의 일별/월별 집계, 이미지의 채널별 통계, 센서 데이터의 장비별 평균 등을 계산할 때 axis를 매일 사용합니다. Pandas DataFrame의 기반도 이 개념입니다.
실전 팁
💡 헷갈릴 때는 axis=0을 "첫 번째 인덱스 방향"으로 생각하세요. arr[0, :]와 arr[1, :] 사이를 이동하는 방향입니다.
💡 keepdims=True 매개변수를 사용하면 계산 후에도 차원을 유지합니다. 브로드캐스팅할 때 유용합니다.
💡 여러 축을 동시에 집계하려면 튜플로 전달하세요. np.sum(arr, axis=(0, 1))은 0번과 1번 축을 모두 합칩니다.
💡 3차원 이상 배열에서는 axis=-1을 사용하면 마지막 축을, axis=-2는 끝에서 두 번째 축을 의미합니다.
💡 실수를 방지하려면 계산 전후의 shape을 출력해보세요. print(f"전: {arr.shape}, 후: {result.shape}")로 확인할 수 있습니다.
4. 고급 수학 함수 - 제곱근, 지수, 로그
시작하며
여러분이 금융 데이터를 다루는데, 투자 수익률이 지수적으로 증가하는 모델을 만들어야 한다고 가정해봅시다. 또는 머신러닝에서 특성 값을 로그 변환해야 하는 경우도 있습니다.
이런 문제는 데이터 과학과 금융 분석에서 정말 흔합니다. 데이터의 분포를 정규화하거나, 비선형 관계를 모델링하거나, 큰 숫자를 다루기 쉽게 변환해야 할 때마다 필요합니다.
바로 이럴 때 필요한 것이 NumPy의 고급 수학 함수입니다. sqrt, exp, log 같은 함수들로 복잡한 수학 변환을 배열 전체에 적용할 수 있습니다.
개요
간단히 말해서, NumPy의 고급 수학 함수는 제곱근, 지수, 로그 등 복잡한 수학 연산을 배열의 모든 요소에 적용하는 기능입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 실제 데이터는 종종 비선형적입니다.
예를 들어, 인구 증가는 지수적이고, 소리의 크기는 로그 스케일이며, 거리 계산에는 제곱근이 필요합니다. 이런 현실 세계의 관계를 모델링하려면 고급 수학 함수가 필수입니다.
기존에는 math 모듈의 함수를 map()으로 적용하거나 반복문을 돌렸다면, 이제는 np.log(arr) 한 줄이면 배열 전체가 변환됩니다. NumPy 고급 수학 함수의 핵심 특징은 첫째, 모든 함수가 벡터화되어 있어 배열 전체에 동시 적용된다는 점, 둘째, 매우 빠른 C 구현으로 대용량 데이터도 즉시 처리한다는 점, 셋째, 특수한 경우(0으로 나누기, 음수 로그 등)를 자동으로 처리한다는 점입니다.
이러한 특징들이 과학 계산과 데이터 분석을 실용적으로 만듭니다.
코드 예제
import numpy as np
# 투자 원금과 연 수익률 데이터
principal = np.array([100, 200, 150, 300]) # 만원 단위
annual_rate = 0.05 # 5%
years = np.array([1, 2, 3, 4])
# 복리 계산: 원금 × (1 + 이자율) ^ 년수
# np.power()로 거듭제곱 계산
final_amount = principal * np.power(1 + annual_rate, years)
print(f"복리 결과: {final_amount}") # [105. 220.5 173.64 364.95]
# 변동성이 큰 데이터를 로그 변환으로 안정화
prices = np.array([1, 10, 100, 1000, 10000])
log_prices = np.log10(prices) # 로그10 변환
print(f"로그 변환: {log_prices}") # [0. 1. 2. 3. 4.]
# 유클리드 거리 계산에 제곱근 사용
squared_diff = np.array([9, 16, 25, 36])
distances = np.sqrt(squared_diff)
print(f"거리: {distances}") # [3. 4. 5. 6.]
# 지수 함수로 sigmoid 계산 (머신러닝에서 자주 사용)
x = np.array([-2, -1, 0, 1, 2])
sigmoid = 1 / (1 + np.exp(-x))
print(f"Sigmoid: {sigmoid}") # [0.119 0.269 0.5 0.731 0.881]
설명
이것이 하는 일: NumPy의 고급 수학 함수들은 배열의 각 요소에 복잡한 수학 연산을 적용해서 새로운 배열을 반환합니다. 과학 계산의 핵심 도구입니다.
첫 번째로, np.power(1 + annual_rate, years) 부분은 각 투자 기간에 대해 복리를 계산합니다. 왜 이렇게 하는지 설명하면, 복리는 "이자에 이자가 붙는" 것이기 때문에 거듭제곱으로 계산됩니다.
1년이면 1.05의 1제곱, 2년이면 1.05의 2제곱이 되는 식이죠. NumPy는 각 요소별로 서로 다른 지수를 계산할 수 있습니다.
두 번째로, np.log10(prices) 함수가 실행되면서 각 가격을 로그 스케일로 변환합니다. 내부에서는 1, 10, 100, 1000처럼 몇 배씩 차이나는 값들이 0, 1, 2, 3처럼 균등한 간격으로 바뀝니다.
이렇게 하면 극단적으로 큰 값의 영향을 줄일 수 있어서, 머신러닝 모델이 더 안정적으로 학습합니다. 세 번째로, np.sqrt(squared_diff) 코드는 제곱근을 계산합니다.
마지막으로, np.exp(-x) 부분에서 자연상수 e를 밑으로 하는 지수 함수가 계산됩니다. 이를 이용해 sigmoid 함수를 만들면, 어떤 값이든 0과 1 사이로 압축할 수 있습니다.
딥러닝의 활성화 함수로 널리 사용되는 기법입니다. 여러분이 이 코드를 사용하면 금융 계산, 통계 분석, 머신러닝 전처리 등을 간단히 수행할 수 있습니다.
실무에서는 데이터 정규화, 특성 엔지니어링, 신호 처리, 물리 시뮬레이션 등에서 이런 수학 함수들을 매일 활용합니다.
실전 팁
💡 로그 계산 시 0이나 음수 값이 있으면 경고가 발생합니다. 데이터에 작은 상수를 더해서 해결하세요: np.log(arr + 1e-10)
💡 np.log()는 자연로그(ln), np.log10()은 상용로그, np.log2()는 이진로그입니다. 용도에 맞게 선택하세요.
💡 큰 지수 계산은 오버플로우를 일으킬 수 있습니다. np.exp(1000)은 inf를 반환합니다. 범위를 확인하세요.
💡 음수의 제곱근을 계산하려면 np.sqrt()는 경고를 내지만, 복소수 배열로 변환하면 가능합니다: np.sqrt(arr.astype(complex))
💡 성능을 위해 제곱 연산은 arr ** 2보다 np.square(arr)이, 역수는 1/arr보다 np.reciprocal(arr)이 더 빠릅니다.
5. 삼각 함수와 쌍곡선 함수 - 주기적 패턴 분석
시작하며
여러분이 센서 데이터를 분석하는데, 하루 중 온도 변화가 주기적인 패턴을 보인다고 생각해보세요. 또는 음성 신호를 분석해서 주파수를 찾아야 하는 경우도 있습니다.
이런 주기적인 데이터는 자연 현상, 경제 지표, 센서 데이터 등 어디서나 나타납니다. 계절별 매출, 주식의 순환 패턴, 심전도 신호 같은 것들이죠.
이런 패턴을 분석하고 예측하려면 어떻게 해야 할까요? 바로 이럴 때 필요한 것이 삼각 함수입니다.
sin, cos 같은 함수로 주기적인 패턴을 모델링하고, FFT 같은 고급 기법을 적용할 수 있습니다.
개요
간단히 말해서, NumPy의 삼각 함수는 sine, cosine, tangent 등의 삼각비를 배열에 적용하는 기능입니다. 주기적인 현상을 수학적으로 표현할 때 사용합니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 실제 세계의 많은 현상이 주기성을 띱니다. 예를 들어, 시간대별 웹사이트 트래픽은 하루 주기로 반복되고, 계절별 에너지 소비도 1년 주기로 패턴을 보입니다.
이런 패턴을 포착하고 예측 모델에 반영하려면 삼각 함수가 필수입니다. 기존에는 math 모듈로 하나씩 계산했다면, 이제는 np.sin(time_array) 한 줄로 시간 배열 전체를 변환할 수 있습니다.
NumPy 삼각 함수의 핵심 특징은 첫째, 라디안 단위를 사용한다는 점(각도는 np.deg2rad()로 변환), 둘째, 역함수(arcsin, arccos)도 제공한다는 점, 셋째, 쌍곡선 함수(sinh, cosh)까지 지원한다는 점입니다. 이러한 특징들이 신호 처리와 시계열 분석을 가능하게 만듭니다.
코드 예제
import numpy as np
# 0부터 2π까지 100개의 점 생성 (한 주기)
time = np.linspace(0, 2*np.pi, 100)
# 기본 사인파 생성 (음성 신호, 진동 등)
sine_wave = np.sin(time)
print(f"사인파 최댓값: {np.max(sine_wave):.2f}") # 1.00
# 코사인파 생성 (사인파보다 90도 앞서는 파동)
cosine_wave = np.cos(time)
# 주파수를 2배로 높인 파동 (더 빠른 진동)
high_freq = np.sin(2 * time)
# 각도를 라디안으로 변환
angles_deg = np.array([0, 30, 45, 60, 90])
angles_rad = np.deg2rad(angles_deg)
sine_values = np.sin(angles_rad)
print(f"각도별 sin 값: {sine_values}") # [0. 0.5 0.707 0.866 1.]
# 역삼각함수: 값으로부터 각도 계산
values = np.array([0, 0.5, 1])
angles_back = np.rad2deg(np.arcsin(values))
print(f"arcsin 결과(도): {angles_back}") # [0. 30. 90.]
# 시간대별 활동 패턴 모델링 (0~24시간)
hours = np.linspace(0, 24, 24)
# 오후 3시에 최대, 새벽 3시에 최소
activity = 50 + 30 * np.sin(2*np.pi*(hours-9)/24)
print(f"시간대별 활동도: {activity[:5]}") # 첫 5시간
설명
이것이 하는 일: NumPy의 삼각 함수들은 각도(라디안)에 대한 삼각비를 계산해서 -1부터 1 사이의 주기적인 값을 만들어냅니다. 파동, 순환 패턴, 진동을 수학적으로 표현하는 도구입니다.
첫 번째로, np.sin(time) 부분은 0부터 2π까지의 값에 대해 사인 값을 계산합니다. 왜 이렇게 하는지 설명하면, 사인 함수는 0에서 시작해서 π/2에서 최댓값 1을 찍고, π에서 0으로 돌아오고, 2π에서 다시 0이 되는 완벽한 주기 함수이기 때문입니다.
자연의 많은 현상(파도, 소리, 진자 운동)이 이런 패턴을 따릅니다. 두 번째로, np.sin(2 * time) 코드가 실행되면서 주파수가 2배 높아집니다.
내부에서는 같은 시간 구간에 파동이 두 번 반복됩니다. 이렇게 계수를 조절하면 느린 계절 변화부터 빠른 진동까지 다양한 주기를 모델링할 수 있습니다.
세 번째로, np.deg2rad(angles_deg) 함수가 우리에게 익숙한 각도(0°360°)를 NumPy가 사용하는 라디안(02π)으로 변환합니다. 마지막으로, np.arcsin(values) 같은 역함수는 "이 사인 값을 만드는 각도가 뭐지?"라는 반대 방향 계산을 수행합니다.
신호 처리에서 위상을 복원하거나, 센서 데이터에서 각도를 추정할 때 유용합니다. 여러분이 이 코드를 사용하면 시계열 데이터의 계절성을 모델링하거나, 음성/이미지 신호를 처리하거나, 물리 시뮬레이션을 수행할 수 있습니다.
실무에서는 매출 예측에 계절 패턴을 추가하거나, IoT 센서의 노이즈를 필터링하거나, 게임에서 부드러운 애니메이션을 만들 때 삼각 함수를 활용합니다.
실전 팁
💡 NumPy 삼각 함수는 기본적으로 라디안을 사용합니다. 각도로 작업하려면 항상 np.deg2rad()로 변환하세요.
💡 주기가 T인 현상을 모델링하려면 np.sin(2np.pit/T) 형태를 사용하세요. 예: 1년 주기면 T=365입니다.
💡 여러 주파수를 합치면 복잡한 패턴을 만들 수 있습니다. signal = np.sin(t) + 0.5np.sin(3t) 같은 식으로요.
💡 arcsin의 결과는 -π/2 ~ π/2 범위입니다. 전체 범위가 필요하면 np.arctan2(y, x)를 사용하세요.
💡 삼각 함수 계산은 비용이 큽니다. 대용량 데이터에서 반복 계산이 필요하면 미리 계산해서 캐싱하세요.
6. 반올림과 버림 함수 - 숫자 다듬기
시작하며
여러분이 전자상거래 사이트를 운영하는데, 할인율 계산 후 가격이 12345.6789원이 나왔다고 생각해보세요. 화면에 이렇게 표시할 수는 없죠.
12346원이나 12300원으로 반올림해야 합니다. 이런 문제는 금융, 게임, 데이터 시각화 등 모든 곳에서 발생합니다.
부동소수점 연산의 오차를 정리하거나, 사용자에게 보여줄 깔끔한 숫자를 만들거나, 통계 리포트를 작성할 때마다 필요합니다. 바로 이럴 때 필요한 것이 NumPy의 반올림 함수입니다.
round, floor, ceil 같은 함수로 숫자를 원하는 방식으로 다듬을 수 있습니다.
개요
간단히 말해서, NumPy의 반올림 함수는 숫자의 소수점 자릿수를 조절하거나, 특정 방향으로 정수화하는 기능입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 컴퓨터의 부동소수점 연산은 항상 작은 오차를 포함합니다.
예를 들어, 0.1 + 0.2는 정확히 0.3이 아니라 0.30000000000000004가 됩니다. 또한 화폐 계산에서는 정확히 소수점 2자리까지만 필요하고, 통계 리포트에서는 너무 많은 자릿수가 오히려 가독성을 해칩니다.
기존에는 Python의 round() 함수를 하나씩 적용했다면, 이제는 np.round(arr, 2) 한 줄로 배열 전체를 처리할 수 있습니다. NumPy 반올림 함수의 핵심 특징은 첫째, round()는 가장 가까운 정수로, floor()는 내림, ceil()은 올림을 수행한다는 점, 둘째, decimals 매개변수로 소수점 자릿수를 지정할 수 있다는 점, 셋째, trunc()는 0 방향으로 자르는 특수한 동작을 한다는 점입니다.
이러한 특징들이 정확한 금융 계산과 깔끔한 데이터 표현을 가능하게 만듭니다.
코드 예제
import numpy as np
# 할인 계산 후 불규칙한 가격들
prices = np.array([12345.6789, 8765.4321, 5432.9876])
# 소수점 둘째 자리까지 반올림 (일반적인 가격 표시)
rounded_prices = np.round(prices, 2)
print(f"반올림 가격: {rounded_prices}") # [12345.68 8765.43 5432.99]
# 정수로 반올림 (간단한 표시)
int_prices = np.round(prices)
print(f"정수 가격: {int_prices}") # [12346. 8765. 5433.]
# 내림 (floor) - 항상 아래로
floor_prices = np.floor(prices)
print(f"내림 가격: {floor_prices}") # [12345. 8765. 5432.]
# 올림 (ceil) - 항상 위로
ceil_prices = np.ceil(prices)
print(f"올림 가격: {ceil_prices}") # [12346. 8766. 5433.]
# 0 방향으로 자르기 (trunc) - 소수점 버림
trunc_prices = np.trunc(prices)
print(f"자르기 가격: {trunc_prices}") # [12345. 8765. 5432.]
# 백원 단위로 반올림 (실용적!)
# 방법: 100으로 나누고 반올림 후 다시 100 곱하기
hundred_rounded = np.round(prices / 100) * 100
print(f"백원 단위: {hundred_rounded}") # [12300. 8800. 5400.]
# 음수에서의 동작 차이
negatives = np.array([-2.7, -2.5, -2.3])
print(f"floor: {np.floor(negatives)}") # [-3. -3. -3.] 더 작은 쪽
print(f"ceil: {np.ceil(negatives)}") # [-2. -2. -2.] 더 큰 쪽
print(f"trunc: {np.trunc(negatives)}") # [-2. -2. -2.] 0 방향
설명
이것이 하는 일: NumPy의 반올림 함수들은 배열의 각 숫자를 지정된 규칙에 따라 정수나 특정 소수점 자릿수로 변환합니다. 데이터를 다듬는 필수 도구입니다.
첫 번째로, np.round(prices, 2) 부분은 각 가격을 소수점 둘째 자리까지만 남기고 반올림합니다. 왜 이렇게 하는지 설명하면, 화폐는 보통 센트나 전 단위까지만 표현하므로 소수점 둘째 자리가 적절하기 때문입니다.
decimals=2는 "소수점 아래 2자리까지"를 의미하고, decimals=0이나 생략하면 정수로 반올림됩니다. 두 번째로, np.floor(prices) 함수가 실행되면서 각 숫자보다 작거나 같은 최대 정수를 반환합니다.
내부에서는 항상 아래쪽 정수로 내립니다. 예를 들어 12345.9도 12345가 되고, -2.1도 -3이 됩니다(더 작은 정수 쪽으로).
할인 쿠폰 계산처럼 "최대 이만큼까지"를 보장해야 할 때 유용합니다. 세 번째로, np.ceil(prices) 코드는 반대로 항상 위로 올립니다.
5432.1도 5433이 되는 식이죠. 마지막으로, np.trunc(prices) 함수는 소수점을 그냥 잘라버립니다.
floor와 비슷하지만, 음수에서 다르게 동작합니다. trunc(-2.7)은 -2인데(0 방향), floor(-2.7)은 -3입니다(더 작은 정수).
여러분이 이 코드를 사용하면 금융 계산의 정확성을 보장하고, 리포트의 가독성을 높이고, 부동소수점 오차를 제거할 수 있습니다. 실무에서는 결제 시스템, 회계 프로그램, 데이터 시각화, 통계 리포트 등에서 이런 반올림 함수들을 매일 사용합니다.
실전 팁
💡 파이썬의 round()와 NumPy의 round()는 음수 자릿수를 다르게 처리합니다. round(1234, -2)는 1200이 됩니다(백의 자리).
💡 금융 계산에서는 Decimal 타입을 고려하세요. float는 정확한 10진수 표현이 불가능해서 오차가 누적될 수 있습니다.
💡 반올림 후 정수로 사용하려면 .astype(int)로 타입을 변환하세요. np.round()의 결과는 여전히 float입니다.
💡 대량의 데이터를 반올림할 때는 np.around()를 사용할 수도 있습니다. np.round()의 별칭이지만 더 명확할 수 있습니다.
💡 "반올림 후 합계"가 원래 합계와 달라지는 문제를 조심하세요. 중요한 계산에서는 합계를 마지막에 반올림하세요.
7. 배열 비교와 조건 연산 - 데이터 필터링
시작하며
여러분이 학생 100명의 시험 점수를 가지고 있는데, 80점 이상인 학생이 몇 명인지 알고 싶다고 가정해봅시다. 또는 음수 값을 모두 0으로 바꿔야 하는 경우도 있습니다.
이런 조건부 처리는 데이터 분석의 핵심입니다. 이상치를 제거하거나, 특정 조건을 만족하는 데이터만 선택하거나, 값의 범위를 제한하는 작업은 매일 일어납니다.
바로 이럼 때 필요한 것이 NumPy의 비교 연산과 조건 함수입니다. 불린 인덱싱, where, clip 같은 기능으로 데이터를 자유롭게 필터링하고 변환할 수 있습니다.
개요
간단히 말해서, NumPy의 비교 연산은 배열의 각 요소를 조건과 비교해서 True/False 배열을 만들고, 이를 이용해 데이터를 선택하거나 변환하는 기능입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 실제 데이터는 항상 완벽하지 않습니다.
예를 들어, 센서 데이터에 오류 값(-999)이 섞여 있거나, 고객 나이에 비현실적인 값(200세)이 들어 있는 경우가 많습니다. 이런 문제를 자동으로 감지하고 수정하려면 조건 연산이 필수입니다.
기존에는 if 문과 반복문으로 하나씩 체크했다면, 이제는 arr > 80 같은 벡터화된 비교로 전체를 한번에 처리할 수 있습니다. NumPy 비교 연산의 핵심 특징은 첫째, 모든 비교 연산자(<, >, ==, !=)가 배열 전체에 적용된다는 점, 둘째, 결과를 불린 배열로 받아 인덱싱에 사용할 수 있다는 점, 셋째, where, clip 같은 고급 함수로 복잡한 조건 처리가 가능하다는 점입니다.
이러한 특징들이 데이터 정제와 탐색 분석을 실용적으로 만듭니다.
코드 예제
import numpy as np
# 학생들의 시험 점수
scores = np.array([85, 92, 78, 95, 88, 72, 90, 65, 98, 80])
# 80점 이상인지 비교 (불린 배열 생성)
passed = scores >= 80
print(f"합격 여부: {passed}") # [T T F T T F T F T T]
# 합격한 학생 수 세기
num_passed = np.sum(passed) # True=1, False=0으로 계산됨
print(f"합격자 수: {num_passed}") # 7명
# 합격한 학생들의 점수만 추출 (불린 인덱싱)
passed_scores = scores[passed]
print(f"합격 점수: {passed_scores}") # [85 92 95 88 90 98 80]
# 조건에 따라 값 변경: 60점 미만은 0으로 (재시험)
adjusted = np.where(scores < 60, 0, scores)
print(f"조정된 점수: {adjusted}")
# 복잡한 조건: 90점 이상은 'A', 80점 이상은 'B', 나머지는 'C'
grades = np.where(scores >= 90, 'A',
np.where(scores >= 80, 'B', 'C'))
print(f"등급: {grades}") # ['B' 'A' 'C' 'A' 'B' 'C' 'A' 'C' 'A' 'B']
# 값의 범위 제한 (clip): 70~95 사이로 제한
clipped = np.clip(scores, 70, 95)
print(f"제한된 점수: {clipped}") # [85 92 78 95 88 72 90 70 95 80]
# 여러 조건 결합: 80점 이상이면서 90점 미만
between = (scores >= 80) & (scores < 90)
print(f"80~90점: {scores[between]}") # [85 88 80]
설명
이것이 하는 일: NumPy의 비교 연산은 배열의 각 요소를 조건과 비교해서 True/False로 이루어진 불린 배열을 만듭니다. 이 불린 배열을 마스크로 사용해서 원하는 데이터만 선택하거나 변환할 수 있습니다.
첫 번째로, scores >= 80 부분은 각 점수를 80과 비교해서 불린 배열을 생성합니다. 왜 이렇게 하는지 설명하면, 이 불린 배열이 "어떤 학생이 조건을 만족하는지"를 기록하는 마스크 역할을 하기 때문입니다.
np.sum(passed)를 하면 True가 1로, False가 0으로 취급되어 합격자 수를 셀 수 있습니다. 두 번째로, scores[passed] 같은 불린 인덱싱이 실행되면서 True인 위치의 값만 추출됩니다.
내부에서는 NumPy가 마스크를 순회하며 True인 인덱스의 데이터만 모아서 새 배열을 만듭니다. SQL의 WHERE 절과 비슷한 개념이죠.
세 번째로, np.where(condition, x, y) 함수는 조건이 True인 곳은 x를, False인 곳은 y를 선택합니다. 마지막으로, np.clip(scores, 70, 95) 함수는 70보다 작으면 70으로, 95보다 크면 95로 제한합니다.
이렇게 하면 이상치를 자동으로 제거할 수 있어서, 머신러닝 전처리나 데이터 시각화에서 매우 유용합니다. 여러분이 이 코드를 사용하면 데이터를 조건에 따라 필터링하고, 이상치를 제거하고, 복잡한 비즈니스 로직을 간결하게 구현할 수 있습니다.
실무에서는 고객 세그먼트 분류, 센서 오류 감지, 금융 데이터 검증, A/B 테스트 분석 등에서 이런 조건 연산을 매일 활용합니다.
실전 팁
💡 여러 조건을 결합할 때는 &(AND), |(OR), ~(NOT)을 사용하고, 각 조건을 괄호로 묶으세요: (a > 0) & (a < 10)
💡 np.where()는 3차원 이상 배열에서도 작동합니다. 브로드캐스팅 규칙이 적용되어 매우 유연합니다.
💡 불린 인덱싱은 복사본을 만듭니다. 원본을 수정하려면 arr[condition] = new_value 형태로 대입하세요.
💡 any()와 all() 함수로 조건을 요약할 수 있습니다. np.any(scores < 60)는 "60점 미만이 하나라도 있나?"를 확인합니다.
💡 대용량 데이터에서는 불린 인덱싱이 메모리를 많이 쓸 수 있습니다. 가능하면 np.where()나 np.compress()를 고려하세요.
8. 분산과 표준편차 - 데이터의 퍼짐 정도 측정
시작하며
여러분이 두 영업팀의 월별 매출을 비교한다고 생각해보세요. A팀은 평균 1000만원, B팀도 평균 1000만원입니다.
같은 성과일까요? 아닙니다!
A팀은 매달 9501050만원으로 안정적이고, B팀은 5001500만원으로 들쭉날쭉할 수 있습니다. 이런 "데이터가 얼마나 흩어져 있는지"를 측정하는 것은 평균만큼이나 중요합니다.
투자 위험도 평가, 제품 품질 관리, 성능 예측 가능성 등 모든 분야에서 필요합니다. 바로 이럴 때 필요한 것이 분산과 표준편차입니다.
데이터가 평균에서 얼마나 멀리 퍼져 있는지를 수치로 알려줍니다.
개요
간단히 말해서, 분산(variance)은 각 데이터가 평균에서 떨어진 거리의 제곱의 평균이고, 표준편차(standard deviation)는 분산의 제곱근입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 평균만으로는 데이터의 특성을 완전히 파악할 수 없습니다.
예를 들어, 제조 공정에서 제품 무게의 평균은 맞지만 편차가 크다면 불량품이 많이 나오는 것입니다. 주식 투자에서 수익률의 표준편차가 크다면 위험한 투자라는 뜻이죠.
기존에는 손으로 (각 값 - 평균)²을 계산하고 평균내고 제곱근을 구했다면, 이제는 np.std() 한 줄이면 됩니다. 분산과 표준편차의 핵심 특징은 첫째, 표준편차가 원래 데이터와 같은 단위를 가져서 해석이 쉽다는 점, 둘째, 작을수록 데이터가 평균 주변에 몰려 있다는 점, 셋째, 이상치에 민감해서 극단값의 영향을 크게 받는다는 점입니다.
이러한 특징들이 리스크 관리와 품질 관리의 핵심 지표가 됩니다.
코드 예제
import numpy as np
# 두 영업팀의 월별 매출 (단위: 만원)
team_a = np.array([950, 980, 1020, 1010, 990, 1030, 1000, 1010])
team_b = np.array([500, 1200, 800, 1500, 900, 1300, 700, 1100])
# 평균은 둘 다 비슷
print(f"A팀 평균: {np.mean(team_a):.1f}만원") # 1000만원
print(f"B팀 평균: {np.mean(team_b):.1f}만원") # 1000만원
# 하지만 표준편차는 크게 다름!
std_a = np.std(team_a)
std_b = np.std(team_b)
print(f"A팀 표준편차: {std_a:.1f}만원") # 23.5만원
print(f"B팀 표준편차: {std_b:.1f}만원") # 307.9만원
# 분산 (표준편차의 제곱)
var_a = np.var(team_a)
var_b = np.var(team_b)
print(f"A팀 분산: {var_a:.1f}") # 552.3
print(f"B팀 분산: {var_b:.1f}") # 94875.0
# 변동계수 (CV): 표준편차를 평균으로 나눈 값 (상대적 변동성)
cv_a = std_a / np.mean(team_a) * 100
cv_b = std_b / np.mean(team_b) * 100
print(f"A팀 변동계수: {cv_a:.1f}%") # 2.4%
print(f"B팀 변동계수: {cv_b:.1f}%") # 30.8%
# ddof 매개변수: 표본 vs 모집단
# ddof=0: 모집단 표준편차 (기본값)
# ddof=1: 표본 표준편차 (통계학에서 더 일반적)
sample_std = np.std(team_a, ddof=1)
print(f"표본 표준편차: {sample_std:.1f}") # 25.0 (약간 더 큼)
설명
이것이 하는 일: 표준편차는 데이터가 평균에서 평균적으로 얼마나 떨어져 있는지를 측정합니다. 데이터의 "퍼짐 정도" 또는 "일관성"을 하나의 숫자로 요약하는 강력한 도구입니다.
첫 번째로, np.std(team_a) 부분은 복잡한 계산을 수행합니다. 왜 이렇게 하는지 설명하면, 단순히 "평균과의 차이"를 평균내면 양수와 음수가 상쇄되어 0이 되기 때문입니다.
그래서 제곱을 해서 모두 양수로 만들고, 평균을 낸 후(분산), 다시 제곱근을 구해서(표준편차) 원래 단위로 돌아옵니다. 두 번째로, 표준편차가 실행되면서 A팀은 23.5, B팀은 307.9라는 결과가 나옵니다.
내부적으로는 각 팀의 매출이 평균에서 얼마나 벗어나는지를 모두 계산한 것입니다. B팀의 표준편차가 13배나 크다는 것은 매출 예측이 매우 어렵고 리스크가 높다는 뜻입니다.
세 번째로, 변동계수(CV)를 계산하면 서로 다른 스케일의 데이터를 비교할 수 있습니다. 마지막으로, ddof 매개변수는 자유도를 조정합니다.
전체 모집단 데이터면 ddof=0, 표본 데이터면 ddof=1을 사용하는 것이 통계학적으로 올바릅니다. 표본 표준편차가 약간 더 큰 이유는 표본이 모집단보다 덜 퍼져 있을 가능성을 보정하기 때문입니다.
여러분이 이 코드를 사용하면 데이터의 안정성을 평가하고, 투자 리스크를 비교하고, 제품 품질을 관리할 수 있습니다. 실무에서는 주식 포트폴리오 분석, 제조 공정 관리(6 시그마), 서버 응답 시간 모니터링, A/B 테스트 신뢰도 평가 등에서 표준편차를 핵심 지표로 사용합니다.
실전 팁
💡 표준편차는 이상치에 매우 민감합니다. 로버스트한 측정이 필요하면 IQR(사분위수 범위)를 고려하세요.
💡 정규분포에서는 평균±1σ 안에 68%, ±2σ 안에 95%의 데이터가 들어갑니다. 이를 활용해 이상치를 정의할 수 있습니다.
💡 표본 데이터를 분석할 때는 항상 ddof=1을 사용하세요. Pandas는 기본값이 ddof=1이지만 NumPy는 ddof=0입니다.
💡 변동계수(CV)는 단위가 다른 데이터를 비교할 때 유용합니다. 예: 매출액(만원)과 고객수(명)의 변동성 비교
💡 대용량 데이터의 표준편차는 np.std(arr, dtype=np.float64)로 정밀도를 높이면 수치 오차를 줄일 수 있습니다.
9. 백분위수와 사분위수 - 데이터 분포의 세밀한 분석
시작하며
여러분이 입사 시험 점수를 받았는데 "당신은 상위 25%입니다"라는 말을 들었다고 생각해보세요. 이게 정확히 무슨 뜻일까요?
평균이나 최댓값만으로는 알 수 없습니다. 이런 "상대적 위치"를 파악하는 것은 데이터 분석에서 매우 중요합니다.
연봉 협상, 성적 평가, 고객 세분화 등 순위와 분포가 중요한 모든 상황에서 필요합니다. 바로 이럴 때 필요한 것이 백분위수(percentile)와 사분위수(quartile)입니다.
데이터를 정렬했을 때 특정 위치의 값이 무엇인지 알려줍니다.
개요
간단히 말해서, 백분위수는 데이터를 정렬했을 때 하위 몇 퍼센트에 해당하는 값인지를 나타내고, 사분위수는 25%, 50%, 75% 지점의 특별한 백분위수입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 평균은 이상치에 크게 영향을 받지만 백분위수는 그렇지 않습니다.
예를 들어, 연봉 분포에서 CEO 한 명이 수십억을 받으면 평균이 왜곡되지만, 중앙값(50번째 백분위수)은 영향받지 않습니다. 또한 상위 10% 고객이 매출의 50%를 차지하는지 같은 비즈니스 인사이트를 얻을 수 있습니다.
기존에는 데이터를 직접 정렬하고 인덱스를 계산했다면, 이제는 np.percentile(data, 25) 한 줄로 해결됩니다. 백분위수의 핵심 특징은 첫째, 중앙값(median)이 50번째 백분위수라는 점, 둘째, IQR(사분위수 범위)로 이상치를 탐지할 수 있다는 점, 셋째, Box plot 같은 시각화의 기초가 된다는 점입니다.
이러한 특징들이 로버스트한 통계 분석을 가능하게 만듭니다.
코드 예제
import numpy as np
# 100명 학생의 시험 점수 시뮬레이션
np.random.seed(42)
scores = np.random.normal(75, 15, 100) # 평균 75, 표준편차 15
# 사분위수 계산
q1 = np.percentile(scores, 25) # 1사분위수 (하위 25%)
q2 = np.percentile(scores, 50) # 2사분위수 = 중앙값
q3 = np.percentile(scores, 75) # 3사분위수 (하위 75%)
print(f"Q1 (하위 25%): {q1:.1f}점") # 약 67점
print(f"Q2 (중앙값): {q2:.1f}점") # 약 76점
print(f"Q3 (하위 75%): {q3:.1f}점") # 약 85점
# IQR (Inter-Quartile Range): 중간 50%의 범위
iqr = q3 - q1
print(f"IQR: {iqr:.1f}점") # 약 18점
# 이상치 탐지: Q1 - 1.5*IQR 미만 또는 Q3 + 1.5*IQR 초과
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
outliers = (scores < lower_bound) | (scores > upper_bound)
print(f"이상치 개수: {np.sum(outliers)}개")
# 여러 백분위수 한번에 계산
percentiles = np.percentile(scores, [10, 25, 50, 75, 90])
print(f"10, 25, 50, 75, 90 백분위: {percentiles}")
# quantile 함수: 0~1 비율로 지정 (percentile과 동일하지만 단위 다름)
median = np.quantile(scores, 0.5) # 0.5 = 50%
print(f"중앙값 (quantile): {median:.1f}점")
# 특정 점수의 백분위 순위 계산 (역방향)
my_score = 85
rank = (scores < my_score).sum() / len(scores) * 100
print(f"{my_score}점은 상위 {100-rank:.1f}%")
설명
이것이 하는 일: 백분위수 함수는 데이터를 오름차순 정렬했을 때, 전체의 몇 퍼센트 위치에 해당하는 값이 무엇인지 알려줍니다. 분포의 형태를 파악하는 강력한 도구입니다.
첫 번째로, np.percentile(scores, 25) 부분은 데이터를 정렬한 후 하위 25% 지점의 값을 찾습니다. 왜 이렇게 하는지 설명하면, 100명 중 25번째 학생의 점수가 바로 1사분위수(Q1)이기 때문입니다.
25% 학생이 이 점수 이하이고, 75% 학생이 이 점수 이상인 것이죠. 평균과 달리 극단값의 영향을 받지 않아 더 안정적입니다.
두 번째로, IQR(사분위수 범위) 계산이 실행되면서 Q3 - Q1을 구합니다. 내부적으로는 중간 50% 학생의 점수 범위를 나타냅니다.
이 범위가 작으면 대부분 학생이 비슷한 점수를 받았다는 뜻이고, 크면 실력 차이가 크다는 뜻입니다. 통계학에서는 "Q1 - 1.5IQR 미만 또는 Q3 + 1.5IQR 초과"를 이상치로 정의합니다.
세 번째로, np.percentile() 함수에 리스트를 전달하면 여러 백분위수를 한번에 계산할 수 있습니다. 마지막으로, 역방향 계산도 가능합니다.
"(scores < my_score).sum()"으로 내 점수보다 낮은 학생 수를 세어서, 전체 대비 비율을 구하면 내 백분위 순위를 알 수 있습니다. 85점이면 상위 25% 정도라는 식으로요.
여러분이 이 코드를 사용하면 데이터의 분포를 세밀하게 파악하고, 이상치를 객관적으로 탐지하고, 상대적 위치를 정확히 평가할 수 있습니다. 실무에서는 고객 세그먼트 분류(상위 20% VIP), 서버 성능 모니터링(95번째 백분위 응답 시간), 연봉 협상 근거, 제품 품질 관리 등에서 백분위수를 핵심 지표로 사용합니다.
실전 팁
💡 중앙값은 평균보다 이상치에 로버스트합니다. 연봉, 부동산 가격 같은 왜곡된 분포에서는 중앙값을 사용하세요.
💡 np.quantile()과 np.percentile()은 같지만 단위가 다릅니다. quantile은 01, percentile은 0100 범위입니다.
💡 interpolation 매개변수로 계산 방식을 조절할 수 있습니다. 'linear', 'lower', 'higher', 'midpoint', 'nearest' 중 선택하세요.
💡 Box plot은 Q1, Q2, Q3와 이상치를 시각화하는 표준 방법입니다. matplotlib의 plt.boxplot()을 사용하세요.
💡 대용량 데이터에서는 np.quantile()이 np.sort() + 인덱싱보다 훨씬 빠릅니다. 전체 정렬이 필요없기 때문입니다.
10. 누적 합과 누적 곱 - 시계열 변환
시작하며
여러분이 매일 몇 명의 신규 고객이 가입하는지 기록하고 있다고 생각해보세요. 그런데 경영진이 "지금까지 총 몇 명이 가입했나요?"라고 물어봅니다.
매일의 합계를 계속 더해야 하죠. 이런 "누적" 계산은 시계열 데이터에서 정말 자주 필요합니다.
일별 매출을 누적해서 월 누적 매출을 보거나, 분기별 이익을 누적해서 연간 성장 추세를 파악하는 경우가 많습니다. 바로 이럴 때 필요한 것이 NumPy의 누적 함수입니다.
cumsum, cumprod 같은 함수로 각 시점까지의 누적값을 자동으로 계산할 수 있습니다.
개요
간단히 말해서, 누적 합(cumsum)은 배열의 각 위치까지의 합계를 계산하고, 누적 곱(cumprod)은 각 위치까지의 곱을 계산하는 기능입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 많은 비즈니스 지표는 누적값으로 표현됩니다.
예를 들어, 스타트업에서는 일별 가입자보다 누적 가입자 추세가 더 중요하고, 투자에서는 일별 수익률보다 누적 수익률이 실제 자산 증가를 나타냅니다. 또한 이동 평균 같은 고급 분석의 기초가 됩니다.
기존에는 반복문으로 누적 변수를 계속 업데이트했다면, 이제는 np.cumsum(arr) 한 줄로 전체 누적 배열을 얻을 수 있습니다. 누적 함수의 핵심 특징은 첫째, 결과 배열이 원본과 같은 크기라는 점(각 위치의 누적값), 둘째, 시계열 데이터의 추세를 시각화할 때 매우 유용하다는 점, 셋째, 재무 분석에서 복리 계산에 누적 곱이 필수적이라는 점입니다.
이러한 특징들이 시계열 분석과 금융 모델링을 실용적으로 만듭니다.
코드 예제
import numpy as np
# 일주일간 일별 신규 가입자 수
daily_signups = np.array([12, 15, 8, 23, 19, 11, 17])
# 누적 가입자 수 (각 날짜까지의 총합)
cumulative_signups = np.cumsum(daily_signups)
print(f"일별 가입: {daily_signups}")
print(f"누적 가입: {cumulative_signups}")
# [12 27 35 58 77 88 105]
# 일주일 총 가입자는 마지막 값
total = cumulative_signups[-1]
print(f"일주일 총 가입자: {total}명") # 105명
# 투자 수익률 예제: 일별 수익률 1% ~ -1%
daily_returns = np.array([0.01, 0.02, -0.01, 0.015, 0.005, -0.008, 0.012])
# 누적 수익률 계산 (복리): (1+r1) * (1+r2) * (1+r3) ...
# 먼저 각 수익률을 1+r로 변환
multipliers = 1 + daily_returns
# 누적 곱 계산
cumulative_multiplier = np.cumprod(multipliers)
print(f"누적 수익 배율: {cumulative_multiplier}")
# [1.01 1.0302 1.0199... 최종 약 1.044]
# 최종 수익률 (4.4% 증가)
final_return = (cumulative_multiplier[-1] - 1) * 100
print(f"일주일 총 수익률: {final_return:.2f}%") # 4.4%
# 초기 투자금 100만원일 때 각 날의 자산
initial_investment = 100
daily_assets = initial_investment * cumulative_multiplier
print(f"일별 자산: {daily_assets}")
# 2차원 배열에서 축 지정
# 월별(행) × 주차별(열) 매출
sales = np.array([
[100, 120, 110, 130], # 1월
[140, 150, 145, 160] # 2월
])
# 주차별 누적 (axis=1: 가로 방향)
weekly_cumsum = np.cumsum(sales, axis=1)
print(f"주차별 누적 매출:\n{weekly_cumsum}")
# [[100 220 330 460]
# [140 290 435 595]]
설명
이것이 하는 일: 누적 함수는 배열을 순회하면서 각 위치까지의 합이나 곱을 계속 누적해서, 원본과 같은 크기의 새 배열을 만듭니다. 시계열 추세를 파악하는 핵심 도구입니다.
첫 번째로, np.cumsum(daily_signups) 부분은 첫 번째 값은 그대로(12), 두 번째 값은 첫+두(12+15=27), 세 번째는 첫+두+세(27+8=35) 이런 식으로 계산합니다. 왜 이렇게 하는지 설명하면, 각 날짜 시점에서 "지금까지 총 몇 명이 가입했나"를 알려주기 때문입니다.
그래프로 그리면 상승하는 곡선이 나오는데, 이것이 성장 추세를 보여줍니다. 두 번째로, 투자 수익률에서 np.cumprod(1 + daily_returns)가 실행됩니다.
내부에서는 복리 계산이 일어납니다. 첫날 1% 수익이면 자산이 1.01배, 둘째날 2% 추가 수익이면 1.01 × 1.02 = 1.0302배가 되는 식이죠.
단순히 수익률을 더하면(1%+2%=3%) 복리 효과를 놓치지만, 누적 곱을 쓰면 정확합니다. 세 번째로, 2차원 배열에서 axis=1을 지정하면 각 행에 대해 가로 방향으로 누적합을 계산합니다.
마지막으로, 1월의 [100, 220, 330, 460]은 "1주차 100, 12주차 220, 13주차 330, 1~4주차(월 전체) 460"을 나타냅니다. 월별 목표 달성률을 추적할 때 매우 유용합니다.
여러분이 이 코드를 사용하면 시계열 데이터의 추세를 시각화하고, 복리 수익률을 정확히 계산하고, 누적 목표 달성률을 추적할 수 있습니다. 실무에서는 매출 누적 차트, 포트폴리오 성과 추적, 프로젝트 진행률 모니터링, 생산 라인 누적 생산량 등에서 누적 함수를 매일 활용합니다.
실전 팁
💡 diff() 함수는 cumsum()의 역연산입니다. np.diff(np.cumsum(arr))는 원본 배열을 복원합니다(첫 요소 제외).
💡 cumprod()는 큰 숫자에서 빠르게 오버플로우될 수 있습니다. 로그 공간에서 계산하는 것을 고려하세요.
💡 이동 평균(moving average)은 cumsum()과 슬라이싱으로 효율적으로 구현할 수 있습니다.
💡 Pandas Series는 .cumsum() 메서드를 제공하며, 인덱스를 유지해서 더 편리합니다.
💡 대용량 시계열에서 메모리가 부족하면 청크 단위로 나누어 누적 계산을 수행하되, 이전 청크의 마지막 값을 다음 청크 시작 값으로 사용하세요.
댓글 (0)
함께 보면 좋은 카드 뉴스
데이터 증강과 정규화 완벽 가이드
머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.
ResNet과 Skip Connection 완벽 가이드
딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.
CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet
컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.
CNN 기초 Convolution과 Pooling 완벽 가이드
CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.
TensorFlow와 Keras 완벽 입문 가이드
머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.