-
Notifications
You must be signed in to change notification settings - Fork 0
Review TOMM20 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Review TOMM20 #31
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Все здорово, очень чисто и аккуратно. Мне понравилось!
Из нового смог лишь предложить пару "оптимизаций", возможно, не вполне уместных, но это скорее, чтобы показать, как можно еще нетривиально :)
if self.check_seq_type('DNA'): | ||
self.seq_type = 'DNA' | ||
elif self.check_seq_type('RNA'): | ||
self.seq_type = 'RNA' | ||
elif self.check_seq_type('Protein'): | ||
self.seq_type = 'Protein' | ||
else: | ||
raise ValueError(f'Sequence {self.seq} can not be analysed!') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно еще вот так, и когда сравнение больше чем из 2х случаев, работает чуть быстрее
if self.check_seq_type('DNA'): | |
self.seq_type = 'DNA' | |
elif self.check_seq_type('RNA'): | |
self.seq_type = 'RNA' | |
elif self.check_seq_type('Protein'): | |
self.seq_type = 'Protein' | |
else: | |
raise ValueError(f'Sequence {self.seq} can not be analysed!') | |
match True: | |
case self.check_seq_type('DNA'): | |
self.seq_type = 'DNA' | |
case self.check_seq_type('RNA'): | |
self.seq_type = 'RNA' | |
case self.check_seq_type('Protein'): | |
self.seq_type = 'Protein' | |
case _: | |
raise ValueError(f'Sequence {self.seq} can not be analysed!') |
for nucleotide in self.seq: | ||
output_seq.append(self.complement_alphabet[nucleotide]) | ||
return NucleicAcidSequence(''.join(output_seq)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
функция map() должна работать чуть побыстрее
for nucleotide in self.seq: | |
output_seq.append(self.complement_alphabet[nucleotide]) | |
return NucleicAcidSequence(''.join(output_seq)) | |
output_seq = ''.join(map(lambda nucleotide: self.complement_alphabet[nucleotide], self.seq)) | |
return NucleicAcidSequence(output_seq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ну вот мы только сейчас прошли map и lambda :)
Но да, так более функционально
gc_result = (self.seq.count('C') + self.seq.count('G')) / len(self.seq) * 100 | ||
return round(gc_result, 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
классно!
output_seq = self.seq.replace('T', 'U').replace('t', 'u') | ||
return RNASequence(output_seq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
огонь
if not super().check_seq_type('Protein'): | ||
raise ValueError(f'Sequence {self.seq} is not protein') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
как будто немного странно делать повторные проверки в дочерних классах, ведь в родительском классе уже была проверка. получается, если последовательность прошла первую проверку (что это ДНК, РНК или протеин), то дальше все должно работать, а тут вдруг она дальше неожиданно решит упасть, так как оказалось, что ДНК != РНК
if output_filename is None: | ||
output_filename = 'filtered_fastq' | ||
|
||
with open(input_path) as handle, open(os.path.join(output_data_dir, output_filename), mode='w') as file: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
о, не знал, что можно открывать через запятую в одном блоке with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Приятно было смотреть на код. В докстрингах всё аккуратно описано. Немного оставила своих предложений по коду, но они не критические. В некоторых местах показалось, что код избыточен. Но всё запускается и работает правильно. Удачи!
Предлагаю поразвлекаться, запустив это: print('\n'.join(' '.join(*zip(*row)) for row in ([["*" if row==0 and col%3!=0 or row==1 and col%3==0 or row-col==2 or row+col==8 else " " for col in range(7)] for row in range(6)])))
def filter_fastq(input_path: str, gc_bounds: tuple or int = (0, 100), | ||
length_bounds: tuple or int = (0, 2 ** 32), | ||
quality_threshold: int = 0, | ||
output_filename=None, | ||
output_data_dir: str = 'filter_fastq_results'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аннотация типов есть - класс! Понравилось, что тут аккуратно всё оформленно.
if not os.path.isdir(output_data_dir): | ||
os.mkdir(output_data_dir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Круто, что отсутствие папки продуманно
if type(length_bounds) != tuple: | ||
length_bounds = tuple([0, length_bounds]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Проверку типов можно было вынести из цикла for в тело функции filter_fastq
, чтобы сразу один раз проверить и подправить всё. На времени работы, наверно, не сильно сказывается.
out = 1 | ||
|
||
if out == 0: | ||
file.write(lin.format("fastq")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Оригинально сделано с переменной out
. Даже, считай, дискретка использовалась, чтобы с логическими выражениями поработать)
ALPHABET_FOR_DNA = {'A', 'T', 'G', 'C', 'a', 't', 'g', 'c'} | ||
ALPHABET_FOR_RNA = {'A', 'U', 'G', 'C', 'a', 'u', 'g', 'c'} | ||
ALPHABET_FOR_PROTEIN = set('FLIMVSPTAYHQNKDECWRG') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если занудствовать, то можно было унифицировать и сделать всё через set.
Но это конечно "Откройте окно! Слишком душно"
ALPHABET_FOR_DNA = {'A', 'T', 'G', 'C', 'a', 't', 'g', 'c'} | |
ALPHABET_FOR_RNA = {'A', 'U', 'G', 'C', 'a', 'u', 'g', 'c'} | |
ALPHABET_FOR_PROTEIN = set('FLIMVSPTAYHQNKDECWRG') | |
ALPHABET_FOR_DNA = set('ATGCatgc') | |
ALPHABET_FOR_RNA = set('AUGCaugc') | |
ALPHABET_FOR_PROTEIN = set('FLIMVSPTAYHQNKDECWRG') |
Return: | ||
- sequence gc content, % | ||
""" | ||
gc_result = (self.seq.count('C') + self.seq.count('G')) / len(self.seq) * 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Строго говоря, в алфавите есть и прописные буквы. И в данном случае маленькие c и g не посчитаются (но у меня, если честно, также было).
gc_result = (self.seq.count('C') + self.seq.count('G')) / len(self.seq) * 100 | |
gc_result = (self.seq.upper().count('C') + self.upper().seq.count('G')) / len(self.seq) * 100 |
return: | ||
- RNA sequence | ||
""" | ||
output_seq = self.seq.replace('T', 'U').replace('t', 'u') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Выглядит лаконично. «Краткость – сестра таланта».
def counting_molecular_weight(self): | ||
""" | ||
Counts the molecular mass of a protein sequence seq | ||
Arguments: | ||
- seq (str): sequence to count the molecular weight | ||
Return: | ||
- output (int): molecular weight value | ||
""" | ||
output = 0 | ||
for amino_acid in self.seq: | ||
output += DICT_MOLECULAR_MASS[amino_acid] | ||
return output - 18 * (len(self.seq) - 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Классный метод!
if output_filename is None: | ||
output_filename = 'filtered_fastq' | ||
|
||
with open(input_path) as handle, open(os.path.join(output_data_dir, output_filename), mode='w') as file: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
тоже не знала про with с запятыми)
if super().check_seq_type('DNA'): | ||
self.complement_alphabet = COMPLEMENT_ALPHABET_DNA | ||
elif super().check_seq_type('RNA'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Возможно, можно было оставить проверку типов только в конкретных классах (отдельно ДНК, РНК), чтобы не было повторностей.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Очень хорошая работа! Могу похвалить за интересную реализацию fastq-фильтратора, подробную аннотацию типов и докстринги, обилие методов в классах биологических последовательностей.
Из идей для оптимизации прежде всего хочу предложить более активное использование полиморфных методов и атрибутов, что поможет избавиться от дублирования в дочерних классах.
ALPHABET_FOR_DNA = {'A', 'T', 'G', 'C', 'a', 't', 'g', 'c'} | ||
ALPHABET_FOR_RNA = {'A', 'U', 'G', 'C', 'a', 'u', 'g', 'c'} | ||
ALPHABET_FOR_PROTEIN = set('FLIMVSPTAYHQNKDECWRG') | ||
COMPLEMENT_ALPHABET_DNA = {'A': 'T', 'T': 'A', 'G': 'C', 'C': 'G', | ||
'a': 't', 't': 'a', 'g': 'c', 'c': 'g'} | ||
COMPLEMENT_ALPHABET_RNA = {'U': 'A', 'A': 'U', 'G': 'C', 'C': 'G', | ||
'u': 'a', 'a': 'u', 'g': 'c', 'c': 'g'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Будет совсем замечательно, если все эти глобальные переменные превратить в полиморфные атрибуты классов DNASequence и RNASequence. Тогда бы они имели одинаковые названия (например, ALPHABET
и COMPLEMENT_ALPHABET
без всяких приставок), но разное содержание в зависимости от класса.
Благодаря этому, в функции check_seq_type
можно было бы обойтись без проверки типа последовательности
return self.seq[start: end] | ||
|
||
def __repr__(self): | ||
return f'The sequence is: {self.seq}, type is {self.seq_type}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
су-пер!
|
||
def __init__(self, seq): | ||
self.seq = seq | ||
self.seq_type = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В целом... создание объектов BiologicalSequence (как и NucleicAcidSequence), на мой взгляд, не предполагается, а для объектов DNASequence, RNASequence и AminoAcidSequence название класса говорит само за себя.
self.seq_type = None |
} | ||
|
||
|
||
class BiologicalSequence(str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Абстрактные классы обычно наследуются от базового класса ABC из модуля abc. Но конкретно в этом случае, наследование от str
выглядит вполне логичным :)
class BiologicalSequence(str): | |
from abc import ABC | |
class BiologicalSequence(ABC): |
- RNA sequence | ||
""" | ||
output_seq = self.seq.replace('T', 'U').replace('t', 'u') | ||
return RNASequence(output_seq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
output_seq = [] | ||
for nucleotide in self.seq: | ||
output_seq.append(self.complement_alphabet[nucleotide]) | ||
return NucleicAcidSequence(''.join(output_seq)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Так мы сможем возвращать объект того же класса, который принимаем на вход:
return NucleicAcidSequence(''.join(output_seq)) | |
return type(self)(''.join(output_seq)) |
Опять же, если считаем, что класс NucleicAcidSequence мы используем для наследования, а не для создания объектов
if output_filename is None: | ||
output_filename = 'filtered_fastq' | ||
|
||
with open(input_path) as handle, open(os.path.join(output_data_dir, output_filename), mode='w') as file: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
присоединяюсь к комментариям, очень продуманно!
record = SeqIO.parse(handle, "fastq") | ||
for lin in record: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Скорее придирка, но такие названия кажутся более логичными
record = SeqIO.parse(handle, "fastq") | |
for lin in record: | |
records = SeqIO.parse(handle, "fastq") | |
for record in records: |
Review TOMM20