[파이썬 데이터 분석가 되기] 책 1주차 스터디 노트
파이썬 데이터 분석가 되기 + 챗GPT | 셀레나 - 교보문고
파이썬 데이터 분석가 되기 + 챗GPT | ★ 파이썬으로 데이터 분석을 하고 싶다면? ★ 파이썬 입문 그다음에 꼭 보세요! ★ ‘패스트캠퍼스’, ‘메가스터디’ 셀레나 쌤과 함께 실패 없이 완주하
product.kyobobook.co.kr
[스터디 노트 작성에 앞서...]
케글 대회 참여나 팀프로젝트를 진행하면서 주어진 데이터를 분석하는 과정을 가졌지만, 데이터 분석에 대해 진심으로 다가간 적은 없던 것 같다.
사실 어떻게 하면 데이터 분석을 '잘' 하는지 몰랐기도 하고 여러 모델들을 사용하고 튜닝을 통해 모델링 성능을 끌어올리는데 급급했던 것이 부끄럽지만... 내 과거다.
이번 책 스터디를 기회로 데이터를 분석하는 기법을 제대로 숙지하고, 데이터를 보는 시각을 확장시켜 보기로 다짐했다.
이 책을 공부하고 나만의 시각으로 스터디 노트를 작성해서 레벨업 하는 과정을 공유해보려 한다.
(글을 열심히 작성하겠지만 추후 코테 문제를 풀면서 아하?! 이런 것도 있구나 하는 부분도 추가될 수도 있다. maybe...)
[스터디 참여 1주 차]
- 스터디 내용
- 스터디는 위 책을 기반으로 진행했다.
- 책 1장 [수치 계산 라이브러리, 넘파이]을 공부했다.
- 코랩 실습 파일을 기반으로 코드를 작성하여 배운 내용을 실시간으로 복습했다.
- 코랩 연습문제 파일을 기반으로 코드를 작성하여 넘파이를 제대로 숙지했는지 확인했다.
- 내 공부 방법
- 책을 보면서 각 내용을 읽으면서 개인적으로 중요하다고 생각하는 부분을 체크했다.
- 교재 기반 코랩 실습파일과 정답파일도 있어서 듀얼 모니터로 화면을 띄우고 내가 작성한 코드와 정답코드의 출력값을 비교했다.
- 작성한 코드에 에러가 있거나, 요구하는 출력값을 잘 출력했지만 조금 더 알았으면 하는 부분의 경우에는 책을 다시 살펴봤고, 그럼에도 충족되지 않았다면 챗GPT에게 내가 작성한 코드가 어떠한 원리로 작동하는지 질문했다.
- 위의 과정으로 알게 된 내용의 경우에는 스터디노트에 따로 작성하기 위해 정리했고, 실습코드에도 주석으로 설명을 덧붙였다.
- 넘파이를 공부하기에 앞서, 데이터 분석이란 무엇일까?
- 데이터로부터 유의미한 정보나 인사이트를 추출하는 모든 과정
- 방대한 양의 데이터를 수집, 정제, 가공한 다음 통계적 기법이나 머신러닝, 시각화 기법 등을 활용하여 현재 상황을 분석하거나 미래를 예측하고, 이를 의사결정에 활용하는 과정
- 데이터 분석이 필요한 이유?
- 인사이트 도출
- 문제 해결
- 미래 예측
- 효율성 개선
- 데이터 분석의 기본 과정
- 문제 정의(이 분석의 최종 목표는 무엇인가?)
- 데이터 수집(웹 크롤링 등)
- 데이터 정제(결측값, 이상치 처리, 인코딩 등)
- 데이터 탐색 EDA(통계적 요약, 시각화 등을 통해 데이터 관계나 패턴 파악)
- 모델링 & 분석(통계분석 or 머신러닝을 적용해 인사이트를 도출)
- 결과 해석 및 시각화(분석 결과를 쉽고 명확하게 전달, 그래프 차트로 시각화)
- 결론(분석 결과를 바탕으로 실무에서 적용할 전략 수립)
이제 본론으로 넘어와서 간략하게 내가 공부한 내용을 작성하겠다.
내가 Ebook으로 교재를 구매해서 공부했다면 저자님의 허락을 구하고 책의 설명 부분을 끌어왔겠지만
종이책으로 구매했기에 그러한 부분은 아쉽지만 생략.
책에 있는 모든 내용을 스터디 노트로 작성하는 건 책을 복붙 하는 것이라 그럴 수 없고
내가 느꼈을 때 핵심적인 부분이라 생각하는 부분만 작성하겠다.
+ 코드 부분은 설명이 필수적인 부분만 첨부
[1.1 넘파이란 뭘까?]
넘파이는 Numerical Python의 줄임말이다.
파이썬 기반의 수치 계산 라이브러리인데, 데이터를 분석할 때 수치 계산을 해야 할 일이 있는데 그럴 때 사용한다.
다차원 배열 연산을 수행할 수 있는 라이브러리라 생각하면 되겠다.
수학에서 행렬을 배우고 계산했던 기억이 있을 텐데, 그것을 코드로 실현시켜 줄 수 있는 라이브러리다.
[1.2 넘파이 배열, ndarray]
책에도 적혀있다시피 넘파이의 핵심은 n-dimensional array이다.
행렬은 2차원 배열이지만 ndarray는 1차원, 2차원, 3차원 배열 등 다양한 차원을 지원한다.
- 파이썬 리스트와 넘파이 배열의 차이점?
파이썬 리스트와 배열 둘 다 다양한 수치연산이 가능하지만 차이점이 있음
파이썬 리스트 * 2의 결과는 리스트를 2번 이어 붙이지만
넘파이 배열 * 2의 결과는 배열의 각 요소에 2를 곱한 결과가 나온다.
- 2차원 배열은 어떻게 사용할까?
데이터 분석에서 2차원 배열은 표(table) 형태로 구성
가로를 행(row), 세로를 열(column)이라 함
예시가 있으면 좋을 것 같아서 실습코드 부분을 가져왔다.
import numpy as np
# 2차원 파이썬 리스트를 ndarray로 변환
arr_2 = np.array(
[[1,2,3],
[4,5,6],
[7,8,9]]
)
print(arr_2)
쉽게 말하자면 1차원 배열을 또 다른 배열이 감싼 형태라고 보면 된다.
- 그러면 3차원 배열은 어떻게 사용할까?
3차원 배열은 큐브 형태라고 생각하면 된다.
가로, 세로, 높이를 가지는 정육면체로 나타낼 수 있다.
import numpy as np
# 3차원 파이썬 리스트를 넘파이 배열로 변환
arr_3 = np.array(
[[[1,2,3],[4,5,6]],
[[7,8,9],[10,11,12]],
[[13,14,15],[16,17,18]]]
)
print(arr_3)
쉽게 말해 2차원 배열을 또 다른 배열이 감싸고 있는 형태이다.
1차원 배열을 모아서 만들면 2차원 배열이고
2차원 배열을 모아서 만들면 3차원 배열이다.
- 넘파이 배열의 축 개념 이해 (★)
내가 개인적으로 바로 이해하지 못한 부분이다.
(축은 axis라고 한다)
이해력이 좋으신 분들은 자료 사진만 보고 '아. 넘파이 배열은 이런 식으로 축 개념을 가지는구나?' 할 수 있지만
아쉽게도 나는 머리가 좋은 편이 아니라 그러지 못했다.
내가 이해하기로는 1차원 배열의 형태일 때 (4,) axis 0 =4
2차원 배열 형태일 때 (2, 4) axis 0 = 2, axis 1 = 4
3차원 배열 형태일때 (4, 3, 2) axis 0 = 4, axis 1 = 3, axis 2 = 2
내가 이해하지 못한 부분은 축 번호가 늘어나는 과정이다.
1차원 배열의 axis 0이 2차원 배열에서는 axis 1이 되어버린 것!!! (대체 왜? 머리에서 인지부조화가 와버린 것)
어떤 사악한 사람이 이런 장난을 쳤나 싶었지만 축 번호가 매겨지는 과정을 본다면 납득할 수 있다.
말로만 설명하기 어려워서 실습코드의 설명 부분을 가져왔다. (출처 : 파이썬 데이터 분석가되기 1장 실습코드)
아까 이야기했지만, 2차원 배열은 1차원 배열이 모여서 만들어진 형태다.
위 사진을 보면 축의 번호는 바깥쪽부터 안쪽으로 순서대로 매겨진다.
결과를 보면 가장 바깥쪽 대괄호는 첫 번째 축 axis 0에 해당하며
그다음 안쪽 대괄호는 두 번째 축 axis 1에 해당한다.
axis 0의 크기 : 2, axis 1의 크기 : 3
그렇다면 3차원 배열에서는 어떻게 축 번호가 매겨질까?
3차원 배열도 2차원 배열과 마찬가지로 축의 번호를 바깥족부터 안쪽으로 매긴다.
결과를 보면 가장 바깥쪽 대괄호는 첫 번째 축 axis 0
그 안쪽 대괄호는 두 번째 축 axis 1
가장 안쪽의 대괄호가 세 번째 축 axis 2이다.
axis 0의 크기 : 4, axis 1의 크기 : 2, axis 2의 크기 : 3
워낙 책의 설명이 잘 되어있어서 나 같은 둔재도 이해할 수 있었다.
- 넘파이 배열의 데이터 타입
이 코드 부분만 알아두면 데이터 타입 확인하는 게 끝난다.
import numpy as np
arr_1 = np.array([1,2,3,4,5])
print(arr_1.dtype)
.dtype을 기억해 두고 데이터 타입을 확인해야 할 때 사용해 보자.
데이터 타입과 데이터 타입 코드가 적혀있는 표가 있는데 구글링 하면 쉽게 나온다.
데이터 분석하다가 모르겠다면 해당 표를 확인하면 될 것 같다.
참고로 위 코드 실행 결과로 나온 int64는 정수로 구성된 넘파이 배열의 데이터 타입이다.
- 넘파이 배열의 장점?
1. 메모리 사용 효율이 좋고, 계산 속도가 빠르다.
2. 선형 대수 연산 지원
3. 벡터화 연산 지원
자세한 설명은 생략하겠다.
궁금하다면 책을 구매해서 읽어보자(ㅎㅎ)
- 다양한 방법으로 넘파이 배열 생성 (★)
위에서도 계속 사용한 방법처럼 파이썬 리스트로 넘파이 배열을 생성할 수도 있지만, 다른 방법도 있다.
종류가 많으므로 번호를 매겨보자.
1. 파이썬 리스트로 넘파이 배열 생성
2. 파이썬 튜플로 넘파이 배열 생성
c = np.array(('i', 'j', 'k'))
print(c)
print(c.dtype)
+ 불리언 데이터도 가능
d = np.array((True, True, False))
print(d)
print(d.dtype)
3. dtype 매개변수로 넘파이 배열의 문자열 길이 지정
e = np.array(['ab', 'cd', 'e'], dtype='<U1')
print(e)
print(e.dtype)
e = np.array(['ab', 'cd', 'e'], dtype='<U2')
print(e)
print(e.dtype)
4. 초기화 함수로 넘파이 배열 생성
함수 기본형 : np.zeros(shape, [dtype])
# (3, 4) 크기의 배열을 생성하여 0으로 채움
print(np.zeros(shape = (3,4)))
# (3, 4) 크기의 배열을 데이터 타입을 정수로 생성한 후, 0으로 채움
print(np.zeros((3,4),dtype=int))
5. 모든 요소가 1로 초기화된 넘파이 배열 생성
함수 기본형 : np.ones(shape, [dtype])
# (2, 3, 4) 크기의 배열을 생성하여 데이터 타입을 정수로 설정한 후, 1로 채움
print(np.ones((2,3,4), dtype=int))
참고로 1로 초기화된 배열은 이미지 처리 작업에서 mask 배열을 생성하고 초기화할 때 주로 사용함
* 마스크란? 이미지 처리 작업에서 특정 영역을 선택 or 필터링하는 데 사용되는 배열
6. 모든 요소가 비어 있는 넘파이 배열 생성
# (2, 3) 크기의 빈 배열 생성
print(np.empty((2,3)))
참고로 empty() 함수로 만든 배열은 처음에는 아무 의미 없는 값들로 채워져 있다.
큰 배열을 빠르게 만들기만 해야 할 때 사용
7. 일정한 간격의 넘파이 배열 생성
(1) arange() 함수로 넘파이 배열 생성
함수 기본형 : np.arange(start, stop, step, dtype)
# 10 이상 30 미만까지 5 간격으로 생성(매개변수 생략)
print(np.arange(10, 30, 5))
# 0 이상 2 미만까지 0.3 간격으로 생성(매개변수 표시)
print(np.arange(start = 0, stop = 2, step = 0.3))
(2) linspace() 함수로 넘파이 배열 생성
함수 기본형 : np.linspace(start, stop, num, endpoint, restep, dtype)
# 1 이상 10 이하까지 값을 가진 크기 10짜리 배열 생성
print(np.linspace(start = 1, stop = 10, num = 10, dtype = int))
!! 주의사항 : 매개변수 순서를 지키지 않고 인수를 전달하여 함수를 사용하면 오류가 발생함!!
+ arange() 함수와 linspace()함수 비교
쓰이는 곳이 다름 : arange() 함수는 시계열 데이터 처리, linspace() 함수는 일정한 구간 데이터를 생성할 때 사용
범위를 지정하는 방식이 다름 : arange() 함수는 끝 범위를 포함하지 않음, linspace() 함수는 끝 범위를 포함
반환 요소 개수를 지정하는 방식이 다름 : arange()는 간격을 step으로 직접 지정, linspace()는 요소 개수를 num으로 지정
- 넘파이 배열 속성 이해하고 출력하기
[1.3 넘파이 배열로 다양하게 연산]
- 요소별 연산 (★)
배열의 각 요소에 독립 연산을 수행한다는 의미
두 배열이 있을 때 같은 인덱스의 요소끼리 연산하는 것을 요소별 연산이라고 함
요소별 연산으로 가능한 연산
1. 사칙연산
# a와 b 배열 생성하여 출력
a = np.array([10, 20, 30, 40])
b = np.arange(start = 1, stop = 5)
print(a)
print(b)
# 덧셈 연산
print(a+b)
# 뺄셈 연산
print(a-b)
# 곱셈 연산
print(a*b)
# 나눗셈 연산
print(a/b)
2. 행렬 곱셈
참고 : 행렬 곱셈 연산자는 *가 아니라 @이다.
# A와 B 배열 생성
A = np.array(
[[1,1],
[0,1]])
B = np.array(
[[2,0],
[3,4]])
# A * B : 요소별 곱셈 연산
print(A * B)
# A @ B : 행렬 곱셈 사용 (이건 뭐지 처음 보는데... 아하!!)
print(A @ B)
3. 비교 연산
참고 : 비교 연산은 배열 요소들을 비교하여 조건에 맞는 데이터를 필터링할 때 사용
ex) 특정 임계값을 초과하는 데이터를 찾는 작업
# A와 B 배열 생성
A = np.array([1, 2, 3, 4])
B = np.array([1, 1, 3, 3])
# 비교 연산
print(A > B)
print(A == B)
- 벡터화 연산
벡터화 연산은 반복문 없이 배열의 모든 요소에 연산을 적용하는 것
ex) sqrt() 함수로 배열의 요소별 제곱근을 계산하여 반환할 수 있음
- 집계 함수와 함께 벡터화 연산 (★)
실제로 여러 번 사용했어서 기억에 남아있는 함수들이다.
집계 함수는 데이터 요약, 그룹화한 데이터 분석, 데이터 필터링에 사용된다.
# a 배열 생성 & 출력
# 0부터 8 미만까지 출력하고 (2, 4) 크기로 재가공하고 제곱하여 출력
a = np.arange(start = 0, stop = 8).reshape(2,4)**2
print(a)
# 모든 요소의 합
print(a.sum())
# 모든 요소 중 평균
print(a.mean())
# 모든 요소 중 최솟값
print(a.min())
# 모든 요소 중 최댓값
print(a.max())
# 모든 요소의 누적합
print(a.cumsum())
# 모든 요소 중 최댓값의 인덱스
print(a.argmax())
+ 집계 함수에 축 개념 사용
# 0부터 8미만까지 출력하고 (2, 4) 크기로 재가공하고 제곱하여 출력
# a == array([[ 0, 1, 4, 9], [16, 25, 36, 49]])
a = np.arange(8).reshape(2,4)**2
# axis = 0은 열 기준으로 연산
print(a.mean(axis = 0))
# axis = 1은 행 기준으로 연산
print(a.max(axis = 1))
[1.4 배열 인덱싱과 슬라이싱]
코테 공부할 때도 배웠던 인덱싱과 슬라이싱 부분이다.
넘파이는 인덱싱과 슬라이싱을 지원하고 이를 이를 이용해서 배열에서 원하는 요소 또는 부분 배열을 선택할 수 있다.
개인적으로 알고 있는 부분이기 때문에 자세하게 다루지는 않겠다.
기본적인 것만 적어두면 파이썬에서는 배열의 인덱스가 0부터 시작된다.
- 인덱싱
a = np.array([0, 1, 2, 3, 4, 5])
print(a)
# 인덱스가 0인, 인덱스가 3인 요소 출력
print(a[0])
print(a[3])
2차원 배열에서도 인덱스를 당연히 적용할 수 있는데 앞에서 말한 axis 부분을 이해하지 못했다면 헷갈릴 수도 있다.
# 2차원 배열의 특정 위치를 선택
b = np.array([[0, 1, 2], [3, 4, 5]])
print(b)
# 인덱스가 [1, 0]인, 인덱스가 [1, 2]인 요소 출력
print(b[1,0])
print(b[1,2])
- 슬라이싱
슬라이싱은 배열의 부분 집합을 선택하는 방법이다.
A[0:3]과 같이 콜론 : 을 사용하여 인덱스 범위를 선택한다.
시작 인덱스 값은 결과에 포함하고, 끝 인덱스의 값을 결과에 포함하지 않는다.
a = np.array([0, 1, 2, 3, 4, 5])
print(a)
# 인덱스 0 이상 인덱스 3 미만 범위의 요소 출력
print(a[0:3])
- 논리형 인덱싱 (★)
이름에 논리가 들어간다고 전혀 어려워할 필요가 없는 부분이다.
배열에서 조건에 따라 요소를 선택하는 방법이다.
코드 예시를 통해 이해할 수 있다.
# 2차원 배열에서 4보다 큰 값에 일대일 대응하는 논리형 배열 생성
a = np.arange(12).reshape(3, 4)
print(a)
# a > 4 조건을 적용한 논리형 배열 생성
b = a > 4
print(b)
b = a > 4라는 조건을 적용한 논리형 배열을 생성했다.
그리고 b 논리형 배열의 True 값에 해당하는 a 배열 요소들을 선택해서
해당 요소들의 값을 우리가 원하는 값으로 변경할 수 있다.
방금 말한 부분들은 아래 코드로 구현이 되어있다.
코테 문제를 풀 때에도 사용할 수 있으니 숙지를 꼭 해야 하는 부분이다!
# 논리형 배열 b를 사용하여 조건에 맞는 요소 추출
print(a[b])
# 추출한 요소들의 배열 모양
print(a[b].shape)
# 논리형 배열인 b를 이용하여 원본 a 배열의 값 변경하기(True인 값의 위치에 변경하기)
a[b] = 1000
print(a)
- 정수 배열 인덱싱
배열이 있을 때, 내가 원하는 요소들을 선택해서 쏙쏙 빼온 뒤에 새로운 배열로 만들 수 있다.
한마디로 골라먹기 가능
어떻게 하면 잘 골라 먹을 수 있는지 코드를 보면서 이해해 보도록 하자
a = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
# i 배열에 a 배열에서 추출할 인덱스 값을 지정
i = np.array([1, 1, 3, 4])
# 배열 a에서 배열 i에 지정된 인덱스의 원소들을 출력 / 해당 인덱스 값으로 새 배열이 생성됨
print(a[i])
# 2차원 배열에서도 적용할 수 있는지 확인
j = np.array([[3, 4], [5, 6]])
# 배열 a에서 배열 j에 지정된 인덱스의 원소들을 출력
print(a[j])
+ 응용
원하는 순서로 배열의 요소들을 재배열할 수도 있음!
b = np.array(['a', 'b', 'c', 'd'])
k = np.array([3, 2, 1, 0])
# 배열 b에서 배열 k에 지정된 인덱스의 원소들을 재배열
b = b[k]
print(b)
[1.5 배열의 형태 변형]
넘파이 배열은 형태를 변형할 수도 있다.
how? -> reshape() 함수와 resize() 함수를 사용해서 변형할 수 있음
- reshape() 함수
배열의 형태를 변형하여 새 배열을 반환하는 함수다.
풀어 말하면 원본 배열을 변경하지는 않고, 원본 배열의 형태를 변형한 새로운 배열을 반환한다.
이때 원본 배열의 데이터 순서는 유지하고 배열의 차원이나 모양만 변경하는 게 포인트.
그리고 요소 개수는 변경할 수 없다. 억지로 변경하려 한다면 오류가 발생한다.
# a 배열 생성
a = np.arange(12).reshape(3, 4)
print(a)
print(a.shape)
print(a.ndim)
# a 배열을 다시 reshape( ) 함수로 변형하여 b에 저장
# .reshape : 지정한 차원으로 변경하고 새로운 배열 반환
b = a.reshape(2, 6)
print(b)
print(b.shape)
# 원본 a 배열은 기존의 (3, 4) 형태 유지
print(a)
print(a.shape)
- reshape() 함수에 -1을 인수로 전달하면?
reshape() 함수에 인수로 -1을 전달하면 변형하려는 배열에 맞게 요소 개수를 맞춰준다.
아래 코드를 예로 들면, 함수의 첫 번째 인수를 -1로 지정하면 해당하는 축은 자동으로 4로 계산해서
배열의 형태를 (4,3)으로 결정함
# 1번째 매개변수에 -1을 전달해도 같은 결과가 나옴(자동 계산)
c = a.reshape(-1, 3)
print(c)
+ 참고사항
-1은 특별히 예약된 값으로, -2 같은 값을 넣으면 그러한 기능이 없다.
- resize() 함수
resize() 함수는 위에서 다룬 reshape() 함수와는 다르게 원본 배열 자체를 변형함
한마디로 새 배열을 반환하지 않고 원본 배열의 구조를 변경하는 데 사용됨
데이터 순서는 그대로 유지하면서 배열의 차원과 모양만을 재조정함
# .resize() : 지정한 차원으로 변경하고 원본 배열 자체를 변경
a = np.arange(12)
a.resize(4, 3)
print(a)
print(a.shape)
# 따로 반환하는 값 없음
print(a.resize(4, 3))
+ 참고사항
resize() 함수는 원본 배열을 변경만 하고 따로 반환값이 없음
따라서 위 코드와 같이 resize(4,3) 함수의 반환값은 None임
- 현업에서 사용하는 reshape() 함수와 resize() 함수
1. 다양한 형태의 배열 사용 : reshape() 함수와 resize() 함수
2. 데이터 무결성 유지 : reshape() 함수. 원본 배열 변경 x. 새로운 배열 반환하여 데이터의 무결성을 유지
3. 메모리 관리 : resize() 함수. 원본 배열 직접 변경하여 메모리 효율적으로 관리
- 다차원 배열을 1차원 배열로 변형하기
다차원 배열을 1차원 배열로 변형할 때는 ravel() 함수와 reshape(-1) 함수를 사용
ravel() 함수는 다차원 배열을 1차원 배열로 평탄화(flatten)함
위에서 언급한 reshape() 함수에 -1을 인자로 전달하는 방법을 사용할 수도 있음
# (3, 4) 형태의 배열을 1차원 배열로 평탄화
a = np.arange(12).reshape(3, 4)
# .ravel : 1차원 배열로 크기 변경
print(a.ravel())
# .reshape(-1) : 1차원 배열로 크기 변경
print(a.reshape(-1))
- 전치 연산하기
전치(transpose)는 넘파이 2차원 배열에서 사용함
전치 연산은 행렬에서 행과 열을 서로 바꾸는 연산을 말함
학부생 때 미적분학? 공부하면서 행렬 부분 계산할 때 전치 연산 관련 문제를 풀어본 적 있는 것 같음(정확하진 않음 ㅠ)
쉽게 생각해서 a[1][0]을 a[0][1]로 바꾼다고 생각하면 됨
넘파이 라이브러리에서는 T 속성을 사용하여 2차원 배열의 전치를 계산할 수 있음!
.T를 붙여서 사용함
A = np.arange(12).reshape(2, 6)
print(A)
# .T : [2, 6]을 [6, 2] 반환으로 전치(transpose) 변환
print(A.T)
print(A.T.shape)
# 만약 전치 형태로 원본 A 배열에 저장하고 싶다면, A배열에 A.T 값을 할당
A = A.T
print(A)
print(A.shape)
+ 참고사항
전치 연산은 현업에서 데이터 분석, 이미지 처리에 사용한다.
[1.6 배열 합치고 분할하기]
넘파이 라이브러리는 다차원 배열 연산을 지원하므로, 배열을 합치거나 분할하는 함수도 제공함
- 배열 합치기
넘파이 라이브러리의 vstack() 함수와 hstack() 함수는 다차원 배열을 수직 및 수평으로 합치는 데 사용함
(함수 기본 형태)
np.vstack ((배열1, 배열2, ...))
np.hstack((배열1, 배열2, ...))
여기서 v는 vertical(수직), h는 horizontal(수평)이라는 의미
# a 배열, b 배열 생성
a = np.array([1, 2, 3, 4]).reshape(2, 2)
b = np.array([5, 6, 7, 8]).reshape(2, 2)
# 수직으로 합치기
print(np.vstack((a,b)))
# 수평으로 합치기
print(np.hstack((a,b)))
+ 의문점 해결(?!)
Q) 왜 위 코드에서 np.vstack(a, b)라고 작성하지 않고 괄호를 하나 더 사용했을까?
A) np.vstack() 함수 정의를 살펴보면 인자로 "단일 리스트, 튜플 등"을 받게 되어있음
만약 np.vstack(a, b)처럼 쓰면, 함수 정의에 맞지 않는 두 개의 인자 a와 b를 별도로 넘기는 형태가 되어 오류가 발생
(TypeError)
반면 np.vstack((a, b))처럼 쓰면, (a, b)가 튜플 형태로 묶여서 "하나의 인자"로 전달이 됨
이때 함수 내부에서는 이 튜플 안에 들어있는 a, b를 순회하며 수직으로 이어 붙이게 되는 것
정리하면 np.vstack() 인자는 여러 배열을 담고 있는 한 덩어리여야 함
그래서 (a, b) 튜플로 묶어서 하나의 인자에 담아주거나, [a, b] 리스트에 담아 한 번에 넘겨줘야 함
ex) np.vstack((a, b)) or np.vstack([a, b]) 형태로 사용해야 함
- 배열 분할하기
위에서 사용한 vstack, hstack처럼 분할하는 함수도 존재함
넘파이 라이브러리의 vsplit() 함수와 hsplit() 함수는 배열을 수직 또는 수평으로 분할하는 데 사용함
(함수 기본형)
np.vsplit((배열1, 배열2, ...))
np.hsplit((배열1, 배열2, ...))
# a 배열, b 배열 생성
a = np.arange(12).reshape(2, 6)
print(a)
# 수직으로 분할하기
print(np.vsplit(a, 2))
# 수평으로 분할하기
print(np.hsplit(a, 3))
[연습문제]
총 10문제가 있는데, 내가 주석으로 설명을 추가한 문제는 그중 3문제다.
그 3문제의 코드만 글에서 다루도록 하겠다! 나머지는 생략
## 열 단위로 총합 계산
column_sums = combined_data.sum(axis = 0)
total_comment_length = column_sums[0]
total_likes = column_sums[1]
'''
[코드 설명]
1. sum(axis = 0)를 쓰는 이유?
axis = 0 은 행을 따라 (세로방향) 연산을 수행하겠다는 의미
즉, 각 열(column)에 대해 합계를 구한다는 뜻
axis = 1은 열을 따라 (가로방향) 연산을 수행한다는 의미
즉, 각 행(row)에 대해 합계를 구한다는 의미
axis = 0 (기본값)은 배열의 모든 원소를 하나로 봐서 전체 합을 구한다는 의미
-> 이때는 스칼라(단일 값) 결과가 나옴
그러면 sum(axis = 0)를 안 쓰면 어떤 일이 생길까?
combined_data.sum() (즉 axis = none)은 배열 안 모든 원소를 합쳐서 스칼라 값을 반환
그 결과, 예를 들어 column_sums = combined_data.sum()를 하면 column_sums는 더 이상 배열이 아니라
'단일 숫자'가 되어버림
단일 숫자는 인덱싱(ex. columns_sums[0])을 할 수 없기 때문에, TypeError같은 오류가 발생함
따라서 열 별 합계를 구하기 위해서는 sum(axis = 0)가 필수적임!
2. column_sum(0)가 아닌 column_sums[0]를 써야 하는 이유?
파이썬에서 함수 호출은 () 소괄호, 인덱싱은 [] 대괄호를 사용
column_sums(0)는 column_sums라는 함수를 인자로 0을 넣어서 호출하겠다는 의미
(column_sums는 함수가 아닌 배열임)
column_sums[0]는 column_sums의 첫 번째 원소(index 0)을 가져오겠다는 의미
column_sums가 넘파이 배열일 때는, 함수가 아닌 배열 객체이므로 당연히 ()로 호출할 수 없음
배열의 특정 원소를 얻으려면 인덱싱을 해야 하므로 []를 써야함!
'''
## 열 단위로 평균 계산
column_means = combined_data.mean(axis = 0)
average_comment_length = column_means[0]
average_likes = column_means[1]
'''
[어떤 과정으로 최종 합계/평균이 계산되는가?]
combined_data가 문제 2번에 구한 것 처럼 있음
열별 합계를 구하기 위해 sum(axis =0) 실행
첫 번째 열(댓글 길이) 합계 : 150 + 200 + 50 + 300 + 120 + 180 + 75 + 160
두 번째 열(좋아요 수) 합계 : 25 + 30 + 10 + 45 + 20 + 35 + 5 + 25
세 번째 열(기타 컬럼) 합계 : 0 + 0 + 1 + 0 + 0 + 1 + 0 + 0
계산 후 column_sums는 모양이 (3,)인 1차원 배열이 된다~
[1235, 195, 2]
열별 평균을 구하기 위해 mean(axis = 0) 실행하면
첫 번째 열(댓글 길이) 평균 : 1235/8
두 번째 열(좋아요 수) 평균 : 195/8
세 번째 열(기타 컬럼) 평균 : 2/8
이 결과도 (3,) 크기의 1차원 배열이 된다~
따라서 column_sums[0], column_sums[1] 처럼 인덱싱을 통해
첫 번째 컬럼과 두 번째 컬럼의 합/평균을 각각 꺼내서 사용할 수 있음
'''
## 결과 확인
print("댓글 길이 총합:", total_comment_length)
print("좋아요 수 총합:", total_likes)
print("댓글 길이 평균:", average_comment_length)
print("좋아요 수 평균:", average_likes)
## 첫 번째(인덱스 0), 세 번째(인덱스 2), 다섯 번째(인덱스 4) 고객의 데이터 추출
selected_customers = combined_data[[0, 2, 4], :]
'''
[코드 설명]
[0, 2, 4]는 뭘까?
행(row) 인젝스로 0, 2, 4만 선택하겠다는 의미
첫 번째, 세 번째, 다섯 번째 행을 가져온다는 의미
: 는 뭘까?
열(column)인덱스로 모든 열을 선택하겠다는 의미
즉 해당 행에 대해서 열을 전부 가져온다는 뜻
'''
## 결과 확인
print(selected_customers)
## 조건을 만족하는 고객 수 계산
num_selected_customers = selected_customers.shape[0]
'''
[코드 설명]
selected_customers.shape는 배열의 (행 크기, 열 크기)를 나타내는 tuple임
예를 들어 ~~~.shape가 (5, 3)이라면 5행 3열 배열이라는 뜻
shape[0]는 그 중 첫 번째 값인 "행의 개수"를 의미함
selected_customers.shape[0] 를 하면 현재 배열에 몇 행의 데이터가 있는지 알 수 있음
(몇 명의 고객 데이터가 있는지)
'''
## 결과 확인
print("상품을 받을 고객 수:", num_selected_customers)
[1주 차 스터디노트 작성 후기]
책과 실습코드로 공부를 한 뒤에 이렇게 글로 공부한 부분을 정리하고
내가 어려움을 느꼈던 부분도 나름대로 설명을 추가했다.
나는 공부를 시간으로 밀어붙이는 타입이라 똑똑한 사람들처럼 효율적인 공부를 하지 못하는 것을 스스로 알기에
내가 이번에 공부한 방법이 제대로 공부하고 있는 방법인지 의문이 계속 들었다.
피드백을 통해서 파이썬 데이터 분석가 되기 책을 잘 공부할 수 있는지
스터디 노트는 어떻게 정리하는 게 좋은 방법인지 알고 싶다.
아무튼 넘파이 부분 공부를 했고, 이를 바탕으로 코딩테스트 문제도 풀어본 뒤 풀이도 공유할 예정이다.
[추가로 정리할 부분]
1. 파이썬에서 초보들이 많이 헷갈리는 대괄호와 소괄호 사용 방법2. 왜 np.vstack((a,b))라고 쓰고 np.vstack(a,b)라고 쓰지 않을까? (완료)
3. 연습문제에서 코드 구현에서 이해가 되지 않은 부분 솔루션 작성 + 내 연습문제 풀이 코드 공유 (★) (완료)
4. 코테 문제에서 넘파이(배열)를 사용한 문제들 공유 (★)