이번 포스팅에서는 Python pandas의 DataFrame에서 범주형 변수의 항목(class)을 기준 정보(mapping table, reference table)를 이용하여 일괄 변환하는 방법을 소개하겠습니다. 


(1) 범주형 변수의 항목 매핑/변환에 사용한 기준 정보를 dict 자료형으로 만들어 놓고, 


(2) dict.get() 함수를 이용하여 매핑/변환에 사용할 사용자 정의 함수를 만든 후에 


(3) map() 함수로 (2)번에서 만든 사용자 정의 함수를 DataFrame의 범주형 변수에 적용하여 매핑하기



차근차근 예를 들어서 설명해보겠습니다. 


먼저, 간단한 예제 데이터프레임을 만들어보겠습니다. 



import pandas as pd

from pandas import DataFrame


df = DataFrame({'name': ['kim', 'KIM', 'Kim', 'lee', 'LEE', 'Lee', 'wang', 'hong'], 

                'value': [1, 2, 3, 4, 5, 6, 7, 8], 

                'value_2': [100, 300, 200, 100, 100, 300, 50, 80]

               })


df

namevaluevalue_2
0kim1100
1KIM2300
2Kim3200
3lee4100
4LEE5100
5Lee6300
6wang750
7hong880

 




위의 df 라는 이름의 DataFrame에서, name 변수의 (kim, KIM, Kim) 를 (kim)으로, (lee, LEE, Lee)를 (lee)로, 그리고 (wang, hong)을 (others) 라는 항목으로 매핑하여 새로운 변수 name_2 를 만들어보려고 합니다. 



  (1) 범주형 변수의 항목 매핑/변환에 사용할 기준 정보를 dict 자료형으로 만들기



name_mapping = {

    'KIM': 'kim',

    'Kim': 'kim', 

    'LEE': 'lee', 

    'Lee': 'lee', 

    'wang': 'others', 

    'hong': 'others'

}


name_mapping

 {'KIM': 'kim',

 'Kim': 'kim',
 'LEE': 'lee',
 'Lee': 'lee',
 'hong': 'others',
 'wang': 'others'}




  (2) dict.get() 함수를 이용하여 매핑/변환에 사용할 사용자 정의 함수 만들기


dict 자료형에 대해 dict.get() 함수를 사용하여 정의한 아래의 사용자 정의 함수 func는 '만약 매핑에 필요한 정보가 기준 정보 name_mapping dict에 있으면 그 정보를 사용하여 매핑을 하고, 만약에 기준정보 name_mapping dict에 매핑에 필요한 정보가 없으면 입력값을 그대로 반환하라는 뜻입니다. 'lee', 'kim'의 경우 위의 name_mapping dict 기준정보에 매핑에 필요한 정보항목이 없으므로 그냥 자기 자신을 그대로 반환하게 됩니다. 



func = lambda x: name_mapping.get(x, x)

 




  (3) map() 함수로 매핑용 사용자 정의 함수를 DataFrame의 범주형 변수에 적용하여 매핑/변환하기


위의 기준정보 name_mapping dict를 사용하여 'name_2' 라는 이름의 새로운 범주형 변수를 만들어보았습니다. 



df['name_2'] = df.name.map(func)


df

namevaluevalue_2name_2
0kim1100kim
1KIM2300kim
2Kim3200kim
3lee4100lee
4LEE5100lee
5Lee6300lee
6wang750others
7hong880others

 




  (4) groupby() 로 범주형 변수의 그룹별로 집계하기


범주형 변수에 대해서 항목을 매핑/변환하여 새로운 group 정보를 만들었으니, groupby() operator를 사용해서 새로 만든 name_2 변수별로 연속형 변수들('value', 'value_2')의 합계를 구해보겠습니다. 



# aggregation by name

df.groupby('name_2').sum()

valuevalue_2
name_2
kim6600
lee15500
others15130

 




'name_2'와 'name' 범주형 변수 2개를 groupby()에 함께 사용하여 두개 범주형 변수의 계층적인 인덱스(hierarchical index) 형태로 'value_2' 연속형 변수에 대해서만 합계를 구해보겠습니다. (아래의 결과에 대해 unstack()을 하면 name 변수를 칼럼으로 올려서 cross-tab 형태로 볼 수도 있겠습니다.)



df.groupby(['name_2', 'name'])['value_2'].sum()

name_2  name
kim     KIM     300
        Kim     200
        kim     100
lee     LEE     100
        Lee     300
        lee     100
others  hong     80
        wang     50
Name: value_2, dtype: int64

 



많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 GroupBy 를 사용하여 그룹별로 반복 작업(iteration over groups)하는 방법을 소개하겠습니다. 

pandas의 GroupBy 객체는 for loop 반복 시에 그룹 이름과 그룹별 데이터셋을 2개의 튜플로 반환합니다. 이러한 특성을 잘 활용하면 그룹별로 for loop 반복작업을 하는데 유용하게 사용할 수 있습니다. 


[ GroupBy로 그룹별로 반복 작업하기 ]




예제로 사용할 데이터는 UCI machine learning repository에 등록되어 있는 abalone 공개 데이터셋입니다. 



import numpy as np

import pandas as pd


abalone = pd.read_csv("/Users/ihongdon/Documents/Python/abalone.txt", 

                      sep=",", 

                      names = ['sex', 'length', 'diameter', 'height', 

                               'whole_weight', 'shucked_weight', 'viscera_weight', 

                               'shell_weight', 'rings'], 

                      header = None)



abalone['length_cat'] = np.where(abalone.length > np.median(abalone.length), 

                                 'length_long', 

                                 'length_short')



abalone.head()

sexlengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightringslength_cat
0M0.4550.3650.0950.51400.22450.10100.15015length_short
1M0.3500.2650.0900.22550.09950.04850.0707length_short
2F0.5300.4200.1350.67700.25650.14150.2109length_short
3M0.4400.3650.1250.51600.21550.11400.15510length_short
4I0.3300.2550.0800.20500.08950.03950.0557length_short





위의 abalone 데이터셋을 '성별(sex)'로 GroupBy를 한 후에, for loop을 돌려서 그룹 이름(sex: 'F', 'I', 'M')별로 데이터셋을 프린트해보겠습니다. 



for sex, group_data in abalone[['sex', 'length_cat', 'whole_weight', 'rings']].groupby('sex'):

    print sex

    print group_data[:5]

 

F    sex    length_cat  whole_weight  rings

2 F length_short 0.6770 9 6 F length_short 0.7775 20 7 F length_short 0.7680 16 9 F length_long 0.8945 19 10 F length_short 0.6065 14

I    sex    length_cat  whole_weight  rings
4    I  length_short        0.2050      7
5    I  length_short        0.3515      8
16   I  length_short        0.2905      7
21   I  length_short        0.2255     10
42   I  length_short        0.0700      5

M    sex    length_cat  whole_weight  rings
0    M  length_short        0.5140     15
1    M  length_short        0.2255      7
3    M  length_short        0.5160     10
8    M  length_short        0.5095      9
11   M  length_short        0.4060     10





이번에는 두 개의 범주형 변수(sex, length_cat)를 사용하여 for loop 반복문으로 그룹 이름 (sex와 leggth_cat 의 조합: F & length_long, F & length_short, I & length_long, I & length_short, M & length_long, M & length_short)과 각 그룹별 데이터셋을 프린트해보겠습니다. 


참고로, 아래 코드에서 '\' 역슬래쉬는 코드를 한줄에 전부 다 쓰기에 너무 길 때 다음줄로 코드를 넘길 때 사용합니다. 



for (sex, length_cat), group_data in abalone[['sex', 'length_cat', 'whole_weight', 'rings']]\

.groupby(['sex', 'length_cat']):

    print sex, length_cat

    print group_data[:5]

 

F length_long
   sex   length_cat  whole_weight  rings
9    F  length_long        0.8945     19
22   F  length_long        0.9395     12
23   F  length_long        0.7635      9
24   F  length_long        1.1615     10
25   F  length_long        0.9285     11
F length_short
   sex    length_cat  whole_weight  rings
2    F  length_short        0.6770      9
6    F  length_short        0.7775     20
7    F  length_short        0.7680     16
10   F  length_short        0.6065     14
13   F  length_short        0.6845     10
I length_long
    sex   length_cat  whole_weight  rings
509   I  length_long        0.8735     16
510   I  length_long        1.1095     10
549   I  length_long        0.8750     11
550   I  length_long        1.1625     17
551   I  length_long        0.9885     13
I length_short
   sex    length_cat  whole_weight  rings
4    I  length_short        0.2050      7
5    I  length_short        0.3515      8
16   I  length_short        0.2905      7
21   I  length_short        0.2255     10
42   I  length_short        0.0700      5
M length_long
   sex   length_cat  whole_weight  rings
27   M  length_long        0.9310     12
28   M  length_long        0.9365     15
29   M  length_long        0.8635     11
30   M  length_long        0.9975     10
32   M  length_long        1.3380     18
M length_short
   sex    length_cat  whole_weight  rings
0    M  length_short        0.5140     15
1    M  length_short        0.2255      7
3    M  length_short        0.5160     10
8    M  length_short        0.5095      9
11   M  length_short        0.4060     10





다음으로, 성별(sex)로 GroupBy를 해서 성별 그룹('F', 'I', 'M')을 key로 하고, 데이터셋을 value로 하는 dict를 만들어보겠습니다. 



abalone_sex_group = dict(list(abalone[:10][['sex', 'length_cat', 'whole_weight', 'rings']]

                              .groupby('sex')))

 

abalone_sex_group


{'F':   sex    length_cat  whole_weight  rings
 2   F  length_short        0.6770      9
 6   F  length_short        0.7775     20
 7   F  length_short        0.7680     16
 9   F   length_long        0.8945     19,
 'I':   sex    length_cat  whole_weight  rings
 4   I  length_short        0.2050      7
 5   I  length_short        0.3515      8,
 'M':   sex    length_cat  whole_weight  rings
 0   M  length_short        0.5140     15
 1   M  length_short        0.2255      7
 3   M  length_short        0.5160     10
 8   M  length_short        0.5095      9}





이렇게 그룹 이름을 key로 하는 dict 를 만들어놓으면 그룹 이름을 가지고 데이터셋을 indexing하기에 편리합니다.  예로 성별 중에 'M'인 데이터셋을 indexing해보겠습니다. 



abalone_sex_group['M'] 

sexlengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightringslength_cat
0M0.4550.3650.0950.51400.22450.10100.15015length_short
1M0.3500.2650.0900.22550.09950.04850.0707length_short
3M0.4400.3650.1250.51600.21550.11400.15510length_short
8M0.4750.3700.1250.50950.21650.11250.1659length_short




물론 abalone[:10][abalone['sex'] == 'M']  처럼 원래의 처음 abalone 데이터프레임에 boolean 형태로 indexing을 해도 됩니다. 대신에 dict 로 만들어놓으면 데이터셋 indexing 하는 속도가 더 빠를겁니다. 


많은 도움이 되었기를 바랍니다. 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas의 groupby() 연산자를 사용하여 집단, 그룹별로 데이터를 집계, 요약하는 방법을 소개하겠습니다. 


전체 데이터를 그룹 별로 나누고 (split), 각 그룹별로 집계함수를 적용(apply) 한후, 그룹별 집계 결과를 하나로 합치는(combine) 단계를 거치게 됩니다. (Split => Apply function => Combine)



[ GroupBy aggregation mechanics ]




groupby() 는 다양한 변수를 가진 데이터셋을 분석하는데 있어 그룹별로 데이터를 집계하는 분석은 일상적으로 이루어지는 만큼 사용빈도가 매우 높고 알아두면 유용합니다. 


실습에 사용할 예제는 바다 해산물인 전복(abalone)에 대한 공개 데이터셋을 사용하겠습니다. 



[ UCI Machine Learning Repository ]

  • Abalone CSV dataset download:  abalone.txt
  • Variables

    Name Data Type Meas. Description ---- --------- ----- ----------- Sex nominal M, F, and I (infant) Length continuous mm Longest shell measurement Diameter continuous mm perpendicular to length Height continuous mm with meat in shell Whole weight continuous grams whole abalone Shucked weight continuous grams weight of meat Viscera weight continuous grams gut weight (after bleeding) Shell weight continuous grams after being dried Rings integer +1.5 gives the age in years



먼저, 바로 위에 링크해놓은 abalone.txt를 다운받은 후에, abalone.txt 데이터셋을 pandas의 read_csv() 로 불러와서 DataFrame을 만들어보겠습니다. 



# Importing common libraries

import pandas as pd

from pandas import DataFrame

from pandas import Series

import numpy as np

 

# Reading abalone data set

abalone = pd.read_csv("/Users/ihongdon/Documents/Python/abalone.txt", 

                      sep=",", 

                      names = ['sex', 'length', 'diameter', 'height', 

                               'whole_weight', 'shucked_weight', 'viscera_weight', 

                               'shell_weight', 'rings'], 

                      header = None)





abalone 라는 이름의 pandas DataFrame을 만들었으니, 데이터가 어떻게 생겼는지 탐색해보겠습니다. 다행히 결측치는 없으며, 4,177개의 관측치를 가지고 있네요. 전복의 성별(sex) 변수가 범주형 변수입니다. 



# View of top 5 observations

abalone.head()


sex length diameter height whole_weight shucked_weight viscera_weight shell_weight rings

0 M 0.455 0.365 0.095 0.5140 0.2245 0.1010 0.150 15

1 M 0.350 0.265 0.090 0.2255 0.0995 0.0485 0.070 7

2 F 0.530 0.420 0.135 0.6770 0.2565 0.1415 0.210 9

3 M 0.440 0.365 0.125 0.5160 0.2155 0.1140 0.155 10

4 I 0.330 0.255 0.080 0.2050 0.0895 0.0395 0.055 7



# Check the missing value

np.sum(pd.isnull(abalone))


sex               0
length            0
diameter          0
height            0
whole_weight      0
shucked_weight    0
viscera_weight    0
shell_weight      0
rings             0
dtype: int64

 


# Descriptive statics

abalone.describe()

lengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightrings
count4177.0000004177.0000004177.0000004177.0000004177.0000004177.0000004177.0000004177.000000
mean0.5239920.4078810.1395160.8287420.3593670.1805940.2388319.933684
std0.1200930.0992400.0418270.4903890.2219630.1096140.1392033.224169
min0.0750000.0550000.0000000.0020000.0010000.0005000.0015001.000000
25%0.4500000.3500000.1150000.4415000.1860000.0935000.1300008.000000
50%0.5450000.4250000.1400000.7995000.3360000.1710000.2340009.000000
75%0.6150000.4800000.1650001.1530000.5020000.2530000.32900011.000000
max0.8150000.6500001.1300002.8255001.4880000.7600001.00500029.000000





자, 데이터 준비가 되었으니 이제부터 '전복 성별(sex)' 그룹('F', 'M', 'I')별로 전복의 전체 무게('whole_weight') 변수에 대해서 GroupBy 집계를 해보겠습니다. 


집단별 크기는 grouped.size(), 집단별 합계는 grouped.sum(), 집단별 평균은 grouped.mean() 을 사용합니다. 



grouped = abalone['whole_weight'].groupby(abalone['sex'])


grouped 

<pandas.core.groupby.SeriesGroupBy object at 0x112668c10>


grouped.size()

sex
F    1307
I    1342
M    1528
Name: whole_weight, dtype: int64


grouped.sum()

sex
F    1367.8175
I     578.8885
M    1514.9500
Name: whole_weight, dtype: float64


grouped.mean()

sex
F    1.046532
I    0.431363
M    0.991459
Name: whole_weight, dtype: float64





위의 예에서는 'whole_weight' 라는 하나의 연속형 변수에 대해서만 '성별(sex)' 집계를 하였습니다만, 집계를 하는 key를 제외한 데이터프레임 안의 전체 연속형 변수에 대해서 한꺼번에 집계를 할 수도 있습니다. 



abalone.groupby(abalone['sex']).mean()

lengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightrings
sex
F0.5790930.4547320.1580111.0465320.4461880.2306890.30201011.129304
I0.4277460.3264940.1079960.4313630.1910350.0920100.1281827.890462
M0.5613910.4392870.1513810.9914590.4329460.2155450.28196910.705497




DataFrame.groupby('key_var').func() 형식으로도 사용가능하며, 위의 abalone.groupby(abalone['sex']).mean()은 아래처럼 abalone.groupby('sex').mean() 처럼 써도 똑같은 결과를 얻을 수 있습니다. 



abalone.groupby('sex').mean()

lengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightrings
sex
F0.5790930.4547320.1580111.0465320.4461880.2306890.30201011.129304
I0.4277460.3264940.1079960.4313630.1910350.0920100.1281827.890462
M0.5613910.4392870.1513810.9914590.4329460.2155450.28196910.705497





이제부터는 '성별(sex)'  이외에 '길이(length)'를 가지고 범주형 변수를 하나 더 만들어서, 2개의 범주형 변수 key 값을 가지고 그룹별 집계를 해보겠습니다. 


np.where()  함수를 사용하여 length 의 중앙값보다 크면 'length_long'으로, 중앙값보다 작으면 'length_short'의 이름으로하는 계급으로하는 새로운 범주형 변수를 만들어보겠습니다. 



abalone['length_cat'] = np.where(abalone.length > np.median(abalone.length), 

                                 'length_long', # True

                                 'length_short') # False


abalone[['length', 'length_cat']][:10]


  length length_cat

0 0.455 length_short

1 0.350 length_short

2 0.530 length_short

3 0.440 length_short

4 0.330 length_short

5 0.425 length_short

6 0.530 length_short

7 0.545 length_short

8 0.475 length_short

9 0.550 length_long





그럼, 이제 성별 그룹(sex)과 길이 범주(length_cat) 그룹별로  GroupBy 를 사용하여 평균을 구해보겠습니다. 



mean_by_sex_length = abalone['whole_weight'].groupby([abalone['sex'], abalone['length_cat']]).mean()

 

mean_by_sex_length


sex  length_cat  
F    length_long     1.261330
     length_short    0.589702
I    length_long     0.923215
     length_short    0.351234
M    length_long     1.255182
     length_short    0.538157
Name: whole_weight, dtype: float64




위의 집계 결과가 SQL로 집계했을 때의 형태로 결과가 제시가 되었는데요, unstack() 함수를 사용하면 집계 결과를 가로, 세로 축으로 좀더 보기에 좋게 표현을 할 수 있습니다. 



mean_by_sex_length.unstack()

length_catlength_longlength_short
sex
F1.2613300.589702
I0.9232150.351234
M1.2551820.538157

 




abalone['whole_weight'].groupby([abalone['sex'], abalone['length_cat']]).mean() 를 좀더 간결하게 아래처럼 쓸 수도 있습니다. 대상 데이터프레임을 제일 앞에 써주고, groupby()에 집계의 기준이 되는 key 변수들을 써주고, 제일 뒤에 집계하려는 연속형 변수이름을 써주었습니다. 



abalone.groupby(['sex', 'length_cat'])['whole_weight'].mean()

 

sex  length_cat  
F    length_long     1.261330
     length_short    0.589702
I    length_long     0.923215
     length_short    0.351234
M    length_long     1.255182
     length_short    0.538157
Name: whole_weight, dtype: float64




많은 도움이 되었기를 바랍니다. 

728x90
반응형
Posted by Rfriend
,

Graphviz AT&T Bell Labs에서 만든 오픈소스 시각화 소프트웨어입니다. Graphviz 구조화된 정보를 추상화된 그래프나 네트워크 형태의 다이어그램으로 제시 해줍니다. 가령, 기계학습의 Decision Tree 학습 결과를 Tree 형태로 시각화 한다든지, Process Mining 통해 찾은 workflow 방향성 있는 네트워크 형태로 시각화 Graphviz 사용할 있습니다. 




PyGraphviz 는 Python으로 Graphviz 소프트웨어를 사용할 수 있게 인터페이스를 해주는 Python 패키지입니다. PyGraphviz를 사용하여 Graphviz 그래프의 데이터 구조와 배열 알고리즘에 접근하여 그래프를 생성, 편집, 읽기, 쓰기, 그리기 등을 할 수 있습니다. 



Python으로 Graphviz를 사용하려면 (a) 먼저 Graphviz S/W를 설치하고, (b) 다음으로 PyGraphviz를 설치해야 합니다.  만약 순서가 바뀌어서 Graphviz 소프트웨어를 설치하지 않은 상태에서 PyGraphviz를 설치하려고 하면 Graphviz를 먼저 설치하라는 에러 메시지가 뜰 겁니다. 순서가 중요합니다! 


  Your Graphviz installation could not be found.

  

          1) You don't have Graphviz installed:

             Install Graphviz (http://graphviz.org) 




이번 포스팅에서는 


(1) Mac OS High Sierra Graphviz 소프트웨어 설치하기

(2) Python 2.7 PyGraphviz library 설치하기

(3) Graphviz와 PyGraphviz를 사용하여 Decision Tree 시각화 해보기


에 대해서 소개하겠습니다. 



  (1) Mac OS High Sierra  Graphviz 소프트웨어 설치하기


(참고로, 저는 Mac OS High Sierra version 10.13.6을 사용하고 있습니다.)


(1-1) Homebrew 를 설치합니다. 

Homebrew는 애플 Mac OS 에서 소프트웨어 패키지를 설치를 간소화해주는 소프트웨어 패키지 관리 오픈소스 툴입니다. 터미널을 하나 열고 아래의 코드를 복사해서 실행하면 됩니다. 


$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null



ihongdon-ui-MacBook-Pro:~ ihongdon$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
==> This script will install:
/usr/local/bin/brew
/usr/local/share/doc/homebrew
/usr/local/share/man/man1/brew.1
/usr/local/share/zsh/site-functions/_brew
/usr/local/etc/bash_completion.d/brew
/usr/local/Homebrew
==> The following existing directories will be made group writable:
/usr/local/bin
/usr/local/include
/usr/local/lib
/usr/local/share
/usr/local/lib/pkgconfig
/usr/local/share/info
/usr/local/share/man
/usr/local/share/man/man1
/usr/local/share/man/man3
/usr/local/share/man/man5
/usr/local/share/man/man7
==> The following existing directories will have their owner set to ihongdon:
/usr/local/bin
/usr/local/include
/usr/local/lib
/usr/local/share
/usr/local/lib/pkgconfig
/usr/local/share/info
/usr/local/share/man
/usr/local/share/man/man1
/usr/local/share/man/man3
/usr/local/share/man/man5
/usr/local/share/man/man7
==> The following existing directories will have their group set to admin:
/usr/local/bin
/usr/local/include
/usr/local/lib
/usr/local/share
/usr/local/lib/pkgconfig
/usr/local/share/info
/usr/local/share/man
/usr/local/share/man/man1
/usr/local/share/man/man3
/usr/local/share/man/man5
/usr/local/share/man/man7
==> The following new directories will be created:
/usr/local/Cellar
/usr/local/Homebrew
/usr/local/Frameworks
/usr/local/etc
/usr/local/opt
/usr/local/sbin
/usr/local/share/zsh
/usr/local/share/zsh/site-functions
/usr/local/var
==> /usr/bin/sudo /bin/chmod u+rwx /usr/local/bin /usr/local/include /usr/local/lib /usr/local/share /usr/local/lib/pkgconfig /usr/local/share/info /usr/local/share/man /usr/local/share/man/man1 /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7
Password:
==> /usr/bin/sudo /bin/chmod g+rwx /usr/local/bin /usr/local/include /usr/local/lib /usr/local/share /usr/local/lib/pkgconfig /usr/local/share/info /usr/local/share/man /usr/local/share/man/man1 /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7
==> /usr/bin/sudo /usr/sbin/chown ihongdon /usr/local/bin /usr/local/include /usr/local/lib /usr/local/share /usr/local/lib/pkgconfig /usr/local/share/info /usr/local/share/man /usr/local/share/man/man1 /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7
==> /usr/bin/sudo /usr/bin/chgrp admin /usr/local/bin /usr/local/include /usr/local/lib /usr/local/share /usr/local/lib/pkgconfig /usr/local/share/info /usr/local/share/man /usr/local/share/man/man1 /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7
==> /usr/bin/sudo /bin/mkdir -p /usr/local/Cellar /usr/local/Homebrew /usr/local/Frameworks /usr/local/etc /usr/local/opt /usr/local/sbin /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var
==> /usr/bin/sudo /bin/chmod g+rwx /usr/local/Cellar /usr/local/Homebrew /usr/local/Frameworks /usr/local/etc /usr/local/opt /usr/local/sbin /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var
==> /usr/bin/sudo /bin/chmod 755 /usr/local/share/zsh /usr/local/share/zsh/site-functions
==> /usr/bin/sudo /usr/sbin/chown ihongdon /usr/local/Cellar /usr/local/Homebrew /usr/local/Frameworks /usr/local/etc /usr/local/opt /usr/local/sbin /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var
==> /usr/bin/sudo /usr/bin/chgrp admin /usr/local/Cellar /usr/local/Homebrew /usr/local/Frameworks /usr/local/etc /usr/local/opt /usr/local/sbin /usr/local/share/zsh /usr/local/share/zsh/site-functions /usr/local/var
==> /usr/bin/sudo /bin/mkdir -p /Users/ihongdon/Library/Caches/Homebrew
==> /usr/bin/sudo /bin/chmod g+rwx /Users/ihongdon/Library/Caches/Homebrew
==> /usr/bin/sudo /usr/sbin/chown ihongdon /Users/ihongdon/Library/Caches/Homebrew
==> /usr/bin/sudo /bin/mkdir -p /Library/Caches/Homebrew
==> /usr/bin/sudo /bin/chmod g+rwx /Library/Caches/Homebrew
==> /usr/bin/sudo /usr/sbin/chown ihongdon /Library/Caches/Homebrew
==> Downloading and installing Homebrew...
HEAD is now at 1c7c876f3 Merge pull request #4736 from scpeters/bottle_json_local_filename
==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
https://github.com/Homebrew/brew#donations
==> Tapping homebrew/core
ihongdon-ui-MacBook-Pro:~ ihongdon$
ihongdon-ui-MacBook-Pro:~ ihongdon$

 




(1-2) Graphviz를 설치합니다.


Homebrew를 설치하였으면,이제 아래의 Homebrew 코드를 터미널에서 실행하여 Graphviz를 설치해줍니다.


$ brew install graphviz




ihongdon-ui-MacBook-Pro:~ ihongdon$
ihongdon-ui-MacBook-Pro:~ ihongdon$ brew install graphviz
==> Installing dependencies for graphviz: libtool, libpng, freetype, fontconfig, jpeg, libtiff, webp, gd
==> Installing graphviz dependency: libtool
==> Downloading https://homebrew.bintray.com/bottles/libtool-2.4.6_1.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring libtool--2.4.6_1.high_sierra.bottle.tar.gz
==> Caveats
In order to prevent conflicts with Apple's own libtool we have prepended a "g"
so, you have instead: glibtool and glibtoolize.
==> Summary
🍺 /usr/local/Cellar/libtool/2.4.6_1: 71 files, 3.7MB
==> Installing graphviz dependency: libpng
==> Downloading https://homebrew.bintray.com/bottles/libpng-1.6.35.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring libpng--1.6.35.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/libpng/1.6.35: 26 files, 1.2MB
==> Installing graphviz dependency: freetype
==> Downloading https://homebrew.bintray.com/bottles/freetype-2.9.1.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring freetype--2.9.1.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/freetype/2.9.1: 60 files, 2.6MB
==> Installing graphviz dependency: fontconfig
==> Downloading https://homebrew.bintray.com/bottles/fontconfig-2.13.0.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring fontconfig--2.13.0.high_sierra.bottle.tar.gz
==> Regenerating font cache, this may take a while
==> /usr/local/Cellar/fontconfig/2.13.0/bin/fc-cache -frv
🍺 /usr/local/Cellar/fontconfig/2.13.0: 511 files, 3.2MB
==> Installing graphviz dependency: jpeg
==> Downloading https://homebrew.bintray.com/bottles/jpeg-9c.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring jpeg--9c.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/jpeg/9c: 21 files, 724.5KB
==> Installing graphviz dependency: libtiff
==> Downloading https://homebrew.bintray.com/bottles/libtiff-4.0.9_4.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring libtiff--4.0.9_4.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/libtiff/4.0.9_4: 246 files, 3.5MB
==> Installing graphviz dependency: webp
==> Downloading https://homebrew.bintray.com/bottles/webp-1.0.0.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring webp--1.0.0.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/webp/1.0.0: 38 files, 2MB
==> Installing graphviz dependency: gd
==> Downloading https://homebrew.bintray.com/bottles/gd-2.2.5.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring gd--2.2.5.high_sierra.bottle.tar.gz
🍺 /usr/local/Cellar/gd/2.2.5: 35 files, 1.1MB
==> Installing graphviz
==> Downloading https://homebrew.bintray.com/bottles/graphviz-2.40.1.high_sierra.bottle.1.tar.gz
######################################################################## 100.0%
==> Pouring graphviz--2.40.1.high_sierra.bottle.1.tar.gz
🍺 /usr/local/Cellar/graphviz/2.40.1: 500 files, 11.2MB
==> Caveats
==> libtool
In order to prevent conflicts with Apple's own libtool we have prepended a "g"
so, you have instead: glibtool and glibtoolize.
ihongdon-ui-MacBook-Pro:~ ihongdon$
ihongdon-ui-MacBook-Pro:~ ihongdon$

 





  (2) Python 2.7 PyGraphviz library 설치하기



PyGraphviz는 Python 3.x 버전, 그리고 Python 2.7 버전에서 사용할 수 있습니다. 이번 포스팅에서는 Python 2.7 버전에 PyGraphviz 패키지를 설치해보겠습니다. 


conda env list로 가상환경 리스트를 확인하고, source activate 로 Python2.7 버전의 가상환경을 활성화시킨 후에, pip install --upgrade pip 로 pip 버전 업그레이드 한 후에, easy_install pygraphviz 로 PyGraphviz를 설치하였습니다. 


왜 그런지 이유는 모르겠으나 pip install pygraphviz 로 설치하려고 하니 설치가 되다가 막판에 에러가 났습니다. 

pip install git://github.com/pygraphviz/pygraphviz.git 도 시도를 해봤는데 역시 에러가 났습니다. 

다행히 easy_install pygrapviz 로 설치가 되네요. 




ihongdon-ui-MacBook-Pro:~ ihongdon$ conda env list

# conda environments:

#

base                  *  /Users/ihongdon/anaconda3

py2.7_tf1.4              /Users/ihongdon/anaconda3/envs/py2.7_tf1.4

py3.5_tf1.4              /Users/ihongdon/anaconda3/envs/py3.5_tf1.4

ihongdon-ui-MacBook-Pro:~ ihongdon$ 

ihongdon-ui-MacBook-Pro:~ ihongdon$ source activate py2.7_tf1.4

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ pip install --upgrade pip

Requirement already up-to-date: pip in ./anaconda3/envs/py2.7_tf1.4/lib/python2.7/site-packages (18.0)

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ 

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ 

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ easy_install pygraphviz

Searching for pygraphviz

Reading https://pypi.python.org/simple/pygraphviz/

Downloading https://files.pythonhosted.org/packages/87/5e/40efbb2d02ee9d0282f6c8b9e477f6444a025a7ecf8cc0b15fe87a288708

/pygraphviz-1.4rc1.zip#sha256=e0b3a7f1d9203f9748b94e8365656755201966b562e53fd6424bed89e98fdc4e

Best match: pygraphviz 1.4rc1

Processing pygraphviz-1.4rc1.zip

Writing /var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T/easy_install-U3TQDK/pygraphviz-1.4rc1/setup.cfg

Running pygraphviz-1.4rc1/setup.py -q bdist_egg --dist-dir /var/folders/6q/mtq6ftrj6_z4txn_zsxcfyxc0000gn/T/easy_install-U3TQDK/pygraphviz-1.4rc1/egg-dist-tmp-wc0yf4

warning: no previously-included files matching '*~' found anywhere in distribution

warning: no previously-included files matching '*.pyc' found anywhere in distribution

warning: no previously-included files matching '.svn' found anywhere in distribution

no previously-included directories found matching 'doc/build'

pygraphviz/graphviz_wrap.c:3354:12: warning: incompatible pointer to integer conversion returning 'Agsym_t *' (aka 'struct Agsym_s *') from a function with result type 'int' [-Wint-conversion]

    return agattr(g, kind, name, val);

           ^~~~~~~~~~~~~~~~~~~~~~~~~~

pygraphviz/graphviz_wrap.c:3438:7: warning: unused variable 'fd1' [-Wunused-variable]

  int fd1 ;

      ^

pygraphviz/graphviz_wrap.c:3439:13: warning: unused variable 'mode_obj1' [-Wunused-variable]

  PyObject *mode_obj1 ;

            ^

pygraphviz/graphviz_wrap.c:3440:13: warning: unused variable 'mode_byte_obj1' [-Wunused-variable]

  PyObject *mode_byte_obj1 ;

            ^

pygraphviz/graphviz_wrap.c:3441:9: warning: unused variable 'mode1' [-Wunused-variable]

  char *mode1 ;

        ^

pygraphviz/graphviz_wrap.c:3509:7: warning: unused variable 'fd2' [-Wunused-variable]

  int fd2 ;

      ^

pygraphviz/graphviz_wrap.c:3510:13: warning: unused variable 'mode_obj2' [-Wunused-variable]

  PyObject *mode_obj2 ;

            ^

pygraphviz/graphviz_wrap.c:3511:13: warning: unused variable 'mode_byte_obj2' [-Wunused-variable]

  PyObject *mode_byte_obj2 ;

            ^

pygraphviz/graphviz_wrap.c:3512:9: warning: unused variable 'mode2' [-Wunused-variable]

  char *mode2 ;

        ^

9 warnings generated.

zip_safe flag not set; analyzing archive contents...

pygraphviz.graphviz: module references __file__

pygraphviz.release: module references __file__

pygraphviz.tests.test: module references __file__

creating /Users/ihongdon/anaconda3/envs/py2.7_tf1.4/lib/python2.7/site-packages/pygraphviz-1.4rc1-py2.7-macosx-10.6-x86_64.egg

Extracting pygraphviz-1.4rc1-py2.7-macosx-10.6-x86_64.egg to /Users/ihongdon/anaconda3/envs/py2.7_tf1.4/lib/python2.7/site-packages

Adding pygraphviz 1.4rc1 to easy-install.pth file


Installed /Users/ihongdon/anaconda3/envs/py2.7_tf1.4/lib/python2.7/site-packages/pygraphviz-1.4rc1-py2.7-macosx-10.6-x86_64.egg

Processing dependencies for pygraphviz

Finished processing dependencies for pygraphviz

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ 

(py2.7_tf1.4) ihongdon-ui-MacBook-Pro:~ ihongdon$ 







  (3) Graphviz와 PyGraphviz를 사용하여 Decision Tree 시각화 해보기



이제 Graphviz와 PyGraphviz를 사용해서 Jupyter Notebook 에서 Decision Tree를 시각화해보겠습니다. Iris 데이터셋을 사용해서 Decision Tree로 Iris 종류 분류하는 예제입니다. 







# Common imports

import numpy as np

import os


# To plot pretty figures

%matplotlib inline

import matplotlib

import matplotlib.pyplot as plt


# Where to save the figures

PROJECT_ROOT_DIR = "."

SUB_DIR = "decision_trees"


def image_path(fig_id):

    return os.path.join(PROJECT_ROOT_DIR, "images", SUB_DIR, fig_id)

 


# Iris dataset import

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier


iris = load_iris()
list(iris.keys())
['target_names', 'data', 'target', 'DESCR', 'feature_names']


print(iris.DESCR)
Iris Plants Database
====================

Notes
-----
Data Set Characteristics:
    :Number of Instances: 150 (50 in each of three classes)
    :Number of Attributes: 4 numeric, predictive attributes and the class
    :Attribute Information:
        - sepal length in cm
        - sepal width in cm
        - petal length in cm
        - petal width in cm
        - class:
                - Iris-Setosa
                - Iris-Versicolour
                - Iris-Virginica
    :Summary Statistics:

    ============== ==== ==== ======= ===== ====================
                    Min  Max   Mean    SD   Class Correlation
    ============== ==== ==== ======= ===== ====================
    sepal length:   4.3  7.9   5.84   0.83    0.7826
    sepal width:    2.0  4.4   3.05   0.43   -0.4194
    petal length:   1.0  6.9   3.76   1.76    0.9490  (high!)
    petal width:    0.1  2.5   1.20  0.76     0.9565  (high!)
    ============== ==== ==== ======= ===== ====================

    :Missing Attribute Values: None
    :Class Distribution: 33.3% for each of 3 classes.
    :Creator: R.A. Fisher
    :Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
    :Date: July, 1988

This is a copy of UCI ML iris datasets.
http://archive.ics.uci.edu/ml/datasets/Iris

The famous Iris database, first used by Sir R.A Fisher

This is perhaps the best known database to be found in the
pattern recognition literature.  Fisher's paper is a classic in the field and
is referenced frequently to this day.  (See Duda & Hart, for example.)  The
data set contains 3 classes of 50 instances each, where each class refers to a
type of iris plant.  One class is linearly separable from the other 2; the
latter are NOT linearly separable from each other.

References
----------
   - Fisher,R.A. "The use of multiple measurements in taxonomic problems"
     Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions to
     Mathematical Statistics" (John Wiley, NY, 1950).
   - Duda,R.O., & Hart,P.E. (1973) Pattern Classification and Scene Analysis.
     (Q327.D83) John Wiley & Sons.  ISBN 0-471-22361-1.  See page 218.
   - Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System
     Structure and Classification Rule for Recognition in Partially Exposed
     Environments".  IEEE Transactions on Pattern Analysis and Machine
     Intelligence, Vol. PAMI-2, No. 1, 67-71.
   - Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule".  IEEE Transactions
     on Information Theory, May 1972, 431-433.
   - See also: 1988 MLC Proceedings, 54-64.  Cheeseman et al"s AUTOCLASS II
     conceptual clustering system finds 3 classes in the data.
   - Many, many more ...

 

 


iris.feature_names

['sepal length (cm)',
 'sepal width (cm)',
 'petal length (cm)',
 'petal width (cm)']


iris.data[:5,]

array([[ 5.1,  3.5,  1.4,  0.2],
       [ 4.9,  3. ,  1.4,  0.2],
       [ 4.7,  3.2,  1.3,  0.2],
       [ 4.6,  3.1,  1.5,  0.2],
       [ 5. ,  3.6,  1.4,  0.2]])

 





Scikit Learn 의 DecisionTreeClassifier 클래스를 사용하여 iris 분류 모델을 적합시켜 보겠습니다. 





X = iris.data[:, 2:] # petal length and width

y = iris.target

 

# Train the model

tree_clf = DecisionTreeClassifier(max_depth=2)

tree_clf.fit(X, y)






위의 Decision Tree 모형을 export_grapviz() 함수를 사용하여 graphviz의 dot format 파일로 내보내기(export)해보겠습니다. 





# Visualization

from sklearn.tree import export_graphviz


export_graphviz(

        tree_clf,

        out_file=image_path("iris_tree.dot"),

        feature_names=iris.feature_names[2:],

        class_names=iris.target_names,

        rounded=True,

        filled=True

    )

 




위의 export_graphviz() 코드를 실행시키면 ""/Users/ihongdon/images/decision_trees/" 폴더에 iris_tree.dot 파일이 생성됩니다. 이 dot format 파일을 워드나 노트패드를 사용해서 열어보면 아래와 같이 되어있습니다. 



digraph Tree {
node [shape=box, style="filled, rounded", color="black", fontname=helvetica] ;
edge [fontname=helvetica] ;
0 [label="petal width (cm) <= 0.8\ngini = 0.667\nsamples = 150\nvalue = [50, 50, 50]\nclass = setosa", fillcolor="#e5813900"] ;
1 [label="gini = 0.0\nsamples = 50\nvalue = [50, 0, 0]\nclass = setosa", fillcolor="#e58139ff"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="petal width (cm) <= 1.75\ngini = 0.5\nsamples = 100\nvalue = [0, 50, 50]\nclass = versicolor", fillcolor="#39e58100"] ;
0 -> 2 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
3 [label="gini = 0.168\nsamples = 54\nvalue = [0, 49, 5]\nclass = versicolor", fillcolor="#39e581e5"] ;
2 -> 3 ;
4 [label="gini = 0.043\nsamples = 46\nvalue = [0, 1, 45]\nclass = virginica", fillcolor="#8139e5f9"] ;
2 -> 4 ;
}

 





마지막으로, 위의 iris_tree.dot 의 dot format파일을 가지고 pygraphviz 패키지를 사용하여 Decision Tree를 시각화해보겠습니다. 





import pygraphviz as pgv

from IPython.display import Image

graph = pgv.AGraph("/Users/ihongdon/images/decision_trees/iris_tree.dot")

graph.draw('iris_tree_out.png', prog='dot')

Image('iris_tree_out.png')

 




[Reference]

  • Graphviz: https://graphviz.gitlab.io/
  • PyGraphviz: https://pygraphviz.github.io/


많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,

가변수(dummy variable)는 해당 범주(category)에 해당하는 경우 '1', 해당하지 않는 경우 '0'으로 값을 입력해주어서 통계나 기계학습을 할 때 컴퓨터가 범주형 자료의 값을 인식할 수 있도록 해줍니다. 


이번 포스팅에서는 Python pandas 라이브러리의 get_dummies() 함수를 사용하여, 


(1) 하나의 cell 당 값이 한개씩 들어있는 범주형 자료를 가지고 가변수 만들기

(2) 하나의 cell 당 범주 값이 여러개씩 들어있는 범주형 자료를 가지고 가변수 만들기


를 해보겠습니다. 


첫번째 것는 간단하구요, 두번째 것은 하나의 cell 안에 들어있는 복수개의 범주형 자료들을 분할(split) 한고 가변수 만드는 작업을 해야 해서 복잡합니다.  첫번째 것은 이전 포스팅(http://rfriend.tistory.com/273)에서 한번 소개했던 적이 있었구요, 이번 포스팅은 두번째 것을 살펴보기 위해서 글을 씁니다. 



  (1) 하나의 cell 당 값이 한개씩 들어있는 범주형 자료를 가지고 가변수 만들기



먼저 필요한 라이브러리들을 불러오고, 음악별 장르를 매핑해놓은 간단한 예제 데이터프레임을 만들어보겠습니다. 



# importing libraries

import numpy as np

import pandas as pd

from pandas import DataFrame

from pandas import Series



# make an example DataFrame

music_df = DataFrame({'music_id': [1, 2, 3, 4, 5], 

                      'music_genre': ['rock', 

                                      'disco', 

                                      'pop', 

                                      'rock', 

                                      'pop']}

                      , columns = ['music_id', 'music_genre'])


In [3]: music_df

Out[3]:

   music_id    music_genre

0 1              rock

1 2              disco

2 3              pop

3 4              rock

4 5              pop

 



위의 범주형 값이 들어있는 'music_genre' 범주형 변수에 대해서 pandas의 get_dummies() 함수를 사용하여 가변수(dummy variable) 을 만들어보겠습니다. 



In [4]: music_dummy_mat = pd.get_dummies(music_df['music_genre'])


In [5]: music_dummy_mat

Out[5]:

     disco       pop      rock

0         0         0         1

1         1         0         0

2         0         1         0

3         0         0         1

4         0         1         0

 



이번에는 (a) 가변수의 변수 이름들 앞에 'genre_' 라는 접두사(prefix)를 붙이고, (b) 원래의 music_df 데이터프레임에다가 가변수를 생성하면서 만든 music_dummy_mat 데이터프레임을 join() 함수를 사용하여 합쳐보겠습니다. 



In [6]: music_dummy_mat = music_df.join(music_dummy_mat.add_prefix('genre_'))


In [7]: music_dummy_mat

Out[7]:

   music_id  music_genre   genre_disco  genre_pop  genre_rock

0       1       rock                0               0                1

1       2       disco               1               0                0

2       3       pop                0                1                0

3       4       rock                0                0                1

4       5       pop                0                1                0

 




  (2) 하나의 cell에 범주 값이 여러개씩 들어있는 범주형 자료를 가지고 가변수 만들기


음악 한개당 구분자는 수직바('|') 로 해서 복수개의 음악 장르 범주 값이 들어있는 예제 데이터프레임을 만들어보겠습니다.  



# making example DataFrame

music_multi_df = DataFrame({'music_id': [1, 2, 3, 4, 5], 

                      'music_genre': ['rock|punk rock|heavy metal', 

                                      'hip hop|reggae', 

                                      'pop|jazz|blues', 

                                      'disco|techo', 

                                      'rhythm and blues|blues|jazz']}

                      , columns = ['music_id', 'music_genre'])

 


In [9]: music_multi_df

Out[9]:

     music_id      music_genre

0      1             rock|punk rock|heavy metal

1      2             hip hop|reggae

2      3             pop|jazz|blues

3      4             disco|techo

4      5             rhythm and blues|blues|jazz




'music_genre' 변수의 각 값에 수직바('|')로 구분되어 묶여있는 여러개의 범주 값들을 split() 문자열 메소드를 사용하여 분리를 한 후에, set.union() 함수를 사용하여 각 음악 장르 범주 값들을 원소로 가지는 하나의 집합을 만들어 보겠습니다. 



In [10]: music_genre_iter = (set(x.split('|')) for x in music_multi_df.music_genre)


In [11]: music_genre_set = sorted(set.union(*music_genre_iter))


In [12]: music_genre_set

Out[12]:

['blues',

 'disco',

 'heavy metal',

 'hip hop',

 'jazz',

 'pop',

 'punk rock',

 'reggae',

 'rhythm and blues',

 'rock',

 'techo']

 



다음으로, np.zeros() 함수를 사용하여 music_multi_df 의 행의 개수만큼의 행과 music_genre_set 의 개수만큼의 열을 가지는 '0'으로 채워진 데이터프레임을 만들어보겠습니다.  '0'만 채워진 데이터프레임은 다음번에 가변수의 '1' 값을 채워나갈 빈 집으로 보면 되겠습니다. 



In [14]: indicator_mat = DataFrame(np.zeros((len(music_multi_df), len(music_genre_set))),

    ...: columns=music_genre_set)


In [15]: indicator_mat

Out[15]:

blues disco heavy metal hip hop jazz pop punk rock reggae \

0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0


rhythm and blues rock techo

0 0.0 0.0 0.0

1 0.0 0.0 0.0

2 0.0 0.0 0.0

3 0.0 0.0 0.0

4 0.0 0.0 0.0  

 



이제 for loop 문을 사용하여 각 row 별로 music_genre 의 값을 순회하면서 split() 메소드로 분리를 한 다음에, 각 음악별로 해당 장르를 방금 위에서 '0'으로 자리를 채워두었던 indicator_mat 데이터프레임의 행, 열을 참조하여 해당 위치에 '1'의 값을 입력해주겠습니다. 



In [18]: for i, genre in enumerate(music_multi_df.music_genre):

              indicator_mat.loc[i, genre.split('|')] = 1


In [19]: indicator_mat

Out[19]:

     blues disco heavy metal hip hop jazz pop punk rock reggae \

0      0.0      0.0      1.0      0.0       0.0    0.0      1.0      0.0

1       0.0      0.0      0.0      1.0      0.0    0.0      0.0      1.0

2      1.0      0.0      0.0      0.0       1.0    1.0      0.0      0.0

3      0.0      1.0      0.0      0.0       0.0    0.0      0.0      0.0

4      1.0      0.0      0.0      0.0       1.0    0.0      0.0      0.0


     rhythm and blues      rock      techo

0      0.0                       1.0       0.0

1      0.0                       0.0       0.0

2      0.0                       0.0       0.0

3      0.0                       0.0       1.0

4      1.0                       0.0       0.0  

 



마지막으로, (a) 음악 장르 가변수의 앞 머리에 'genre_' 라는 접두사를 붙이고, (b) 원래의 'music_multi_df' 데이터프레임에 방금전에 새로 만든 'indicator_mat' 가변수 데이터프레임을 join() 함수를 이용하여 합쳐보겠습니다. 



In [20]: music_indicator_mat = music_multi_df.join(indicator_mat.add_prefix('genre_'))


In [21]: music_indicator_mat

Out[21]:

  music_id music_genre genre_blues genre_disco \

0 1 rock|punk rock|heavy metal 0.0 0.0

1 2 hip hop|reggae 0.0 0.0

2 3 pop|jazz|blues 1.0 0.0

3 4 disco|techo 0.0 1.0

4 5 rhythm and blues|blues|jazz 1.0 0.0


  genre_heavy metal genre_hip hop genre_jazz genre_pop genre_punk rock \

0 1.0 0.0 0.0 0.0 1.0

1 0.0 1.0 0.0 0.0 0.0

2 0.0 0.0 1.0 1.0 0.0

3 0.0 0.0 0.0 0.0 0.0

4 0.0 0.0 1.0 0.0 0.0


  genre_reggae genre_rhythm and blues genre_rock genre_techo

0 0.0 0.0 1.0 0.0

1 1.0 0.0 0.0 0.0

2 0.0 0.0 0.0 0.0

3 0.0 0.0 0.0 1.0

4 0.0 1.0 0.0 0.0  


 



많은 도움이 되었기를 바랍니다. 

728x90
반응형
Posted by Rfriend
,

학교 다닐 때 행렬로 연립방정식 풀었던 기억이 날 듯 합니다. 선형대수(Linear Algebra)는 통계, 기계학습, 공학, 영상/이미지 처리 등 여러 분야에서 활용이 됩니다. 선형대수를 전부 다루려면 너무나 방대하므로, 이번 포스팅에서는 Python의 NumPy에 있는 선형대수(Linear Algebra) 함수들 중에서 자주 사용하는 함수에 대해서만 선별적으로 소개하겠습니다. 그리고 선형대수의 이론적인 부분은 별도로 참고할 수 있는 링크를 달도록 하겠습니다. 


  • 단위행렬 (Unit matrix): np.eye(n)
  • 대각행렬 (Diagonal matrix): np.diag(x)
  • 내적 (Dot product, Inner product): np.dot(a, b)
  • 대각합 (Trace): np.trace(x)
  • 행렬식 (Matrix Determinant): np.linalg.det(x)
  • 역행렬 (Inverse of a matrix): np.linalg.inv(x)
  • 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)
  • 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)
  • 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)
  • 최소자승 해 풀기 (Compute the Least-squares solution): m, c = np.linalg.lstsq(A, y, rcond=None)[0]




 1. 단위행렬 혹은 항등행렬 (Unit matrix, Identity matrix): np.eye(n)


단위행렬은 대각원소가 1이고, 나머지는 모두 0인 n차 정방행렬을 말하며, numpy의 eye() 함수를 사용해서 만들 수 있습니다. 


* 참고 링크 : https://rfriend.tistory.com/141



import numpy as np


unit_mat_4 = np.eye(4)


print(unit_mat_4)

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

 




  1. 대각행렬 (Diagonal matrix): np.diag(x)


대각행렬은 대각성분 이외의 모든 성분이 모두 '0'인 n차 정방행렬을 말합니다. 아래 예시의 행렬에서 빨간색으로 표시한 원소를 '0'으로 바꾼 행렬이 대각행렬입니다. 


* 참고 링크 : http://rfriend.tistory.com/141


 

In [1]: import numpy as np


In [2]: x = np.arange(9).reshape(3, 3)


In [3]: print(x)

[[0 1 2]

 [3 4 5]

 [6 7 8]]


In [4]: np.diag(x)

Out[4]: array([0, 4, 8])


In [5]: np.diag(np.diag(x))

Out[5]:

array([[0, 0, 0],

        [0, 4, 0],

        [0, 0, 8]])





  2. 내적 (Dot product, Inner product): np.dot(a, b), a.dot(b)


matrix dot product, inner product, scalar product, projection product

Python에서 '*' 를 사용한 두 행렬 간 곱은 원소 간 곱(element-wise product)을 반환하며, 선형대수에서 말하는 행렬 간 내적 곱을 위해서는 np.dot() 함수를 이용해야 합니다. 


원소 간 곱 (element-wise product)

: a*b

내적 (dot product, inner product)

: np.dot(a, b)

 

In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [8]: a*a

Out[8]:

array([[0, 1],

        [4, 9]])


In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [9]: np.dot(a, a)

Out[9]:

array([[ 2, 3],

        [ 6, 11]])



np.dot(a, b) NumPy 함수와 a.dot(b)의 배열 메소드의 결과는 동일합니다. 



In [10]: a.dot(a)

Out[10]:

array([[ 2, 3],

        [ 6, 11]])

 




  3. 대각합 (Trace): np.trace(x)


정방행렬의 대각에 위치한 원소를 전부 더해줍니다. 

아래의 2차 정방행렬 예의 대각합은 0+5+10+15 = 30 이 됩니다. (파란색으로 표시함)



In [12]: b = np.arange(16).reshape(4, 4)


In [13]: print(b)

[[ 0 1 2 3]

 [ 4 5 6 7]

 [ 8 9 10 11]

 [12 13 14 15]]


In [14]: np.trace(b)

Out[14]: 30

 



3차원 행렬에 대해서도 대각합을 구할 수 있습니다. 2차원은 대각선 부분의 원소 값을 전부 더하면 되지만 3차원 행렬에서는 대각(diagonal)이 어떻게 되나 좀 헷갈릴 수 있겠습니다. 아래의 3차원 행렬의 대각합을 구하는 예를 살펴보면, [0+12+24, 1+13+25, 2+14+26] = [36, 39, 42] 가 됩니다. 



In [15]: c = np.arange(27).reshape(3, 3, 3)


In [16]: print(c)

[[[ 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 25 26]]]


In [17]: np.trace(c)

Out[17]: array([36, 39, 42])

 




  4. 행렬식 (Matrix Determinant): np.linalg.det(x)


역행렬이 존재하는지 여부를 확인하는 방법으로 행렬식(determinant, 줄여서 det)이라는 지표를 사용합니다. 이 행렬식이 '0'이 아니면 역행렬이 존재하고, 이 행렬식이 '0'이면 역행렬이 존재하지 않습니다. 


* 참고 링크 : http://rfriend.tistory.com/142


아래의 예에서 array([[1, 2], [3, 4]]) 의 행렬식이 '-2.0'으로서, '0'이 아니므로 역행렬이 존재한다고 판단할 수 있습니다. 



In [18]: d = np.array([[1, 2], [3, 4]])


In [19]: np.linalg.det(a)

Out[19]: -2.0

 




  5. 역행렬 (Inverse of a matrix): np.linalg.inv(x)


역행렬은 n차정방행렬 Amn과의 곱이 항등행렬 또는 단위행렬 In이 되는 n차정방행렬을 말합니다. A*B 와 B*A 모두 순서에 상관없이 곱했을 때 단위행렬이 나오는 n차정방행렬이 있다면 역행렬이 존재하는 것입니다.

역행렬은 가우스 소거법(Gauss-Jordan elimination method), 혹은 여인수(cofactor method)로 풀 수 있습니다. 




In [20]: a = np.array(range(4)).reshape(2, 2)


In [21]: print(a)

[[0 1]

 [2 3]]


In [22]: a_inv = np.linalg.inv(a)


In [23]: a_inv

Out[23]:

array([[-1.5, 0.5],

        [ 1. , 0. ]])




위의 예제에서 np.linalg.inv() 함수를 사용하여 푼 역행렬이 제대로 푼 것인지 확인을 해보겠습니다. 역행렬의 정의에 따라서 원래의 행렬에 역행렬을 곱하면, 즉, a.dot(a_inv) 또는 np.dot(a, a_inv) 를 하면 단위행렬(unit matrix)가 되는지 확인해보겠습니다. 



In [24]: a.dot(a_inv)

Out[24]:

array([[1., 0.],

         [0., 1.]])

 




  6. 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)


정방행렬 A에 대하여 Ax = λx  (상수 λ) 가 성립하는 0이 아닌 벡터 x가 존재할 때 상수 λ 를 행렬 A의 고유값 (eigenvalue), x 를 이에 대응하는 고유벡터 (eigenvector) 라고 합니다. 


np.linalg.eig() 함수는 고유값(eigenvalue) w, 고유벡터(eigenvector) v 의 두 개의 객체를 반환합니다. 


In [25]: e = np.array([[4, 2],[3, 5]])


In [26]: print(e)

[[4 2]

[3 5]]


In [27]: w, v = np.linalg.eig(e)


#  w: the eigenvalues lambda

In [28]: print(w)

[2. 7.]


# v: the corresponding eigenvectors, one eigenvector per column

In [29]: print(v)

[[-0.70710678 -0.5547002 ]

[ 0.70710678 -0.83205029]]

 



고유벡터는 배열 인덱싱하는 방법을 사용해서 각 고유값에 대응하는 고유벡터를 선택할 수 있습니다. 



# eigenvector of eigenvalue lambda 2

In [30]: print(v[:, 0]

[-0.70710678 0.70710678]


# eigenvector of eigenvalue labmda 7

In [31]: print(v[:, 1]

[-0.5547002 -0.83205029]

 




  7. 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)


특이값 분해는 고유값 분해(eigen decomposition)처럼 행렬을 대각화하는 한 방법으로서, 정방행렬뿐만 아니라 모든 m x n 행렬에 대해 적용 가능합니다. 특이값 분해는 차원축소, 데이터 압축 등에 사용할 수 있습니다. 이론적인 부분은 설명하자면 너무 길기 때문에 이 포스팅에서는 설명하지 않겠으며, 아래의 링크를 참고하시기 바랍니다. 


* 참고 링크 : http://rfriend.tistory.com/185


아래의 np.linalg.svd(A) 예제는 위의 참고 링크에서 사용했던 예제와 동일한 것을 사용하였습니다. 


In [32]: A = np.array([[3,6], [2,3], [0,0], [0,0]])


In [33]: print(A)

[[3 6]

 [2 3]

 [0 0]

 [0 0]]


In [34]: u, s, vh = np.linalg.svd(A)


In [35]: print(u)

[[-0.8816746 -0.47185793 0. 0. ]

 [-0.47185793 0.8816746 0. 0. ]

 [ 0. 0. 1. 0. ]

 [ 0. 0. 0. 1. ]]


In [36]: print(s)

[7.60555128 0.39444872]


In [37]: print(vh)

[[-0.47185793 -0.8816746 ]

 [ 0.8816746 -0.47185793]]

 




  8. 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)


아래의 두 개 연립방정식의 해(x0, x1)를 np.linalg.solve(a, b) 함수를 사용하여 풀어보겠습니다. 



위의 연립방정식을 어떻게 행렬로 입력하고, np.linalg.solve(a, b)에 입력하는지 유심히 살펴보시기 바랍니다. 



In [38]: a = np.array([[4, 3], [3, 2]])


In [39]: b = np.array([23, 16])


In [40]: x = np.linalg.solve(a, b)


In [41]: print(x)

[2. 5.]

 



NumPy 가 제대로 x0, x1의 해를 풀었는지 확인해보겠습니다. x0=2, x1=5 가 해 맞네요!



In [42]: np.allclose(np.dot(a, x), b)

Out[42]: True

 




  9. 최소자승 해 풀기 (Compute the Least-squares solution)

     : m, c = np.linalg.lstsq(A, y, rcond=None)[0]


회귀모형 적합할 때 최소자승법(Least-squares method)으로 잔차 제곱합을 최소화하는 회귀계수를 추정합니다. 


* 참고 링크 : https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html



아래의 예에서는 회귀계수 m, y절편 c를 최소자승법을 사용해서 구해보겠습니다. 



In [43]: x = np.array([0, 1, 2, 3])


In [44]: y = np.array([-1, 0.2, 0.9, 2.1])


In [45]: A = np.vstack([x, np.ones(len(x))]).T


In [46]: A

Out[46]:

array([[0., 1.],

        [1., 1.],

        [2., 1.],

        [3., 1.]])


In [47]: m, c = np.linalg.lstsq(A, y, rcond=None)[0]


In [48]: print(m, c)

0.9999999999999999 -0.9499999999999997

 



아래의 그래프에서 점은 원래의 데이터이며, 빨간색 선은 최소자승법으로 추정한 회귀식의 적합선이 되겠습니다. 



In [49]: import matplotlib.pyplot as plt

    ...: plt.plot(x, y, 'o', label='Original data', markersize=10)

    ...: plt.plot(x, m*x + c, 'r', label='Fitted line')

    ...: plt.legend()

    ...: plt.show()




많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 함수나 클래스의 구현을 미룰 때 쓰는 pass statement 에 대해서 알아보겠습니다. 


Python은 함수나 클래스를 정의할 때 { } 를 사용하지 않고 들여쓰기(indentation)로 함수나 클래스가 실행할 코드 블록을 정의하는데요, 만약 코드 블록 부분에 실행해야 할 코드가 없다면 def function_name:  이후의 줄에 아무것도 없게 되어 Python은 'SyntaxError: unexpected EOF while parsing' 에러를 발생시킵니다. 



In [1]: def null_func():

   ...:

   ...:

File "<ipython-input-1-85c822900a5a>", line 2

^

SyntaxError: unexpected EOF while parsing


 




이처럼 함수나 클래스 이름 정의 후에 ':' 다음 줄에 아무것도 실행시키지 않으려면 'pass' 문을 명시적으로 표기해주어야만 SyntaxError 가 발생하지 않습니다. 





다음은 아무것도 실행할 것이 없는 null_func() 라는 이름의 함수에 pass 문을 사용한 예입니다. 



In [2]: def null_func():

   ...:       pass

 




다음은 분류 모델의 BaseClassifier() 클래스를 정의할 때 fit() 함수에 pass 문을 사용한 예입니다. 



In [3]: from sklearn.base import BaseEstimator

   ...:   class BaseClassifier(BaseEstimator):

   ...:       def fit(self, X, y=None):

   ...:           pass

   ...:       def predict(self, X):

   ...:           return np.zeros((len(X), 1), dtype=bool)

 



'빈 구현'을 만드는 pass 문의 사용 용도 만큼이나 이번 포스팅은 별 내용이 없네요. ^^; 





이전에 for loop 포스팅을 했을 때 pass, continue, break 문을 비교해서 설명했던 적이 있는데요, 이번에 함수 파트에서 pass 문이 다시 나온만큼 복습하는 차원에서 pass와 continue문을 간단한 예를 들어서 한번 더 비교해서 설명하겠습니다. 



  pass : 아무것도 실행하지 않고 다음 행으로 넘어감



In [4]: for i in [1, 2, 3, 4, 5, 6]:

   ...:       if i == 4:

   ...:           pass

   ...:           print("This pass block will be printed before 4")

   ...:       print("The number is ", i)

   ...:   print("The end")

   ...:


The number is 1

The number is 2

The number is 3

This pass block will be printed before 4

The number is 4

The number is 5

The number is 6

The end

 




  continue : 다음 순번의 loop로 되돌아가서 loop문을 실행함



In [5]: for i in [1, 2, 3, 4, 5, 6]:

   ...:       if i == 4:

   ...:           continue

   ...:           print("This continue block and No. 4 will not be printed")

   ...:       print("The number is ", i)

   ...:   print("The end")


The number is 1

The number is 2

The number is 3

The number is 5

The number is 6

The end

 



많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 함수 안에 isinstance() 함수를 사용하여 매개변수의 형식(Type)을 확인하고, 함수에서 필요로 하는 데이터 형식이 아니면 에러 메시지를 반환하여 함수 사용자로 하여금 에러를 수정하는데 참고할 수 있는 정보를 제공할 수 있도록 하는 방법에 대해서 소개하겠습니다. 


  • 데이터 형식 확인 함수: isinstance(my_number, int), isinstance(my_string, str)
  • raise TypeError("message")
  • raise ValueError("message")




  (1) 정수형이 아니면 TypeError, 양수가 아니면 ValueError 반환하기


아래의 [1] 번 함수는 숫자를 input으로 넣으면 짝수(even number) 인지 아니면 홀수(odd number) 인지를 판단해주는 예제입니다. 


- if not isinstance(number, int) 조건문 함수를 사용하여 정수인지 여부를 확인하고, 

- if number <= 0 조건문으로 양수가 아닌지를 확인하여 

정수가 아니면 TypeError, 양수가 아니면 ValueError 를 반환하도록 하였습니다. 



In [1]: def even_odd_num_checker(number):

   ...:

   ...:     # TypeError, ValueError handling

   ...:     if not isinstance(number, int):

   ...:         raise TypeError("'number' is not an integer.")

   ...:     if number <= 0:

   ...:         raise ValueError("'number' must be positive.")

   ...:

   ...:     if number % 2 == 0:

   ...:         print("This number is EVEN")

   ...:     else:

   ...:         print("This number is ODD")


In [2]: even_odd_num_checker(10)

This number is EVEN


In [3]: even_odd_num_checker(9)

This number is ODD

 



아래의 [4]번에서는 '10'이라는 문자열을 입력했을 때 TypeError 가 발생한 예이며, [5]번은 매개변수 값으로 5.5 를 입력했을 때 정수(integer)가 아니기 때문에 역시 TypeEror가 발생한 예입니다. 그리고 [6]번 예는 -2 가 양수가 아니기 때문에 ValueError가 발생하였습니다.  위의 [1]번에서 정의한 함수대로 잘 작동하고 있음을 알 수 있습니다. 



In [4]: even_odd_num_checker('10') # TypeError

Traceback (most recent call last):


File "<ipython-input-4-858c9cb910a4>", line 1, in <module>

even_odd_num_checker('10') # TypeError


File "<ipython-input-1-b74b94983114>", line 5, in even_odd_num_checker

raise TypeError("'number' is not an integer.")


TypeError: 'number' is not an integer.



In [5]: even_odd_num_checker(5.5) # TypeError

Traceback (most recent call last):


File "<ipython-input-5-1125bee4ec3f>", line 1, in <module>

even_odd_num_checker(5.5) # ValueError


File "<ipython-input-1-b74b94983114>", line 5, in even_odd_num_checker

raise TypeError("'number' is not an integer.")


TypeError: 'number' is not an integer.

 


In [6]: even_odd_num_checker(-2) # ValueError

Traceback (most recent call last):


File "<ipython-input-6-8bb80e5d6ca4>", line 1, in <module>

even_odd_num_checker(-2) # ValueError


File "<ipython-input-1-b74b94983114>", line 7, in even_odd_num_checker

raise ValueError("'number' must be positive.")


ValueError: 'number' must be positive.





  (2) 문자열이 아니면 TypeError 반환하기


다음으로 [8]번에서 문자열을 매개변수 값으로 입력하면 문자열의 길이를 반환해주는 함수를 정의해보겠습니다. 이때 매개변수 값이 문자열(string)이 아니면 TypeError 를 반환하도록 하였습니다. 


[9]번에서 string_length() 함수에 "hello world" 값을 넣으니 길이가 11이라고 정상적으로 작동함을 알 수 있습니다. 



In [7]: import sys


In [8]: def string_length(string_input):

   ...:

   ...: # Python version check

   ...:     if sys.version_info[0] == 3:

   ...:         string_arg = str # Python 3x version

   ...:     else:

   ...:         string_arg = basestring # Python 2x version

   ...:

   ...: # TypeError handling

   ...:     if not isinstance(string_input, string_arg):

   ...:         raise TypeError("'string' is not a string")

   ...:

   ...:     string_length = len(string_input)

   ...:     print("The length of the string is {0}".format(string_length))


In [9]: string_length("hello world")

The length of the string is 11

 



[10]번에서 string_length() 함수에 숫자 10을 넣으면 문자열이 아닌 정수형이므로 TypeError 메시지를 반환합니다. 



In [10]: string_length(10) # TypeError

Traceback (most recent call last):


File "<ipython-input-10-997e19d6acd3>", line 1, in <module>

string_length(10) # TypeError


File "<ipython-input-8-8363558b8e40>", line 11, in string_length

raise TypeError("'string' is not a string")


TypeError: 'string' is not a string

 


많은 도움이 되었기를 바랍니다. 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python의 함수 중에서도 '함수 안의 함수 (Function in Function)' 로서 


- (1) 중첩 함수 (Nested Function): 함수 안에 정의된 함수

- (2) 재귀 함수 (Recursive Function): 함수 안에 자기 자신을 호출하는 함수


에 대해서 알아보겠습니다. 


[ Python 함수 안의 함수 (Function in Function) ]




  (1) 중첩 함수 (Nested Function): 함수 안에 정의된 함수


def 로 시작하는 함수 안에 또 다른 하나의 def 로 시작하는 함수를 정의할 수 있는데요, 이때 함수 안에 정의된 또 다른 함수를 중첩 함수 (Nested Function) 이라고 합니다. 


중첩 함수는 자기가 속한 원래 함수의 매개변수를 받아서 사용할 수 있습니다. 아래의 예시는 outer() 함수의 num 매개변수를 중첩함수인 inner() 함수의 매개변수 input 으로 사용해서 최종 결과값인 result2 를 반환하도록 하는 함수입니다. 



In [1]: def outer(num):

   ...:

   ...:     def inner(n): # nested function

   ...:         result1 = n + 1

   ...:         return result1

   ...:

   ...:     result2 = inner(num)**2

   ...:     return result2;


In [2]: outer(9) # (9+1)**2

   ...:

Out[2]: 100

 




중첩 함수 (nested function)는 자신이 속한 원래 함수 안에서만 역할을 하며, 원래 함수의 밖에서는 인식이 안됩니다.  아래의 [3] 처럼 중첩함수인 inner() 함수를 호출하려고 하면 'NameError: name 'inner' is not defined' 라는 에러메시지가 뜹니다. 



In [3]: inner(10) # NameError

Traceback (most recent call last):


File "<ipython-input-3-4050b378a5f5>", line 1, in <module>

inner(10) # NameError


NameError: name 'inner' is not defined

 




중첩함수를 사용하면 복잡한 수식도 여러개의 함수를 하나의 함수로 묶어서 정의할 수 있습니다. 아래의 예시는 모집단의 표준편차(population standard deviation) 사용자 정의 함수를 중첩함수를 사용해서 정의한 것입니다. 





In [4]: import math


In [5]: def stddev(*args):

   ...:

   ...:     def mean_func():  # nested function

   ...:         mean = sum(args)/len(args)

   ...:         return mean

   ...:

   ...:     def variance(mean):  # nested function

   ...:         squared_deviation = 0

   ...:         for arg in args:

   ...:             squared_deviation += (arg - mean)**2

   ...:             var = squared_deviation / len(args)

   ...:         return var

   ...:

   ...:     v = variance(mean_func())

   ...:     return math.sqrt(v)


In [6]: stddev(1.0, 2.0, 3.0, 4.0, 5.0)

Out[6]: 1.4142135623730951



# standard deviation using numpy std()

In [7]: import numpy as np


In [8]: np.std([1.0, 2.0, 3.0, 4.0, 5.0])

Out[8]: 1.4142135623730951

 




  (2) 재귀 함수 (Recursive Function): 함수 안에 자기 자신을 호출하는 함수


재귀 함수(Recursive Function)는 함수 안에서 자기 자신을 호출하는 함수를 말합니다. 




함수가 호출자이자 동시에 피호출자가 되어서 반복에 반복에 반복을 계속하는 함수입니다. 



왼쪽의 사진을 보면 재귀함수가 자기 자신을 계속해서 반복하면서 호출을 하는 것이 무엇을 의미하는지 이해가 될 것 같습니다. 


(*출처: http://www.itcuties.com/java/recursion-and-iteration/)












재귀함수(recursive function)와 중첩함수(nested function)를 같이 이용해서 Factorial 계산하는 함수를 정의해보겠습니다. 



In [9]: def factorial(number):

   ...:

   ...: # error handling

   ...:     if not isinstance(number, int):

   ...:         raise TypeError("Oops. 'number' must be an integer.")

   ...:     if number < 0:

   ...:         raise ValueError("Oops. 'number' must be zero or positive.")

   ...:

   ...:     def inner_factorial(number): # nested function

   ...:         if number == 0:

   ...:             return 1

   ...:         elif number > 0:

   ...:             return inner_factorial(number-1)*number # recursive function

   ...:

   ...:     return inner_factorial(number)


In [10]: factorial(5)

Out[10]: 120

 



위의 factorial(5) 함수를 실행하면 아래에 각 절차를 풀어서 설명해 놓은 것과 같이 자기 자신의 factorial() 함수를 계속 반복해서 호출하면서 값을 계산해서 최종값을 반환하게 됩니다. 




재귀함수는 편리한 점도 있지만 호출 비용이 크므로 성능이 중요한 경우에는 재귀함수 대신 반복문을 사용하는 것이 좋겠습니다.  그리고 재귀함수를 짤 때는 반드시 종료 조건을 정의해 주어야 무한반복의 함정에 빠지지 않습니다. (파이썬이 지정해 놓은 재귀 함수 반복 호출 최대 회수를 초과하면 'RuntimeError: maximum recursion depth exceeded while calling a Python object' 에러가 발생합니다)


많은 도움이 되었기를 바랍니다. 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 변수의 유효범위(scope of variables)에 대해서 알아보겠습니다. 


파이썬은 변수의 유효범위에 따라 


(1) 프로그램 전체에서 유효한 전역변수(Global Variable)

(2) 함수의 코드블록 안에서만 유효한 지역변수(Local Variable)


의 두가지 종류가 있습니다. 


변수의 유효범위에 대해서 정확하게 이해하지 못하면 Name Error, Unbound Local Error 등과 같은 의도치 않은 에러를 유발할 수 있으므로 주의가 필요합니다. 


[ Python Variable Scope : Global Variable vs. Local Variable ]




간단한 예를 들어서 설명해보겠습니다. 


  전역변수(Global Variable) vs. 지역변수(Local Variable)


아래의 [1]번 행에서 var_scope() 함수를 정의하면서 s = "Python is easy" 라는 지역변수(Local variable)을 정의하였습니다. 이 지역변수는 var_scope() 함수가 호출될 경우에만 함수가 실행이 되면서 메모리에 생성이 되었다가 함수 실행이 끝나면 메모리에서도 사라져버립니다. 


반면에 [2]번 행에서 s = "Python is not easy" 라고 할당한 문자열은 전역변수(Global variable)로서 바로 메모리에 저장이 되며, 프로그램을 전체에서 유효하며, 프로그램이 살아있는 한 계속 같이 살아있으면서 이용이 가능합니다. [4]번 행에서 print(s)를 했을 때 전역변수인 s = "Python is not easy"가 출력이 되었으며, var_scope() 함수 안에서만 유효한 지역변수인 s="Python is easy"는 출력이 안되었습니다. 



In [1]: def var_scope():

   ...:     s = "Python is easy" # Local variable

   ...:     print(s)


In [2]: s = "Python is not easy" # Global variable


In [3]: var_scope() # Local variable

Python is easy


In [4]: print(s) # Global variable

Python is not easy

 




  지역변수가 할당되기 전에 호출하려 할 때 발생하는 Unbound Local Error


아래의 [5]번 행에서 정의한 var_scope() 함수의 코드블록을 보면, 지역변수 's'가 할당이 아직 안된 상태에서 빨간색의 print(s) 를 실행하도록 했더니 "Unbound Local Error: local variable: local variable 's' referenced before assignment' 라는 에러 메시지가 떴습니다. 



In [5]: def var_scope():

   ...:     print(s) # Unbound Local Error

   ...:     s = "Python is easy" # local variable

   ...:     print(s); # local variable


In [6]: s = "Python is not easy"


In [7]: var_scope()

Traceback (most recent call last):


File "<ipython-input-7-51074fe12940>", line 1, in <module>

var_scope()


File "<ipython-input-5-5b8d90c83375>", line 2, in var_scope

print(s) # UnboundLocalError


UnboundLocalError: local variable 's' referenced before assignment

 




  함수 안에서 전역변수를 사용할 수 있게 해주는 global keyword


위의 UnblundLocalError 를 해결하려면 아래의 [8]번행에서 함수를 정의할 때 밑줄 그은 'global s' 처럼 global keyword 를 사용해서 전역변수를 사용하겠다고 선언을 해주면 됩니다. 그러면 [9]번 행에서 전역변수로 할당한 s = "Python is not easy"를 var_scope() 함수의 코드블록 안에서 지역변수가 선언되기 전에도 전역변수를 가져다가 함수 호출 시 이용할 수 있습니다. 



In [8]: def var_scope():

   ...:     global s # global variable

   ...:     print(s)

   ...:     s = "Python is easy" # local variable

   ...:     print(s);


In [9]: s = "Python is not easy"


In [10]: var_scope()

Python is not easy     <--- global variable 's'

Python is easy           <--- local variable 's'

 




  지역변수를 함수 밖에서 호출할 때 발생하는 Name Error


함수의 코드블록 안에서 정의된 지역변수는 함수의 밖에서 사용할 수 없습니다. 만약 함수 밖에서 함수안의 지역변수를 호출하려고 하면 "Name Error: name 's2' is not defined' 라는 에러 메시지가 발생합니다. 프로그래밍을 처음하는 분의 경우 지역변수, 전역변수의 변수의 유효범위 개념을 모를 경우 이게 왜 에러가 발생하는가 하고 의아해 할 수 있습니다. 



In [11]: def var_scope2():

    ...:     s2 = "I love Python"

    ...:     print(s2);


In [12]: print(s2) # NameError: name 's2' is not defined

Traceback (most recent call last):


File "<ipython-input-12-ccb37084217a>", line 1, in <module>

print(s2) # NameError: name 's2' is not defined


NameError: name 's2' is not defined

 


많은 도움이 되었기를 바랍니다. 


728x90
반응형
Posted by Rfriend
,