Skip to content

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Review TOMM20 #31

wants to merge 2 commits into from

Conversation

nvaulin
Copy link
Member

@nvaulin nvaulin commented Feb 26, 2024

Review TOMM20

Copy link

@NSapozhnikov NSapozhnikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Все здорово, очень чисто и аккуратно. Мне понравилось!
Из нового смог лишь предложить пару "оптимизаций", возможно, не вполне уместных, но это скорее, чтобы показать, как можно еще нетривиально :)

Comment on lines +90 to +97
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!')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно еще вот так, и когда сравнение больше чем из 2х случаев, работает чуть быстрее

Suggested change
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!')

Comment on lines +127 to +129
for nucleotide in self.seq:
output_seq.append(self.complement_alphabet[nucleotide])
return NucleicAcidSequence(''.join(output_seq))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

функция map() должна работать чуть побыстрее

Suggested change
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)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну вот мы только сейчас прошли map и lambda :)
Но да, так более функционально

Comment on lines +137 to +138
gc_result = (self.seq.count('C') + self.seq.count('G')) / len(self.seq) * 100
return round(gc_result, 3)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

классно!

Comment on lines +164 to +165
output_seq = self.seq.replace('T', 'U').replace('t', 'u')
return RNASequence(output_seq)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

огонь

Comment on lines +204 to +205
if not super().check_seq_type('Protein'):
raise ValueError(f'Sequence {self.seq} is not protein')

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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

о, не знал, что можно открывать через запятую в одном блоке with

Copy link

@Olga-Bagrova Olga-Bagrova left a 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)])))

Comment on lines +220 to +224
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'):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аннотация типов есть - класс! Понравилось, что тут аккуратно всё оформленно.

Comment on lines +234 to +235
if not os.path.isdir(output_data_dir):
os.mkdir(output_data_dir)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Круто, что отсутствие папки продуманно

Comment on lines +245 to +246
if type(length_bounds) != tuple:
length_bounds = tuple([0, length_bounds])

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"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оригинально сделано с переменной out. Даже, считай, дискретка использовалась, чтобы с логическими выражениями поработать)

Comment on lines +5 to +7
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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если занудствовать, то можно было унифицировать и сделать всё через set.
Но это конечно "Откройте окно! Слишком душно"

Suggested change
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Строго говоря, в алфавите есть и прописные буквы. И в данном случае маленькие c и g не посчитаются (но у меня, если честно, также было).

Suggested change
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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Выглядит лаконично. «Краткость – сестра таланта».

Comment on lines +207 to +218
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)

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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тоже не знала про with с запятыми)

Comment on lines +112 to +114
if super().check_seq_type('DNA'):
self.complement_alphabet = COMPLEMENT_ALPHABET_DNA
elif super().check_seq_type('RNA'):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно, можно было оставить проверку типов только в конкретных классах (отдельно ДНК, РНК), чтобы не было повторностей.

Copy link

@artyomtorr artyomtorr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Очень хорошая работа! Могу похвалить за интересную реализацию fastq-фильтратора, подробную аннотацию типов и докстринги, обилие методов в классах биологических последовательностей.
Из идей для оптимизации прежде всего хочу предложить более активное использование полиморфных методов и атрибутов, что поможет избавиться от дублирования в дочерних классах.

Comment on lines +5 to +11
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'}

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}'

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

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 название класса говорит само за себя.

Suggested change
self.seq_type = None

}


class BiologicalSequence(str):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Абстрактные классы обычно наследуются от базового класса ABC из модуля abc. Но конкретно в этом случае, наследование от str выглядит вполне логичным :)

Suggested change
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)

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))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так мы сможем возвращать объект того же класса, который принимаем на вход:

Suggested change
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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

присоединяюсь к комментариям, очень продуманно!

Comment on lines +240 to +241
record = SeqIO.parse(handle, "fastq")
for lin in record:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Скорее придирка, но такие названия кажутся более логичными

Suggested change
record = SeqIO.parse(handle, "fastq")
for lin in record:
records = SeqIO.parse(handle, "fastq")
for record in records:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants