1. Numpy
배열로 저장된 대용량 데이터를 효율적으로 처리할 수 있는 수치 처리 라이브러리
1.1 numpy 내장 메소드
1) np.arange(start, stop, step size)
- 마지막 값은 포함하지 않는다.
- 출력 값은 정수(Integer)이다.
import numpy as np
np.arange(0, 11, 2)
# [out] array([ 0, 2, 4, 6, 8, 10])
2) np.zeros() / np.ones()
- 단일 숫자나 튜플을 입력할 수 있다.
- 이 메소드가 효과적인 이유는 numpy를 통해 배열에서 모든 단일 숫자로 연산할 수 있기 때문이다.
## 1. np.zeros()
# 1차원 배열 출력
np.zeros(3)
[out] array([0., 0., 0.])
# 튜플을 입력하면 2차원 행렬 출력
np.zeros((5,5))
[out]
array([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
## 2. np.ones()
# 1차원 배열 출력
np.ones(3)
[out] array([1., 1., 1.])
# 2차원 행렬 출력
np.ones((3,3))
[out]
array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
# 배열에서 모든 단일 숫자로 연산할 수 있다
np.ones((3,3)) + 4
[out]
array([[5., 5., 5.],
[5., 5., 5.],
[5., 5., 5.]])
3) np.linspace(start, stop, return size)
- 특정 간격에 따라 일정하게 생성된 숫자가 반환되는 선형 간격 배열을 생성한다.
- 시작 값과 마지막 값을 포함한다.
- 출력 값은 실수이다.
# 0 ~ 10 사이 3개 출력
np.linspace(0,10,3)
[out] array([ 0., 5., 10.])
# 0 ~ 5 사이 20개 출력
np.linspace(0,5,20)
[out]
array([0. , 0.26315789, 0.52631579, 0.78947368, 1.05263158,
1.31578947, 1.57894737, 1.84210526, 2.10526316, 2.36842105,
2.63157895, 2.89473684, 3.15789474, 3.42105263, 3.68421053,
3.94736842, 4.21052632, 4.47368421, 4.73684211, 5. ])
4) np.eye()
- 단위 행렬을 생성한다.
- 단일 숫자를 입력하면 숫자만큼의 행과 열을 가진 행렬을 생성한다.
- 대각선엔 숫자가 있으며 나머지는 0인 행렬이다.
np.eye(4)
[out]
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
1.2 Random 배열 생성
1) np.random.rand()
- 0과 1에 대한 균등 분포에서 임의의 샘플이 제공된다.
np.random.rand(2)
[out] array([0.64535635, 0.33512262])
np.random.rand(5,5)
[out]
array([[0.7084575 , 0.60008593, 0.18880341, 0.38554079, 0.93951587],
[0.60448651, 0.36105622, 0.14405703, 0.22331291, 0.37901868],
[0.94351251, 0.19016574, 0.85708585, 0.39351885, 0.63037201],
[0.97048885, 0.70416854, 0.61083625, 0.09226937, 0.70845828],
[0.10431003, 0.51198259, 0.79965687, 0.46712964, 0.79396637]])
2) np.random.randn()
- 표준 정규 분포에서 하나 이상의 샘플을 출력한다.
- 표준 정규 분포 : 평균 0, 표준편차 1
np.random.randn(5,5)
[out]
array([[-0.81963807, -0.18240603, 0.99337253, -1.30802537, -0.75555074],
[ 0.46649618, -0.88107939, 0.65733892, -0.83148483, -1.3108308 ],
[ 0.03155042, 0.88119581, -0.99622131, -0.55027174, 0.82826821],
[-2.36299458, -2.44587152, 1.23031295, -0.22708679, 2.22130336],
[-0.79652704, -0.84972336, -0.19091781, -1.47608535, 1.06609776]])
3) np.random.randint(low, high, return size)
- 최솟값을 포함하고 최댓값을 제외한 범위에서 임의의 정수를 반환
np.random.randint(1,100,10)
[out] array([78, 78, 49, 90, 83, 5, 44, 79, 83, 32])
4) np.random.seed()
- 난수 상태를 설정하는 시드를 설정한다.
- 난수 생성 기록과 일치하고 일관성 있게 숫자를 사용하는 한 어떤 숫자를 사용하는지는 상관 없다.
np.random.seed(42)
np.random.rand(4)
[out] array([0.37454012, 0.95071431, 0.73199394, 0.59865848])
# 동일한 시드이므로 같은 난수가 반환됨
np.random.seed(42)
np.random.rand(4)
[out] array([0.37454012, 0.95071431, 0.73199394, 0.59865848])
1.3 array 속성 & 메소드
- reshape : 데이터를 원하는 형태로 수정해줌 (ex. 1차원 → 5x5 행렬)
- max, min, argmax, argmin : array의 최댓값, 최솟값, 최댓값의 인덱스 위치, 최솟값의 인덱스 위치를 출력
- shape : 현 array의 형태를 출력
- dtype : array의 타입을 출력
arr = np.arange(25)
ranarr = np.random.randint(0,50,10)
arr
[out] array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24])
ranarr
[out] array([38, 18, 22, 10, 10, 23, 35, 39, 23, 2])
## 1. reshape
# 모든 요소가 다 들어갈 수 있도록 형태를 설정해야 함
arr.reshape(5,5)
[out]
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]])
## 2. max, min, argmax, argmin
# 최댓값
ranarr.max() # [out] 39
# 최댓값의 인덱스 위치
ranarr.argmax() # [out] 7
# 최솟값
ranarr.min() # [out] 2
# 최솟값의 인덱스 위치
ranarr.argmin() # [out] 9
## 3. shape
arr.shape # [out] (25,)
## 4. dtype
arr.dtype # [out] dtype('int64')
1.4 브로드캐스팅 (Broadcasting)
파이썬 리스트와 다르게 함수와의 연산을 브로드캐스트 할 수 있다.
슬라이스를 기반으로 변수 재할당에 브로드캐스팅을 수행하면 기존 배열에 적용된다
arr = np.arange(0,11)
arr
[out] array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
slice_of_arr = arr[0:6]
slice_of_arr[:]=99
slice_of_arr
[out] array([99, 99, 99, 99, 99, 99])
# 기존 배열에 영향 받음
arr
[out] array([99, 99, 99, 99, 99, 99, 6, 7, 8, 9, 10])
그렇기 때문에 기존 배열에 영향을 받지 않도록 하려면 복사본을 만들어야 한다.
arr_copy = arr.copy()
arr_copy[:] = 1000
arr_copy
[out] array([1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000])
# 기존 배열에 영향을 받지 않는다.
arr
[out] array([99, 99, 99, 99, 99, 99, 6, 7, 8, 9, 10])
1.5 조건부 선택 (Conditional Selection)
조건을 실행하면 불린 값으로 나오게 되며, 값이 True인 인덱스 위치의 요소를 반환하게 된다.
arr = np.arange(1,11)
arr
[out] array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 1. 불리언 비교 실행
arr > 4
[out] array([False, False, False, False, True, True, True, True, True, True])
# 2. 변수에 할당
bool_arr = arr > 4
# 3. 값이 True인 인덱스 위치의 요소를 반환함
arr[bool_arr]
[out] array([ 5, 6, 7, 8, 9, 10])
# arr[arr > 2]
[out] array([ 3, 4, 5, 6, 7, 8, 9, 10])
1.6 산술
array끼리 곱하기, 더하기 등 다양한 산술 계산을 할 수 있다.
Numpy는 0이나 무한대로 나누는 연산을 수행할 때 error를 발생하는 것이 아닌, 경고를 표시하며 값은 출력된다.
arr = np.arange(0,10)
arr
[out] array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 1. 0을 0으로 나누었을 때, nan 값이 출력이 됨 (+ 경고 메시지 출력)
arr/arr
[out]
<ipython-input-5-4a148932dd5e>:5: RuntimeWarning: invalid value encountered in true_divide
arr/arr
array([nan, 1., 1., 1., 1., 1., 1., 1., 1., 1.])
# 2. # 0이 아닌 값을 0으로 나누면 inf로 출력이 됨 (+ 경고 메시지 출력)
1/arr
[out]
<ipython-input-6-2ee210defa4b>:3: RuntimeWarning: divide by zero encountered in true_divide
1/arr
array([ inf, 1. , 0.5 , 0.33333333, 0.25 ,
0.2 , 0.16666667, 0.14285714, 0.125 , 0.11111111])
1.7 유니버셜 배열 함수 (Universal Array Functions)
- np.sqrt() : 제곱근
- np.log() : 로그연산
- np.exp() : 지수함수 (e^)
- np.sin() : 삼각함수
# 1. 제곱근
np.sqrt(arr)
[out]
array([0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
# 2. 로그 연산
# 0의 로그는 마이너스 무한대이므로 경고가 표시되지만 연산은 정상적으로 수행됨
np.log(arr)
[out]
<ipython-input-9-c850aacc27ea>:3: RuntimeWarning: divide by zero encountered in log
np.log(arr)
array([ -inf, 0. , 0.69314718, 1.09861229, 1.38629436,
1.60943791, 1.79175947, 1.94591015, 2.07944154, 2.19722458])
# 3. 지수 함수
np.exp(arr)
[out]
array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
2.98095799e+03, 8.10308393e+03])
# 4. 삼각 함수
np.sin(arr)
[out]
array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ,
-0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849])
1.8 Array 통계
Numpy는 array 내의 다양한 통계 기능을 제공한다.
arr = np.arange(0,10)
arr
[out] array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr.sum() # [out] 45
arr.mean() # [out] 4.5
arr.max() # [out] 9
arr.var() # [out] 8.25
arr.std() # [out] 2.8722813232690143
axis 매개변수를 통해 array 배열의 축을 기준으로 계산이 가능하다.
arr_2d = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
arr_2d
[out]
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
# 열 별 총합
arr_2d.sum(axis=0) # [out] array([15, 18, 21, 24])
# 행 별 총합
arr_2d.sum(axis=1) # [out] array([10, 26, 42])
2. Pandas
Pandas는 Numpy를 기반으로 한다.
시리즈와 데이터프레임이라는 Pandas가 가진 2개의 주요 자료구조에 대해 학습해보자.
2.1 시리즈 (Series)
시리즈는 Pandas의 기분 구조 요소로 Numpy 배열과 매우 유사하며 인덱스로 구성된 정보 배열을 담고 있다.
Numpy 배열은 인덱스로 구성된 데이터로 파이썬 리스트와 비슷하지만 Numpy가 요소 별 브로드캐스팅 연산 기능을 추가로 갖고 있다는 점에서 다르다.
실제 데이터를 다루는 경우, 직접 인터프리트할 수 있는 인덱스로 구성하고 싶을 때 pandas 시리즈를 사용할 수 있다.
Pandas의 시리즈는 네임드 인덱스를 가지고 있어서 숫자 인덱스에 기초한 정보를 기억하고 불러온다. 더불어 네임드 인덱스로도 데이터를 직접 불러올 수 있다. 그래서 데이터를 쿼리화하기 더욱 용이하다.
✅ 시리즈 만들기
- pd.Series()를 사용하여 시리즈를 만들 수 있다.
- 여러가지 자료형으로 시리즈를 만들 수 있다. (ex. 리스트, array, 딕셔너리 등)
labels = ['a','b','c']
my_list = [10,20,30]
arr = np.array([10,20,30])
d = {'a':10,'b':20,'c':30}
# 1. 리스트 사용
pd.Series(data = my_list, index = labels)
# 2. numpy array 사용
pd.Series(arr,labels)
# 3. 딕셔너리 사용
pd.Series(d)
[out]
a 10
b 20
c 30
dtype: int64
✅ 인덱스 사용하기
인덱스를 문자열로 지정하고, 이를 인덱스로 활용하여 값을 출력할 수 있다.
ser1 = pd.Series([1,2,3,4],index = ['USA', 'Germany','USSR', 'Japan'])
ser1
[out]
USA 1
Germany 2
USSR 3
Japan 4
dtype: int64
ser2 = pd.Series([1,2,5,4],index = ['USA', 'Germany','Italy', 'Japan'])
ser2
[out]
USA 1
Germany 2
Italy 5
Japan 4
dtype: int64
# ------------------------------------------------------
# 인덱스로 값 출력
ser1['USA'] # [out] 1
시리즈끼리 합산이 가능하며, 더할 데이터가 없는 경우에는 NaN 값으로 출력된다.
ser1 + ser2
[out]
Germany 4.0
Italy NaN
Japan 8.0
USA 2.0
USSR NaN
dtype: float64
2.2 DataFrames
📌 기존 데이터 (df)
✅ 행 데이터 출력
- df.loc[행 이름]
- df.iloc[인덱스]
# 행 이름으로 행 데이터 출력
df.loc['A']
[out]
W 2.706850
X 0.628133
Y 0.907969
Z 0.503826
Name: A, dtype: float64
# 인덱스 위치로 행 데이터 출력
df.iloc[2]
[out]
W -2.018168
X 0.740122
Y 0.528813
Z -0.589001
Name: C, dtype: float64
✅ 인덱스 재설정
# 인덱스 리셋
df.reset_index()
원하는 컬럼의 데이터로 인덱스를 설정할 수 있다.
# 1. 인덱스 리스트를 변수에 할당
newind = 'CA NY WY OR CO'.split()
# 2. 컬럼으로 추가
df['States'] = newind
# 3. 인덱스 값으로 원하는 열 선택
df.set_index('States')
2.3 결측치 (Missing Data)
- 결측치를 제거하기 위해서는 df.dropna() 를 사용하면 된다.
- 행, 열 중에 선택하여 제거하고 싶으면 axis = 라는 매개변수를 사용하면 된다. (기본 값은 행 제거)
📌 기존 데이터 (df)
# default : axis = 0 (행 제거)
df.dropna()
# axis = 1 (열 제거)
df.dropna(axis=1)
✅ 결측치 임계값 지정
df.dropna()만 사용하였을 경우, 결측치를 모두 제거하는데 각 행, 열 당 제거할 갯수를 설정할 수 있다. → thresh =
# 임계값 지정 : 최소 n개 이상의 NaN 값을 가진 데이터 제거
df.dropna(thresh=2)
✅ 결측치 보간
결측치를 제거하지 않고 다른 데이터로 대체할 수 있다. → df.fillna()
# 결측치 보간 (value에 보간할 데이터 입력)
df.fillna(value='FILL VALUE')
2.4 groupby
Group by 연산을 수행하는 경우, 3가지 단계가 필요하다.
우선 데이터를 분리하고 어떤 함수로 분리된 그룹에 적용하고 마지막으로 데이터를 다시 통합하여 단일 데이터프레임으로 가져온다.
Pandas가 이 단계를 백엔드에서 처리한다. → .groupby() 메소드
📌 기존 데이터 (df)
그룹화한 데이터에서 평균값 이외에도 표준편차, 최소최댓값, 데이터 갯수 등을 구할 수 있다.
# 그룹화한 데이터에서 평균값 구하기
by_comp.mean()
이 모든 통계값을 한 번에 확인할 수 있다. → .describe()
# 여러 통계값
df.groupby("Company").describe()
2.5 연산
📌 기존 데이터 (df)
1) 고유값
고유값의 데이터와 고유값의 갯수를 출력할 수 있다.
# 고유값 데이터 출력
df['col2'].unique()
[out] array([444, 555, 666])
# 고유값 데이터 갯수 출력
df['col2'].nunique()
[out] 3
각 고유값 별 인스턴스 수를 출력할 수 있다.
# 각 고유값 별 인스턴스 수 출력
# group by를 적용하지 않고 value_counts()를 사용하면 됨!
df['col2'].value_counts()
[out]
444 2
555 1
666 1
Name: col2, dtype: int64
2) 조건부 선택
조건부 선택을 사용하여 원하는 데이터만을 선택하여 출력할 수 있다.
and, or를 사용하지 않고 &, | 를 사용하여야 한다.
newdf = df[(df['col1']>2) & (df['col2']==444)]
newdf
3) 함수 적용
df.apply() 메소드를 사용하여 데이터프레임에 함수를 적용할 수 있다.
def times2(x):
return x*2
# col1 컬럼에 times2 함수 적용
df['col1'].apply(times2)
[out]
0 2
1 4
2 6
3 8
Name: col1, dtype: int64
4) 영구 열 제거
del을 활용하여 영구적으로 열을 제거할 수 있다.
del df['col1']
df
5) 컬럼명, 인덱스명 가져오기
## 행과 열은 하나의 속성이기에 끝에 () 소괄호를 추가하지 않는다.
df.columns
[out] Index(['col2', 'col3'], dtype='object')
df.index
[out] RangeIndex(start=0, stop=4, step=1)
6) 정렬
df.sort_values()를 사용하여 정렬을 할 수 있다.
by 매개변수를 사용하여 정렬의 기준 컬럼을 설정할 수 있다.
ascending 매개변수를 사용하여 오름차순, 내림차순을 설정할 수 있다.
# col2 컬럼을 기준으로 정렬 (default : ascending = False, inplace=False)
df.sort_values(by='col2')
2.6 데이터 불러오기 & 내보내기
1) csv
# 파일 불러오기
df = pd.read_csv()
# 파일 내보내기 (괄호 안에 파일을 저장할 경로 + 파일명 입력)
df.to_csv()
2) Excel
# 파일 불러오기 (엑셀은 원하는 sheet 설정 가능) → sheet_name=
df = pd.read_excel()
# 파일 내보내기 (괄호 안에 파일을 저장할 경로 + 파일명 입력)
df.to_excel()
3) HTML
# html 주소를 활용하여 불러오기 가능
df = pd.read_html()
3. Pandas를 사용한 데이터 시각화
3.1 Pandas 내장 시각화 기능
📌 시각화 기능 리스트
df.plot.hist() histogram
df.plot.bar() bar chart
df.plot.barh() horizontal bar chart
df.plot.line() line chart
df.plot.area() area chart
df.plot.scatter() scatter plot
df.plot.box() box plot
df.plot.kde() kde plot
df.plot.hexbin() hexagonal bin plot
df.plot.pie() pie chart
📌 기존 데이터 (df1, df2)
1) 히스토그램
연속형 데이터의 기본 분포를 보여주기 위해 데이터를 동일한 bin으로 나누고 각 bin으로 나뉜 수를 플롯으로 나타낸다.
✅ 그래프 테두리 색상 추가 → edgecolor 매개변수
df1['A'].plot.hist(edgecolor='k')
✅ 박스의 끝부분을 bin의 가장자리에 오도록 설정 → autoscale(enable=True, axis='both', tight=True)
df1['A'].plot.hist(edgecolor='k').autoscale(enable=True, axis='both', tight=True)
✅ bin 갯수 추가 → bins 매개변수
df1['A'].plot.hist(bins=40, edgecolor='k').autoscale(enable=True, axis='both', tight=True)
✅ plot 제외하고 hist()로만 그래프 그리기
→ 기본 형식이 변경된다. (y축 라벨 제거, 격자 모양 추가)
df1['A'].hist();
✅ 격자 모양 제거 및 y축 라벨 생성 → grid 매개변수, set_ylabel()
df1['A'].hist(grid=False).set_ylabel("Frequency");
2) 막대 그래프
# 세로 막대 그래프
df2.plot.bar();
# 가로 막대 그래프
df2.plot.barh();
✅ 쌓아올리기 → stacked = True
df2.plot.bar(stacked=True);
3) 선 그래프
# y축 설정 : y =
# 그림 크기 수정 : figsize = ()
# 선 굵기 : lw =
df2.plot.line(y='a',figsize=(12,3),lw=2);
✅ y에 대한 컬럼 리스트에 대한 그래프 그리기
df2.plot.line(y=['a','b','c'],figsize=(12,3),lw=2);
4) Area 플롯
df2.plot.area();
✅ 쌓아올리기 기능 해제 → stacked = False
# 투명도 : alpha =
# 쌓아올리기 해제 : stacked = False
df2.plot.area(stacked=False, alpha=0.4);
5) 산점도
# x, y축에 각 컬럼 설정
df1.plot.scatter(x='A',y='B');
✅ 컬러맵 활용 → cmap 매개변수
df1.plot.scatter(x='A',y='B',c='C',cmap='coolwarm');
✅ 점의 크기 지정 → s 매개변수
df1.plot.scatter(x='A',y='B',s=df1['C']*50);
6) 박스플롯
# 아래의 2가지 코드 사용
df2.plot.box();
df2.boxplot(); # grid 표현됨
✅ 박스플롯으로 groupby 진행
by 매개변수에 groupby할 컬럼명을 기재하면 그룹화가 되어 박스플롯이 그려진다.
df2[['b','e']].boxplot(by='e', grid=False);
7) KDE 플롯
밀도를 선으로 나타내는 그래프로, 히스토그램과 동일하며 차이는 단지 선으로 나타낸다는 점이다.
df2['a'].plot.kde();
8) 육각형 bin 플롯 (Hexagonal Bin Plot)
## 1. df 생성
df = pd.DataFrame(np.random.randn(1000, 2), columns=['a', 'b'])
## 2. 그래프 그리기
# 격자 크기 지정 : gridsize =
df.plot.hexbin(x='a',y='b',gridsize=25,cmap='Oranges');
3.2 그래프 옵션 설정 (Customizing)
✅ 색상, 크기, 선 스타일
# 선 스타일 : ls =
# 선 색상 : c =
# 선 두께 : lw =
df2['c'].plot.line(ls='-.', c='r', lw='4', figsize=(8,3));
✅ 제목 및 축 라벨 추가
title='Custom Pandas Plot'
ylabel='Y-axis data'
xlabel='X-axis data'
ax = df2.plot(figsize=(8,3),title=title) # 제목 추가
ax.set(xlabel=xlabel, ylabel=ylabel) # 축 레이블 지정
ax.autoscale(axis='x',tight=True); # 그래프를 x축에 맞추기
✅ 범례 위치 변경하기
기존 위치에서 원하는 위치로 옮기기 위해서는 bbox_to_anchor 매개변수 사용하면 된다.
bbox_to_anchor 매개변수에 숫자를 할당할 때, 그래프 전체를 각 1.0이라 생각하면 된다.
# 범례 위치 : LOWER-LEFT에서 조금 더 우측으로 → loc = 3
# & bbox_to_anchor= 매개변수를 추가하여 현 위치에서 이동
ax = df2.plot(figsize=(8,3))
ax.autoscale(axis='x',tight=True)
ax.legend(loc=3, bbox_to_anchor=(0.1,0.1));
💡 회고
✔ 마무리하며..
유데미 파이썬 강의를 듣기 시작하였다.
오늘 공부한 부분은 교육 초반에 공부했던 파이썬 기초 문법과 관련된 내용이라 비교적 쉽게 공부할 수 있었다.
✔ 유익한 점 & 배운점
Numpy를 제대로 배운 적이 없었다. 교육 초반에 공부한 파이썬에도 Numpy가 없어서 아쉬웠는데 이번 유데미 강의에 Numpy를 자세히 다뤄줘서 많은 내용을 배웠다.
✔ 아쉬운 점
강의의 매 섹션을 마무리하면서 연습 문제를 푸는데 보지 않고는 풀지 못하는 문제가 몇몇개가 있었다.
그래서 이러한 부분들을 스스로 보완해야겠다는 생각이 들었다.