Conversation
NSapozhnikov
left a comment
There was a problem hiding this comment.
Все здорово, очень чисто и аккуратно. Мне понравилось!
Из нового смог лишь предложить пару "оптимизаций", возможно, не вполне уместных, но это скорее, чтобы показать, как можно еще нетривиально :)
| 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.
Можно еще вот так, и когда сравнение больше чем из 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.
функция 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.
Ну вот мы только сейчас прошли map и lambda :)
Но да, так более функционально
| gc_result = (self.seq.count('C') + self.seq.count('G')) / len(self.seq) * 100 | ||
| return round(gc_result, 3) |
| output_seq = self.seq.replace('T', 'U').replace('t', 'u') | ||
| return RNASequence(output_seq) |
| if not super().check_seq_type('Protein'): | ||
| raise ValueError(f'Sequence {self.seq} is not protein') |
There was a problem hiding this comment.
как будто немного странно делать повторные проверки в дочерних классах, ведь в родительском классе уже была проверка. получается, если последовательность прошла первую проверку (что это ДНК, РНК или протеин), то дальше все должно работать, а тут вдруг она дальше неожиданно решит упасть, так как оказалось, что ДНК != РНК
| 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.
о, не знал, что можно открывать через запятую в одном блоке with
Olga-Bagrova
left a comment
There was a problem hiding this comment.
Приятно было смотреть на код. В докстрингах всё аккуратно описано. Немного оставила своих предложений по коду, но они не критические. В некоторых местах показалось, что код избыточен. Но всё запускается и работает правильно. Удачи!
Предлагаю поразвлекаться, запустив это: 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.
Аннотация типов есть - класс! Понравилось, что тут аккуратно всё оформленно.
| if not os.path.isdir(output_data_dir): | ||
| os.mkdir(output_data_dir) |
There was a problem hiding this comment.
Круто, что отсутствие папки продуманно
| if type(length_bounds) != tuple: | ||
| length_bounds = tuple([0, length_bounds]) |
There was a problem hiding this comment.
Проверку типов можно было вынести из цикла for в тело функции filter_fastq, чтобы сразу один раз проверить и подправить всё. На времени работы, наверно, не сильно сказывается.
| out = 1 | ||
|
|
||
| if out == 0: | ||
| file.write(lin.format("fastq")) |
There was a problem hiding this comment.
Оригинально сделано с переменной 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.
Если занудствовать, то можно было унифицировать и сделать всё через 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.
Строго говоря, в алфавите есть и прописные буквы. И в данном случае маленькие 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.
Выглядит лаконично. «Краткость – сестра таланта».
| 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) |
| 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.
тоже не знала про 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.
Возможно, можно было оставить проверку типов только в конкретных классах (отдельно ДНК, РНК), чтобы не было повторностей.
artyomtorr
left a comment
There was a problem hiding this comment.
Очень хорошая работа! Могу похвалить за интересную реализацию 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.
Будет совсем замечательно, если все эти глобальные переменные превратить в полиморфные атрибуты классов 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}' |
|
|
||
| def __init__(self, seq): | ||
| self.seq = seq | ||
| self.seq_type = None |
There was a problem hiding this comment.
В целом... создание объектов BiologicalSequence (как и NucleicAcidSequence), на мой взгляд, не предполагается, а для объектов DNASequence, RNASequence и AminoAcidSequence название класса говорит само за себя.
| self.seq_type = None |
| } | ||
|
|
||
|
|
||
| class BiologicalSequence(str): |
There was a problem hiding this comment.
Абстрактные классы обычно наследуются от базового класса 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) |
| 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.
Так мы сможем возвращать объект того же класса, который принимаем на вход:
| 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.
присоединяюсь к комментариям, очень продуманно!
| record = SeqIO.parse(handle, "fastq") | ||
| for lin in record: |
There was a problem hiding this comment.
Скорее придирка, но такие названия кажутся более логичными
| record = SeqIO.parse(handle, "fastq") | |
| for lin in record: | |
| records = SeqIO.parse(handle, "fastq") | |
| for record in records: |
Review TOMM20