제33 KOBIC 차세대 생명정보학 교육 워크샵 데이터 과학을 위한 파이썬 기초
Table of Contents
개요 #
- 일시: 2016-08-24 ~ 2016-08-26
- 장소: (주)인실리코젠 본사 人CoFLEX
- 내용: 프로그래밍 언어 파이썬 집중 단기 학습을 통해 생물정보 데이터 분석 실무 능력 습득
- URL: http://kobicedu.labkm.net
강의 특징 #
- 이론과 실습 병행 (강의 1시간, 실습 1시간 - 하루 3회)
- 첫째날 서버에 원격 접속 (putty)
- 둘째날 개인 PC에 Jupyter 설치하여 실습
- 모든 실습은 페어프로그래밍으로 진행 (하루 1회 페어 변경)
교육 환경 #
원격 실습 서버 접속 방법 #
Windows #
- Putty 프로그램 설치: Putty download page에서 Windows용 MSI 설치 파일 putty-version-installer.msi 다운로드 및 실행
- Putty 프로그램 실행 후, 설정창에서 다음 입력 후 저장
- Host Name: edu.kobic.re.kr
- Port: 2022
- Saved Sessions: kobic33
- kobic33 선택 후
Open
클릭하여 접속 - login as: 부분에서 부여받은 아이디 입력
- kobic@edu.kobic.re.kr`s password: 부분에서 비밀번호 입력
- 접속 성공!
Linux/OS X #
Term 프로그램 구동 후, 다음처럼 입력하여 접속 (kobic23 사용자라면,)
$ ssh kobic23@edu.kobic.re.kr -p2022
Jupyter 설치 방법 #
Windows #
https://www.continuum.io/downloads#_windows에서 윈도우용 아나콘다 다운로드 한 후, 명령프롬프트에서 다음을 실행
$conda install jupyter
$jupyter notebook
Linux/OS X #
터미널에서 다음을 실행
# homebrew 설치
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# pyhton3 설치
$ brew install python3
# jupyter 설치
$ pip3 install jupyter
# 라이브러리 설치
$ pip install matplotlib
$ pip install pandas
# jupyter 실행
$ jupyter notebook
교육 내용 #
8월 24일 (1일차) #
파이썬 개요 #
참고 정보
- 파이썬 코딩 스타일 PEP 8
- PEP 한글 번역 프로젝트
- 파이썬 코딩 스타일 한국어 정보 파이썬 코딩 컨벤션
기본 자료형 #
연습문제 1. 파이썬 명령행 환경에서 a, b 변수가 다음과 같을 때,
#!python
$ python3
>>> a = 14
>>> b = 3
다음을 계산하세요.
- a를 b로 나눈 몫과 나머지
- a의 b 제곱
- a x log(b) (밑이 3)
- a를 실수부, b를 허수부로 하는 복소수의 켤레복소수
- a를 2진수로
- 16진수 123ab를 10진수로 바꾸면?
정답 1
#!python
>>> a // b # 몫
4
>>> a % b # 나머지
2
>>> divmod(a, b)
(4, 2)
>>>
>>> import math
>>> a * math.log(b, 3)
14.0
>>>
>>> a + b * 1j
(14+3j)
>>> (a + b * 1j).conjugate()
(14-3j)
>>> bin(a)
'0b1110'
>>> 0x123ab
74667
>>>
연습문제 2. 파이썬 명령행 환경에서 my_sentence 변수가 다음과 같을 때,
#!python
$ python3
>>> my_sentence = "Hello my world!"
다음을 출력하는 방법은?
- Hello
- my world
- m
- !
정답 2
#!python
>>> my_sentence = "Hello my world!"
>>> my_sentence[:5]
'Hello'
>>> my_sentence[7:-1]
'y world'
>>> my_sentence[6:-1]
'my world'
>>> my_sentence[5]
' '
>>> my_sentence[6]
'm'
>>> my_sentence[-1]
'!'
연습문제 3. 파이썬 명령행 환경에서 my_list 변수가 다음과 같을 때,
#!python
$ python3
>>> my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
다음을 출력하는 방법은?
- 처음부터 중간까지만
- 짝수자리만
- 3의 배수자리만
연습문제 4. 다음처럼 두 집합의 내용(공백으로 분리)을 입력받아, 합집합, 교집합, 차집합을 출력하는 프로그램을 작성하시오.
#!python
$ python3 set_operation.py
Set A: 1 2 3 4 5
Set B: 5 6 7 8 9
----
Union --> 1 2 3 4 5 6 7 8 9
Intersection --> 5
Diference --> 1 2 3 4
(힌트)
- 사용자로부터 입력을 받는 함수는 input 입니다.
- 공백 포함 문자열을 공백으로 나눈 리스트로 변환하려면 string.split() 메쏘드를 이용합니다.
정답 4
#!python
a = input('Set A: ')
b = input('Set B: ')
set_a = set(a.split())
set_b = set(b.split())
print('Union -->', set_a | set_b)
print('Intersection -->', set_a & set_b)
print('Difference -->', set_a - set_b)
제어구문 #
연습문제 1. 다음을 출력하는 파이썬 코드를 작성하시오.
#!python
$ python3 p1.py
*
**
***
****
*****
$ python3 p2.py
*****
****
***
**
*
정답 1
#!python
for i in range(1, 6):
print(‘* * i)
for i in range(5, 0, -1):
print(' ' * (5-i) + '*' * i)
연습문제 2. 다음을 출력하는 파이썬 코드를 작성하시오.
#!python
$ python3 times_table.py
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
...
9 x 8 = 72
9 x 8 = 81
정답 2
#!python
for x in range(2, 10):
for y in range(1, 10):
print(x, 'x', y, '=', x * y)
파일입출력 #
연습문제 3. 임의의 텍스트 파일내 단어의 출현 빈도를 조사하여 가장 많이 출현한 단어부터 정렬하여 출력하는 프로그램 (특수기호 제외, 소문자로 통일)
#!python
$ python3 word_frequency.py < input.txt
32 the
28 of
17 boy
…
정답 3
#!python
import sys
frequencies = {}
special_characters = ['"', "'", '%', '(', ')', ':', '=', ']', '[', ',']
for line in sys.stdin:
for sc in special_characters:
line = line.replace(sc, ' ')
words = line.strip().split()
for word in words:
if word in frequencies:
frequencies[word] += 1
else:
frequencies[word] = 2
for word, frequency in sorted(
frequencies.items(), key=lambda x: x[1], reverse=True)[:20]:
print(frequency, word)
연습문제 4. 임의의 FASTA 파일을 읽어, GC content를 출력 (하나의 레코드이며 서열은 한줄만 있음을 가정)
#!python
$ cat test.fasta
>gene1
AGCAGACGTCGAGCAAG
$ python gc_content.py < test.fasta
0.588
정답
#!python
import sys
title = next(sys.stdin)
sequence = next(sys.stdin)
A = sequence.count('A')
G = sequence.count('G')
C = sequence.count('C')
T = sequence.count('T')
gc_content = (G + C) / (A + G + C + T)
print(gc_content)
연습문제 5. 임의의 FASTA 파일을 읽어, GC content를 출력 (하나의 레코드이며 서열은 여러줄 있을 수 있음)
#!python
$ cat test.fasta
>gene1
AGCAGACGTCGAGCAAG
AGCAGACGTCGAGCAAG
$
$ python3 gc_content.py < test.fasta
0.588
정답
#!python
import sys
title = sys.stdin.readline()
sequences = []
for line in sys.stdin:
sequences.append(line)
sequence = ''.join(sequences)
A = sequence.count('A')
G = sequence.count('G')
C = sequence.count('C')
T = sequence.count('T')
print((G + C) / (A + G + C + T))
연습문제 6. 임의의 FASTA 파일을 읽어, Reverse complement 서열을 출력
#!python
$ cat test.fasta
>gene1
AGCAGACGTCGAGCAAG
AGCAGACGTCGAGCAAG
$
$ python3 gc_content.py < test.fasta
CTTGCTCGACGTCTGCT
CTTGCTCGACGTCTGCT
모듈, 패키지, 예외처리 #
연습문제 1. 구구단 출력 함수 (원하는 단을 입력 받도록, 함수로 작성)
#!python
$ multi.py
Please insert an integer: 3
3 * 1 = 3
3 * 2 = 6
$ multi.py
Please insert an integer: 12
12 * 1 = 12
12 * 2 = 24
정답 1
#!python
def time_table(x):
for y in range(1, 10):
print('{} x {} = {}'.format(x, y, x*y))
n = input('Please insert an integer: ')
time_table(n)
연습문제 2. Euclidean distance 계산하기
>>> p1 = [1, 2, 3, 4]
>>> p2 = [5, 6, 7, 8]
>>> get_euclidean_distance(p1, p2)
8.0
정답 2
#!python
import math
def get_euclidean_distance(X, Y):
s = 0
for i in range(len(X)):
x = X[i]
y = Y[i]
s += (x - y) ** 2
return math.sqrt(s)
if __name__ == '__main__':
p1 = [1, 2, 3, 4, 5]
p2 = [1, 4, 6, 8, 9]
print(get_euclidean_distance(p1, p2))
연습문제 3. 임의 개수의 숫자들을 함수의 인수로 받아 평균값을 출력하는 함수
>>> mysum(1, 2, 3)
2.0
>>> mysum(2, 4, 5, 7, 8)
5.2
정답 3
#!python
def mysum(*args):
return sum(args) / len(args)
연습문제 4. datetime 모듈을 사용하여 다음 질문에 답하세요
- 오늘은 2016년 8월 10일입니다. 1000일 후는 며칠?
위 계산을 하는 모듈을 별도로 만들고, import 하기
>>> import date_calculator
>>> date_calculator.date_from_today(1000)
정답 4
#!python
import datetime
def date_calculator(days):
return datetime.date.today() + datetime.timedelta(days)
연습문제 5. 임의의 FASTA 파일을 읽어, GC content를 출력 (Single FASTA 형식, 만일 AGTC 이외 다른 문자가 있으면 자체 오류출력, A,G,T,C 합계가 전체 길이와 다르면 예외발생)
$ cat test.fasta
>gene1
CGCAGACGTCGAGCAAB
AGCGACAGTCAGTCAGA
$ python gc_content.py < test.fasta
>gene1
AssertionError: Sequence characters have to be A, T, C, G
8월 25일 (2일차) #
Matplolib 한글폰트 설치 #
윈도우에서
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
맥에서
matplotlib.font_manager.get_fontconfig_fonts()
krfont = {'family': 'AppleGothic', 'weight': 'bold', 'size': 10}
matplotlib.rc('font', **krfont)
리눅스에서
matplotlib.font_manager.get_fontconfig_fonts()
krfont = {'family': 'UnDotum', 'weight': 'bold', 'size': 10}
matplotlib.rc('font', **krfont)
Rosalind 생물정보 문제 사례 #
연습문제 1. Counting DNA Nucleotides
#!python
$ cat seq.txt
AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC
$ python3 count.py
20 12 17 21
풀이 1
#!python
##함수 없이
input = open('seq.txt', 'r')
seq = input.read()
a_count = seq.count('A')
c_count = seq.count('C')
g_count = seq.count('G')
t_count = seq.count('T')
out = "{} {} {} {}".format(a_count, c_count, g_count, t_count)
print(out)
## 함수 사용
def count_seq(seq):
a_count = seq.count('A')
c_count = seq.count('C')
g_count = seq.count('G')
t_count = seq.count('U')
out = "{} {} {} {}".format(a_count, c_count, g_count, t_count)
return out
count(open(‘seq.txt’).read())
연습문제 2. Transcribing DNA into RNA
#!python
$ cat seq.txt
GATGGAACTTGACTACGTAAATT
$
$ python trans.py
GAUGGAACUUGACUACGUAAAUU
풀이 2
#!python
##함수 없이
cat trans.py
input = open(‘seq.txt’, ‘r’)
seq = input.read()
rna = seq.replace(’T’, ‘U’)
print(rna)
##함수 사용
def dna2rna(seq):
rna_seq = seq.replace('T', 'U')
return rna_seq
seq = open('seq.txt', 'r').read()
rna_seq = dna2rna(seq)
print(rna_seq)
연습문제 3. Complementing a Strand of DNA
#!python
$ cat seq.txt
AAAACCCGGT
$
$ python revc.py
ACCGGGTTTT
풀이 3
#!python
## 방법1
$ cat prob3_revc/revc.py
import sys
#filename = sys.argv[1]
filename = 'seq.txt'
seq = open(filename).read().strip()
dic = {'A': 'T',
'T': 'A',
'C': 'G',
'G': 'C'}
new = ''
for s in seq:
new += dic[s]
print(new.upper()[::-1])
## 방법 2
$ cat revc/revc2.py
import sys
filename = sys.argv[1]
seq = open(filename).read().strip()
seq = seq[::-1] #reverse seq
seq = seq.replace("A", "t")
seq = seq.replace("C", "g")
seq = seq.replace("T", "a")
seq = seq.replace("G", "c")
print(seq.upper())
연습문제 4. Identifying Unknown DNA Quickly
#!python
$ cat seq.txt
>Rosalind_6404
CCTGCGGAAGATCGGCACTAGAATAGCCAGAACCGTTTCTCTGAGGCTTCCGGCCTTCCC
TCCCACTAATAATTCTGAGG
>Rosalind_5959
CCATCGGTAGCGCATCCTTAGTCCAATTAAGTCCCTATCCAGGCGCTCCGCCGAAGGTCT
ATATCCATTTGTCAGCAGACACGC
>Rosalind_0808
CCACCCTCGTGGTATGGCTAGGCATTCAGGAACCGGAGAACGCTTCAGACCAGCCCGGAC
TGGGAACCTGCGGGCAGTAGGTGGAAT
$ python gc.py seq.txt
Rosalind_0808
0.6091954022988506
풀이 4
#!python
import sys
filename = sys.argv[1]
seqs = open(filename).read().strip().split('>')
max_name = ""
max_gc = 0
for seq in seqs:
if not seq:
continue
name = seq.split('\n')[0]
seq = ''.join(seq.split('\n')[1:])
gc = (seq.count('G') + seq.count('C')) / len(seq) * 100
if max_gc < gc:
max_gc = gc
max_name = name
print(max_name)
print(max_gc)
연습문제 5. Counting Point Mutation
#!python
$ cat seq.txt
GAGCCTACTAACGGGAT
CATCGTAATGACGGCCT
$
$ python hamm.py seq.txt
7
풀이 5
#!python
import sys
filename = sys.argv[1]
seqs = open(filename).read().strip().split('\n')
#for seq in zip(*seqs):
count = 0
for seq1, seq2 in zip(seqs[0], seqs[1]):
if seq1 != seq2:
count += 1
print(count)
데이터 과학 개요 #
Jupyter & pandas #
참고정보
예제 데이터
- 인코고등학교 성적: 성적표.xlsx --> http://nbviewer.jupyter.org/gist/yong27/715c0ef9a09dd6eb37e9
- 대출현황: loan.csv
- 미국신생아 이름 분석: names.tgz --> http://nbviewer.jupyter.org/gist/yong27/966bd7c60a232add08c953e8519a95f1
- Allelic Differences Account for Gene Expression Differences Among Population: GSE5859, ethnicity.csv --> http://nbviewer.jupyter.org/gist/yong27/394cc70617977ee162993a78718f1eca
8월 26일 (3일차) #
Matplotlib #
예제 데이터 다운로드 한 후, DataFrame으로 읽어오기
#!python
import pandas as pd
data = pd.read_csv('weight.csv')
연습문제 1 국가별 샘플 수 bar plot으로 그리기 (figsize: 10x10)
#!python
import matplotlib.pyplot as plt
%matplotlib inline
data = pd.read_csv('weight.csv')
data['Country'].value_counts().plot(kind='bar', figsize=(10,10))
연습문제 2 성별 샘플 수 pie plot으로 그리기 (figsize: 10x10, colors: blue, red)
#!python
data['Sex'].value_counts().plot(
kind='pie’,
figsize=(10,10),
colors=['blue', 'red’]
)
연습문제 3 성별로 구분하여 체중으로 boxplot 그리기 (figsize: 20x10, color:gray)
#!python
data['Weight'].hist(
by=data['Sex'],
figsize=(20,10),
color=‘gray'
)
연습문제 4 국가별로 구분하여 체중으로 히스토그램 그리기 (range: 0~120, figzise:20x10, bins:20)
#!python
data['Weight'].hist(
bins=20, range=(0,120),
by=data['Country'],
figsize=(20,10)
)
연습문제 5 세 개의 서브플롯을 만들고 다음과 같이 구성
- 첫번째 서브플롯: 국가별 체중 boxplot
- 두번째 서브플롯: 체중 histogram (20 bins)
- 세번째 서브플롯: 성별 pie chart
- 서브플롯 간의 위아래 간격: 0.1
- figsize: 5,20
-
'ex5.png'로 저장
#!python import matplotlib.pyplot as plt %matplotlib inline data = pd.read_csv('weight.csv') # figure, subplot 생성 fig, axes = plt.subplots(3,1, figsize=(5,20), ) # 국가별 체충의 boxplot 그리기 data.boxplot(column='Weight', by='Country', ax=axes[0]) # 체중의 histogram 그리기 data['Weight'].hist(ax=axes[1], color='gray', bins=20, range=(0,120)) # 성별로 pie차트 그리기 data['Sex'].value_counts().plot(ax=axes[2], kind='pie') plt.subplots_adjust(hspace=0.5) #ex5.png에 저장 plt.savefig(‘ex5.png’)
유전변이 데이터(VCF) 분석 #
예제데이터
- 희귀 유전병 Pfeiffer syndrom 4가족 유전변이 데이터 Pfeiffer-quartet.vcf.gz --> http://nbviewer.jupyter.org/gist/yong27/1e18d3b07d4c7fe6b2366bfe642a3df3
- 1KG 22chr 5000개 변이 only_rs_22-5000.vcf.gz, 1KG_sample.xlsx --> http://nbviewer.jupyter.org/gist/yong27/6a9db652ed13f371cfdb3bdc4e546e82
KOBIC 서버 vcf 파일 경로
#!python
df = pd.read_table('/BiO/home/kobic/Pfeiffer-quartet.vcf', skiprows=134, dtype={'#CHRO<' : str})
df = pd.read_table('/BiO/home/kobic/only_rs_22-5000.vcf', skiprows=18)
명령행 인터페이스 #
FASTA manipulator 만들기 - 명령행 옵션을 받아 FASTA 형식 변환하기
$ python fastaman.py -c reverse-complement -f 60 -i in.fasta -o out.fasta
$ python fastaman.py -c transcribe -f 60 < in.fasta > out.fasta
정답
#!python
import sys
def transcribe(sequence):
return sequence.replace('T', 'U')
def reverse_complement(sequence):
base_pairs = {
'A': 'T',
'T': 'A',
'G': 'C',
'C': 'G',
}
result = []
for nucleotide in sequence:
result.append(base_pairs[nucleotide])
return ''.join(reversed(result))
def main(**kwargs):
command = kwargs['command']
infile = kwargs['infile']
outfile = kwargs['outfile']
columns = kwargs['columns']
title = next(infile).strip()
sequences = []
for line in infile:
sequences.append(line.strip())
sequence = ''.join(sequences)
if command == 'transcribe':
sequence = transcribe(sequence)
elif command == 'reverse-complement':
sequence = reverse_complement(sequence)
outfile.write('{} reverse complement\n'.format(title))
while sequence:
outfile.write('{}\n'.format(sequence[:columns]))
sequence = sequence[columns:]
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--command", required=True,
choices=('reverse-complement', 'transcribe'),
help='Select the type of command')
parser.add_argument('-f', '--columns', type=int, default=60,
help='Length of columns')
parser.add_argument('-i', '--infile', default=sys.stdin,
type=argparse.FileType('r'),
help='Intput fasta file name. default is standard in')
parser.add_argument('-o', '--outfile', default=sys.stdout,
type=argparse.FileType('w'),
help='Output file name. default is standard out')
args = parser.parse_args()
main(**vars(args))
연습문제 1. VCF 파일을 읽고 PCA 그림을 그리는 프로그램 작성
$ cat group.txt
GroupA: NA001 NA003 NA005
GroupB: NA008 NA098 NA099
$ python vcf_pca.py --vcf=a.vcf --group=group.txt --out=a.png
(a.png 그림 생성)
유틸리티 실습 #
연습문제 2. N-glycosylation motif는 N{P}[ST]{P}
이다. input.txt 에 uniprot_id들이 다음처럼 있을 때, 각각의 FASTA정보를 인터넷에서 획득한 뒤, 서열에 N-glycosylation motif가 어느 위치에 있는지 출력하는 프로그램을 작성하시오. (UniProt URL은 http://www.uniprot.org/uniprot/uniprot_id.fasta)
단백질 모티프는 보통 다음처럼 표기한다. (Prosite)
- []: 괄호내 있는것들 중 하나
- {}: 괄호내 있는 것들 빼고 나머지중 하나
- x: 아무거나 하나
실행 예는 다음과 같다.
#!python
$ cat input.txt
A2Z669
B5ZC00
P07204_TRBM_HUMAN
P20840_SAG1_YEAST
$ python uniprot-n-glycosylation-sites.py < input.txt
B5ZC00
85 118 142 306 395
P07204_TRBM_HUMAN
47 115 116 382 409
P20840_SAG1_YEAST
79 109 135 248 306 348 364 402 485 501 614
정답 2
import sys
import urllib.request
import re
filename = []
for line in sys.stdin:
line = re.sub(r'\n', '', line)
filename.append(line)
for name in filename:
data = urllib.request.urlopen('http://www.uniprot.org/uniprot/'+name+'.fasta')
title = next(data).strip()
sequence = ''
for line in data:
sequence = sequence+str(line)
fasta = re.sub(r"'|b|\\n", '', sequence)
pattern = re.compile(r'N[^P][ST][^P]') #reg exp
motif = []
m = re.finditer(pattern, fasta)
for match in m:
motif.append(match.start()) # when it prints, add 1 b/c it counts from 0
if motif:
print(name)
location = []
for loc in motif:
loc += 1
location.append(loc)
print(*location)
else:
pass
연습문제 3. 로또 (6/45) 당첨 번호가 모두 홀수일 확률은? (직접 세어서 계산해보기)
#!python
>>> import itertools
>>> range(1, 46)
>>> itertools.combinations
정답 3
#!python
>>> i = 0
>>> j = 0
>>> for lotto in itertools.combinations(range(1, 46), 6):
... if all(n % 2 for n in lotto):
... j += 1
... i += 1
...
>>> j / i
0.012393647192285877
>>>
연습문제 4. Boys 4명과 girls 3명이 소개팅중이다. 적어도 3커플이 나올 수 있는 가지수는?
#!python
>>> import itertools
>>> boys = ["smith", 'neo', 'morphius', 'cyper']
>>> girls = ['trinity', 'oracle', 'suji']
>>> itertools.permutations
정답 4
#!python
>>> for selected_boys in itertools.permutations(boys, 3):
... print(list(zip(girs, selected_boys)))
...
[('trinity', 'smith'), ('oracle', 'neo'), ('suji', 'morphius')]
[('trinity', 'smith'), ('oracle', 'neo'), ('suji', 'cyper')]
[('trinity', 'smith'), ('oracle', 'morphius'), ('suji', 'neo')]
[('trinity', 'smith'), ('oracle', 'morphius'), ('suji', 'cyper')]
[('trinity', 'smith'), ('oracle', 'cyper'), ('suji', 'neo')]
[('trinity', 'smith'), ('oracle', 'cyper'), ('suji', 'morphius')]
[('trinity', 'neo'), ('oracle', 'smith'), ('suji', 'morphius')]
[('trinity', 'neo'), ('oracle', 'smith'), ('suji', 'cyper')]
[('trinity', 'neo'), ('oracle', 'morphius'), ('suji', 'smith')]
[('trinity', 'neo'), ('oracle', 'morphius'), ('suji', 'cyper')]
[('trinity', 'neo'), ('oracle', 'cyper'), ('suji', 'smith')]
[('trinity', 'neo'), ('oracle', 'cyper'), ('suji', 'morphius')]
[('trinity', 'morphius'), ('oracle', 'smith'), ('suji', 'neo')]
[('trinity', 'morphius'), ('oracle', 'smith'), ('suji', 'cyper')]
[('trinity', 'morphius'), ('oracle', 'neo'), ('suji', 'smith')]
[('trinity', 'morphius'), ('oracle', 'neo'), ('suji', 'cyper')]
[('trinity', 'morphius'), ('oracle', 'cyper'), ('suji', 'smith')]
[('trinity', 'morphius'), ('oracle', 'cyper'), ('suji', 'neo')]
[('trinity', 'cyper'), ('oracle', 'smith'), ('suji', 'neo')]
[('trinity', 'cyper'), ('oracle', 'smith'), ('suji', 'morphius')]
[('trinity', 'cyper'), ('oracle', 'neo'), ('suji', 'smith')]
[('trinity', 'cyper'), ('oracle', 'neo'), ('suji', 'morphius')]
[('trinity', 'cyper'), ('oracle', 'morphius'), ('suji', 'smith')]
[('trinity', 'cyper'), ('oracle', 'morphius'), ('suji', 'neo')]