넘파이는 파이썬 리스트를 확장해서 만들었기 떄문에 리스트가 제공하는 대부분의 기능을 사용할 수 있다 인덱싱에 사용하는 인덱스는 리스트와 동일하게 데이터 하나에 하나씩 맵핑됩니다
!pip install numpy
import numpy as np
arr = np.arange(4).reshape(2,2)#arnage 함수로 0~3 범위의 데이터가 저장된 ndarray를 만들고 reshape 메서드로 2행 2열로 변환한다
print(arr[0])
#[0 1]
#한번에 인덱싱하는 코드 (한번에 인덱싱하는 코드가 1.5배정도 더 빠르다
a=np.arange(10000).reshape(100, 100)
%timeit a[0, 50]
#출력: 176 ns ± 54.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
#출력되는 결과를 참고하면 반복 실행한 결과 176 ns(nano second)의 시간이 평균적으로 소모된걸 알 수 있습니다 참고로 1초는 1000ms이며 1ms는 1000us이고 1us는 1000ms입니다
주피터 노트북은 timeit이라는 특수 명령을 제공합니다. timeit뒤에 나오는 코드를 1000만 번 반복 실행하고 걸린 시간의 평균을 반환합니다
#두 번에 결쳐 연쇄적으로 인덱싱하는 시간을 측정하는
a=np.arange(10000).reshape(100, 100)
%timeit a[0][50]
#출력: 270 ns ± 49.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
#270ns가 걸렸다
1: target = [ 0, 2 ]#가져오려는 데이터의 인덱스를 리스트로 정의한다 이때 인덱스는 데이터 하나하나에 맵핑되는 숫자이다
2: print(arr[ target ])#가여오려는 데이터의 위치 정보가 담긴 리스트를 사용해서 ndarray를 슬라이싱 합니다
arr = np.arange(4)
print(arr[ [0, 2] ])#바깥쪽의 대괄호는 arr 변수와 함께 사용했으니 리시트의 인덱싱/슬라이싱 기호이고 안쪽은 대괄호 앞에 변수 없이 홀로 사용됐으므로 리스트의 정의 기호입니다
arr = np.arange(20).reshape(4, 5)#4행 5열로 구성된 ndarray
print(arr[ :2])#두 개의 행을 슬라이싱 합니다 여기까지는 리스트의 슬라이싱과 크게 다를 것이 없습니다
arr = np.arange(20).reshape(4, 5)
print(arr[ 1:4, 2:5])
print(arr[ 1: , 2: ])#행은 2이고 열은 1이다
#아래의 코드와 위의 코드는 똑같다 굳이 target을 쓸필요 없이 이중 괄호로 지정할 수 있다
#: target = [ 1: , 2: ]#1행 2열
#: print(arr[ target ])
기초 파이썬 문법에서는 반복문을 사용해서 전체 데이터에 연산을 적용했던 것과는 달리 넘파이는 연산이 전체 데이터로 확장됩니다. 반복문을 사용하지 않으니 코드가 짧아지고 읽기도 좋아집니다
우선 같은 크기를 갖는 두 개의 ndarray간의 연산을 살펴보겠습니다.
a = np.array( [ 1, 2, 3] )
b = np.array( [ 2, 3, 4] )
print( a + b )
print( a * b )
print( a % b )
#[3 5 7] 두 배열을 더한 값
#[ 2 6 12]두 배열을 곱한 값
#[1 2 3]두 배열을 나눈값
스칼라 연산은 전체 데이터에 확장 적용됩니다
a = np.array( [ 1, 2, 3] )
print( a + 3 )
#[4 5 6] 3이라는 숫자가 하나 있다면 배열1,2,3에 한번에 해줄 수 있다
| a | 1 | 2 | 3 |
|---|---|---|---|
| 3 | → | → | |
브로드캐스팅이 필요하다는 것은 연산하려는 ndarray 일부가 작은 크기를 갖는다는 것으로, 작은 크기의 ndarray가 확장 가능한지를 체크해야 합니다. 조건-1에서 언급하는 뒤 축은 이차원 데이터에서 열을 가리킵니다. 이차원 데이터는 (행, 열) 형태로 표시하기 때문에 뒤에 있는 열을 보고 확장 여부를 알 수 있습니다. 그림 2.5.2에서 왼쪽 ndarray는 shape (2, )로 표시되지만, 물리적으로는 (1, 2) 공간에 데이터가 들어있습니다. 그림 2.5.2에서 오른쪽은 이차원 데이터이며 (3, 2) shape을 갖습니다. 두 ndarray의 뒤 축이 2로 같으므로 작은 블록인 왼쪽의 ndarray는 브로드캐스팅(확장)될 수 있습니다.
shape (2, )의 ndarray는 첫 번째 행을 복사해서 그림 2.5.3의 음영 처리된 것과 같은 3행 2열의 ndarray로 브로드캐스팅됩니다. 이제는 두 ndarray의 shape이 완전히 같아졌기 때문에 같은 위치에 있는 데이터끼리의 연산을 지원할 수 있습니다.
두 번째 조건은 축의 길이가 1일 경우에는 브로드캐스팅을 할 수 있다는 뜻입니다. 그림 2.5.4의 왼쪽은 (3, 1), 오른쪽은 (3, 2)의 shape을 갖습니다. 뒤 축 열의 길이가 1과 2로 다르지만 조건-2를 충족하므로 작은 크기의 왼쪽 ndarray는 3행 2열 형태로 브로드캐스팅됩니다.
만약 위에서 언급한 조건을 충족하지 못한 경우에는 ValueError가 발생합니다.
두 개의 1차원 ndarray 간의 브로드캐스팅을 살펴봅시다. 그림을 그려서 브로드캐스팅이 가능한지 추측해 보겠습니다. 그림의 (1)을 참고하면 2개의 값을 갖는 b를 a의 앞과 뒤 두 개의 세트에 연산을 적용할 수 있습니다. 혹은 그림의 (2)와 같이 슬라이딩 윈도우 방식으로 반복 연산할 “수도” 있습니다. 두 ndarray의 어느 위치에 연산을 적용할지 명확하지 않죠? 그래서 넘파이는 위 두 개의 ndarray에 대한 브로드캐스팅을 할 수 없습니다.
ndarray와의 연산은 브로드캐스팅이 적용되어 전체 데이터에 연산이 반복 적용됩니다. 다음은 ndarray와 스칼라의 비교 연산 결과를 출력합니다.
arr = np.arange(8).reshape(4, 2)#0~7까지 있는 리시트를 생성하고 4행 2열의 배열을 생성한다
print(arr.sum())#sum메서드를 적용하면 전체 데이터의 합이 계산됩니다
#28
arr = np.arange(8).reshape(4, 2)
print(arr.sum())
print(arr.sum(axis=0))#axis=0은 열 방향으로 합을 구하는 것입니다 열 방향으로 합을 구하는 것입니다. 즉, 각 열을 기준으로 합을 계산합니다. 즉, 각 열을 기준으로 합을 계산합니다
print(arr.sum(axis=1))#axis=1은 행 방향으로 합을 구하는 것입니다. 즉, 각 행을 기준으로 합을 계산합니다.
배열에서 각 행을 더해봅시다
#[[0 1]
#[2 3]
#[4 5]
#[6 7]]
#28
#[12 16]
#[ 1 5 9 13]
#axis=0이 row 기준이니 가로 방향으로 갈 것으로 예상이 되어 헷갈리지만
#진행방향은 반대라고 생각하는게 마음이 편하다.
numpy를 사용해서 임의의 숫자를 만들 수도 있습니다. randint 함수는 입력된 범위에서 임의의 정수를 반환합니다. 다음 코드는 0부터 2까지 범위에서 임의의 숫자를 하나 선택합니다. 실행할 때마다 숫자가 변경됩니다.
np.random.randint(3)#radint 함수는 입력된 범위에서 임의의 정수를 반환합니다 다음 코드는 0부터 2까지 범위에서 임의의 숫자를 하나 선택합니다
1: np.random.randint(46, size=5)#0부터 45까지의 수를 5개 생성해서 ndarray로 반환합니다.
2: np.random.randint(46, size=(2, 5))#0부터 45까지의 수를 2행 5열의 이차원 ndarray로 반환합니다.
판다스의 시리즈는 일차원 데이터를 관리하는 자료구조로, 데이터와 함께 인덱스(index)라는 것을 사용해서 데이터에 레이블을 달아둘 수 있습니다. 그림의 (a)는 ndarray의 예제로 세 개의 데이터가 저장돼 있습니다. 그림의 (b)는 시리즈의 예제로 데이터에 맵핑되는 날짜 정보가 함께 연결돼 있습니다. 시리즈에서는 데이터에 맵핑되는 레이블을 인덱스라고 부릅니다. 연관된 값이 함께 저장돼 있으니 데이터의 의미를 파악하기가 쉽죠? 딕셔너리처럼 인덱스를 사용해 킷값으로 데이터를 가져올 수도 있습니다. 게다가 시리즈는 ndarray를 확장해서 만들어서 ndarray가 지원하는 숫자 인덱싱과 슬라이싱을 사용할 수 있습니다. 브로드캐스팅도 사용할 수 있고요.
#기본 설정
!pip install pandas
import pandas as pd
from pandas import Series#판다스에서 시리즈를 가져온다, 시리즈는 1차원이다 2차원이 안된다
import numpy as np
from pandas import Series
data = [10, 20, 30]
s = Series(data)
print(s)
#0 10
#1 20
#2 30
#dtype: int64
data = ["시가", "고가"]
s = Series(data)
print(s)
#0 시가
#1 고가
#dtype: object
s = Series(['samsung', 81000])
print(s)
#0 samsung
#1 81000
#dtype: object
파이썬의 딕셔너리를 사용하면 데이터에 레이블을 붙여서 저장할 수 있는 것처럼 시리즈도 각 데이터에 인덱스를 설정할 수 있습니다. 인덱스의 설정을 하지 않으면, 0부터 시작하는 숫자 값을 RangeIndex 타입으로 생성합니다. 시리즈를 만들고 인덱스를 얻어와 보겠습니다.
from pandas import Series
data = [1000, 2000, 3000]
s = Series(data)
print(s.index)
print(s.index.to_list())
#인덱스 설정을 하지 않으면 0부터 시작하는 숫자 값을 Rangelndex 타입으로 생성한다
#RangeIndex(start=0, stop=3, step=1)시작이 = 0, 멈추는게 = 3,몇칸씩 움직이냐 = 1
#[0, 1, 2]
시리즈 객체를 생성한 후에 인덱스를 수정할 수 있습니다. index라는 변수에 리스트 혹은 ndarray로 데이터를 전달하면 자동으로 맵핑된 RangeIndex가 제거되고 지정한 값으로 대체됩니다. 당연하게도 데이터와 같은 개수의 인덱스를 넣어야 합니다.
data = [1000, 2000, 3000]
s = Series(data)
s.index = ["메로나", "구구콘", "하겐다즈"]
print(s)
#메로나 1000
#구구콘 2000
#하겐다즈 3000
#dtype: int64
#출력 결과를 살펴보면 파이썬 딕셔너리처럼 각데이터마다 레이블이달려있는 것을 확인 할 수 있습니다
# 리스트에 저장된 순서대로 index와 data가 맵칭된다
이번에는 시리즈를 생성할 때 인덱스를 같이 지정해 보겠습니다. 시리즈 객체를 생성할 때 두 번째 인자로 리스트로 정의된 인덱스 값을 전달합니다.
1: data = [1000, 2000, 3000]#시리즈에 저장될 데이터를 리스트로 정의합니다.
2: index = ["메로나", "구구콘", "하겐다즈"]#시리즈에 저장될 인덱스를 리스트로 정의합니다.
3:
4: s = Series(data, index)#시리즈를 생성할 때 데이터와 인덱스를 차례로 전달합니다.
5: print(s)
#메로나 1000
#구구콘 2000
#하겐다즈 3000
#dtype: int64
시리즈 객체를 생성할 때 파라미터의 이름을 직접 지정하는 keyword argument를 사용할 수 있습니다. 라인 4의 코드는 시리즈 생성자의 data 파라미터에 가격이 정의된 라인 1의 리스트를 넣고 index 파라미터에 라인 2에서 정의한 리스트를 대입하라는 것을 표현합니다.
1: data = [1000, 2000, 3000]
2: index = ["메로나", "구구콘", "하겐다즈"]
3:
4: s = Series(data=data, index=index)
keyword argument를 사용하면 파라미터의 순서를 변경해도 같은 결과를 얻을 수 있습니다. 단, 라인 1과 라인 2에서 data는 keyword argument를 사용하지 않아서 반드시 첫 번째 위치에 data 파라미터가 위치해야 합니다.
1: s = Series(data, index)
2: s = Series(data, index=index)
3: s = Series(data=data, index=index)
4: s = Series(index=index, data=data)
data = [1000, 2000, 3000]
index = ['메로나', '구구콘', '하겐다즈']
s = Series(data=data, index=index)
s2 = s.reindex(["메로나", "비비빅", "구구콘"])#S에는 비비빅이 존재하지 않기 때문에 결측값(nan)으로 처리됐습니다
print(s2)
#메로나 1000.0
#비비빅 NaN
#구구콘 2000.0
#dtype: float64
NaN을 0으로 변경할 때는 fillna 메서드를 사용할 수 있습니다. 다음 코드는 결측값이 0으로 채워진 결과를 반환합니다. s2가 변경되지 않음에 주의하세요
data = [1000, 2000, 3000]
index = ['메로나', '구구콘', '하겐다즈']
s = Series(data=data, index=index)
s2 = s.reindex(["메로나", "비비빅", "구구콘"])#결측값을 reindex 메서드를 사용해서 0으로 값을 채운다
print(s2.fillna(0)
#메로나 1000.0
#비비빅 0.0
#구구콘 2000.0
#dtype: float64
data = [1000, 2000, 3000]
index = ['메로나', '구구콘', '하겐다즈']
s = Series(data=data, index=index)
s2 = s.reindex(["메로나", "비비빅", "구구콘"])#결측값을 reindex 메서드를 사용해서 0으로 값을 채운다
print(s2.fillna(0))
삼성전자의 5일 종가를 시리즈 객체로 표현해 봅시다.인덱스는 문자열로 표현된 문자열로 표현된 날짜를 사용한다
price = [42500, 42550, 41800, 42550, 42650]#종가를 파이썬 리스트로 저장합니다.
date = ["2019-05-31",
"2019-05-30",
"2019-05-29",
"2019-05-28",
"2019-05-27"]#각 거래일의 날짜를 문자열로 표현한 후 이를 리스트에 저장합니다.
s = Series(price, date)#시리즈 객체를 생성합니다. 이때 데이터와 인덱스를 지정합니다.
print(s)
#2019-05-31 42500
#2019-05-30 42550
#2019-05-29 41800
#2019-05-28 42550
#2019-05-27 42650
#dtype: int64
자료구조에서 하나의 값에 접근하는 것을 인덱싱이라고 합니다. 리스트는 인덱스로만 인덱싱을 했다면 시리즈는 행 번호와 인덱스를 사용해서 인덱싱을 할 수 있습니다. 시리즈 객체는 생성될 때 인덱스 이외에도 표 3.4.1과 같이 행 번호(row number)가 자동으로 부여됩니다. 눈에 보이지 않지만 내부적으로 부여되는 번호라고 생각하면 쉽습니다. 시리즈 객체는 행 번호와 인덱스가 존재하기 때문에 두 가지 방법으로 인덱싱할 수 있습니다. 시리즈 객체의 행 번호를 사용해서 인덱싱할 때 iloc 연산(속성)을, 인덱스를 사용할 때 loc 연산을 사용합니다
우선 iloc 연산을 사용해서 행 번호로 인덱싱해 봅시다. 행 번호를 사용하는 인덱싱은 리스트의 정수 인덱싱과 동일합니다. 양수 값과 음수 값을 모두 사용할 수 있습니다.
data = [1000, 2000, 3000]
s = Series(data=data)
print(s.iloc[0])#행 번호 0의 데이터를 출력합니다.
print(s.iloc[1])#행 번호 1의 데이터를 출력합니다.
print(s.iloc[2])#행 번호 2의 데이터를 출력합니다.
print(s.iloc[-1])#행 번호 -1의 위치의 데이터인 3000을 출력합니다.
#1000
#2000
#3000
#3000
이번에는 loc 연산을 사용해봅시다. loc 연산은 시리즈의 인덱스 값을 통해 인덱싱합니다. 따라서 표 3.4.2의 시리즈는 loc 로 인덱스 0, 1, 2를 사용해서 데이터에 접근할 수 있습니다. 하지만 s.loc[-1]은 -1이라는 인덱스가 시리즈 객체에 없으므로 에러가 발생합니다.
from pandas import Series
data = [1000, 2000, 3000]
s = Series(data=data)
print(s.loc[0])
print(s.loc[1])
print(s.loc[2])
print(s.loc[-1])
#에러
이번에는 문자열 인덱스가 설정된 시리즈 객체에 대한 인덱싱을 살펴봅시다. 문자열 인덱스가 설정된 경우에도 loc 연산은 인덱스를 iloc는 행 번호를 사용한다는 것만 기억하면 됩니다. 라인 5와 라인 6 두 코드는 모두 메로나의 가격을 출력합니다.
data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
print(s.iloc[0])
print(s.loc['메로나'])
#둘다 메로나의 데이터를 출력한다
#1000
#1000
인덱스가 문자열로 되있으면 숫자처럼 동작한다 인덱스를 a b c문자열로 정의해도 인덱스 0을 출력하면 a의 값이 나온다
s3 = Series([10, 20, 30], index=['a', 'b', 'c'])
print(s3[0])
#10
s3 = Series([10, 20, 30], index=['a', 'b', 'c'])
print(s3.index[ 0 ])#인덱스a를 출력한다
print(s3.index[ -1 ])#인덱스c를 출력한다
#a
#c
data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
print(s.loc['메로나':'구구콘'])# 메로나와 구구콘의 인덱스와 데이터 출력
#메로나 1000
#구구콘 2000
#dtype: int64
시리즈 객체는 파이썬 리스트와 달리 연속적이지 않은 값들에 대해서도 슬라이싱 할 수 있습니다. 접근하고자 하는 데이터의 행 번호나 인덱스를 리스트로 표현한 후 iloc나 loc 연산에서 리스트를 사용한 슬라이싱을 적용하면 됩니다.
data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
indice = [0, 2]#행 번호를 의미하는 0번과 2번을 리스트로 저장합니다.
print(s.iloc[ indice ])#리스트에는 인덱스가 저장돼 있으므로 iloc로 슬라이싱합니다.
print(s.iloc[ [0, 2] ])#라인5, 라인6의 코드는 간단하기 때문에 한 줄로 표현할 수 있습니다. 대괄호가 중첩해서 사용돼 이상해 보일 수 있지만, 이는 판다스 시리즈의 올바른 문법입니다. 바깥쪽의 대괄호는 인덱싱 기호로, 안쪽의 대괄호는 리스트 기호입니다.
#메로나 1000
#하겐다즈 3000
#dtype: int64
#메로나 1000
#하겐다즈 3000
#dtype: int64
data = [1000, 2000, 3000]
index = ["메로나", "구구콘", "하겐다즈"]
s = Series(data=data, index=index)
indice = ["메로나","하겐다즈"]#행 번호를 의미하는 0번과 2번을 리스트로 저장합니다.
print(s.loc[ indice ])#리스트에는 인덱스가 저장돼 있으므로 iloc로 슬라이싱합니다.
print(s.loc[ ["메로나", "하겐다즈"] ])#라인5, 라인6의 코드는 간단하기 때문에 한 줄로 표현할 수 있습니다. 대괄호가 중첩해서 사용돼 이상해 보일 수 있지만, 이는 판다스 시리즈의 올바른 문법입니다. 바깥쪽의 대괄호는 인덱싱 기호로, 안쪽의 대괄호는 리스트 기호입니다.
#메로나 1000
#하겐다즈 3000
#dtype: int64
#메로나 1000
#하겐다즈 3000
#dtype: int64