-
Notifications
You must be signed in to change notification settings - Fork 0
Review GAGE5 #42
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 GAGE5 #42
Conversation
alibibio
left a comment
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.
В целом все хорошо, молодец!
Есть пара замечаний в комментариях. А также желательно сделать обработку исключений.
В задании "Biological sequences world". Не обработан крайний случай: длина последовательностей 0.
| elif not output_filename.endswith('.fastq'): | ||
| output_filename += '.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.
супер
| filtered_seqs.append(record) | ||
|
|
||
| if output_filename is None: | ||
| output_filename = f"filtered_{input_path}" |
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.
Если в переменной input_path будет путь с использованием / или :, то такое имя файла будет невалидным.
| def complement(self): | ||
| """Return the complement sequence.""" | ||
| comp_map_dna = {"A": "T", "G": "C", "T": "A", "C": "G", "a": "t", "t": "a", "g": "c", "c": "g"} | ||
| return ''.join(comp_map_dna.get(base, base) for base in self.sequence) |
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.
Если будет буква "B", которой нет в словаре, то она перепишется в последовательность, что нарушает биологический смысл.
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.
Верно подмечено. Здорово тогда было бы добавить что это решается использованием не get а [...]. В целом через get я очень редко встречал чтобы делали
| def transcribe(self): | ||
| """Transcribe the DNA sequence into an RNA sequence.""" | ||
| transcribed_seq = ''.join(self.TRANSCRIBE_DICT.get(base, base) for base in self.sequence) | ||
| return transcribed_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.
сначала требует вызова complement(), а затем замену по словарю TRANSCRIBE_DICT
|
|
||
| def check_alphabet(self): | ||
| """Check if the sequence contains valid amino acid alphabet characters.""" | ||
| if not set(self.sequence).issubset(NucleicAcidSequence.AMINO_ACID_LETTERS): |
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 лучше вынести из класса NucleicAcidSequence
Olga-Bagrova
left a comment
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("\u2764\uFE0F")
| from Bio.SeqUtils import GC | ||
| from abc import ABC, abstractmethod | ||
|
|
||
| def filter_fastq(input_path, gc_bounds=(0, 100), length_bounds=(0, float('inf')), quality_threshold=0, output_filename=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.
Переменные могут загрустить, что не знают свои типы здесь (но они точно есть в докстринге и это хорошо):
| def filter_fastq(input_path, gc_bounds=(0, 100), length_bounds=(0, float('inf')), quality_threshold=0, output_filename=None): | |
| def filter_fastq(input_path: str, gc_bounds: tuple = (0, 100), length_bounds: tuple = (0, float('inf')), quality_threshold: float = 0, output_filename: str = None)->str: |
| """ | ||
| Filters a FASTQ file based on GC content, sequence length, and quality threshold using Biopython. | ||
|
|
||
| Args: | ||
| - input_path (str): Path to the input FASTQ file. | ||
| - gc_bounds (tuple): Tuple specifying the minimum and maximum GC content for filtering. Default is (0, 100). | ||
| - length_bounds (tuple): Tuple specifying the minimum and maximum sequence length for filtering. Default is (0, infinity). | ||
| - quality_threshold (float): Minimum quality score for filtering. Default is 0. | ||
| - output_filename (str): Name of the output file. If None, the default filename will be used. | ||
|
|
||
| Returns: | ||
| - str: Message indicating the success of the filtering process. | ||
| """ |
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 gc_content(self): | ||
| """Return the GC content of the sequence.""" | ||
| gc_count = (self.sequence.upper().count('G') + self.sequence.upper().count('C')) / len(self.sequence) * 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.
Учтено, что могут быть заглавные и прописные буквы. Класс!
| """Initialize a DNASequence object with a given sequence.""" | ||
| super().__init__(sequence) | ||
| if not self.check_alphabet(): | ||
| raise ValueError("Invalid DNA sequence") |
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('AUTCWT'), то тоже пройдёт проверку на алфавит, хотя тут есть и U (РНК), и T (ДНК), и W (Белок).
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.
Верно подмечено
|
|
||
| DNA_LETTERS = set("ATGCatgc") | ||
| RNA_LETTERS = set("AUGCaugc") | ||
| AMINO_ACID_LETTERS = set("ACDEFGHIKLMNPQRSTVWY") |
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 complement(self): | ||
| """Return the complement sequence.""" | ||
| comp_map_dna = {"A": "T", "G": "C", "T": "A", "C": "G", "a": "t", "t": "a", "g": "c", "c": "g"} | ||
| return ''.join(comp_map_dna.get(base, base) for base in self.sequence) |
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.
Подкину к комментарию про метод get для словарей возможную альтернативу. Хотя конечно в дочерних классах и так идёт проверка алфавита при инициализации - это продуманно).
| return ''.join(comp_map_dna.get(base, base) for base in self.sequence) | |
| return ''.join([comp_map_dna[base] for base in self.sequence]) |
| total_length = len(self.sequence) | ||
| for group, count in profile.items(): | ||
| profile[group] = round((count / total_length), 2) | ||
| return profile |
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 check_alphabet(self): | ||
| """Check if the sequence contains valid amino acid alphabet characters.""" | ||
| if not set(self.sequence).issubset(NucleicAcidSequence.AMINO_ACID_LETTERS): | ||
| raise ValueError("Invalid amino acid sequence") |
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.
Если просто запустить AminoAcidSequence('QWERTY').check_alphabet(), то ничего не вернёт. Для ДНК и РНК было классно сделано с return'ом булевого значения.
Cucumberan
left a comment
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 reverse(self): | ||
| """Return the reverse of the RNA sequence.""" | ||
| return RNASequence(self.sequence[::-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.
Здесь также, как и в предыдущих случаях, можно возвращать просто развернутую последовательность, а не создавать новый экземпляр класса
| return RNASequence(self.sequence[::-1]) | |
| return self.sequence[::-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.
Ну не совсем.
У нас был объект сиквенс. Мы его развернули. Почему в ходе разворота тип данных изменила и последовательность превратилась в строку? Хотя тут проблема с тем что захардкожено имя класса, лучше через type(self)
|
|
||
| def check_alphabet(self): | ||
| """Check if the sequence contains valid nucleic- or aminoacid alphabet characters.""" | ||
| return set(self.sequence).issubset(self.DNA_LETTERS | self.RNA_LETTERS | self.AMINO_ACID_LETTERS) |
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.
Мне кажется, что проверку на принадлежность к аминокислотной последовательности можно было бы убрать из класса NucleicAcidSequence и оставить только в классе AminoAcidSequence.
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.
check_alphabet должа быть у всех из них
но про наборы букв - справедливо. Лучше иметь просто одну переменную - классовый атрибут alphabet с которым мы будем сравивать, но у разных классов он будет разный
| def transcribe(self): | ||
| """Transcribe the DNA sequence into an RNA sequence.""" | ||
| transcribed_seq = ''.join(self.TRANSCRIBE_DICT.get(base, base) for base in self.sequence) | ||
| return transcribed_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.
Здесь в методе transcribe возвращается просто строка transcribed_seq, а ниже в классе RNASequence при вызове метода reverse возращается новый объект класса RNASequence. Можно было бы для достижения единообразия и там и там возращать строки, либо чтобы после вызова метода transcribe создавался объект класса RNASequence.
| return transcribed_seq | |
| return RNASequence(transcribed_seq) | |
Ахахах |
Review GAGE5