>>> import pandas as pd
>>> import pydataset
>>> mpg = pydataset.data("mpg")
mpg.rename({'class':'nclass'}, axis = 1, inplace = True)
mpg.head()
>>> mpg['displ'].quantile([0, 0.25, 0.5, 0.75, 1])
>>> import time
>>> time.sleep(10)
print('retire')
>>> n = 3
while True :
time.sleep(n)
n -= 1
if n == 0:
print('retire')
break
>>> diamonds = pydataset.data("diamonds")
diamonds.head()
>>> import seaborn as sns
>>> sns.scatterplot(data = diamonds, x = 'carat', y = 'price', hue = 'color')
#carat의 데이터 타입을 정수로 바꿔보자
>>> diamonds['carat'] = diamonds['carat'].astype(int)
diamonds.info()
#carat별 평균값
>>> diamonds.groupby('carat').mean()
#carat의 변화에 따른 price의 차이가 가장 큰 구간은?
#방법1
>>> df_c = diamonds.groupby('carat').mean()
>>> df_c['diff'] = 0
for i in range(5):
print(df_c['price'][i+1] - df_c['price'][i])
df_c['diff'][i] = df_c['price'][i+1] - df_c['price'][i]
#방법2
>>> df_c['price2'] = df_c['price'].shift(1)
df_c
>>> df_c['diff'] = df_c['price'] - df_c['price2']
df_c
#방법3
>>> diamonds.groupby(['carat']).mean().diff()['price']
#li = ['A','B','C'] 를 인덱스 번호와 함께 딕셔너리로 나타내보자
>>> li = ['A','B','C']
dic = {}
for i, j in enumerate(li):
dic[j] = i
dic
>>> li = ['A','B','C']
dic = {}
for i, j in enumerate(li):
dic[i] = j
dic
#list
>>> li = [1,2,3,4]
li1 = []
for i in range(len(li)):
li1.append(i*2)
li1
>>> [i*2 for i in range(len([1,2,3,4]))]
#dictionary
>>> {i:j for i, j in enumerate(['A','B','C'])}
>>> {j:i for i, j in enumerate(['A','B','C'])}
#구구단
>>> for i in range(2, 10):
for j in range(1, 10):
print(i*j, end = " ")
>>> [i*j for i in range(2, 10) for j in range(1, 10)]
>>> [i*j for i in range(2, 10) for j in range(1, 10) if i*j < 10]
같은 일을 반복하거나 비슷한 코드를 한번 이상 실행해야 할 때
'def 함수이름' 으로 정의하고, 'return' 으로 값을 반환한다
return 문은 개수에 상관이 없다 -> return 하나를 통과하면 함수 실행이 끝나기 때문
함수 블록이 끝날 때 까지 return이 없다면 None이 자동으로 반환된다
함수는 여러개의 인자와 키워드 인자(디폴트값, 기본값)을 받을 수 있다
함수 내에서 선언된 변수, 지역변수는 함수가 호출될 때 생성되며, 함수의 실행이 끝나면 사라진다
>>> def func():
a = []
for i in range(5):
a.append(i)
func() 함수를 호출하면 비어있는 리스트 a가 실행되고, 다섯개의 원소가 a에 추가된다
함수가 끝나면 이 리스트 a는 사라진다
>>> a = []
def func():
for i in range(5):
a.append(i)
함수 밖에 있는 변수, 전역변수는 함수의 실행이 끝나고 나서도 사라지지 않는다
함수 밖에서 변수에 값을 대입하려면 그 변수를 global 예약어를 이용해서 전역변수로 선언해야 한다
>>> a = None
def func():
global a
a = []
for i in range(5):
a.append(i)
>>> print(a)
파이썬에서는 함수도 객체이므로 다른 언어에서는 힘든 객체 생성 표현을 쉽게 할 수 있다
#데이터 정제하기
>>> states = ['Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda']
>>> import re
def clean_strings(strings):
result = []
for value in strings:
value = value.strip()
value = re.sub('[!#?]', '', value)
value = value.title()
result.append(value)
return result
>>> clean_strings(states)
['Alabama', 'Georgia', 'Georgia', 'Georgia', 'Florida']
>>> def remove_punctuation(value):
return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]
def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
>>> clean_strings(states, clean_ops)
['Alabama', 'Georgia', 'Georgia', 'Georgia', 'Florida']
>>> for i in map(remove_punctuation, states):
print(i)
Alabama
Georgia
Georgia
georgia
FlOrIda
데이터를 변형하는 함수에서 인자로 함수를 받아야 하는 경우가 많은데, 람다 함수를 사용하면 코드를 적게 쓰고 간결하게 쓸 수 있다
>>> (lambda x: x*2)[1,2,3,4]
순회 가능한 객체를 생성하는 방법, for 문에서 많이 쓰인다
일반함수는 단일 값을 반환하지만, 제너레이터는 순차적인 값을 매 요청시마다 하나씩 반환한다
return 대신 yield 를 사용한다
>>> def squares(n = 10):
#print('Generating squares from 1 to {0}'.fomat(n**2))
for i in range(1, n+1):
yield i**2
>>> gen = squares()
gen
<generator object squares at 0x00000170402B0270>
>>> for x in gen:
print(x, end = ' ')
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> (x**2 for x in range(10))
<generator object <genexpr> at 0x0000015C01084510>
>>> g = (x**2 for x in range(10))
for i in g:
print(i, end = " ")
0 1 4 9 16 25 36 49 64 81
Numpy가 파이썬 산술 계산 영역에서 중요한 위치를 차지하는 이유 중 하나는 대용량 데이터 배열을 효과적으로 다룰 수 있기 때문이다
-데이터를 다른 내장 파이썬 객체와 구분된 연속된 메모리 블록에 저장한다
-각종 알고리즘이 모두 C로 작성되어 메모리를 직접 조작할 수 있다
-Numpy 배열은 내장 파이썬의 연속된 자료형들보다 훨씬 더 적은 메모리를 사용한다
-파이썬 반복문을 사용하지 않는다
Numpy의 핵심 기능 중 하나이다
>>> import numpy as np
>>> data = np.random.randn(2, 3) #표준정규분포, 2행 3열
data
ndarray의 모든 원소는 같은 자료형이어야 한다
'shape' : 각 차원의 크기 (튜플)
'dtype' : 배열에 저장된 자료형
>>> data1 = [6, 7.5, 8, 0, 1]
>>> arr1 = np.array(data1)
arr1
#같은 길이의 리스트는 다차원 배열로 변환 가능하다
>>> data2 = [[1,2,3,4], [5,6,7,8]]
>>> arr2 = np.array(data2)
array([1,2,3,4],
[5,6,7,8]) #2차원 배열
>>> np.zeros((2,3,2)) #3차원 배열, 3행 2열 2장
배열 생성 함수
np.array()
np.arange()
np.ones(), np.ones_like() : 내용을 모두 '1' 로 초기화 한다
np.zeros(), np.zeros_like() : 내용을 모두 '0' 으로 초기화 한다
np.empty(), np.empty_like() : 0으로 초기화된 배열을 반환하지 않는다. 초기화되지 않은 '가비지'값으로 채워진 배열을 반환한다
np.full(), np.full_like() : 인자로 받은 값으로 배열을 채운다
np.eye(), np.identity() : NxN 크기의 단위행렬 생성 (대각선이 1로 채워지고, 나머지는 0)
dtype
ndarray가 메모리에 있는 특정 데이터를 해석하기 위해 필요한 정보(또는 메타데이터)를 담고 있는 특수한 객체이다
(np.astype() : 배열의 dtype를 다른 자료형으로 변환)
산술 연산
백터화 : for 문을 작성하지 않고 데이터를 일괄 처리할 수 있다
같은 크기의 배열 간의 산술 연산은 배열의 각 원소 단위로 적용된다(+, -, * , /, ** )
크기가 다른 배열 간의 연산도 가능하다 -> 브로드캐스팅
리스트와 배열의 중요한 차이점은 배열 조각은 원본 배열의 '뷰(view)' 라는 점이다
데이터는 복사되지 않고 뷰에 대한 변경은 그대로 원본 배열에 반영된다
>>> arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr[5]
>>> arr[5:8]
>>> arr[5:8] = 12
arr
array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
>>> arr_s = arr[5:8]
arr_s
array([12, 12, 12])
>>> arr_s[1] = 123
arr
array([ 0, 1, 2, 3, 4, 12, 123, 12, 8, 9])
>>> arr_s[:] = 64
arr
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
2차원 배열에서 각 인덱스에 해당하는 요소는 1차원 배열이다
>>> arr2d = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> arr2d[1]
array([4, 5, 6])
#개별 요소는 콤마로 구분하여 인덱스를 나타낸다
>>> arr2d[0, 2]
3
>>> arr2d
>>> arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
>>> arr2d[:2, 1:]
array([[2, 3],
[5, 6]])
>>> arr2d[1, :2]
array([4, 5])
>>> arr2d[:2, 2]
array([3, 6])
>>> arr2d[:, :1]
array([[1],
[4],
[7]])
>>> a = np.array([[1,2,3], [4,5,6], [7,8,9], [10,11,12]])
a
>>> a1 = np.arange(1, 13).reshape(2, 2, 3)
a1[0,0,0]
1
>>> a1[1,1]
array([10, 11, 12])
#8x4 크기의 배열
>>> import numpy as np
>>> np.array([0., 0., 0., 0.]) + np.array([[0.], [1.], [2.], [3.], [4.], [5.], [6.], [7.]])
>>> np.arange(8).reshape(8,-1) * np.array([1,1,1,1])
>>> np.zeros(4) + np.arange(8).reshape(8,1)
>>> arr = np.zeros(4) + np.arange(8).reshape(8,1)
#특정한 순서로 로우(행)를 선택하고 싶다면 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면 된다
>>> arr[[4,3,0,6]]
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[6., 6., 6., 6.]])
>>> arr[[-3, -5, -7]]
array([[5., 5., 5., 5.],
[3., 3., 3., 3.],
[1., 1., 1., 1.]])
>>> arr[[0,1,2], [1,2,3]]
array([0., 1., 2.])
#(0,1), (1,2), (2,3) 에 대응하는 원소들이 선택되었다
배열이 몇차원이든지 팬시 색인의 결과는 항상 1차원 이다
ndarray 안에 있는 데이터 원소별로 연산을 고속으로 수행한다
>>> a = np.arange(1, 17).reshape(4,4)
a
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
#a의 각 원소들을 제곱 후 루트
>>> np.sqrt(a**2)
np.sqrt, np.exp 함수는 단항 유니버설 함수이다
np.add 나 np.maximum 처럼 2개의 인자를 취해서 단일 배열을 반환하는 함수는 이항 유니버설 함수라고 한다
>>> b = np.arange(16,0,-1).reshape(4,4)
b
array([[16, 15, 14, 13],
[12, 11, 10, 9],
[ 8, 7, 6, 5],
[ 4, 3, 2, 1]])
>>> np.maximum(a,b) #a와 b의 원소별로 가장 큰 값이 무엇인지 확인
array([[16, 15, 14, 13],
[12, 11, 10, 9],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
>>> for x in range(-5, 6): #x는 모든 실수
print(np.maximum(0, x), end = " ")
0 0 0 0 0 0 1 2 3 4 5
>>> divmod(16, 5)
(3,1)
>>> np.mod(16, 5) #첫번째 배열의 원소를 두번째 배열의 원소로 나눈 나머지
1
>>> a = np.random.randn(3, 3)
np.modf(a)
(array([[ 0.67409286, -0.85773666, -0.52395223],
[-0.59372658, -0.32630946, -0.49069093],
[-0.33677526, -0.3157427 , 0.57756631]]),
array([[ 0., -1., -1.],
[-0., -0., -1.],
[-1., -0., 1.]]))
np.meshngrid() 는 2개의 1차원 배열을 받아서 가능한 모든 (x,y) 짝을 만들 수 있는 2차원 배열 2개를 반환한다
>>> points = np.arange(1, 4)
points
>>> xs, ys = np.meshgrid(points, points)
>>> z = np.sqrt(xs**2 + ys**2)
z
#2차원 배열 시각화
>>> import matplotlib.pyplot as plt
>>> plt.imshow(z, cmap = plt.cm.gray)
>>> plt.imshow(z, cmap = plt.cm.gray); plt.colorbar()
>>> plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
통계 메서드 cumsum() : 각 원소의 누적합을 구할 수 있다
불리언 배열을 위한 메서드 sum()
1 = True, 0 = False 이므로 sum() 을 실행하면 불리언 배열에서 True인 원소의 개수를 셀 수 있다
>>> arr = np.random.randn(100)
>>> (arr > 0).sum() #양수인 원소의 개수
42
정렬을 위한 메서드 np.sort() : 배열을 직접 변경하지 않고 정렬된 결과를 가지고 있는 복사본을 반환한다
집합 관련 함수 np.unique()
배열 내에서 중복된 원소를 제거하고 남은 원소를 정렬된 형태로 반환한다
>>> ints = np.array([3,3,3,2,2,1,1,4,4])
>>> np.unique(ints)
array([1, 2, 3, 4])
Numpy는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장할 수 있다
배열 데이터 저장 : np.save(), 배열은 기본적으로 압축되지 않은 원시 (가공되지 않은) 바이너리 형식의 '.npy' 파일로 저장된다
저장된 배열 불러오기 : np.load()
여러개의 배열을 압축된 형식으로 저장하기 : np.savez(), '.npz' 파일로 저장된다
'.npz' 파일을 불러올 때는 각각의 배열을 필요할 때 불러올 수 있도록 '사전 형식' 의 객체에 저장한다
numpy.random 모듈은 효과적으로 표본값을 생성하는 데 주로 사용된다
예를 들어 normal을 사용하여 '표준정규분포' 로 4x4 크기의 표본을 생성할 수 있다
>>> a = np.random.normal(size = (4,4))
a
numpy.random은 매우 큰 표본을 생성하는데에 파이썬 내장 모듈보다 수십 배 이상 빠르다
이를 '유사 난수' 라고 부르는데, 난수 생성기의 '시드값(시작값)'에 따라 정해진 난수를 알고리즘으로 생성하기 때문이다
Numpy의 난수 시드값은 np.random.seed()를 이용해서 변경할 수 있다
numpy.random에서 제공하는 데이터를 생성할 수 있는 함수들은 '전역 난수 시드값'을 이용한다
numpy.random.RandomState 를 이용해서 다른 난수 생성기로부터 격리된 난수 생성기를 만들 수 있다 (실행할 때만 시드값을 주고, 실행 후에는 사라진다)
>>> np.random.randint(3, 10)
>>> np.random.randint(3, 10, size = (2,3))
>>> np.random.randn(2,3)
#np.random.normal(기준점값, 표준편차, 배열의 크기)
>>> np.random.normal(172, 3, 3)
>>> np.random.normal(172, 3, (2,3))
>>> (np.random.normal(172, 3, (2,3)) - 172) / 3
>>> np.random.randn(2,3)*3 + 172
>>> np.random.uniform(size = (2,3))
>>> import random
random.uniform(0, 1) #하나의 값만 추출 가능
>>> random.random() #계속 랜덤하게 추출할 수 있음
엄밀히 말하면 컴퓨터는 난수를 발생시키지 못한다. 단순히 정해진 난수 생성 알고리즘에 따라 값을 출력할 뿐이다. 이렇게 난수 알고리즘을 실행하기 위해 seed 값을 주면(input) 그 출력값(output)이 무작위로 반환되는 것처럼 보이기 때문에 '난수' 라고 칭하는 것이다
따라서 만약 계속 같은 seed 값을 사용한다면 계속 같은 패턴의 난수가 나올 것이다
seed 값을 따로 주지 않는 경우도 있는데, 이 경우는 기본적으로 계속 바뀌는 숫자 중에 '시간' 이 seed값으로 (내부적으로 = 자동으로) 입력되었기 때문에 계속 다른값으로 출력될 수 있는 것이다
>>> import random
>>> #random.seed(12345)
position = 0
#walk = [0]
walk = [position]
steps = 100
for i in range(steps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)
>>> walk
>>> plt.plot(walk)
>>> nsteps = 1000
draws = np.random.randint(0,2, size = nsteps)
draws
>>> steps = np.where(draws > 0, 1, -1)
>>> walk = steps.cumsum()
>>> print(walk.max())
walk.min()
>>> plt.plot(walk)
#계단의 처음 위치에서 10칸 떨어지기까지 얼마나 걸렸는지
>>> (np.abs(walk) >= 10).argmax()
>>> nwalks = 5000
>>> nsteps = 1000
>>> draws = np.random.randint(0, 2, size = (nwalks, nsteps)) #0 또는 1
>>> steps = np.where(draws > 0, 1, -1)
>>> walks = steps.cumsum(1)
>>> walks
>>> print(walk.max())
walk.min()
#누적합이 30 또는 -30 이 되는 최소시점
>>> hits30 = (np.abs(walks) >= 30).any(1)
>>> hits30
>>> hits30.sum() #누적합이 30 또는 -30 이 되는 경우의 수
#처음 위치에서 30칸 이상 멀어지는 최소 횟수
>>> crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
>>> crossing_times.mean()