toolz API

pDestiny·2021년 8월 12일
0

Python

목록 보기
1/1
post-thumbnail

사실 함수형 프로그래밍은 시도는 하고 있지만 생각대로 잘 되지는 않습니다. 하지만 파이프라인에 넣고 함수를 교체하고 간단하게 집어 넣는 방식과 무엇보다도 데이터가 스트림으로 들어옴으로 문제를 매우 잘게 쪼갤 수 있다는 것은 매력적으로 다가오기에 toolz의 compose만은 도저히 포기할 수가 없네요. 여기에는 지속적으로 toolz api를 보고 익힌 함수들을 실제 사용 후기와 함께 올려볼 생각입니다. toolz는 functoolz, itertoolz, dicttoolz 그리고 sandbox로 이루어져 있습니다. 순서는 무작위로 그때 그때 포스팅할 생각입니다.

functoolz

compose

pipe

compose_left

flip

curry

do

juxt

itertoolz

frequencies

iterable 객체의 데이터에서 데이터별 개수를 세줍니다. DNA Kmer에서 가장 많이 존재하는 서열을 찾아라 라는 문제에서 사용했죠. 배열을 집어 넣으면 요소의 개수와 함께 dictionary 형태로 나옵니다.
아래는 예시 코드입니다.

test_seq = "TAAACGTGAGAGAAACGTGCTGATTACACTTGTTCGTGTGGTAT"
k = 3

def gen_k_mer(seq, k=3):
  kmers = []
  for i in range(len(seq) - k):
    kmers.append(seq[i:i+k])
  
  return kmers

run = tz.compose(
	get(0), # 튜플에서 첫번째 요소인 키를 반환합니다.
    tz.first, # 가장 첫번째 값, 즉 횟수가 가장 많은 요소를 반환합니다.
    tz.curry(sorted, key=lambda x: x[1], reverse=True), # 가장 많은 횟수를 것으로 내림차순으로 정렬합니다.
    lambda x: x.items(), # items() 함수로 키와 카운트 한 횟수를 튜플로 반환합니다.
    tz.frequencies, # 각 요소별 개수를 구합니다. itertools의 Counter를 써도 되긴 하지만 이게 더 편합니다. 왜냐하면 Counter는 dictionary가 아니라 dictionary 처럼 생긴 개체를 반환합니다.
    tz.curry(gen_k_mer, k=3)) # 전체 kmer를 구한다음 ex) TAA, AAA, AAC ... so on

print(run(test_seq))

get

쓸데없어보이는데 pipe 라인에서 생각보다 자주 쓰입니다. 두가지 경우에 쓰일 수 있는데 첫번째는 list에서 쓰일 수 있고, 두번째는 dictionary에서 쓰일 수 있습니다. 두가지 모두 키값을 인자값으로 받아서 value 값을 반환합니다.

# list 활용
ls = [5,4,3,2,1]
print(get(2, ls)) # 3이 출력됩니다.

# dictionary 활용
d = {
	"name" : "me",
	"price" : Math.inf,
    "age" : 3
}
print(get("name", d)) # "me" 가 출력됩니다.

empty = []

print(get(30, empty, None)) # None이 출력됩니다. index error를 발생시키지 않습니다.

first, last

first는 정말 last와 함께 리스트 다룰때 많이 쓰이는 함수입니다. ls[0], ls[-1] 과 같긴 하지만 훨씬 이쁘네요!

ls = [1,2,3]
print(tz.first(ls)) # 1
print(tz.last(ls)) # 3

topk

sorted와 비슷하지만 조금더 좋은 기능을 가지고 있는게, sorted 쓰면 순서 맞춰서 그냥 다 나열하지만 topk 쓰면 tz.first 안쓰고도 가장 큰 값을 기준으로 가져올 수 있습니다. key값도 인자로 받아서 얼마든지 기준은 선택할 수 있습니다. (오름차순을 쓰는 법은 모두 알고 있으실꺼라 생각합니다.)

test_seq = "TAAACGTGAGAGAAACGTGCTGATTACACTTGTTCGTGTGGTAT"
k = 3

run = tz.compose(
	get_in([0,0]),
    topk(1, key=lambda x: x[1]), # sorting에 위에서 n개를 골라주기까지 해줍니다!
    dict.items, 
    tz.frequencies, 
    map(''.join),
    sliding_window(k))

print(run(test_seq))

dicttoolz

update_in

update_in은 특정 dict 타입 객체의 특정 키에만 함수를 apply하여 새로운 변경된 새로운 딕션어리 를 반환해 줍니다. 예를들어 아래와 같은 데이터가 있다고 해봅시다.

data = {
	"dna_seq": "ATGCATGCATGCATGC",
   	"introns": ["GC"]
}

제가 하고 싶은 것은 dna_seq 키의 데이터를 상보적 DNA 염기서열로 변경하고 다른것들은 변경하지 않은 채 반환하고 싶습니다. 그럴 때 toolz.dicttoolz.update_in 함수가 pipe라인에서 빛을 발합니다.

def compl_dna(dna_seq):
    compl_table = {
        "A":"T",
        "G":"C",
        "C":"T",
        "T":"A"
    }
    return ''.join(map(lambda x: compl_table[x], dna_seq))
    
tz.dicttoolz.update_in(data, keys=["dna_seq"], func=compl_dna) # dna_seq 키 값만 변경되어 나옵니다!

어찌보면 data["dna_seq"] = compl_dna(data["dna_seq"]) 하면 되는데 왜 저렇게 어렵게 하냐고 그러실 수도 있을 것이라 생각합니다.
하지만 함수형 프로그램에서 파이프라인을 쓰고 있는데 중간에 오는 딕셔너리 데이터의 일부분만 바꿔야 한다고 생각하면 편리한 방법이라고 생각합니다.

sandbox

profile
Bioinformatician

0개의 댓글