이번 포스팅에서는 Python numpy 모듈의 random.choice() 메소드를 사용하여 임의(무작위, 확률) 추출 (random sampling)하는 방법을 소개하겠습니다. 

 

numpy.random.choice() 메소드의 기본 Syntax는 아래와 같습니다. 각 parameter별로 예를 들어서 설명을 해보겠습니다. 

 

numpy.random.choice(a, size=None, replace=True, p=None)

 

이때 표본을 추출할 모집단에 해당하는 a 는 1-D array-like 또는 np.arange(n) 의 정수가 됩니다. 

 

 

 

 

  (1) 1부터 5까지의 정수 모집단에서 3개의 균등확률표본을 복원추출 하기
  (Generate a uniform random sample from np.arange(5) of size 3)

 

표본이 뽑힐 확률 p를 명시적으로 지정해주지 않으면 모든 원소가 뽑힐 확률이 동일한 (즉, p=1/N) 균등확률분포를 가정하고 표본이 추출됩니다. 

그리고, 복원추출(replacement)이 기본 설정값이므로 똑같은 값이 2번 이상 표본으로 뽑힐 수도 있습니다. 

 



import numpy as np
 
np.random.choice(5, 3) # with replacement
Out[3]: array([4, 0, 0])
 

 

참고로, 위의 np.random.choice(5, 3) 코드는 np.random.randint(0,5,3) 과 동일합니다. 

 

 

균등확률분포로 부터 임의 추출이므로 매번 표본으로 뽑히는 값이 바뀌게 됩니다. 위와 코드는 같지만 추출된 표본은 다르지요?

 



np.random.choice(5, 3) # sampled with different values
Out[4]: array([2, 0, 0])
 

 

 

복원추출을 하게 되면 1~5 의 정수를 가지는 모집단에서 5개를 초과하는 표본을 뽑는데 문제가 없습니다. 

 



np.random.choice(5, 10) # with replacement
Out[5]: array([2, 2, 4, 2, 4, 0, 0, 4, 3, 2])
 

 

 

 

 (2) 1~5의 정수 모집단에서 비균등 확률 p 로 3개 원소 임의 표본 복원추출 하기
    (Generate a non-uniform random sample from np.arange(5) of size 3:)

 

만약 모집단 내 각 원소별로 표본으로 뽑힐 확률 p를 알고 있다면, 혹은 명시적으로 지정을 해주고 싶다면 모수 p에 표본이 추출될 확률을 입력해주면 됩니다. 

 



p = [0.1, 0, 0.3, 0.6, 0]
np.random.choice(5, 3, p=p)
Out[7]: array([2, 3, 3])
 

 

 

만약 표본을 추출할 모집단(a)의 원소 개수(n)과 각 원소별 표본이 뽑힐 확률(p)의 원소 개수가 서로 다를 경우 ValueError 가 발생합니다. (아래 예에서는 확률 p에는 3개가, 모집단 a는 5개 원소로서 서로 다름)

 



# ValueError: 'a' and 'p' must have same size
p = [0.1, 0, 0.3]           # size 3
np.random.choice(5, 3, p=p) # size 5


ValueError: 'a' and 'p' must have same size
 

 

 

만약 모집단 원소별 표본으로 뽑힐 확률 p의 전체 합이 1 이 아니거나 0~1사이 값이 아닌 경우도 ValueError가 발생합니다. 왜냐하면, 확률의 정의 상 (1) 각 사건별 확률의 전체 합은 1이고, (2) 각 사건별 확률은 0~1 사이의 실수를 가져야 하기 때문입니다. 

 



# ValueError: probabilities do not sum to 1
p = [0.4, 0, 0.3, 0.6, 0]   # sum to 1.3 (not 1)
np.random.choice(5, 3, p=p)


ValueError: probabilities do not sum to 1




# ValueError: probabilities are not non-negative
p = [-0.4, 0, 0.3, 1.6, 0]   
np.random.choice(5, 3, p=p)


ValueError: probabilities are not non-negative

 

 

 

  (3) 1~5 정수 모집단에서 3개의 균등확률표본을 비복원추출(non-replacement) 하기
  (Generate a uniform random sample from np.arange(5) of size 3 
   without replacement)

 

옵션 중에서 replace=False 로 설정을 해주면 비복원추출(without replacement)을 합니다. 즉, 모집단에서 표본을 추출할 때 각 원소를 딱 한번만 추출하기 때문에 동일한 원소가 2번 이상 뽑히는 일은 없습니다. (default 옵션은 replace=True 임)

 



# Generate a uniform random sample from np.arange(5) of size 3 without replacement:
np.random.choice(5, 3, replace=False)
Out[11]: array([3, 2, 1])
 

 

참고로 위의 np.random.choice(5, 3, replace=False) 코드는 np.random.permutation(np.arange(5))[:3] 과 동일합니다. 

 

 

비복원추출을 할 때는 한가지 조심해야 할 것이 있는데요, 모집단의 원소 개수보다 많은 수의 샘플을 비복원추출(replace=False)하려고 하면 ValueError가 발생합니다. (아래 예처럼, 5개의 원소를 가진 모집단에서 10개 표본을 비복원(즉, 중복 없이) 추출할 수는 없겠지요!)

 



# ValueError: Cannot take a larger sample than population when 'replace=False'
np.random.choice(5, 10, replace=False)


ValueError: Cannot take a larger sample than population when 'replace=False'
 

 

 

위의 (2)번에서 표본추출확률 p 를 설정하는 것과, 이번 (3)번의 비복원추출(replace=False)을 함께 설정해줌으로써 비균등확률(non-uniform random sample)로 비복원추출(without replacement) 샘플링도 물론 가능합니다. 

 



# Generate a non-uniform random sample from np.arange(5) of size 3 without replacement:
p = [0.1, 0, 0.3, 0.6, 0]
np.random.choice(5, 3, replace=False, p=p)
Out[13]: array([3, 0, 2])
 

 

 

 

  (4) 정수 대신에 임의의 배열처럼 생긴 객체의 원소를 확률추출하기
  (Any of the above can be repeated with an arbitrary array-like instead of just integers.)

 

np.random.choice(a, size=None, replace=True, p=None) 의 syntax에서 a 부분에 정수 배열 (np.arange(n)) 말고도 1-D array-like 객체를 대상으로 할 수도 있습니다. 아래 예에서는 과일 이름을 원소로 가지는 리스트로부터 비복원 비균등 확률 표본추출을 해보았습니다. 

 



# Any of the above can be repeated with an arbitrary array-like instead of just integers.
fruits = ['apple', 'banana', 'cherries', 'durian', 'grapes', 'lemon', 'mango']
p = [0.1, 0, 0.2, 0.5, 0.1, 0.05, 0.05]
np.random.choice(fruits, 3, p=p, replace=False)
Out[14]: array(['cherries', 'lemon', 'durian'], dtype='<U8')
 

 

 

 

  (5) 초기값 설정을 통한 재현가능성 확보
  (setting seed number for reproducibility)

 

np.random.seed() 로 초기값(seed value)을 설정해주면 매번 똑같은 확률표본을 추출할 수 있습니다. 만약 재현가능성(reproducibility)이 필요한 경우라면 초기값을 설정해주세요. 

 



# set seed number for reproducibility
np.random.seed(1004)
np.random.choice(5, 3)
Out[15]: array([2, 3, 3])


np.random.seed(1004)
np.random.choice(5, 3)
Out[16]: array([2, 3, 3])

 

 

참고로, 기계학습을 할 때 train set, test set 을 무작위로 분할하는 여러가지 방법은 https://rfriend.tistory.com/519 를 참고하시기 바랍니다. 

 

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

이번 포스팅이 도움이 되었다면 아래의 '공감~

'를 꾹 눌러주세요. :-)

 

* Reference: https://docs.scipy.org/doc//numpy-1.10.4/reference/generated/numpy.random.choice.html

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 numpy의 meshgrid()와 matplotlib의 contourf(xx, yy, Z) 함수를 사용하여 Random Forest Classifer 의 의사결정 경계(Decision Boundary)를 시각화해보겠습니다.  


(1) iris dataset 의 species 를 분류할 수 있는 Random Forest 분류 모델 훈련

(2) numpy의 meshgrid() 로 설명변수 공간의 좌표들을 생성하여 Random Forest 분류 모델로 예측

(3) matplotlib의 contourf() 함수로 Random Forest Classifier 예측 결과 시각화(채우기)

    : 의사결정 경계(Decision Boundary)

(4) iris dataset 의 species 의 산점도를 겹쳐서 그리기


의 순서로 진행해보겠습니다. 


  (1) iris dataset 의 species 를 분류할 수 있는 Random Forest 분류 모델 훈련


먼저 iris dataset 을 업로드하고 어떻게 생긴 데이터인지 살펴보겠습니다. 



#%% import libraries

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import seaborn as sns


#%% iris dataset

iris = sns.load_dataset('iris')

iris.info()

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 150 entries, 0 to 149

Data columns (total 5 columns):

 #   Column        Non-Null Count  Dtype  

---  ------        --------------  -----  

 0   sepal_length  150 non-null    float64

 1   sepal_width   150 non-null    float64

 2   petal_length  150 non-null    float64

 3   petal_width   150 non-null    float64

 4   species       150 non-null    object 

dtypes: float64(4), object(1)

memory usage: 6.0+ KB



iris.groupby('species').size()

Out[1]: 

species

setosa        50

versicolor    50

virginica     50

dtype: int64



#%% scatter plot by 'species'

sns.scatterplot(x='petal_length', 

                y='petal_width', 

                hue='species', 

                style='species', 

                s=100, 

                data=iris)

plt.show()



이번 포스팅의 시각화 예에서는 'sepal_length', 'sepal_width' 의 두개 칼럼을 설명변수로 사용하고, 'species'를 목표변수로 하여 붓꽂 종류(species)를 Random Forest Classifier 로 분류하는 모델을 만들어보겠습니다. 


(* 이번 포스팅의 주 목적은 모델의 의사결정경계 시각화이므로, 모델링 단계에서의 train/test set 분할, 모델 성과 평가 등은 생략합니다.)



#%% Classification using Random Forest

from sklearn.ensemble import RandomForestClassifier


X = np.array(iris[['petal_length', 'petal_width']])

y = iris['species']

y = np.where(y=='setosa', 0, np.where(y=='versicolor', 1, 2))


rfc = RandomForestClassifier(max_depth=2, n_estimators=200, random_state=1004)

rfc.fit(X, y)

 



  (2) numpy의 meshgrid() 로 설명변수 공간의 좌표들을 생성하여 

      Random Forest 분류 모델로 예측


위의 (1)번에서 적합된 Random Forest Classifier 모델을 사용해서 붓꽂 종류를 예측하고자 할때는 predict() 라는 메소드를 사용하면 됩니다. 



#%% predict

pred = rfc.predict(X)

print(pred == y)

[ True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True False  True

  True  True  True  True  True False  True  True  True  True  True False

  True  True  True  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True  True  True  True  True False  True

  True  True  True  True  True  True  True  True  True  True  True False

  True  True  True  True  True  True  True  True  True  True  True  True

  True False False  True  True  True  True  True  True  True  True  True

  True  True  True  True  True  True]

 



(a) 이제 x 축('petal_length')과 y 축('petal_width')의 최소값, 최대값을 구하고, (b) 이들 x축과 y축의 최소값 ~ 최대값 사이의 구간을 h = 0.01 단위의 간격으로 좌표를 생성하여, (c) np.meshgrid()로 이들 x축과 y축의 h=0.01 단위의 좌표 벡터를 가지고 격자 행렬을 생성합니다. 

(d) 이렇게 생성한 격자 행렬 좌표값들에 대해 Random Forest Classifier 훈련된 모델의 predict() 메소드로 예측을 하여 contour plot의 높이에 해당하는 Z 값을 구합니다. 



# create coordinate matrices from x_min~x_max, y_min~y_max coordinates

h = 0.01

x_min, x_max = X[:, 0].min() - .1, X[:, 0].max() + .1

y_min, y_max = X[:, 1].min() - .1, X[:, 1].max() + .1

xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))

XY = np.c_[xx.ravel(), yy.ravel()]


XY.shape

Out[4]: (158600, 2)


# predict

pred_cls = rfc.predict(XY)


# align the shape of Z with xx

Z = pred_cls.reshape(xx.shape)

 




  (3) matplotlib.contourf() 로 Random Forest Classifier 예측 결과 시각화

      -->  (4) iris dataset 의 species 의 산점도를 겹쳐서 그리기


위의 (2)번에서 만들어놓은 x, y 격자 행렬 좌표값별로 Random Forest Classifier 예측 결과를 matplotlib의 confourf() 함수로 시각화 (채우기)를 하면 아래와 같습니다. 아래에 색깔로 구분된 영역들과 경계선이 바로 Random Forest Classifier가 훈련 데이터로 부터 학습한 species 분류 의사결정 경계(Decision Boundary)가 되겠습니다. 



# Random Forest Classifier: Decision Boundary

plt.contourf(xx, yy, Z)

plt.axis('off')




좀더 보기에 쉽도록 위의 의사결정경계 그래프 위에다가 학습에 사용했던 iris species 값들을 species별로 marker를 구분하여 겹쳐서 다시 한번 시각화해보겠습니다. 



plt.contourf(xx, yy, Z)

plt.axis('off')


sns.scatterplot(x='petal_length', 

                y='petal_width', 

                hue='species', 

                style='species', 

                s=100, 

                data=iris)

plt.title('Decision Boundary by Random Forest', fontsize=14)

plt.show()



 



iris species 별 산점도를 의사결정경계 배경 위에 겹쳐 그릴 때 seaborn.scatterplot() 대신에 아래의 코드처럼 matplotlib.scatter() 과 for loop을 사용해서 그릴 수도 있습니다. 



plt.contourf(xx, yy, Z)

plt.axis('off')


# or plot data points using matplotlib and for loop

N = 50

CLS_NUM = 3

markers = ['o', 'x', 's']

for i in range(CLS_NUM):

    plt.scatter(X[i*N: (i+1)*N, 0], X[i*N:(i+1)*N, 1], s=40, marker=markers[i])


plt.title('Decision Boundary by Random Forest', fontsize=14)
plt.show()



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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python의 NLTK (Natural Language Toolkit) 라이브러리WordNet 말뭉치를 사용하여 자연어 처리 (Natural Language Processing) 하는 몇 가지 방법을 맛보기로 소개하겠습니다. 

(저는 Python 3.8 버전에 NLTK 3.5 버전을 사용하였습니다. 자연어 처리, NLTK를 제대로 살펴보려면 책 한권, 한 학기 수업 분량이며, 이번 포스팅은 말 그대로 맛보기 정도의 소개입니다. ^^;)


Python NLTK (Natural Language Toolkit) 라이브러리는 텍스트 분류, 토큰화, 단어 stemming, 품사 태킹, 텍스트 파싱, semantic reasoning 등을 하는데 사용하는 텍스트 자연어 처리 파이썬 모듈입니다.

(* reference: https://www.nltk.org/book/ch02.html )


WordNet은 어휘에 중점을 둔 영어 사전 Database 로서, 전통적인 Thesaurus (유의어 사전)와 유사한 측면이 있지만 보다 풍성한 구조를 가지고 있습니다. 1985년부터 프린스턴 대학교에서 구축하기 시작하였으며, 약 16만개의 단어(155287 words)와 12만개의 유사어 집합(117,659 synonym sets)을 포함하고 있는 방대한 Thesaurus 입니다. 

(* reference: https://www.nltk.org/howto/wordnet.html )





NLTK와 WordNet에 대한 간단한 소개를 마쳤으니 이제 직접 코드를 짜보면서 예를 들어보겠습니다. 먼저, 명령 프롬프트 창에서 pip install로 NLTK 라이브러리를 설치하고 pip show로 버전과 내용을 확인해보겠습니다. (Anaconda 배포판으로 Python 가상 환경을 만들었다면 아마 디폴트로 NLTK 모듈이 설치되어 있을 겁니다.)



-- (명령 프롬프트 창에서) NLTK 모듈 설치

$ pip install nltk


-- NLTK 모듈 확인

$ pip show nltk

Name: nltk

Version: 3.5

Summary: Natural Language Toolkit

Home-page: http://nltk.org/

Author: Steven Bird

Author-email: stevenbird1@gmail.com

License: Apache License, Version 2.0

Location: /Users/ihongdon/anaconda3/envs/py3.8_tf2.3/lib/python3.8/site-packages

Requires: regex, click, tqdm, joblib

Required-by: 




 (1) NLTK로 Word Tokenization 하기


Token 이란 더이상 나눌 수 없는 언어요소를 말하며, Tokenization은 텍스트 문자열을 Token으로 분해하여 쪼개는 것입니다. 


처음 사용하는 경우라면 먼저 nltk.download('punkt') 를 실행하여 Punket Tokenizer Models (13MB) 를 다운로드 해줍니다. 


nltk.word_tokenize() 함수를 사용해서 괄호 안에 텍스트 문자열 객체를 넣어주면 Token으로 쪼개줍니다. 

(영어는 띄어쓰기 기준으로 단어 토큰화가 되었는데요, 한글은 단순히 띄어쓰기 기준만으로 토큰화가 되지 않는 어려움이 있습니다.)



#%% word tokenization


import nltk


# Please use the NLTK Downloader to obtain the resource:

nltk.download('punkt')


sentense = "NLTK is a leading platform for building Python programs to work with human language data."


tokens = nltk.word_tokenize(sentense)

tokens

Out[1]: 

['NLTK',

 'is',

 'a',

 'leading',

 'platform',

 'for',

 'building',

 'Python',

 'programs',

 'to',

 'work',

 'with',

 'human',

 'language',

 'data',

 '.']

 


* 참고로, keras 라이브러리로의 text_to_word_seqence() 메소드로 단어 토큰화를 할 수 있습니다. 

(from tensorflow.keras.preprocessing.text import text_to_word_sequence)




  (2) NLTK로 품사 태깅하기 (Tagging)


품사 태깅을 하려면 먼저 nltk.download('averaged_perceptron_tagger') 로 태깅에 필요한 자원을 다운로드 해줍니다. 그리고 nltk.pos_tag() 메소드를 사용해서 위의 (1)번에서 만든 단어 토큰들에 대해서 품사 태킹을 할 수 있습니다. 



#%% word tagging


# Please use the NLTK Downloader to obtain the resource:

nltk.download('averaged_perceptron_tagger')


tagged = nltk.pos_tag(tokens)

tagged

Out[2]: 
[('NLTK', 'NNP'),
 ('is', 'VBZ'),
 ('a', 'DT'),
 ('leading', 'VBG'),
 ('platform', 'NN'),
 ('for', 'IN'),
 ('building', 'VBG'),
 ('Python', 'NNP'),
 ('programs', 'NNS'),
 ('to', 'TO'),
 ('work', 'VB'),
 ('with', 'IN'),
 ('human', 'JJ'),
 ('language', 'NN'),
 ('data', 'NNS'),
 ('.', '.')]

 



위의 단어 태킹 예에서 보면, ('NLTK', 'NNP') 처럼 (단어 토큰, 품사) 의 쌍을 이룬 튜플들의 리스트로 되어있습니다. 품사의 약어가 무엇을 의미하는지는 아래의 품사 약어를 참고하시기 바랍니다. 

(예: ('is', 'VBZ') 에서 'VBZ'는 verb, 3rd person sing. present takes 입니다)



POS tag list:


CC coordinating conjunction

CD cardinal digit

DT determiner

EX existential there (like: "there is" ... think of it like "there exists")

FW foreign word

IN preposition/subordinating conjunction

JJ adjective 'big'

JJR adjective, comparative 'bigger'

JJS adjective, superlative 'biggest'

LS list marker 1)

MD modal could, will

NN noun, singular 'desk'

NNS noun plural 'desks'

NNP proper noun, singular 'Harrison'

NNPS proper noun, plural 'Americans'

PDT predeterminer 'all the kids'

POS possessive ending parent\'s

PRP personal pronoun I, he, she

PRP$ possessive pronoun my, his, hers

RB adverb very, silently,

RBR adverb, comparative better

RBS adverb, superlative best

RP particle give up

TO to go 'to' the store.

UH interjection errrrrrrrm

VB verb, base form take

VBD verb, past tense took

VBG verb, gerund/present participle taking

VBN verb, past participle taken

VBP verb, sing. present, non-3d take

VBZ verb, 3rd person sing. present takes

WDT wh-determiner which

WP wh-pronoun who, what

WP$ possessive wh-pronoun whose

WRB wh-abverb where, when


* source: https://pythonprogramming.net/part-of-speech-tagging-nltk-tutorial/ 





  (3) WordNet에서 동의어 (Synonyms) 찾기


WordNet을 처음 사용하는 사용자라면 먼저 nltk.download('wordnet') 을 다운로드 해줍니다. 그리고 from nltk.corpus import wordnet as wn 으로 WordNet을 wn 이라는 alias로 importing 해주었습니다. 


단어의 의미가 여러개가 있을 수 있는데요, NLTK WordNet에서 wordnet.synsets() 함수를 사용해서 동의어 집합을 찾을 수 있습니다. 아래 예에서는 'car'라는 단어가 5개의 단어 집합을 가지고 있네요. 



#%% Senses and Synonyms from Wordnet


# Please use the NLTK Downloader to obtain the resource:

nltk.download('wordnet')


from nltk.corpus import wordnet as wn

wn.synsets('car')

Out[3]: 

[Synset('car.n.01'),

 Synset('car.n.02'),

 Synset('car.n.03'),

 Synset('car.n.04'),

 Synset('cable_car.n.01')]




가령, Synset('car.n.01') 는 '단어.품사.그룹인덱스' 를 나타내는데요, 특정 의미의 단어를 보려면 '그룹인덱스'를 사용해서 명시적으로 지정을 해줘야 합니다. 아래 예에서는 첫번째 인덱스의 'car.n.01' 표제어의 c단어 정의(definition())와, 동의어 단어 집합(lemma_names())을 알아보겠습니다. 



car = wn.synset('car.n.01')

car.definition()

Out[4]: 'a motor vehicle with four wheels; usually propelled by an internal combustion engine'


car.lemma_names()

Out[5]: ['car', 'auto', 'automobile', 'machine', 'motorcar']




for loop 을 사용해서 car의 5개 표제어 모두에 대해서 차례대로 모두 동의어 단어집합을 인쇄해볼 수도 있습니다. 



for synset in wn.synsets('car'):

    print(synset.lemma_names())


['car', 'auto', 'automobile', 'machine', 'motorcar']

['car', 'railcar', 'railway_car', 'railroad_car']

['car', 'gondola']

['car', 'elevator_car']

['cable_car', 'car']





  (4) WordNet에서 반대말 (Antonyms) 찾기


위의 (3)번에서는 WordNet에서 동의어(Synonym)를 찾아보았다면, 이제는 WordNet에서 반대말, 반의어(Antonym)를 찾아보겠습니다. 가령, 아래 예에서는 공급(supply)의 반대말은 수요(demand), 왕(king)의 반대말은 여왕(queen) 이라고 대답해주네요. 



#%% antonym


wn.lemma('supply.n.02.supply').antonyms()

Out[7]: [Lemma('demand.n.02.demand')]


wn.lemma('king.n.01.king').antonyms()

Out[8]: [Lemma('queen.n.02.queen')]

 




  (5) WordNet에서 단어 위계구조 (Hierarchy) 찾기


WordNet에는 단어 간의 관계를 상/위계구조로 정리가 되어있습니다. 'car.n.01' 표제어에 대해서 상/하 위계구조를 hypernym_paths() 메소드를 사용해서 찾아보면 아래와 같이 2개가 나옵니다. car.hypernym_paths()[0] 으로 '0' 번째 것만 indexing 해서 볼 수 있습니다. 



#%% The Wordnet Hierarchy


# 상위어 (hypernym)

from nltk.corpus import wordnet as wn

car = wn.synset('car.n.01')


car.hypernym_paths()

Out[9]: 

[[Synset('entity.n.01'),

  Synset('physical_entity.n.01'),

  Synset('object.n.01'),

  Synset('whole.n.02'),

  Synset('artifact.n.01'),

  Synset('instrumentality.n.03'),

  Synset('container.n.01'),

  Synset('wheeled_vehicle.n.01'),

  Synset('self-propelled_vehicle.n.01'),

  Synset('motor_vehicle.n.01'),

  Synset('car.n.01')],

 [Synset('entity.n.01'),

  Synset('physical_entity.n.01'),

  Synset('object.n.01'),

  Synset('whole.n.02'),

  Synset('artifact.n.01'),

  Synset('instrumentality.n.03'),

  Synset('conveyance.n.03'),

  Synset('vehicle.n.01'),

  Synset('wheeled_vehicle.n.01'),

  Synset('self-propelled_vehicle.n.01'),

  Synset('motor_vehicle.n.01'),

  Synset('car.n.01')]]



# indexing

car.hypernym_paths()[0]

Out[10]: 

[Synset('entity.n.01'),

 Synset('physical_entity.n.01'),

 Synset('object.n.01'),

 Synset('whole.n.02'),

 Synset('artifact.n.01'),

 Synset('instrumentality.n.03'),

 Synset('container.n.01'),

 Synset('wheeled_vehicle.n.01'),

 Synset('self-propelled_vehicle.n.01'),

 Synset('motor_vehicle.n.01'),

 Synset('car.n.01')]

 



위에 위계구조 리스트를 상/하 네트워크 그래프로 시각화해서 보면 좀더 직관적으로 이해할 수 있습니다. 이때 노드(nodes, vertex)는 동의어 집합(synsets)에 해당하며, 연결선(edges, links)은 단어 개념 상의 상/하 관계(hypernym/hyponym relation)을 나타냅니다. 



* image source: https://www.nltk.org/book/ch02.html




  (6) WordNet을 이용한 단어 간 의미 유사도 (Semantic Similarity) 측정


단어 간 의미 유사도를 측정하는 방법에는 다양한 기준이 있을 수 있는데요, 아래의 3가지 방법을 소개하겠습니다. 

  • 경로 거리 기반 유사도 (Path Distance Similarity)
  • Leacock Chordorow 유사도 (Leacock Chordorow Similarity)
  • Wu-Palmer 유사도 (Wu-Palmer Similarity)


(6-1) 경로 거리 기반 유사도 (Path Distance Similarity)


먼저, 경로 거리 유사도는 위의 (5)번에서 소개했던 단어 간 상/하 위계구조에서의 최단 경로 (Shortest Path)의 거리를 기반으로 유사도를 0~1 사이의 실수로 측정합니다. (즉, 경로 거리가 가까울 수록 유사하고, 거리가 멀 수록 유사하지 않게 평가하며, 유사도가 1에 가까울 수록 유사한 것을 의미함). 


아래 예에서는 고래 right whale 과 orca, minke whale 는 경로 거리 유사도가 높게 나왔고, 거북이 tortoise 는 좀 낮게 나왔으며, 소설 novel 은 매우 낮게 나왔습니다. 상식과 어느정도 부합하네요. 



#%% Semantic Similarity


# (1) Path Distance Similarity

right_whale = wn.synset('right_whale.n.01')

orca = wn.synset('orca.n.01')

right_whale.path_similarity(orca)

Out[11]: 0.16666666666666666


minke = wn.synset('minke_whale.n.01')

right_whale.path_similarity(minke)

Out[12]: 0.25


tortoise = wn.synset('tortoise.n.01')

right_whale.path_similarity(tortoise)

Out[13]: 0.07692307692307693


novel = wn.synset('novel.n.01')

right_whale.path_similarity(novel)

Out[14]: 0.043478260869565216




(6-2) Leacock Chordorow 유사도 (Leacock Chordorow Similarity)


 Leacock Chodorow 유사도는 단어 간 위계구조에서의 최단 거리(shortest path)와 단어 의미가 발생하는 최대 깊이(maximum depth)에 기반해서 유사도를 계산합니다. 위의 path_similarity() 가 0~1 사이의 표준화된 값을 반환하는 반면에, lch_similarity() 는 표준화되지 않은 차이가 있습니다. 

아래 예에서 right whale 과 minke whale, tortoise, novel 간의 Leacock Chordorow 유사도와 위의 (6-1) Path distance similarity 의 경향, 유사도 순서는 비슷하게 나왔습니다.  



# (3) Leacock Chordorow (LCH) Similarity

right_whale.lch_similarity(orca)

Out[15]: 1.845826690498331


right_whale.lch_similarity(minke)

Out[16]: 2.2512917986064953


right_whale.lch_similarity(tortoise)

Out[17]: 1.072636802264849


right_whale.lch_similarity(novel)

Out[18]: 0.5020919437972361

 



(6-3) Wu-Palmer 유사도 (Wu-Palmer Similarity)


Wu-Palmer 유사도는 단어 위계구조에서 두 단어의 깊이(depth of the tow senses in the taxonomy)와 단어 간의 최소 공통 포함(Least Common Subsumer)에 기반해서 유사도를 계산합니다. 



# (2) Wu-Palmer Similarity

right_whale.wup_similarity(orca)

Out[19]: 0.8484848484848485


right_whale.wup_similarity(minke)

Out[20]: 0.9090909090909091


right_whale.wup_similarity(tortoise)

Out[21]: 0.6


right_whale.wup_similarity(novel)

Out[22]: 0.08333333333333333




위의 3가지 외에도 NLTK 라이브러리는 최소 공통 포함 Information Content (IC of the Least Common Subsumer) 기반의 Resnik Similarity, Lin Similarity, Jiang-Conrath Similarity 등의 알고리즘을 제공합니다. (* reference: http://jaganadhg.github.io/wornet-nltk-sense/)


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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 A, B 두개의 값이 들어있는 배열과 위치를 지정해둔 idx 배열이 있다고 했을 때, A배열의 idx 배열 내 index 위치에다가 B배열의 원소를 순서대로 더하기하는 두가지 방법을 소개하겠습니다. 


(방법 1) for loop 으로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기

(방법 2) np.add.at(A, idx, B) 메소드로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


for loop을 이용한 (방법 1)은 numpy의 add.at() 함수를 몰라도 어렵지 않게 구현할 수 있고 또 코드를 해석하기도 편한 장점이 있습니다만, 만약 더해야 하는 원소의 개수가 많아지면 for loop 연산을 하는데 시간이 많이 소요되는 단점이 있습니다. 


numpy의 add.at() 메소드를 사용하는 (방법 2)는 Vectorization 연산을 하여 한꺼번에 두 배열 간 idx 위치에 맞게 더하기 연산을 수행하므로 위의 for loop 방법 1 대비 빠르다는 장점이 있습니다. (더해야 하는 원소 개수가 작으면 방법1과 방법2의 속도 차이를 느끼기 힘든데요, 만약 몇 몇 백만개라면 for loop으로 수 분~ 수 시간 걸릴 것이 numpy.add.at() 메소드로는 수 초안에 끝낼 수도 있을만큼 성능 차이가 많이 날 것입니다. (대신 numpy.add.at() 함수를 기억하고 사용법도 알고 있어야 하겠지만요.)





  (방법 1) for loop 으로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


먼저, 예제로 사용할 A배열, B배열, idx 배열을 만들어보겠습니다. 



import numpy as np


A = np.arange(24).reshape(8, 3)

print(A)

[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]
 [15 16 17]
 [18 19 20]
 [21 22 23]]


B = np.arange(12).reshape(4, 3)

print(B)

[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]


idx = np.array([0, 0, 3, 5])




자, 이제 준비가 되었으니 for loop 문과 enumerate() 함수를 사용하여 A배열의 idx 배열 내 숫자의 위치에다가 B배열의 원소를 순서대로 더해보겠습니다. 

idx 가 array([0, 0, 2, 5]) 이므로 A배열의 1차원의 idx([0, 0, 3, 5]) 번째 위치(0, 0, 3, 5)의 원소에 B배열의 0, 1, 2, 3 번째 위치의 원소를 더하는 연산을 수행하게 됩니다. 


말로만 설명을 들으면 좀 헷갈리고 이해가 잘 안갈수도 있는데요, 아래에 배열 덧셈 연산식과 연산이 끝난 A배열에서 결과값이 바뀐 0, 2, 5 번째 원소를 빨간색으로 표시해두었으니 참고하기 바랍니다. (* idx([0, 0, 3, 5]) 에서 A[0]에 B[0]과 B[1]이 각 각 더해지는 것에 주의)


array A at idx([0])  + array B[0]          = array A[0]

array([0, 1, 2])        + array([0, 1, 2])    = array([0, 2, 4])


array A at idx([0])  + array B[1         = array A[0]

array([0, 2, 4])      + array([3, 4, 5])     = array([3, 6, 9])


array A at idx([3])  + array B[2]          = array A[3]

array([9, 10, 11])     + array([6, 7, 8])    = array([15, 17, 19])


array A at idx([5])  + array B[3]          = array A[5]

array([15, 16, 17])   + array([9, 10, 11])  = array([24, 25, 26])



for i, id in enumerate(idx):

    A[id] += B[i]


print(A)

[[ 3  6  9]
 [ 3  4  5]
 [ 6  7  8]
 [15 17 19]
 [12 13 14]
 [24 26 28]
 [18 19 20]
 [21 22 23]]

 




  (방법 2) np.add.at(A, idx, B) 함수로 A배열의 idx 위치에 B배열의 원소 순서대로 더하기


위의 for loop을 이용한 (방법 1)과 똑같은 연산을 numpy의 add.at(A, idx, B) 메소드를 이용해서 수행하면 아래와 같습니다. 코드도 깔끔하고 연산속도도 더 빠르므로 특히 대용량 배열을 다루어야 하는 경우라면 알아두면 좋겠습니다. 



import numpy as np


A = np.arange(24).reshape(8, 3)

B = np.arange(12).reshape(4, 3)

idx = np.array([0, 0, 3, 5])


np.add.at(A, idx, B)


print(A)

[[ 3  6  9]
 [ 3  4  5]
 [ 6  7  8]
 [15 17 19]
 [12 13 14]
 [24 26 28]
 [18 19 20]
 [21 22 23]]

 



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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^_^


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 PostgreSQL, Greenplum DB에서 문자열형의 종류, 생성, 처리 연산자(Character Type Operators) 및 함수(Character Functions)에 대해서 소개(https://rfriend.tistory.com/543)하였습니다. 


이번 포스팅에서는 PostgreSQL, Greenplum DB에서 문자열 패턴 매칭(String Pattern Matching)하는 세가지 방법을 소개하겠습니다. 


(1) 전통적인 SQL LIKE 연산자 (LIKE Operator)

(2) SIMILAR TO 연산자 (the SIMILAR TO Operator)

(3) 정규 표현식 (POSIX-Style Regular Expressions)




  (1) SQL LIKE, ~~ 연산자 ('LIKE', or '~~' Operator)


'LIKE' 연산자는 전통적인 SQL에서 문자열 패턴 매칭할 때 가장 보편적으로 사용되며, 아래와 같은 syntax를 사용합니다. 


string LIKE pattern [ESCAPE escape-character]


위의 LIKE 연산자는 LIKE 앞의 문자열이 LIKE 뒤의 패턴과 일치할 경우 TRUE를 반환하며, 일치하지 않을 경우는 FALSE를 반환합니다. 


와일드카드 문자(wildcard character)로서 (1) 퍼센트 부호(%)는 그 위치부터 어떤 문자열(any string)이 와도 상관없으며, (2) 밑줄 부호(_)는 해당 위치에 하나의 문자가 일치하는지를 봐서 TRUE나 FALSE를 반환합니다. LIKE와 '%', '_' 와일드카드 문자를 사용한 패턴 매칭 예 몇개를 살펴보겠습니다. 


 pattern matching

 returns

  SELECT 'abc' LIKE 'abc';

 true

  SELECT 'abc' LIKE 'a%';

 true 

  SELECT 'abc' LIKE '%b'; false 

  SELECT 'abc' LIKE '%b%';

 true
  SELECT 'abc' LIKE '%c';

 true 

  SELECT 'abc' LIKE '_b_';

 true 

  SELECT 'abc' LIKE 'c'; false

  SELECT 'abc' LIKE '_c';

 fals

  SELECT 'abc' LIKE '__c';

 true 



 '~~' 연산자를 'LIKE' 연산자 대신 사용할 수 있습니다. 위에 예 중에서 한개만 'LIKE'를 '~~' 연산자로 바꾸어보면 아래와 같습니다. (Jupyter Notebook에서 sql_magic 라이브러리로 SQL문 사용할 때 LIKE 대신 ~~ 연산자를 사용했었습니다.) 



 SELECT 'abc' ~~ 'a%';  --> returns true

 



패턴 일치여부를 판단하는데 있어 대소문자를 구분합니다. 



 SELECT 'abc' LIKE 'a%';  --> returns true

 SELECT 'abc' LIKE 'A%';  --> returns false

 



일치하지 않는 패턴을 찾고 싶으면 'NOT LIKE' 또는 '!~~' 연산자를 사용합니다. 



 SELECT 'abc' LIKE 'a%';         --> returns true

 SELECT 'abc' NOT LIKE 'a%';  --> retruns false


 SELECT 'abc' ~~ 'a%';          --> returns true

 SELECT 'abc' !~~ 'a%';         --> returns false

 




  (2) SIMILAR TO 연산자 (the SIMILAR TO Operator)




 SELECT 'abc' SIMILAR TO 'abc'; -- true

 SELECT 'abc' SIMILAR TO 'a';    -- false


 SELECT 'abc' SIMILAR TO 'a__'; -- true

 SELECT 'abc' SIMILAR TO '_b_'; -- true

 SELECT 'abc' SIMILAR TO '_bc'; -- true


 SELECT 'abc' SIMILAR TO 'a%';   -- true

 SELECT 'abc' SIMILAR TO '%b';   -- false

 SELECT 'abc' SIMILAR TO '%b%'; -- true

 



-- | denotes alternation (either of two alternatives).

-- Parentheses () can be used to group items into a single logical item.

 SELECT 'abc' SIMILAR TO '%(b|d)%'; -- true

 SELECT 'abc' SIMILAR TO '%(d|e)%'; -- false


-- {m} denotes repetition of the previous item exactly m times.

 SELECT 'abbbc' SIMILAR TO '%b{3}%'; -- true

 SELECT 'abbbc' SIMILAR TO '%b{4}%'; -- false


-- {m,} denotes repetition of the previous item m or more times.

 SELECT 'abbbc' SIMILAR TO '%b{2,}%'; -- true

 SELECT 'abbbc' SIMILAR TO '%b{4,}%'; -- false


-- {m,n} denotes repetition of the previous item at least m and not more than n times.

 SELECT 'abbbc' SIMILAR TO '%b{2,3}%'; -- true

 




 SELECT substring('foobar' from '%#"o_b#"%' for '#'); -- oob


--  It returns null if there is no match.

 SELECT substring('foobar' from '#"o_b#"%' for '#');  -- NULL


 SELECT substring('foobar' from 'o...r'); -- oobar 

 SELECT substring('foobar' from 'o(...)r'); -- oba

 




  (3) 정규 표현식 (POSIX-Style Regular Expressions)


정규표현식을 이용하면 위의 (1) LIKE 연산자, (2) SIMILAR TO 연산자보다 더욱 강력한 패턴 매칭을 할 수 있습니다. 



-- Regular Expressions Matching Operators: ~, ~*, !~, !~*

-- ~ operator: Matches regular expression, case sensitive

 SELECT 'thomas' ~ '.*thomas.*'; -- true

 SELECT 'thomas' ~ '.*Thomas.*'; -- False


-- ~* operator: Matches regular expression, case insensitive

 SELECT 'thomas' ~* '.*Thomas.*'; -- true


-- !~ operator: Does not match regular expression, case sensitive

 SELECT 'thomas' !~ '.*thomas.*'; -- False

 SELECT 'thomas' !~ '.*Thomas.*'; -- True


-- !~* operator: Does not match regular expression, case insensitive

 SELECT 'thomas' !~* '.*Thomas.*'; -- false

 SELECT 'thomas' !~ '.*Thomas.*'; -- true

 SELECT 'thomas' !~* '.*steve.*'; -- true




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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


* Reference: https://www.postgresql.org/docs/9.5/functions-matching.html


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 PostgreSQL, Greenplum Database의 문자열 데이터형 (Character Types)의 종류와, 생성, 운영자, 함수 등에 대해서 알아보겠습니다. 


먼저, PostgreSQL 의 문자열형 종류는 아래처럼 

 - (1) 고정 길이 n 바이트 이내의 문자열 character(n), char(n)

 - (2) 가변 길이 n 바이트 이내의 문자열 varchar(n)

 - (3) 제한 없는 가변 길이 문자열 text, varchar

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

PostgreSQL은 이들 3가지 문자열 종류별로 성능 차이는 없습니다.




  (1) PostgreSQL 문자열형 포함한 테이블 생성 및 문자열 입력


위에서 소개한 3개의 문자열형 종류인 CHAR(2), VARCHAR(10), TEXT 를 포함하고 있는 예제 테이블을 만들어보겠습니다. 괄호안의 숫자는 Bytes 길이를 의미합니다. 


-- Create a table for an example

DROP TABLE IF EXISTS char_type_test;

CREATE TABLE char_type_test (

id SERIAL PRIMARY KEY 

, col_1 CHAR(2)

, col_2 VARCHAR(10)

, col_3 TEXT

);




아래의 두가지 경우는 CHAR(2), VARCHAR(10) 처럼 바이트 길이 제약이 있는데 이를 만족하지 않는 경우 SQL ERROR가 발생한 예입니다. 



(1-1) col_1 의 바이트 길이가 CHAR(2) 와 정확하게 맞지 않아서 에러가 나는 경우



-- PostgreSQL issued an error

-- SQL Error [22001]: ERROR: value too long for type character(2)

INSERT INTO char_type_test (col_1, col_2, col_3) 

VALUES (

'Hollo'

, 'PostgreSQL WORLD'

, 'I love PostgreSQL all the time'

);





(1-2) col_2 의 바이트 길이가 VARCHAR(10) 보다 길어서 에러가 나는 경우


-- PostgreSQL issued an error

-- SQL Error [22001]: ERROR: value too long for type character varying(10)

INSERT INTO char_type_test (col_1, col_2, col_3) 

VALUES (

'Hi'

, 'PostgreSQL WORLD'

, 'I love PostgreSQL all the time'

);





(1-3) col_1, col_2, col_3 가 모두 문자열형 조건을 만족하여 정상 입력된 경우



-- No error

INSERT INTO char_type_test (col_1, col_2, col_3) 

VALUES (

'Hi'

, 'PostgreSQL'

, 'I love PostgreSQL all the time')

, (

'Hi'

, 'Greenplum'

, 'I love Greenplum too'

);


SELECT * FROM char_type_test ORDER BY id;






  (2) PostgreSQL 문자열 연산자 (Operator)와 함수 (Functions)



(2-1) 문자열 합치기 (String Concatenation)


  • 문자열 합치기: '||' 연산자
  • 문자열 합치기: CONCAT() 함수
  • 구분자를 포함하여 문자열 합치기: CONCAT_WS() 함수


(2-1-1) 문자열 합치기 연산자: '||'


지난번 포스팅에서는 PostgreSQL의 4가지 유형의 연산자로서 산술 연산자, 비교 연산자, 논리 연산자, 비트 연산자에 대해서 소개하였는데요, 문자열에 특화된 연산자 중에서 두 문자열을 합칠 때 사용하는 '||' 연산자를 소개하겠습니다. 


string || string

string || non-string or non-string || string



SELECT

*

, col_1 || ', ' || col_2 AS db_name

, id || ' : ' || col_2 AS db_id

FROM char_type_test;





(2-1-2) 두 문자열 합치기 함수: CONCAT()


위의 (2-1-1)과 동일한 결과를 concat() 함수를 사용하여 얻을 수 있습니다. 



SELECT 

*

, CONCAT(col_1, ', ', col_2) AS db_name_2

, CONCAT(id, ' : ', col_2) AS db_id_2

FROM char_type_test;





문자열을 합치는데 사용하는 '||' 연산자와 CONCAT() 함수 사이에는 NULL 값을 처리하는데 있어 차이가 존재합니다. '||' 연산자는 NULL 값이 있으면 NULL 값을 반환하는 반면에, CONCAT() 함수는 NULL 값을 무시하고 나머지 값을 반환합니다. 



-- Concatenate using || with a NULL value returns NULL

SELECT col_1 || NULL AS result_string FROM char_type_test;




-- Concat function ignores NULL arguments.

SELECT CONCAT(col_1, NULL) AS result_string_2 FROM char_type_test;





(2-1-3) 구분자를 포함하여 문자열을 합치기 (CONCAT With Separator): CONCAT_WS()



-- (3) CONCAT_WS : CONCAT With Separator

-- : CONCAT_WS(separator, str_1, str_2, ...);

SELECT 

*

, CONCAT_WS(', ', col_1, col_2) AS db_name_3

, CONCAT_WS(' : ', id, col_2) AS db_id_3

FROM char_type_test;





합치려는 문자열의 개수가 여러개이고 구분자를 포함시키고 싶을 경우, CATCAT_WS() 함수는 구분자를 한번만 써줘도 되므로, 구분자를 매번 써줘어야 하는 CATCAT() 함수보다 편리합니다. 



-- Useful when there are lots of values to concatenate

SELECT CONCAT('a', ': ', 'b', ': ', 'c', ': ', 'd', ': ', 'e', ': ', 'f') AS concat_result_1;





SELECT CONCAT_WS(': ', 'a', 'b', 'c', 'd', 'e', 'f') AS concat_ws_result_2;






(2-2) 문자열 길이 (String Length) 


  • 문자열 내 비트의 길이: BIT_LENGTH(string)
  • 문자열 내 바이트의 길이: OCTET_LENGTH(string)
  • 문자열 내 문자 길이: LENGTH(string)



SELECT

col_3

, BIT_LENGTH(col_3) AS bit_len_col_3

, OCTET_LENGTH(col_3) AS cotet_len_col_3

, LENGTH(col_3) AS char_len_col_3

FROM char_type_test;




문자열 내 바이트의 길이를 재는 OCTET_LENGTH()와 문자 길이를 재는 LENGTH() 함수가 알파벳과 숫자에서는 차이가 없는데요, 한글처럼 문자열 내 바이트와 문자 길이가 다른 경우도 있습니다. 따라서 한글의 바이트를 재고 싶다면 LENGTH()가 아니라 OCTET_LENGTH() 함수를 사용해야 겠습니다. 


 SELECT OCTET_LENGTH('abc123');

 ---------

 result

 6

 SELECT LENGTH('abc123');

 ----------

 result 

 6


 SELECT OCTET_LENGTH('안녕하세요');

 ----------

 result

 15

 SELECT LENGTH('안녕하세요');

 ----------

 result 

 5




(2-3) 문자열 대/소문자 변환 (String Case Conversion)


  • 문자열 내 문자를 소문자로 바꾸기: LOWER(string)
  • 문자열 내 문자를 대문자로 바꾸기: UPPER(string)
  • 문자열 내 첫번째 문자를 대문자로, 나머지 문자는 소문자로 바꾸기: INITCAP(string)


-- lower(string): convert a string to lower case

-- upper(string): convert a string to upper case

-- initcap(string): convert words in a string to title case

SELECT 

col_2

, LOWER(col_2) AS lower_col_2 

, UPPER(col_2) AS upper_col_2 

, INITCAP(col_2) AS initcap_col_2

FROM char_type_test;






(2-4) 문자열 겹쳐쓰기, 바꾸기, 뒤집기, 반복하기


  • 문자열 겹쳐쓰기: OVERLAY(string PLACING string FROM int [FOR int])
  • 문자열 바꾸기: REPLACE(string, FROM, TO)

아래 예에서 OVERLAY() 함수에 대해 부언설명하자면요, 'col_3' 칼럼에서 8번째 자리부터 시작해서 10개 문자에 해당하는 부분을 'Opensource DB' 라는 새로운 문자열로 덮어쓰기(overlay)를 하라는 뜻입니다. 


-- overlay(string placing string from int [for int])

-- replace(string, from, to)

SELECT 

col_3

, OVERLAY(col_3 PLACING 'Opensource DB ' FROM 8 FOR 10) AS overlayed_col_3

, REPLACE(col_3, 'I', 'You') AS replaced_col_3

FROM char_type_test;


 



  • 문자열 순서 뒤집기: REVERSE(string)
  • 문자열 n번 반복하기: REPEAT(string, int)


-- reverse(string)

-- repeat(string, int)

SELECT 

col_2

, REVERSE(col_2) AS reversed_col_2

, REPEAT(col_2, 3) AS repeated_col_2

FROM char_type_test;


 




(2-5) 문자열의 위치 인덱스: POSITION(substring IN string)


POSITION() 함수이 첫번째 인자로 받는 substring 은 대/소문자를 구분합니다. 



----------------------------------

-- Location of specified substring

----------------------------------

SELECT 

col_3

, POSITION('g' in col_3) AS g_position

, POSITION('G' in col_3) AS "G_position"

, POSITION('love' in col_3) AS love_position

FROM char_type_test;






(2-6) 문자열의 부분 문자열을 잘라오기: SUBSTR(string [from int] [for int])



-- substring(string [from int] [for int])

SELECT 

*

, SUBSTR(col_2, 1, 3) AS substr_col_2_3

, SUBSTR(col_2, 1, 5) AS substr_col_2_5

FROM char_type_test;


 




(2-7) 문자열을 구분자(Delimeter)를 기준으로 분할 후 일부분 가져오기

: SPLIT_PART(string, delimiter, part_position)



--------------

-- Split a string on a specified delimeter

--------------

SELECT 

'2020-07-04' AS yyyymmdd

, SPLIT_PART('2020-07-04', '-', 1) AS year

, SPLIT_PART('2020-07-04', '-', 2) AS month

, SPLIT_PART('2020-07-04', '-', 3) AS day;


 




(2-8) 문자열 내 특정 하위 문자열을 잘라내기: TRIM()


  • 문자열 내 특정 문자로 시작하는 부분 잘라내기: TRIM(leadnig characters from string)
  • 문자열 내 특정 문자로 끝나는 부분 잘라내기: TRIM(trailing characters from string)
  • 문자열 내 특정 문자로 시작하거나 끝나는 부분 잘라내기: TRIM(both characters from string)
  • 문자열의 왼쪽 시작부분에 특정 문자가 있으면 잘라내기: LTRIM(string, characters)
  • 문자열의 오른쪽 끝부분에 특정 문자가 있으면 잘라내기: RTRIM(string, characters)

TRIM()과 LTRIM(), RTRIM() 간에 인자의 위치가 조금 다른 것에 주의하세요. 



--  trim([leading | trailing | both] [character] from string)

SELECT 

'xTomxx' AS original_str

, TRIM(LEADING 'x' FROM 'xTomxx') AS trim_leading

, TRIM(TRAILING 'x' FROM 'xTomxx') AS trim_trailing

, TRIM(BOTH 'x' FROM 'xTomxx') AS trim_both

, LTRIM('xTomxx', 'x') AS l_trim

, RTRIM('xTomxx', 'x') AS r_trim;






(2-9) 문자열의 처음 n개 문자 가져오기


  • 문자열의 왼쪽으로(시작) 부터 처음 n개 문자 가져오기: LEFT(string, int n)
  • 문자열의 오른쪽으로(끝) 부터 처음 n개 문자 가져오기: RIGHT(string, int n)


--------------------------------

-- First n character in a string

--------------------------------

SELECT 

col_2

, LEFT(col_2, 3) AS first_n_from_left

, RIGHT(col_2, 3) AS first_n_from_right

FROM char_type_test;


 




(2-10) 문자열의 길이가 n개보다 모자라는 개수만큼 채우기(Padding)


  • 문자열의 길이가 n 개보다 모자라는 개수만큼 character로 왼쪽부터 채우기
    : LPAD(string, int n, character)
  • 문자열의 길이가 n 개보다 모자라는 개수만큼 character로 오른쪽부터 채우기
    : RPAD(string, int n, character)

아래 예에서는 id가 정수형(int) 이므로 id::char 을 사용하여 문자형으로 변환한 후에, 문자열형 함수인 LPAD(), RPAD() 함수를 적용하였습니다. 


-----------------------------------

-- PAD on the left or rght a string 

-----------------------------------

SELECT 

id

, LPAD(id::char, 5, '0') AS lpad_id

, RPAD(id::char, 5, '0') AS rpad_id

FROM char_type_test;


 



문자열형의 패턴 매칭(pattern matching)까지 다루기에는 포스팅이 너무 길어지므로 다음번에 별도로 소개하겠습니다. 


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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas 데이터프레임 안에 여러개의 칼럼 별로 결측값을 대체하는 방법(how to fill missing values per each columns)을 다르게 하는 방법을 소개하겠습니다. 




먼저 예제로 사용할 간단할, 각 칼럼별로 결측값을 포함하고 있는 pandas DataFrame을 만들어보겠습니다. 



import numpy as np

import pandas as pd


# Make a DataFrame with missing values

df = pd.DataFrame({'a': [1, 2, 3, np.nan, 5], 

                   'b': [30, 20, np.nan, 35, 32], 

                   'c': [0.2, np.nan, 0.5, 0.3, 0.4], 

                   'd': ['c1', np.nan, 'c3', 'c4', 'c5'], 

                   'e': [10, 11, np.nan, 13, 15]})

 

df

abcde
01.030.00.2c110.0
12.020.0NaNNaN11.0
23.0NaN0.5c3NaN
3NaN35.00.3c413.0
45.032.00.4c515.0




다음으로, 각 칼럼별로 결측값을 대체하는 방법을 Dictionary에 Key (칼럼 이름): Value (결측값 대체 방법/값) 로 정리해보겠습니다. 


[ 칼럼별 결측값 대체 방법(전략) ]

  • 칼럼 'a': 0 으로 결측값 대체
  • 칼럼 'b': 평균(mean)으로 결측값 대체
  • 칼럼 'c': 중앙값(median)으로 결측값 대체
  • 칼럼 'd': 'Unknown'으로 결측값 대체
  • 칼럼 'e': 결측값 보간 (interpolation)



missing_fill_val = {'a': 0, 

                    'b': df.b.mean(), 

                    'c': df.c.median(), 

                    'd': 'Unknown', 

                    'e': df.e.interpolate()}


print(missing_fill_val)

{'a': 0, 'c': 0.35, 'b': 29.25, 'e': 0    10.0
1    11.0
2    12.0
3    13.0
4    15.0
Name: e, dtype: float64, 'd': 'Unknown'}

 



이제 준비가 다 되었으니 df DataFrame의 각 칼럼별로 서로 다른 결측값 대체 전략을 사용하여 결측값을 채워보겠습니다.  fillna() 함수의 괄호 안에 위에서 정의한 Dictionary 를 넣어주면 끝입니다. 간단하지요? ^^



df2 = df.fillna(missing_fill_val)


df2


a
bcde
01.030.000.20c110.0
12.020.000.35Unknown11.0
23.029.250.50c312.0
30.035.000.30c413.0
45.032.000.40c515.0

 



만약 원본 df DataFrame 을 보존할 필요가 없이 바로 결측값을 채워넣기 해서 수정하고 싶으면 inplace=True 옵션을 설정해주면 됩니다. 



df.fillna(missing_fill_val, inplace=True)

abcde
01.030.000.20c110.0
12.020.000.35Unknown11.0
23.029.250.50c312.0
30.035.000.30c413.0
45.032.000.40c515.0

 



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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 PostgreSQL, Greenplum DB의 4가지 연산자(Operators)에 대해서 알아보겠습니다. 

  • 산술 연산자 (Arithmetic Operators)
  • 비교 연산자 (Comparison Operators)
  • 논리 연산자 (Logical Operators)
  • 비트 연산자 (Bitwise Operators)




  (1) 산술 연산자 (Arithmetic Operators)



* Reference: https://www.postgresql.org/docs/9.4/functions-math.html


산술연산자는 어려운 것은 없으므로 추가 설명은 생략하겠으며, 다만 나눗셈(/)에 대해서만 조심해야하는 부분이 있어서 추가 설명을 하겠습니다. 


나눗셈의 분자와 분모가 모두 정수(int)인 경우 나눗셈(/)을 하면 정수의 몫을 반환하며, 소수점 부분은 무시가 되므로 유의할 필요가 있습니다. 만약 소수점자리까지의 나눗셈 계산 결과가 모두 필요한 경우 분자나 혹은 분모를 NUMERIC 혹은 FLOAT 로 데이터 형태 변환을 해주어야 합니다. 아래에 간단한 예를 들어보겠습니다. (이걸 신경을 안쓰면 나중에 소수점 부분의 결과가 무시된걸 모르고서 원하는 값이 아니라면서 당황하는 수가 생깁니다.) 



DROP TABLE IF EXISTS test;

CREATE TABLE test (

a int 

, b int

);


INSERT INTO test VALUES (2, 4), (3, 5), (4, 7);

SELECT * FROM test;

-------------

a      b

-------------

2 4

3 5

4 7

 

-- 나눗셈 결과의 소수점 자리 무시

SELECT b / a AS div FROM test;

----------

div

----------

2

1

1


-- 분자를 Numeric으로 형 변환하면 나눗셈 결과 소수점 자리 나옴

SELECT b::numeric / a AS div_1 FROM test;

----------

div_1

----------

2.0000000000000000

1.6666666666666667

1.7500000000000000



-- 분모를 Numeric으로 형 변환하면 나눗셈 결과 소수점 자리 나옴

SELECT b / a::numeric AS div_2 FROM test;

----------

div_2

----------

2.0000000000000000

1.6666666666666667

1.7500000000000000




계승(factorial)의 경우 SELECT 5!, SELECT !!5 처럼 '!'가 한개냐, 두개냐에 따라서 정수를 써주는 위치가 달라집니다. 


절대값(Absolute value)를 구할 때는 '@ col_nm' 혹은 '@ 숫자' 를 해주면 되는데요, 이때 '@' 다음에 스페이브 1칸을 띄워주어야 합니다. (만약 '@' 다음에 한 칸 띄우지 않으면 SQL Error [42883]: ERROR: operator does not exist: @- numeric 와 같은 ERROR가 발생합니다.)


산술 연산을 한 후에 'AS col_nm' 처럼 alias 별명 칼럼 이름을 부여할 수 있습니다. 




  (2) 비교 연산자 (Comparison Operators)


* Reference: https://www.postgresql.org/docs/9.4/functions-comparison.html



비교 연산자(comparison operators)도 어려운 것은 없으므로 길게 설명할 필요는 없어보입니다. 아래의 간단한 예를 살펴보시기 바랍니다. 


비교 연산자는 WHERE 조건절에서 사용되어 두 값을 비교하게 되며, 비교 연산자의 조건을 만족하면 참(TRUE)을, 비교 연산자의 조건을 만족하지 않으면 거짓(FALSE)을 반환합니다. 이를 이용해서 비교 연산자에 대해 참(TRUE)인 조건을 만족하는 값만을 선택(SELECT)해 올 수 있습니다. 



-----------------------

-- Comparison Operators

-----------------------

DROP TABLE IF EXISTS comparison;

CREATE TABLE comparison (

a int 

, b int

);


INSERT INTO comparison VALUES (1, 1), (1, 2), (2, 1), (2, 2);

SELECT * FROM comparison;

----------

a       b

----------

1 1

1 2

2 1

2 2



-- equal to

SELECT * FROM comparison WHERE a = b;

----------

a       b

----------

1 1

2 2



-- not equal

SELECT * FROM comparison WHERE a != b;

SELECT * FROM comparison WHERE a <> b;

----------

a       b

----------

1 2

2 1



-- greater than

SELECT * FROM comparison WHERE a > b;

----------

a       b

----------

2 1



-- less than

SELECT * FROM comparison WHERE a < b;

----------

a       b

----------

1 2



-- greater than or equal to

SELECT * FROM comparison WHERE a >= b;

----------

a       b

----------

1 1

2 1

2 2



-- less than or equal to

SELECT * FROM comparison WHERE a <= b;

----------

a       b

----------

1 1

1 2

2 2




다만 한가지 조심한 것이 있습니다. 비교 연산자 두개를 이어붙여서 사용하는 경우 순서(sequence)가 틀리면 ERROR가 발생합니다. 따라서 꼭 순서에 맞게 (가령, >= greater than or equal to) 비교 연산자를 써주어야 합니다. 



-- SQL Error [42883]: ERROR: operator does not exist: integer =! integer

SELECT * FROM comparison WHERE a =! b;



-- SQL Error [42883]: ERROR: operator does not exist: integer >< integer

SELECT * FROM comparison WHERE a >< b;



-- SQL Error [42601]: ERROR: syntax error at or near "=>"

SELECT * FROM comparison WHERE a => b;



-- SQL Error [42883]: ERROR: operator does not exist: integer =< integer

SELECT * FROM comparison WHERE a =< b;




  (3) 논리 연산자 (Logical Operators)


논리 연산자(Logical Operators)는 조건절에서 여러개의 조건을 AND, OR, NOT 으로 조합하여 사용할 수 있도록 해줍니다. 아래에 우측에 집합 벤다이어그램으로 그림을 그려놓았으니 참고하시기 바랍니다. 




아래의 표는 WHERE 조건절에 a와 b의 두 조건이 참(TRUE), 거짓(FALSE) 여부의 조합별로 AND, OR, NOT 논리 연산자의 결과값이 참(TRUE)인지 또는 거짓(FALSE)인지를 정리한 표입니다. NULL 은 FALSE 로 간주된다는 점 유의하시기 바랍니다. 


[ PostgreSQL Logical Operators Table by TRUE, FALSE combinations ]

* Reference: https://www.postgresql.org/docs/9.4/functions-logical.html



위의 (2)번에서 만들었던 comparison 테이블에 NULL 값을 포함한 행 두개를 추가해서 간단한 논리 연산자 예제를 만들어보겠습니다. 



---------------------

-- Logical Operators

---------------------

INSERT INTO comparison VALUES (NULL, 5), (NULL, NULL);

SELECT * FROM comparison;

----------

a      b

----------

1 1

1 2

2 1

2 2

[NULL] 5

[NULL] [NULL]



-- AND

SELECT * FROM comparison WHERE a = 1 AND b = 2;

----------

a      b

----------

1 2



-- OR

SELECT * FROM comparison WHERE a = 1 OR b = 5;

----------

a      b

----------

1 1

1 2

[NULL] 5



-- NOT

SELECT * FROM comparison WHERE NOT (a = 1);

2 1

2 2



-- NOT IN

SELECT * FROM comparison WHERE a NOT IN (1);

----------

a      b

----------

2 1

2 2



-- IS NOT NULL

SELECT * FROM comparison WHERE b IS NOT NULL;

----------

a      b

----------

1 1

1 2

2 1

2 2

[NULL] 5



-- IS NOT NULL AND IS NOT NULL

SELECT * FROM comparison WHERE a IS NOT NULL AND b IS NOT NULL;

----------

a      b

----------

1 1

1 2

2 1

2 2


-- NOT BETWEEN

SELECT * FROM comparison WHERE b BETWEEN 1 AND 2;

----------

a      b

----------

1 1

1 2

2 1

2 2



SELECT * FROM comparison WHERE b NOT BETWEEN 1 AND 2;

----------

a      b

----------

[NULL] 5





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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

이전 포스팅에서 Python으로 JSON 데이터 읽고 쓰기, XML 데이터 읽고 쓰기에 대해서 소개한 적이 있습니다.  이번 포스팅에서는 Python의 PyYAML 라이브러리를 이용하여 YAML 파일을 파싱하여 파이썬 객체로 읽어오기, Python 객체를 YAML 파일로 쓰는 방법을 소개하겠습니다. 



YAML에 대한 Wikipedia의 소개를 먼저 살펴보겠습니다. 

YAML 은 "YAML Ain't Markup Language" 의 반복적인 약어(recursive acronym)로서, 인간이 읽을 수 있는 데이터 직렬화 언어(human-readable data-serialization language) 입니다. YAML 은 데이터가 저장되거나 전송되는 구성 파일(configuration file)과 애플리케이션에서 종종 사용됩니다. YAML은 XML 과 동일한 커뮤니케이션 애플리케이션을 대상으로 하지만 최소한의 구문을 가지고 있습니다. 

* source: https://en.wikipedia.org/wiki/YAML


아래는 데이터가 저장되거나 전송되는 구성파일을 XML과 JSON, YAML으로 나타내서 비교한 예입니다. Python 들여쓰기(indentation) 형태로 해서 XML 이나 JSON 대비 Syntax 가 매우 간소해졌음을 알 수 있습니다. 




Python 에서 YAML 파일을 파싱하거나, 반대로 Python 객체를 YAML 파일로 내보낼 때는 PyYAML 라이브러리 (PyYAML is YAML parser and emitter for python)를 사용합니다. 이전에 XML이나 JSON을 다루었을 때와 PyYAML 라이브러리의 load(), dump() 함수 사용법은 비슷합니다. 





먼저, PyYAML 라이브러리가 설치되어 있지 않다면 명령 프롬프트에서 pip로 설치를 해주면 됩니다. 




  (1) YAML 파일을 파싱해서 Python 객체로 읽어오기: yaml.load()


예제로 사용한 YAML 파일은 아래처럼 'Vegetables' 키에 'Pepper', 'Tamato', 'Garlic' 을 값으로 가지는 YAML 파일(vegetables.yml)입니다. (Notepad++ 에디터에서 파일 형식을 YAML로 해서 작성하면 들여쓰기를 알아서 맞추어 줍니다. PyCharm 같은 프로그래밍 에디터를 사용해도 편리합니다.)



vegetables.yml


with open() 으로 'vegetables.yml' YAML 파일을 연 후에, yaml.load() 함수로 YAML 파일을 파싱하여 vegetables 라는 이름의 Python 객체로 저장하였습니다. Python 객체를 인쇄해보면 Key, Value (list) 로 구성된 Dictionary 로 YAML 파일을 파싱했음을 알 수 있습니다. 



import yaml


with open('vegetables.yml') as f:

    vegetables = yaml.load(f, Loader=yaml.FullLoader)

    print(vegetables)

 

{'Vegetables': ['Pepper', 'Tomato', 'Garlic']}




아래와 같이 Kubernetes의 deployment-definition.yaml 처럼 조금 복잡한 YAML 파일을 PyYAML 로 파싱해보면 List와 Nested Dictionary 로 구성된 Dictionary로 파싱합니다. 



k8s_deployment_yaml.yml




import yaml


with open('deployment-definition.yml') as f:

    deployment_def = yaml.load(f, Loader=yaml.FullLoader)


deployment_def

{'apiVersion': 'apps/v1',
 'kind': 'Deployment',
 'metadata': {'name': 'frontend',
  'labels': {'app': 'mywebsite', 'tier': 'frontend'}},
 'spec': {'replicas': 3,
  'template': {'metadata': {'name': 'myapp-pod', 'labels': {'app': 'myapp'}},
   'spec': {'containers': [{'name': 'nginx', 'image': 'nginx'}]}},
  'selector': {'matchLabels': {'app': 'myapp'}}}}





  (2) 여러개의 YAML 문서들을 파싱하여 읽어오기 : yaml.load_all()


YAML 문서를 구분할 때는 '---' 를 사용합니다. 아래는 'Fruits'와 'Vegetables' 의 두개의 YAML 문서를 '---'로 구분해서 하나의 YAML 파일로 만든 것입니다. 



예제 파일:

fruit-vegetable.yml



위의 (1)번에서는 1개의 YAML 문서를 yaml.load() 함수로 Python으로 읽어왔었다면, 이번의 (2)번에서는 '---'로 구분되어 있는 여러개의 YAML 문서를 yaml.load_all() 함수를 사용해서 Python 객체로 파싱하여 읽어오겠습니다. 



import yaml


with open('fruit-vegetable.yml') as f:

    

    fruits_vegetables = yaml.load_all(f, Loader=yaml.FullLoader)

    

    for fruit_vegetable in fruits_vegetables:

        print(fruit_vegetable)

 

{'Fruits': ['Blueberry', 'Apple', 'Orange']}
{'Vegetables': ['Pepper', 'Tomato', 'Garlic']}




  (3) 읽어온 YAML 파일을 정렬하기


아래와 같이 자동차 브랜드, 가격 쌍으로 이우러진 cars.yml YAML 파일을 Python 객체로 파싱해서 읽어올 때 Key 기준, Value 기준으로 각각 정렬을 해보겠습니다. (Dictionary 정렬 방법 사용)



cars.yml


(3-1) 읽어온 YAML 파일을 Key 기준으로 정렬하기 (sorting by Key)

    : 방법 1) yaml.dump(object, sort_keys=True) 



import yaml


with open('cars.yml') as f:

    

    cars_original = yaml.load(f, Loader=yaml.FullLoader)

    print(cars_original)

    

    print("---------------------")

    

    # sorting by Key

    cars_sorted = yaml.dump(cars_original, sort_keys=True)

    print(cars_sorted)

 

{'hyundai': 45000, 'tesla': 65000, 'chevrolet': 42000, 'audi': 51000, 'mercedesbenz': 80000}
---------------------
audi: 51000
chevrolet: 42000
hyundai: 45000
mercedesbenz: 80000
tesla: 65000



  : 방법 2) sorted(object.items()) 메소드 사용 



import yaml


with open('cars.yml') as f:

    

    cars_original = yaml.load(f, Loader=yaml.FullLoader)

    print(cars_original)

    

    print("---------------------")

    # sort by key in ascending order

    for key, value in sorted(cars_original.items()):

        print(key, ':', value


{'hyundai': 45000, 'tesla': 65000, 'chevrolet': 42000, 'audi': 51000, 'mercedesbenz': 80000}
---------------------
audi : 51000
chevrolet : 42000
hyundai : 45000
mercedesbenz : 80000
tesla : 65000

 



(3-2) Key 값의 역순으로 정렬 (sorting in reverse order): sorted(object.items(), reverse=True)



import yaml


with open('cars.yml') as f:

    cars_original = yaml.load(f, Loader=yaml.FullLoader)

    print(cars_original)

    

    print("---------------------")

    

    # sorting by key in reverse order

    for key, value in sorted(cars_original.items(), reverse=True):

        print(key, ':', value)


tesla : 65000
mercedesbenz : 80000
hyundai : 45000
chevrolet : 42000
audi : 51000




(3-3) 읽어온 YAML 파일을 Value 기준으로 정렬하기 (sorting by Value)



import yaml


with open('cars.yml') as f:

    cars_original = yaml.load(f, Loader=yaml.FullLoader)

    print(cars_original)

    

    print("---------------------")

    # sorting by value in ascending order

    for key, value in sorted(cars_original.items(), key = lambda item: item[1]):

        print(key, ':', value)


{'hyundai': 45000, 'tesla': 65000, 'chevrolet': 42000, 'audi': 51000, 'mercedesbenz': 80000}
---------------------
chevrolet : 42000
hyundai : 45000
audi : 51000
tesla : 65000
mercedesbenz : 80000




  (4) Python 객체를 YAML stream으로 직렬화 하기: yaml.dump()


Key, Value 쌍으로 이루어진 Python Dictionary를 yaml.dump() 메소드를 사용해서 YAML stream으로 직렬화해보겠습니다. 



import yaml


fruits = {'fruits': ['blueberry', 'apple', 'orange']}


# serialize a Python object into YAML stream

fruits_serialized_yaml = yaml.dump(fruits)

print(fruits_serialized_yaml)

 

fruits:
- blueberry
- apple
- orange




  (5) Python 객체를 YAML 파일로 쓰기: with open('w') as f: yaml.dump()


위의 (4)번에서 소개한, YAML stream으로 직렬화하는 yaml.dump() 메소드에 with open('w') 함수를 같이 사용해서 이번에는 YAML file 에 쓰기('w')를 해보겠습니다. 



import yaml


fruits = {'fruits': ['blueberry', 'apple', 'orange']}


with open('fruits.yaml', 'w') as f:

    yaml.dump(fruits, f)




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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 (1) 날짜 TimeStamp (m개) 와 (2) 고객 ID (n개) 의 두 개 칼럼을 가진 DataFrame에서 고객ID 별로 중간 중간 날짜 TimeStamp 가 비어있는 경우 모두 '0'으로 채워서 모든 조합 (m * n 개) 을 MultiIndex로 가진 시계열 데이터 형태의 DataFrame을 만들어보겠습니다. 


이번 포스팅에서는 pandas DataFrame에서 index 설정 관련해서 MultiIndex(), set_index(), reindex(), reset_index() 등 index 설정과 재설정 관련된 여러가지 메소드가 나옵니다. 


말로만 설명을 들으면 좀 어려운데요, 아래의 전(Before) --> 후(After) DataFrame의 전처리 Output Image를 보면 이해하기가 쉽겠네요. (시계열 데이터 분석을 할 때 아래의 우측에 있는 것처럼 데이터 전처리를 미리 해놓아야 합니다.) 




먼저, 년-월-일 TimeStamp (ts), 고객 ID (id), 구매 금액 (amt)의 세개 칼럼으로 구성된 거래 데이터(tr)인, 예제로 사용할 간단한 pandas DataFrame을 만들어보겠습니다. 



import pandas as pd


tr = pd.DataFrame({

    'ts': ['2020-06-01', '2020-06-02', '2020-06-03', '2020-06-01', '2020-06-03'], 

    'id': [1, 1, 1, 2, 3], 

    'amt': [100, 300, 50, 200, 150]})


tr

tsidamt
02020-06-011100
12020-06-021300
22020-06-03150
32020-06-012200
42020-06-033150

 



다음으로, 거래 데이터(tr) DataFrame의 날짜(ts)와 고객ID(id)의 모든 조합(all combination)으로 구성된  Multi-Index 를 만들어보겠습니다. pd.MultiIndex.from_product((A, B)) 메소드를 사용하면 Cartesian Product 을 수행하여, 총 A의 구성원소 개수 * B의 구성원소 개수 종류 만큼의 MultiIndex 를 생성해줍니다. 위 예제의 경우 날짜(ts)에 '2020-06-01', '2020-06-02', '2020-06-03'의 3개 날짜가 있고, 고객ID(id) 에는 1, 2, 3 의 3개가 있으므로 Cartesian Product 을 하면 아래의 결과처럼 3 * 3 = 9 의 조합이 생성이 됩니다. 



date_id_idx = pd.MultiIndex.from_product((set(tr.ts), set(tr.id)))

date_id_idx

MultiIndex([('2020-06-01', 1),
            ('2020-06-01', 2),
            ('2020-06-01', 3),
            ('2020-06-02', 1),
            ('2020-06-02', 2),
            ('2020-06-02', 3),
            ('2020-06-03', 1),
            ('2020-06-03', 2),
            ('2020-06-03', 3)],
           )



이제 위에서 Cartesian Product으로 만든 TimeStamp(ts)와 고객ID(id)의 모든 조합으로 구성된 MultiIndex인 date_id_idx 를 사용하여 index를 재설정(reindex) 해보겠습니다. 이때 원래(Before)의 DataFrame에는 없었다가 date_id_idx 로 index를 재설정(reindex) 하면서 새로 생긴 행에 구매금액(amt) 칼럼에는 'NaN' 의 결측값이 들어가게 됩니다. 이로서 처음에 5개 행이었던 것이 이제 9개(3*3=9) 행으로 늘어났습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx)

tr_tsformat

amt
2020-06-011100.0
2200.0
3NaN
2020-06-021300.0
2NaN
3NaN
2020-06-03150.0
2NaN
3150.0

 



날짜(ts)와 고객ID(id)의 MultiIndex로 reindex() 하면서 생긴 NaN 값을 '0'으로 채워넣기(fill_value=0)해서 새로 DataFrame을 만들어보겠습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx, fill_value=0)

tr_tsformat

amt
2020-06-011100
2200
30
2020-06-021300
20
30
2020-06-03150
20
3150

 



만약 날짜(ts)와 고객ID(id)의 MultiIndex로 이루어진 위의 DataFrame에서 MultiIndex를 칼럼으로 변경하고 싶다면 reset_index() 함수를 사용하면 됩니다. 칼럼 이름은 애초의 DataFrame과 동일하게 ['ts', 'id', 'amt'] 로 다시 부여해주었습니다. 



tr_tsformat.reset_index(inplace=True)

tr_tsformat

level_0level_1amt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033

150

 


tr_tsformat.columns = ['ts', 'id', 'amt']

tr_tsformat

tsidamt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033150




참고로, pandas에서 ID는 없이 TimeStamp만 있는 일정한 주기의 시계열 데이터 Series, DataFrame 만들기는 https://rfriend.tistory.com/501 를 참고하세요. 



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

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,