220926 Day22

유예지·2022년 9월 26일

[0923 복습]

* displ의 4분위수 출력

>>> 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])    

* 10초가 지난 후 'retire' 출력

>>> import time

>>> time.sleep(10)
	print('retire')
    
>>> n = 3
	while True :
    	time.sleep(n)
        n -= 1
        if n == 0:
        	print('retire')
            break

* 'diamonds' 에서 carat에 따른 price

>>> 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']

* comprehension

#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]

(7) 함수

같은 일을 반복하거나 비슷한 코드를 한번 이상 실행해야 할 때
'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']        
  • 순차적 자료형에 대해 함수를 적용하는 내장함수인 'map' 함수를 이용해서 함수를 인자로 사용할 수도 있다
>>> for i in map(remove_punctuation, states):
        print(i)
Alabama 
Georgia
Georgia
georgia
FlOrIda        

* lambda (익명 함수)

데이터를 변형하는 함수에서 인자로 함수를 받아야 하는 경우가 많은데, 람다 함수를 사용하면 코드를 적게 쓰고 간결하게 쓸 수 있다

>>> (lambda x: x*2)[1,2,3,4]

③ 제너레이터 (generator)

순회 가능한 객체를 생성하는 방법, 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        

3. Numpy

Numpy가 파이썬 산술 계산 영역에서 중요한 위치를 차지하는 이유 중 하나는 대용량 데이터 배열을 효과적으로 다룰 수 있기 때문이다
-데이터를 다른 내장 파이썬 객체와 구분된 연속된 메모리 블록에 저장한다
-각종 알고리즘이 모두 C로 작성되어 메모리를 직접 조작할 수 있다
-Numpy 배열은 내장 파이썬의 연속된 자료형들보다 훨씬 더 적은 메모리를 사용한다
-파이썬 반복문을 사용하지 않는다

(1) 다차원 배열 객체 (Numpy ndarray)

Numpy의 핵심 기능 중 하나이다

>>> import numpy as np
>>> data = np.random.randn(2, 3)    #표준정규분포, 2행 3열
	data

ndarray의 모든 원소는 같은 자료형이어야 한다
'shape' : 각 차원의 크기 (튜플)
'dtype' : 배열에 저장된 자료형

① ndarray 생성하기

>>> 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
  • 2차원 배열 슬라이싱
>>> 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]])
  • 3차원 배열 슬라이싱
>>> 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차원 이다

(2) 유니버셜 함수 ufunc

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         
  • 여러개의 배열을 반환하는 유니버설 함수 : np.modf
    파이썬 내장함수인 divmod 의 벡터화 버전, 분수를 받아서 몫과 나머지를 함께 반환
>>> 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.]]))
  • 기억해야할 단항 유니버셜 함수
    sqrt : 각 원소의 제곱근 계산 (arr 0.5)
    square : 각 원소의 제곱 계산 (arr
    2)

(3) 배열을 이용한 배열지향 프로그래밍

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' 파일을 불러올 때는 각각의 배열을 필요할 때 불러올 수 있도록 '사전 형식' 의 객체에 저장한다

(4) 난수 생성

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값으로 (내부적으로 = 자동으로) 입력되었기 때문에 계속 다른값으로 출력될 수 있는 것이다

  • 기억해야할 numpy.random 함수
    seed : 난수 생성기의 시드를 지정
    randint : 주어진 최소/최대 범위 안에서 임의의 난수 추출
    randn : 표준편차가 1이고 평균값이 0인 '정규분포' 에서 표본 추출
    normal : 정규분포(가우시안)에서 표본 추출
    uniform : 균등(0,1) 분포에서 표본 추출

(5) 계단 오르내리기 예제

* 순수 파이썬으로 내장 random 모듈을 사용하여 작성한 코드

>>> 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)

* np.random 모듈을 사용해서

>>> 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()

0개의 댓글