관리 메뉴

HAMA 블로그

데이터 분석에 피가 되는 itertools 익히기 본문

Python

데이터 분석에 피가 되는 itertools 익히기

[하마] 이승현 (wowlsh93@gmail.com) 2017. 4. 12. 10:13



itertools 


소개

Python 에서 제공하는 자신만의 반복자를 만드는 훌륭한 모듈입니다. 이 모듈은 APL, Haskell 및 SML의 구성 요소에서 영감을 받은 반복기 빌딩 블록을 구현하며 각각은 파이썬에 적합한 형태로 재 작성되었습니다.

먼가 말이 어렵습니다.  itertools 를 통해서 데이터 조작을 편하게 해보자는 야그겠지요. 그것이 무엇인가 반복되는 요소에 대한 처리일 경우 말이죠. 개발자는 코드로 말하기 때문에 바로 코드 예로 들어갑니다.


패키지 임포트

import itertools

itertools 를 임포트 합니다.


chain()

letters = ['a', 'b', 'c', 'd', 'e', 'f']
booleans = [1, 0, 1, 0, 0, 1]
decimals = [0.1, 0.7, 0.4, 0.4, 0.5]

print list(itertools.chain(letters, booleans, decimals))

결과 : ['a', 'b', 'c', 'd', 'e', 'f', 1, 0, 1, 0, 0, 1, 0.1, 0.7, 0.4, 0.4, 0.5]

설명:  간단히 말하면 리스트( lists/tuples/iterables ) 를 연결하는 것이다.


count()

from itertools import count , izip
for number, letter in izip(count(0, 10), ['a', 'b', 'c', 'd', 'e']):
print '{0}: {1}'.format(number, letter)

결과:

0: a  10: b  20: c  30: d  40: e

설명:  
count 는 반복하고자 하는 최대수를 미리 알지 않아도 되는 경우 사용됩니다. 여기서는 0 에서 시작해서 10씩 5개의 요소에 대해서 필요한 만큼 증가시켜 주고 있네요.  간단하게 말해서 시작과 step 만 있는 range 함수 느낌도 납니다. 


izip 

from itertools import izip

print list(izip([1, 2, 3], ['a', 'b', 'c']))

#  결과 : [(1, 'a'), (2, 'b'), (3, 'c')]

설명:  
파이썬에서는 이미 요소를 튜플에 결합하는 zip () 함수가 표준 라이브러리에 있으며 거의 같은 방식으로 작동하지만 약간의 성능 향상을 위해 iterable 객체를 반환합니다. 

imap


from itertools import imap

print list(imap(lambda x: x * x, xrange(10)))
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

설명:  
파이썬에서는 이미 요소를 튜플에 결합하는 map () 함수가 표준 라이브러리에 있으며 거의 같은 방식으로 작동

xrange() 이야기 - 홍민희님 블로그의  Python 제너레이터+반복자의 마법에서 발췌

Python은 Haskell처럼 모든 인자가 지연 평가되는 언어는 아니지만 워낙 모든 함수가 반복자를 주고 받게 되어 있다보니 반복자의 연쇄가 깊게 이뤄진다. 반복자의 연쇄가 깊다는 것은 함수 호출 스택이 깊어도 맨 끝에서 아래까지 “의도”1가 잘 전달된다는 뜻이다. 예를 들어 아래의 코드를 보자.

odd_numbers_to_10 = itertools.takewhile(lambda i: i <= 10, (x for x in xrange(1000) if x % 2))

너무나 작위적인 예제지만, 의도가 깊게 관통하는 코드의 예로서는 읽을만 하다. 결국 최종적으로는 “0 이상 10 이하의 홀수 목록”을 원하는 건데, 최초로 제공되는 소스인 xrange(1000)은 10을 초과하는 숫자는 생성하지 않는다. 의도가 잘 전달된다는 것은 이러한 뜻이다. 의도가 전달되지 않는 예를 만드려면 xrange range로 바꾸면 된다.2

odd_numbers_to_10 = itertools.takewhile(lambda i: i <= 10, (x for x in range(1000) if x % 2))

이 코드는 최종적으로 얻고자 하는 값이 결국 10 이하의 숫자들뿐임에도 range(1000)이 1000개의 수가 담긴 큰 리스트를 만들어내는도록 냅둔다. 공간도 낭비고 시간도 낭비다. 만약 우리가 xrange(1000) range(1000) 자리에 무한개의 숫자를 만들어내는 함수를 넣는다면 결정적인 차이가 발생한다. 만약 그 함수가 반복자를 반환한다면 우리가 처음 xrange(1000)을 썼던 코드와 효율에 차이가 없겠지만, 리스트를 만들어낸다면 리스트를 만들다가 메모리가 꽉 차서 뻗고 말 것이다.


islice 

from itertools import islice

for i in islice(range(10), 5):
print i

#  결과 : 0 4   |
# 설명 : [0 ~ 10] 의 반복가능 객체에서 5번째 안으로 짤라라

for i in islice(range(100), 0, 100, 10):
print i

#  결과 : 0 10 20 30 40 50 60 70 80 90  
# 설명 : islice () 함수는 slice () 함수와 동일하게 작동합니다. 첫 번째 매개 변수는 반복 가능한 객체이고, 두 번째 매개 변수는 시작 색인입니다. 세 번째 매개 변수는 끝 색인입니다. 마지막 매개 변수는 각 반복 후에 건너 뛸 수있는 단계 또는 숫자입니다. 


tee

from itertools import tee

i1, i2, i3 = tee(xrange(10), 3)
print i1
# <itertools.tee object at 0x2a1fc68>
print list(i1)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print list(i1)
# []
print list(i2)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print list(i2)
# []
print list(i3)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print list(i3)
# []

설명:

tee () 함수는 두 개의 매개 변수를 사용합니다. 첫 번째는 반복 가능이며 두 번째는 만들고자하는 복사본 갯수입니다. 예제를 보시다시피 한번 사용된 레퍼런스는 더 이상 값을 참조 하지 않습니다. 

r = (x for x in range(10) if x < 6)
print r
# <generator object <genexpr> at 0x2a22870>
i1, i2, i3 = tee(r, 3)
print list(r)
# [0, 1, 2, 3, 4, 5]
print list(i1)
# []
print list(i2)
# []
print list(i3)
# []

설명:

이렇게 원본의 제네레이터인 r 을 실행시키면 나머지 복제본들도 다 참조가 끊어 지는 특성이 있습니다.


cycle

from itertools import cycle , izip
for number, letter in izip(cycle(range(2)), ['a', 'b', 'c', 'd', 'e']):
print '{0}: {1}'.format(number, letter)
# 0: a
# 1: b
# 0: c
# 1: d
# 0: e

설명:

순환 가능한 객체에서 요소를 반복적으로 생성합니다. count 는 계속 증가하구요. 둘 모두 함께 계산되어지는 오른쪽 리스트에 따라 한정지어지는 특성을 지녔습니다.


repeat

from itertools import repeat
print list(repeat('Hello, world!', 3))
# ['Hello, world!', 'Hello, world!', 'Hello, world!']

설명:  요소를 반복합니다. 반복되는 갯수를 지정 합니다.


dropwhile

from itertools import dropwhile
print list(dropwhile(lambda x: x < 10, [1, 4, 6, 7, 11, 34, 66, 100, 1]))
# [11, 34, 66, 100, 1]

설명:  필터링 함수중 하나로서,  10보다 큰것이 나올 때까지 모든 요소는 드랍시키고 나머지것을 리턴함. 


takewhile

from itertools import takewhile
print list(takewhile(lambda x: x < 10, [1, 4, 6, 7, 11, 34, 66, 100, 1]))
# [1, 4, 6, 7]

설명:  필터링 함수중 하나로서,  10보다 큰 것이 나올때까지의 모든 요소를 리턴함. 


ifilter

from itertools import ifilter
print list(ifilter(lambda x: x < 10, [1, 4, 6, 7, 11, 34, 66, 100, 1]))
# [1, 4, 6, 7, 1]

설명:  필터링 함수중 하나로서,  10보다 작은 모든것을 리턴함. 


groupby


from operator import itemgetter
from itertools import groupby

attempts = [
('dan', 87),
('erik', 95),
('jason', 79),
('erik', 97),
('dan', 100)
]

# Sort the list by name for groupby
attempts.sort(key=itemgetter(0))

# Create a dictionary such that name: scores_list
print {key: sorted(map(itemgetter(1), value)) for key, value in groupby(attempts, key=itemgetter(0))}
# {'dan': [87, 100], 'jason': [79], 'erik': [95, 97]}

설명:  key 함수로 itemgetter(0) 를 이용하여 정렬후,  첫번째 요소로 groupby 함.  (정렬해야함)

from collections import defaultdict

counts = defaultdict(list)
attempts = [('dan', 87), ('erik', 95), ('jason', 79), ('erik', 97), ('dan', 100)]

for (name, score) in attempts:
counts[name].append(score)

print counts
# defaultdict(<type 'list'>, {'dan': [87, 100], 'jason': [79], 'erik': [95, 97]})

설명:  defaultdict 사용. 널값에 대한 체크를 생략할 수 있는 편의 제공. 



레퍼런스:

https://www.blog.pythonlibrary.org/2016/04/20/python-201-an-intro-to-itertools/

http://programeveryday.com/post/using-python-itertools-to-save-memory/



Comments