어제는 NumPy 배열을 만들고 차원을 확인했다면, 오늘은 배열의 형태를 바꾸고 연산하는 방법을 배웠다. 이어서 pandas의 Series를 만들고, 인덱스로 값을 찾거나 조건에 맞게 묶는 실습을 했다.
np.arange()로 연속된 값을 만들고 reshape()로 모양을 바꿨다.
arr4 = np.arange(12)
arr4.reshape(3, 4)
arr4.reshape(6, 2)
arr4.reshape(2, 2, 3)
같은 12개 값으로 2차원과 3차원 배열을 만들 수 있었다. 반면 reshape(2, 2, 2, 2)는 16개 값이 필요한 형태라 오류가 났다.
ValueError: cannot reshape array of size 12 into shape (2,2,2,2)
형태를 바꿔도 전체 원소 수는 맞아야 한다. -1로 한 축의 크기를 자동 계산하게 하는 방법도 다뤘다.
arr12 = np.arange(120).reshape(2, -1, 12)
# shape: (2, 5, 12)
ravel()과 flatten()으로 1차원으로 펼치고, expand_dims()로 축을 추가하거나 squeeze()로 길이가 1인 축을 제거하는 것도 연습했다. astype()으로 자료형을 바꾸고, 0이나 1로 채운 배열과 난수를 만드는 함수도 사용했다.
브로드캐스팅 예제에서는 (4, 3) 배열에 (3,) 배열을 더했다.
arr1 = np.arange(12).reshape(4, 3)
arr2 = np.arange(3)
print(arr1 + arr2)
[[ 0 2 4]
[ 3 5 7]
[ 6 8 10]
[ 9 11 13]]
[0, 1, 2]가 각 행에 더해진 결과다. 크기가 다르면 무조건 맞춰주는 것은 아니다. 뒤쪽 축부터 비교해 길이가 같거나, 둘 중 하나가 1이어야 한다. 없는 축은 길이 1로 취급한다. NumPy 브로드캐스팅 문서
노트북의 (4,)와 (4, 3) 덧셈에서는 마지막 축의 4와 3이 맞지 않아 오류가 났다. 1차원 배열에 .T를 붙여도 shape는 (4,) 그대로였다. 전치했다고 자동으로 열 형태의 배열이 되는 것은 아니었다.
axis에 따라 합계가 어떻게 달라지는지도 확인했다.
arr = np.arange(15).reshape(3, 5)
np.sum(arr, axis=0) # [15 18 21 24 27]
np.sum(arr, axis=1) # [10 35 60]
이 2차원 예제에서는 axis=0이 각 열의 합계, axis=1이 각 행의 합계가 됐다. 3차원 배열에서도 축을 바꿔가며 결과를 비교했다.
최댓값을 반환하는 max()와 그 위치를 반환하는 argmax()도 구분했다. 또 배열 전체에 조건을 적용해 필요한 값만 꺼내봤다.
is_bound = (arr6 % 2 == 1) & (arr6 < 50)
arr6[is_bound]
홀수이면서 50보다 작은 값을 고르는 조건이다. 실습 배열에서는 [1, 29]가 나왔다.
pandas에서는 Series부터 시작했다. 값과 함께 인덱스를 지정할 수 있는 1차원 데이터다.
names = ["홍길동", "장보고", "이순신"]
scores = [70, 80, 65]
pd.Series(data=scores, index=names)
딕셔너리를 전달해 키를 인덱스로 사용하는 방식도 다뤘다. 라벨 하나로 조회하면 값 하나가 나오고, 라벨을 리스트로 감싸 조회하면 항목이 하나여도 Series로 반환되는 차이를 확인했다.
Series끼리 더할 때는 인덱스를 기준으로 값이 맞춰진다. 상품 가격 예제에서는 양쪽에 모두 있는 상품만 가격이 더해지고, 한쪽에만 있는 상품에는 NaN이 나왔다. 단순히 첫 번째 값끼리 더하는 방식과는 달랐다. pandas Series 문서
결측값을 나타내는 np.nan을 넣은 Series에서는 len()과 count()를 비교했다.
s1 = pd.Series([10, 2, 3, 3, np.nan, 2, 3, 5, 7, 8, np.nan, np.nan])
len(s1) # 12
s1.count() # 9
전체 길이는 12지만, 결측값을 제외한 개수는 9였다. value_counts()로 값별 빈도를 확인하고, head()와 tail()로 앞뒤 일부를 살펴봤다.
조건에 맞는 값만 고르는 필터링, str.contains()로 문자열을 찾는 방법, drop()으로 인덱스 항목을 삭제하는 방법도 연습했다. drop()은 기본적으로 원본을 그대로 두고 결과를 반환했고, inplace=True를 사용한 예제에서는 원본이 바뀌었다.
마지막으로 이름이 반복되는 수입 데이터에서 groupby()로 같은 인덱스를 묶었다.
s1.groupby(s1.index).count()
s1.groupby(s1.index).sum()
s1.groupby(s1.index).mean()
이름별 기록 개수, 합계, 평균을 각각 구했다. s1 >= 600을 기준으로 묶은 예제에서는 조건을 만족하는 데이터가 6건이었다. 같은 이름이 반복되므로 이 값을 서로 다른 사람 6명으로 해석하면 안 된다.
NumPy에서는 shape와 axis를, Series에서는 인덱스와 결측값을 함께 봐야 했다. 값만 보고 계산하기보다 어떤 구조와 기준으로 연산되는지 먼저 확인하는 연습이었다.
다음에는 배열의 shape를 보고 연산 가능 여부를 먼저 예상해보고, Series 집계도 데이터 건수와 중복을 제외한 개수를 구분하며 복습하려고 한다.