2015년 7월 31일 금요일

구글 지도 API를 활용한 지명 주소와 위/경도 주소 변환

구글 지도 API를 활용해서 지명 주소와 위/경도 주소 변환 예제를 기록한다.

* 참조 : https://developers.google.com/maps/documentation/geocoding/intro
* 무료 제약사항 : 24시간 동안 2500건, 초당 5건 질의 가능

미리 key 값을 구글 개발 콘솔에서 발급 받는다. (위 참조 링크 확인)

In [1]:
# 예제키
key = '&key=AIzaSyxxxxiABIfkNsA1237xA_xdb2xyszBfHUI'
In [2]:
# 필요한 라이브러리 로딩
import urllib
import json

위/경도 주소를 지명 주소로 변경

In [3]:
# google api
base_url ='https://maps.googleapis.com/maps/api/geocode/json?language=ko'+\
                '&location_type=ROOFTOP'+\
                '&latlng='
# google에서 찾은 필자의 현재 위치
latlng = '37.499351,127.0347757'
addr = json.loads(urllib.request.urlopen(base_url+latlng+key).read().decode('utf-8'))
# 주소 정보만 뽑기
for i in addr['results']:
    print(i['formatted_address'])
대한민국 서울특별시 강남구 역삼1동 736-36

지명 주소를 위/경도 주소로 변경

In [4]:
# 지명 주소를 위/경도 주소로,
base_url ='https://maps.googleapis.com/maps/api/geocode/json?language=ko'+\
                '&address='
# 부분 주소를 통해서 찾기
address = urllib.parse.quote('강남구 역삼1동 736-36')
addr = json.loads(urllib.request.urlopen(base_url+address+key).read().decode('utf-8'))
# 위치 정보만 뽑기
for i in addr['results']:
    print(i['geometry']['location'])
{'lng': 127.0347583, 'lat': 37.4992707}

지명 주소와 위/경도 주소에 대한 매핑 테이블과 PostGIS의 최단거리 함수를 이용해, 자체적인 변환기를 만들 수 있을까? 라는 고민을 남기고, 오늘은 여기서 정리 한다.

2015년 7월 30일 목요일

행정구역 단위 총인구 변화 조사 ( 2013년 ~ 2014년 )

"지역 별로 인구 증가가 어떨까?"라는 주제는 가지고, 통계자료를 구하고, 시각화하는 과정을 소개한다.

1. KOSIS에서 행정구역(시군구)별 최근 기간(2013 ~ 2014년) 자료를 수집한다.

* 인코딩 문제로 맥북에서 작업 할때는 언어를 영어로 출력해서 작업하거나,
출력 화면 테이블을 클립보드에 복사해서, Numbers 같은 툴에서 작업하면 된다.
필자는 Numbers에서 2014년 인구에서 2013년 인구의 차이를 미리 구했다.

2. 인구 변화를 표현할 지도 자료를 구한다.

* 참고 링크 : https://commons.wikimedia.org/wiki/File:Administrative_divisions_map_of_South_Korea.svg
* 행정구역 이름이 한글로 잘 정리 되어 있어서, 선택하게 되었다.

자료 준비는 이것으로 완료 되었고, 나머지는 파이톤 노트에서 시각화 하는 과정을 소개 한다.

3. 가중치에 따른 온도 색으로 지도 공간 시각화 하기

사용할 라이브러리 로딩
In [1]:
from bs4 import BeautifulSoup
from IPython.display import SVG
import pandas as pd
증감 수치를 온도에 따른 색으로 분류
In [2]:
# 온도 순서대로 10가지 색상 코드 (필자 선정)
color_temper = ['#0000FF', '#3366FF', '#3399FF',\
                '#66CCFF', '#99FFFF', '#FFCC99',\
                '#FF9966', '#FF6633', '#FF6600', '#FF3300']

# 자료에서 인구 증감 순서에 따라 10가지로 분류하고, 색상 라벨 지정
df = pd.read_csv('people_2014-2013.csv')
df['색깔'] = pd.qcut( df['증감'] , 10,labels=color_temper)
SVG 이미지에서 각 행정구역(시군구)의 체우기(fill) 색을 갱신
In [3]:
# SVG 이미지를 파서로 로딩
svg = BeautifulSoup(open('south_korea.svg','r').read(),'html.parser')

# 시군구가 일치하는 오브젝트의 기본 색상을 분류되어진 색상으로 체우기
for id,inc,color in df.values:
    if svg.find('g',{'id':id}):
        for sub in svg.find('g',{'id':id}).find_all('path'):
            sub['fill'] = color
    if svg.find('path',{'id':id}):
        svg.find('path',{'id':id})['fill'] = color
인구 증감 (색상 레드 > 블루)별로 색칠된 SVG 이미지 출력
In [4]:
SVG(svg.__str__())
* 서울에서 경기도 권역으로 인구이동 추정
* 세종시 주변과 제주도에 다수의 인구 유입
* 부산의 중심이 남포동에서 서면으로 이동 추정

지리 시각화 관련해서 통계 지리 정보 서비스가 2015년 4월 27일부터 시범서비스를 오픈했다.
* http://sgis.kostat.go.kr
왜 시범서비스인지를 금방 알게 될 것이다.
Anyway, 알리바바의 마윈이 말한 IT를 넘어선 DT 시대를 준비하고 있는 모습은 매우 반갑다.


2015년 7월 29일 수요일

자연어 처리를 위한 준비 과정와 간단한 응용사례 소개

학습 방식으로 빠른 성능을 보이는 MeCab 형태소 분석기를 구성하고,
자연어처리(NLTK)를 활용한 간략한 응용 예제를 소개한다.

MeCab 형태소 분석기 구성하기 

1. MeCab Korean 설치
 - 한국어의 특성에 대한 가중치 부분 수정된 버전
 - https://bitbucket.org/eunjeon/mecab-ko/downloads/

2. MeCab Korean Dicationay 설치
 - 한국어 말뭉치로 학습되어진 모델(사전)
 - https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/

3. MeCab Python Library 설치
 - pip3 install mecab-python3

4. 참고 홈페이지 (은전한닙프로젝트)
 - https://bitbucket.org/eunjeon/mecab-ko
 - 맥에서는 --prefix로 경로를 특정해서 사용한다.

* 아키 구성
MeCab 분석기에서는, 미리 학습되어진 모델을 통해서, 신규 문장에 대한 형태소 분석 결과를 리턴한다.

MeCab,NLTK 및 위키백과 활용하기

* 문장을 받아서, 질의 문장이면, 주제어를 위키백과에 조회해서, 결과를 출력한다.


가. MeCab을 통해서 문장을 형태소로 분리하기

In [1]:
import MeCab
m = MeCab.Tagger ()
In [2]:
# 문장을 형태소로 분류한다.
out = m.parse("시리님 노아의 방주가 뭐죠?")
print(out)
시리 NNP,인명,F,시리,*,*,*,*,
님 XSN,*,T,님,*,*,*,*
노아 NNG,*,F,노아,*,*,*,*
의 JKG,*,F,의,*,*,*,*
방주 NNG,*,F,방주,*,*,*,*
가 JKS,*,F,가,*,*,*,*
뭐 NP,*,F,뭐,Inflect,NP,NP,뭣/NP/*
죠 VCP+EF,*,F,죠,Inflect,VCP,EF,이/VCP/*+죠/EF/*
? SF,*,*,*,*,*,*,*
EOS

In [3]:
# NLTK 라이브러리에서 사용할 포멧으로 만든다.
def pos_tagger(s):
    """ (단어,태그) 셋을 만들기 """
    word_tag = []
    for r in s.split('\n'):
        p = r.split('\t')
        if len(p) > 1:
            w, o = p
            t = o.split(',')[0]
            word_tag.append((w,t))
    return word_tag
set_wp = pos_tagger(out)
print(set_wp)
[('시리', 'NNP'), ('님', 'XSN'), ('노아', 'NNG'), ('의', 'JKG'), ('방주', 'NNG'), ('가', 'JKS'), ('뭐', 'NP'), ('죠', 'VCP+EF'), ('?', 'SF')]

나. NLTK를 통해서 형태소 패턴을 활용하기

In [4]:
# 문법 패턴을 정의 한다.
grammar = """
명사: {<NNG>}
명사구: {<명사><JKG><명사>}
동사구: {<NP\+VCP\+EF>}
동사구: {<NP><VCP\+EF>}
"""
In [5]:
import nltk
cp = nltk.RegexpParser(grammar)
tree = cp.parse(set_wp)
print(tree)
(S
  시리/NNP
  님/XSN
  (명사구 (명사 노아/NNG) 의/JKG (명사 방주/NNG))
  가/JKS
  (동사구 뭐/NP 죠/VCP+EF)
  ?/SF)

다. 질의 문장에 대해서 주제어를 위키백과에서 조회하기

In [6]:
bucket = []
for i in tree.subtrees(filter=lambda t: t.label() == '동사구'):
    if ''.join([ w for w,p in i.leaves() ]) in ['뭔가요','뭐죠','무엇인가요']:
        for j in tree.subtrees(filter=lambda t: t.label() == '명사' or t.label() == '명사구'):
            space = lambda p : ' ' if p == 'JKG' else ''
            bucket.append(''.join([ w+space(p) for w,p in j.leaves() ])) 
print(bucket)
['노아의 방주', '노아', '방주']
In [7]:
# 주제를 가진 단어들에 대한 위키백과 참고하기.
import wikipedia as wk
wk.set_lang("ko")
for title in bucket:
    page = wk.page(title)
    print(title,'_____위키백과_____\n',page.summary)
노아의 방주 _____위키백과_____
 노아의 방주(영어:Noah's Ark; 히브리어:תיבת נח; 고전히브리어:Teyvat Noaḥ, -方舟)는 아브라함 계통의 종교에서 전승되는 이야기 속에 등장하는 배이다. 모세오경 중의 창세기에 실려있으며, 노아와 관련된 일련의 이야기 속에 등장하기 때문에 노아의 방주로 통칭된다. 주로 기독교의 전승이 널리 알려져 있으나, 그 외의 아브라함 계통 종교들인 유대교, 이슬람교, 만다교 등에서도 모두 각자의 전승을 가지고 있다. 그러나 종교와 전승에 따라서는 전해지는 내용이나 받아들이는 해석에 조금씩 차이가 있다.
노아 _____위키백과_____
 노아(Noah)는 이스라엘과 아랍의 전설에 등장하는 인물이자 성경 구약성서의 창세기의 홍수이야기(창세기 6:5-17)에도 나오는 인물이다. 구약 창세기 4장과 5장에 따르면, 노아는 아담과 하와의 첫째 아들인 카인의 후손이 아니라 셋째 아들인 셋의 후손이다. 성경의 창세기 5장에 따르면, 노아의 아들은 셈 · 함 · 야벳의 3명이다.
실존여부는 불명확하나 기원전 2800년 경 무렵 메소포타미아와 그 주변 지역에 일어났던 대 홍수기에 살던 한 실존인물에서 유래한 것으로 추정된다. 당시 메소포타미아, 앗시리아, 그리스 지역에도 홍수가 범람하여 소수의 생존자가 있었다는 신화들도 존재한다. 노아의 모델이 된 인물은 이때 이스라엘 혹은 아랍 지역에서 홍수를 피해 살아남은 한 인물이 신화화된것으로 추정된다.
방주 _____위키백과_____
 《엡실론의 방주》(일본어: イプシロンの方舟 (-ふね) 이프시론노 후네[*])는 KOTOKO의 4번째 앨범을 말한다. 2009년 10월 14일 발매.

필자가 최근에 같이 일하게 된 시리(Siri)를 보면, 한글 자연어 처리 영역도 꽤 높은 수준에 도달 했음을 느낀다. 가벼운 문자 보내기, 음악 재생이 매우 편리하고, 위키백과 사전을 읽어주는 기능은 10년을 손가락과 눈으로 일한 IT 엔지니어에 대한 축복임이 틀림 없다.

혹시 자연어 처리 관련해서 입문하시는 분이라면, "한국어 형태소 분석과 정보 검색"이라는 책을 추천해 봅니다.


2015년 7월 27일 월요일

전체 사망자 대비 성별/연령별 자살 사망자 비율

국가통계포털(KOSIS)를 뒤적거리다 움찔한 자료가 있어서 오려 봅니다.
(현재 기준으로 가장 최신 자료)

* 2013년 연령별 사망자 수

* 2013년 연령별 전체 사망자 대비 자살 사망자 비율

평소 청소년 자살과, 고령화로 인한 노년층 자살을 많이 다루지만,
비율로 보면 사회에 집입하고 자리를 잡아가는 계층 전반에 걸쳐 있는 것을 추정해 볼 수 있다.
특히 "25세 ~ 30세"에 사망한 여성분들의 자살 비중이 50%가 넘었다. 

최근 TV에 "인문학 강의"가 많은 것이 나름 언론의 노력으로 추정해 보지만, 
교육을 통한 개인의 책임을 강조하는 것보다,
실질적인 환경 개선에 사회 구성원 모두의 노력이 필요해 보인다.

버스에서 블로깅 중인데, (세월호 사건을 계기로 잠시 개선을 보였던) "고속도로 진입 시에 안전벨트하기"가 롤백된 느낌이다.
탑승 가능 자석수 표지판을 달았으니 나머지는 개인의 선택인 걸까?

2015년 7월 24일 금요일

Spark summit 2015의 Netflix 개선 사례 모의 구현하기

HOT cluster computing framework 인 SPARK에 대한 사용 경험을 바탕으로,
최근 spark summit 2015에 소개된 Netflix 개선 사례 모의 구현을 목표로 지속 갱신할 문서를 만든다.

필자가 참고한 architecture 문서는 다음과 같다.
https://spark-summit.org/2015/events/spark-and-spark-streaming-at-netflix/

빅(BIG)한 message event들이 발생하는 Neflix, LinkIn에서 검증된 메세지 브로커(message broker) Kafka와,
AWS(Amazon Web Service)의 HOT Storage Service S3 그리고 Spark Streaming에 대한 모의 기능 구현을 예상한다.

1. Kafka 의 message event 처리하기.

Producer를 통한 메세지 전송하기

In [1]:
from kafka import SimpleProducer, KafkaClient

# 생산자를 생성한다.
kafka = KafkaClient('cook:9092')
producer = SimpleProducer(kafka)

# 메세지를 바이트 코드로 던진다.
point = producer.send_messages(b'frog', b'fall in well.')
producer.send_messages(b'frog', '우물에 빠지다.'.encode('utf-8'))
Out[1]:
[ProduceResponse(topic=b'frog', partition=0, error=0, offset=101)]

Consumer를 통한 메세지 수신하기

In [2]:
from kafka import KafkaConsumer

# 소비자를 만든다.
consumer = KafkaConsumer('frog',
                         bootstrap_servers=['cook:9092'],consumer_timeout_ms=10000)
consumer.set_topic_partitions(("frog", 0, point[0].offset))
for i in consumer.fetch_messages():
    print(i.value.decode('utf-8'))
fall in well.
우물에 빠지다.

2. AWS S3 Bucket 사용하기

boto API를 위한 사전 정보 저장하기
In [3]:
!ls ~/.aws
config credentials

버킷 만들기

In [4]:
import boto3
client = boto3.client('s3')
# 버켓 생성
client.create_bucket(Bucket='frog1nwell')
Out[4]:
{'Location': '/frog1nwell',
 'ResponseMetadata': {'HTTPStatusCode': 200,
  'HostId': 'inuHkVNZZa3OdU1YqijMZXo1d01pFr0OC7QDnAOogkrCv+y9iwvEBoDrrSkVQU9czgE=',
  'RequestId': '09E1B2023C66504A'}}
In [5]:
# 버켓 리스트 보기
client.list_buckets()
Out[5]:
{'Buckets': [{'CreationDate': datetime.datetime(2015, 7, 24, 9, 8, 57, tzinfo=tzutc()),
   'Name': 'frog1nwell'},
  {'CreationDate': datetime.datetime(2015, 7, 1, 7, 47, 36, tzinfo=tzutc()),
   'Name': 'koala1nwell'}],
 'Owner': {'DisplayName': 'jerry',
  'ID': 'b31d91a3e2d0512c901d7fb8e0a7b60702478af922f2f08921237b16'},
 'ResponseMetadata': {'HTTPStatusCode': 200,
  'HostId': 'bBmMrGujXa5Stm3ZAuExl+ZQPo3O5h6ZcS8nHTslNHxU18BFj37pezxJsEOWW++kqFXg=',
  'RequestId': 'B20589EE8D9'}}

오브젝트 추가하기

파일 오브젝트를 버킷으로 업로드 하기.

In [6]:
%%writefile quote_file.txt
You make me to be complete.
당신은 나를 완성시켜요.
Overwriting quote_file.txt
In [7]:
with open('quote_file.txt', 'rb') as f:
    client.put_object(Bucket='frog1nwell',Key='quote_file.txt',Body=f)

스트링을 ByteIO를 이용해서, 오프젝트처럼 처리 하기

In [8]:
from io import BytesIO
bs = BytesIO('You make me to be complete.\n당신은 나를 완성시켜요.'.encode('utf-8'))
In [9]:
client.put_object(Bucket='frog1nwell',Key='quote_byteIO.txt',Body=bs)
Out[9]:
{'ETag': '"372ff0057f46e3d8302856328b3ebba5"',
 'ResponseMetadata': {'HTTPStatusCode': 200,
  'HostId': '+yI5rXDeAsXpc2gWTUWR23aWzlkNRFbBFXf5NHq0+k0bTvqJArfMu5M606VVgeeufnnseM=',
  'RequestId': '5847A4880F82B'}}

오프젝트 읽기.

In [10]:
c1 = client.get_object(Bucket='frog1nwell',Key='quote_file.txt')
c2 = client.get_object(Bucket='frog1nwell',Key='quote_byteIO.txt')
In [11]:
print('File:\n',c1['Body'].read().decode('utf-8'))
print('ByteIO:\n',c2['Body'].read().decode('utf-8'))
File:
 You make me to be complete.
당신은 나를 완성시켜요.
ByteIO:
 You make me to be complete.
당신은 나를 완성시켜요.

추가할 내용으로 다음을 주제를 구상 중에 있다.
* Kafka 와 Spark Streaming 의 처리 소개
* AWS의 S3 와 EMR 의 처리 소개
* Netflix 의 모의 구현을 바탕으로 실 데이터 분석 사례

집단지성 위키백과(Wikipedia) API 활용하기

세계인의 집단지성으로 만들어지는 위키백과 사전의 데이터 조회 API에 대해서 간략히 다룬다.
참고 : https://wikipedia.readthedocs.org/en/latest/code.html

예제로 필자의 현재 위/경도 정보를 통해서, 반경 10km에 등록된 "의미군"을 찾고, 관련 내용을 조회해 보겠다.
( "의미군" : 위키백과 속성으로는 "Title"과 매칭된다.)

1. 현재 위/경도 정보는 구글 지도에 들어가면 바로 조회된다.

오른쪽 하단에 내 위치를 클릭하고, 주소 창에 보이는 위/경도를 가지고 오자.

2. 수집한 위/경도 정보를 통해서 위키백과 사전에 정보를 조회해 본다.

In [1]:
import wikipedia as wk
# 필자가 자신있는 한국어를 통해서,
wk.set_lang("ko")
wk.geosearch(37.5000201,127.0364249,results=10,radius=10000)
Out[1]:
['강남 파이낸스 센터',
 '선릉역',
 '매봉산 (강남구)',
 '서울특별시강남교육지원청',
 '강남구청역',
 '삼풍백화점 붕괴 사고',
 '서울교육대학교',
 '휘문고등학교',
 '횃불트리니티신학대학원대학교',
 '코엑스']
In [2]:
# 깔끔해보이는 코엑스 페이지를 가지고 온다.
p = wk.page('코엑스')
In [3]:
# 코엑스에 대한 분류를 조사하고, 관련 이미지를 가지고 온다.
from IPython.display import HTML
print(' / '.join(p.categories))
s = ['<tr>']
for i in p.images[0:5]:
    s.append('<td><img style="width: 120px;" src=%s></img></td>' % i)
s.append('</tr>')
HTML('\n'.join(s))
분류:1979년 설립 / 분류:대한민국의 컨벤션 센터 / 분류:서울특별시의 경제 / 분류:서울특별시의 기업 / 분류:한국종합무역센터
Out[3]:

백과 사전이라는 것이 편중 될 수 있는 일부 학자들의 정의가 아닌 집단 지성에서 나온다는 단순하면서 빅데이터(BIGDATA)스러운 이 생각이 크다란 바람을 일으키고 있다.
참고:  https://www.ted.com/talks/jimmy_wales_on_the_birth_of_wikipedia?language=ko#

시리(Siri)가 위급할때 마다 찾는 위키(Wiki), 참 매력적이다.

100% 데이터 오너쉽 오픈소스 웹 분석 툴 PIWIK 소개

웹분석(Web Analytics)하면 대략 GA(Google Analytics)를 많이 떠올린다.
GA의 사용 편의성과 기능은 매우 뛰어나고, 구글의 서포트와 함께 지속적으로 개선 중이다.
아카데미를 통한 교육에도 매우 적극적이다.
다만, 일정 이상의 트래픽에 대해서는 제약(유료라이센스필요)이 있으니 도입할때 주의하자.

영화 매트릭스에 보면,
에이전트들의 무자비한 강력함도 매트릭스 안에서 제한된다라는 문구가 있다.

관련해서,
 - 100%의 데이터 오너쉽 그리고 무료
 - 모든 요소에 대한 사용자 컨츄롤 가능
을 외치는 오픈소스 웹분석 플랫폼 PIWIK이라는 툴이 있다.
( 참조 : http://piwik.org/faq/new-to-piwik/faq_15/ )

실제로 DB 스키마부터 설명서까지 모든 것이 오픈되어 있다.

자체 서버 설치 과정은,
APM(Apache, PHP, MySQL)을 구성하고 PIWIK을 시작하면 된다.

웹분석의 레오가 되고 싶은가? PIWIK이 당신을 험난한 길로 인도할 것이다.

아래 내용은 AWS에서 PIWIK 서버를 더이상 운용할 수 없을 듯 해서,
간략하게 남아, PYTHON - PIWIK HTTP API 사용 예제를 작성한다.


In [1]:
# HTTP 질의를 위한 모듈 로딩
import urllib.request,json
import pandas as pd
In [2]:
# PIWIK API를 참조한 RESTful
# 어제를 기점으로 일주일 동안의 방문자 트래픽 정보를 질의
PIWIK_URL = '&'.join(['http://piwik/piwik/?module=API',\
          'method=API.getProcessedReport',\
          'idSite=3',\
          'date=yesterday',\
          'period=month',\
          'apiModule=VisitTime',\
          'apiAction=getVisitInformationPerLocalTime',\
          'format=json',\
          'token_auth='+token,\
          'filter_truncate=30',\
          'language=en'])
In [3]:
# HTTP API로 질의한 JSON 결과를 읽기
with urllib.request.urlopen(PIWIK_URL) as f:
    j = json.loads(f.read().decode('utf-8'))
In [4]:
# 시간대별로 방문자와 페이지 조회수 플롯
df = pd.DataFrame(j['reportData'])
df.plot(kind='bar',x='label',y=['nb_actions','nb_visits'],
        figsize=(9,4),color=['purple','orange'],alpha=.7)
title('PIWIK Result 01')
xlabel('hour')
ylabel('count')
grid()
Out[4]:
<matplotlib.text.Text at 0x7fdc73736c88>
PIWIK의 경우는 API 이외에, DB 질의 (SQL)을 통한 방법도 고려 할 수 있다.
( 오픈소스에서 당신의 상상력은 곧 현실이 된다. )

2015년 7월 22일 수요일

아이파이썬(IPython) 노트북에서 깔끔한 플롯 그리는 방법

판다곰(Pandas)과 한몸 같은 2D 플롯 라이브러리 matplotlib만 있으면,
데이터의 의미를 전달하기에 충분하다. 하지만,
인터렉티브하고 깔끔한 플롯이 필요하다면, Bokeh가 당신을 도와 줄 것이다.

Bokeh : http://bokeh.pydata.org/en/latest/

페이지를 참고 해서, 간략한 테스트를 진행해 본다.

In [1]:
# 아이파이썬 노트북 관련 모듈을 로딩하고,
from bokeh.io import output_notebook, show

# 결과를 아이파이썬 노트북으로 출력할 플롯 엔진을 뛰운다.
output_notebook()
BokehJS successfully loaded.
In [2]:
# 테스트용 데이터를 만든다.
import math,numpy as np

X = np.arange(1,10,.1)
Y = [ math.sin(x) for x in X ]
Ym = [ - math.sin(x) for x in X ]
In [3]:
# 데이터를 플롯팅 한다.
from bokeh.plotting import figure

p = figure(height=300,title='파이썬(Python)')
p.title_text_font = 'PilGi'
p.circle(x=X,y=Y,size=X)
p.triangle(x=X,y=Ym,size=X[::-1],color='orange')
p.line(x=X,y=0,line_width=10,color='red',alpha=.3)
show(p)
Matplotlib와 흡사한 사용법으로 접근성이 높으나,
판다곰에서 바로 바로 플롯팅하던 편의성은 떨어지는 편이다.

매우 깔끔하고 동적인 화면 구성은 최종 결과물을 표현하기 좋고,
R shiny 같은 인터렉티브(interactive)한 비주얼 구성도 가능하다.


아이파이썬(IPython) 노트북에서 플롯(Plot)에 한글 폰트 사용하는 방법

플롯 영역에서의 한글 문제는 어플리케이션을 가리지 않고, 다양하게 발생하지만,
최근에 발생하는 이슈는 대략 폰트 관련된 부분이 많다.

필자는 R Studio, PIWIK과 같은 툴 에서도 한글이 깨지는 문제를 발견 했고,
모두 한글 폰트를 지정하는 방법으로 해결했다.

폰트는 OS 시스템에서 제공되는 기본 폰트 영역을 변경하거나,
각 어플리케이션에서 지정(추가/변경) 가능한 기능을 찾아서 해결하자.

이번 글은 표제와 같이,
리눅스 기반의 2D 플롯 라이브러리인 Matplotlib에서 맥북 다양한 글씨체를 사용하는 것을 목표로 작성한다.

1. 맥북에서 사용할 폰트 선정하기

"서체 관리자"(Font Book.app)에서 원하는 폰트를 찾아서,
서체 파일을 파이톤 노트를 사용하고자 하는 시스템으로 옮긴다.

예제로 필기체(Pilgiche.ttf)를 가지고 진행해 보았다.

2. 폰트 사용하는 2가지 방법 소개

가. 시스템에 폰트를 등록해서 사용하기

In [2]:
# OS 환경마다 조금 다를 수 있지만, UNIX 계열은 대략 비슷하다.
# Pilgiche.ttf를 /usr/share/fonts에 넣고 다음과 같이 강제로 갱신해 준다. 
!fc-cache -f
In [3]:
# 폰트가 추가 되었는지 확인 한다.
# 기존에 사용하던 AppleGothic과 새로 추가한 PilGi
!fc-list | egrep -e "PilGi|AppleGothic"
AppleGothic:style=일반체,標準體,Ordinær,Normal,Regular,Normaali,Regolare,レギュラー,Regulier,Обычный,常规体
PilGi:style=일반체,標準體,Ordinær,Normal,Regular,Normaali,Regolare,レギュラー,Regulier,Обычный,常规体

나. Matplotlib에 등록해서 사용하기

In [4]:
!ls /usr/local/lib/python3.4/site-packages/matplotlib/mpl-data/fonts/ttf/ |grep Pilgi
Pilgiche.ttf

다. 파이톤 노트의 폰트를 갱신하기.

앞서 설명한 두가지 방법 "가|나" 중에 하나를 수행하고 다음을 수행하면 된다.
  • 캐싱 공간 (~/.cache/matplotlib/* )을 걱정 말고 깔끔하게 지워 준다.
  • 파이톤 노트를 다시 구동하면, 모듈이 호출될때, 변경 사항이 반영된다.
Matplotlib에서 제공하는 fontmanager라는 모듈을 사용해서 다체롭게 사용하는 방법도 있지만,
정신 건강을 위해서 rcParams를 사용하는 방법을 추천한다.
In [6]:
# Default font family로 폰트를 지정하는 과정이다.
# 일반적으로 제공되는 기본(Default) 폰트는 한글을 처리 못한다.
rcParams['font.family'] = 'sans-serif'
text(s=rcParams['font.family'].__str__()+'훈민정음',x=.1,y=.7,fontsize=30)
# 한글을 지원하고, 개인이 선호하는 폰트 지정 사용
rcParams['font.family'] = 'AppleGothic'
text(s=rcParams['font.family'].__str__()+'훈민정음',x=.1,y=.5,fontsize=25)
rcParams['font.family'] = 'PilGi'
text(s=rcParams['font.family'].__str__()+'훈민정음',x=.1,y=.3,fontsize=30)
Out[6]:
<matplotlib.text.Text at 0x7f56f5281d30>

각 함수 객체별로 font family 파라메터를 통해서, (대부분) 개별 지정도 가능하다.
rcParams의 경우는 따로 font family가 지정되지 않은 객체들에 대해서 적용된다.


2015년 7월 21일 화요일

판다곰(pandas)으로 주식 시세 확인 및 이동평균선 구하기

분석 라이브러리인 판다스(Pandas)를 통해서 주식 시세를 확인 하고,
이동평균선(7일) 구하는 과정을 요약합니다.


작성하는 시점에 판다스(Pandas)의 원격 데이터 수집 모듈이 독립을 하였기에,
먼저 관련 내용을 정리해 봅니다.

판다곰 문서 중에서,
http://pandas.pydata.org/pandas-docs/stable/remote_data.html

관리 차원에서 분리하는 것으로 보이고,
0.17+ 버전 부터는 pandas_datareader를 통해서 관리됩니다.
https://github.com/pydata/pandas-datareader

변경 사항은 기존 코드에서 단지 한줄이면 됩니다.
import pandas.io.data as web
=>
import pandas_datareader.data as web

다음은 판다곰으로 주식 시세 및 이동평균선 구하는 코드 입니다.

# 판다곰에서 독립한 데이터 입출력 라이브러리
import pandas_datareader.data as web
from datetime import datetime

# 2015년 1월 부터 현제까지의 데이터 수집
start = datetime(2015, 1, 1)
end   = datetime.now()

# 구글에서 대한항공의 주식 시세를 가지고 옵니다.
대한항공 = web.DataReader('KRX:003490','google',start,end)

# 종가 기준으로 플로팅을 하구요,
대한항공['Close'].plot(style='--')

# 7일 이동평균선을 약간 두껍게 플로팅 합니다.
pd.rolling_mean(대한항공['Close'], 7).plot(lw=2)

# 기타 양념을 뿌립니다.
title('2015년 대한항공 종가 시세')
xlabel('time')
ylabel('won')
legend(['종가시세','이동평균선(7일)'])"
결과는,

응용차원에서 최근 항공주 관련한 간략한 분석 플롯 올립니다.
* "조현아 사건" 구간은 유가 호재로 항공주 상승에도 불구하고, 대한항공이 맥을 못춥니다.
* "메르스 발병" 구간은 차별 없이 항공주에 악재로 작용하고 있는 것을 보여 줍니다.

필자는 판다스(Pandas)가 없었다면, 지금 파이톤을 하고 있을까라는 생각을 가끔 합니다.

PostgreSQL의 대형 속성 저장 기술 (TOAST) 요약

PostgreSQL의 데이터 용량 관리에 대해서,


최근에 작업한 80만건 정도의 웹크라울링 데이터의 작업 중에,
흥미로운 이야기가 있어서 소개 합니다.

PostgreSQL 9.4 버전에서 메타(META)과 함께 HTML 페이지를 보관 할때,
* DB 사이즈가 30G
* 덤프(pg_dump)의 압축레벨 1로 압축 파일을 생성 했을때 사이즈가 22GB
* 압축을 풀었을때 파일 사이즈가 90G

코끼리는 기본적으로 가변 타입에서 8k를 넘는 문자열을 압축해서 보관하고,
관련 기술은 TOAST라 불립니다.

TOAST(대형 속성 저장 기술:The Oversized-Attribute Storage Technique)

 - 참고자료 : http://www.postgresql.org/docs/9.4/static/storage-toast.html
 - 요약
 * 8kB가 넘는 데이터에 대해서 TOAST 적용됨
 * 성능에 주안을 둔 LZ 압축 기술 사용
 * 한개의 오브젝트가 논리적으로 1GB까지 지원
 * (주테이블과 TOAST 테이블이 분리되어 있음으로) TOAST 테이블 연관 속성을 조회 하지 않을때는 조회 성능에 이슈가 없음

백문이 불여일견이라, DB 관리자 분들을 위해서 조회 쿼리와 함께 내용을 정리해 봅니다.

순수하게 주테이블에 대한 사이즈를 조사합니다.

cook=> select pg_size_pretty(pg_relation_size('web_crawling_news'));
 pg_size_pretty 
----------------
 210 MB
(1 row)
=> TOAST에 해당되는 속성이 없을 때는, 매우 빠른 조회가 가능합니다.

INDEX나 TOAST 같은 연관 테이블을 합산해서 조사합니다.

cook=> select pg_size_pretty(pg_total_relation_size('web_crawling_news'));
 pg_size_pretty 
----------------
 30 GB
(1 row)

주테이블과 연관된 TOAST 테이블을 조사합니다.

cook=> select relname from pg_class where oid in ( select reltoastrelid from pg_class where relname = 'web_crawling_news');
     relname      
------------------
 pg_toast_3419091
(1 row)

TOAST 테이블의 사이즈를 조사합니다.

cook=# select pg_size_pretty(pg_relation_size('pg_toast.pg_toast_3419091'));
 pg_size_pretty 
----------------
 30 GB
(1 row)

TOAST 테이블의 내부 속성들 입니다.

cook=> \d+ pg_toast.pg_toast_3419091
TOAST table "pg_toast.pg_toast_3419091"
   Column   |  Type   | Storage 
------------+---------+---------
 chunk_id   | oid     | plain
 chunk_seq  | integer | plain
 chunk_data | bytea   | plain

점심을 패스해서 그런지 TOAST 생각이 간절 하네요.

2015년 7월 17일 금요일

쉬는 자원을 활용하기 위한 파이톤 멀티프로세싱 요약

멀티프로세싱에 대해서,


데이터가 양적으로 빅(BIG) 해지면, 수행 성능의 차이를 비율로 바라보는 센스가 필요하다.
가령 10초 걸리는 일을 1초에 한다는 것은, 10년 걸리는 일을 1년에 한다는 멋진 일이다.

성능 문제의 대부분은 고가의 장비로 해결 가능하지만, 가난한 우리들은 있는 자원을 최대한 활용해야 한다.
최근 CPU 집약적인 많은 양의 원시 데이터를 파싱할때, 시간을 비약적으로 줄인 파이톤 멀티프로세싱을 소개한다.

쉬는 자원을 활용하기 위한 멀티프로세싱의 구현 I

  • 할당 받은 가상 코어가 MAX 4개라면, 1개를 여유로 두고, 3개를 자유롭게 쓰겠다.
  • 작업 성격에 따라 검토해야 겠지만, 일반적으로 MAX - 1은 추천되는 방식이다.
In [1]:
from multiprocessing import Process, Queue
from datetime import datetime
import time

def f(task):
    time.sleep(2)
    print ("[ task id : %d - print time : %s ]" % (task,datetime.now().strftime('%s')))

if __name__ == '__main__':
    MAX_CORE = 4
    TASKS = 10
    for i in range(1,TASKS+1):
        p = Process(target=f, args=(i,))
        p.start()
        if i % ( MAX_CORE - 1 ) == 0:
            p.join()
    p.join()
[ task id : 1 - print time : 1437141542 ]
[ task id : 2 - print time : 1437141542 ]
[ task id : 3 - print time : 1437141542 ]
[ task id : 4 - print time : 1437141544 ]
[ task id : 5 - print time : 1437141544 ]
[ task id : 6 - print time : 1437141544 ]
[ task id : 7 - print time : 1437141546 ]
[ task id : 8 - print time : 1437141546 ]
[ task id : 9 - print time : 1437141546 ]
[ task id : 10 - print time : 1437141548 ]

쉬는 자원을 활용하기 위한 멀티프로세싱의 구현 II - Pool 사용

  • 동시 수행을 위한 프로세스 풀을 만들어 놓고 사용한다.
  • 필자 같은 초짜도 쉽게 수행할 수 있는 간략한 방법을 제공한다.
In [2]:
from multiprocessing import Pool

def f(task):
    time.sleep(2)
    return ("[ task id : %d - print time : %s ]" % (task,datetime.now().strftime('%s')))

if __name__ == '__main__':
    MAX_CORE = 4
    TASKS = 10
    with Pool(processes=MAX_CORE-1) as pool:
        out =pool.map(f,range(1,TASKS+1))
    for i in out: print(i)
[ task id : 1 - print time : 1437141553 ]
[ task id : 2 - print time : 1437141553 ]
[ task id : 3 - print time : 1437141553 ]
[ task id : 4 - print time : 1437141555 ]
[ task id : 5 - print time : 1437141555 ]
[ task id : 6 - print time : 1437141555 ]
[ task id : 7 - print time : 1437141557 ]
[ task id : 8 - print time : 1437141557 ]
[ task id : 9 - print time : 1437141557 ]
[ task id : 10 - print time : 1437141559 ]

멀티프로세싱 모니터링 방법

실무적으로는 nmon 같은 실시간 리소스 체크 툴을 사용하고,
리포팅을 위해서는 psutil같은 유용한 라이브러리를 활용하자.
In [3]:
import psutil
import pandas as pd

def cpu_usage_to_df(sec):
    cpus = []
    for i in range(1,sec + 1):
        cpus.append(psutil.cpu_percent(interval=1, percpu=True))
        if i % 30 == 0:
            print('elapsed %1.1f min' % (i / 60))
    return pd.DataFrame(cpus)
In [4]:
# 입력한 초 만큼의 초단위 모니터링 결과를 레코딩한다.
df = cpu_usage_to_df(60)
elapsed 0.5 min
elapsed 1.0 min
프로세스 POOL 3개를 지정하고, CPU 집약적인 다량의 파싱 작업을 수행했을때 1분 동안 모니터링을 돌렸다.
필자의 8코어 가상 서버에서 1분간의 모니터링 결과는 그림과 같이 매우 흡족하다.
In [34]:
df.plot(kind='area')
title('Stacked Area')
xlabel('time (second)')
ylabel('cpu usage (%)')
Out[34]:
<matplotlib.text.Text at 0x7f92fa0a62b0>
 CPU에 큰 병목현상이 있는 경우에는 코어 수 만큼의 성능 향상 효과가 있다.
이해를 돕기 위해서, Pool이 1/4/7/10의 경우에 1분간 각 코어의 평균 사용량을 조사 했다.
오랜지 세로 라인은 산술적인 최대 지점과 수행 평균과의 유격을 표현해 보았다.

  • 산술적인 최대 지점 = 지정 Pool 수 / 전체 CORE 수 * 100
  • 수행 평균 = 각 CORE의 사용량(%)의 합 / 전체 CORE 수

% 다른 외부 환경을 완전히 통제한 상황은 아니지만, 작업이 없을때 1% 이하의 사용량을 보인다. %

보통의 케이스에 Pool 1개의 상황을 상상하면서, 농을 피우는 녀석들이 있는지 가끔 확인하자.
모든 프로세스를 활용하는 상황에서도 기다릴 수 없다면, 서버 간에 처리가 가능한 분산 프로그래밍을 고려하자. (귀도 형과 함께라면 이 또한 심플하리라.)

진정으로 빅(BIG)한 상황이 오면, 하둡이나 DW 솔루션들이 당신을 기다리고 있다.
초기 도입 비용이나 시스템 운영 관리가 힘든 상황이라면,
사용한 만큼만 내는 AWS(아마존웹서비스)가 당신의 파트너가 되어 줄 것이다.