Skip to content

Conversation

nvaulin
Copy link
Member

@nvaulin nvaulin commented Feb 26, 2024

Review RGL1

Copy link

@nerofeeva2001 nerofeeva2001 left a comment

Choose a reason for hiding this comment

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

Хороший код, есть несколько недочетов, но это некритично)

from typing import Union
from Bio import SeqIO, SeqUtils
from abc import ABC, abstractmethod

Choose a reason for hiding this comment

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

Здорово, что используется Union, это упрощает понимание кода

if (quality_threshold < sum(record.letter_annotations['phred_quality']) /
len(record.letter_annotations['phred_quality'])):
return record

Choose a reason for hiding this comment

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

Все три функции выше возвращают record или None. Это подход работает, но его можно упростить, изменив функции так, чтобы они возвращали True или False, что будет более понятно для проверки условий


def filter_fastq(file_path: str, output_file: str = 'input_fasta_name_filter.fastq',
lower_gc_bound: int = 0, upper_gc_bound: int = 30,
lower_length_bound: int = 0, upper_length_bound: int = 2**32,

Choose a reason for hiding this comment

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

Тут тоже можно было написать с помощью Union

gc_filter(record, lower_gc_bound, upper_gc_bound) and \
quality_filter(record, quality_threshold):
SeqIO.write(record, output, 'fastq')

Choose a reason for hiding this comment

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

Повторная запись в файл для каждой подходящей записи через SeqIO.write(record, output, 'fastq') в цикле неэффективна, так как каждая запись обрабатывается отдельно. Лучше собрать подходящие записи в список, а затем записать их одним вызовом SeqIO.write(filtered_records, output, 'fastq')

Copy link
Member Author

Choose a reason for hiding this comment

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

В данном случае есть 2 варианта

  1. Если читаем все разом, то и записывать лучше все разом, да
  2. Но можно обрабатывать данные "на лету" - прочитали одну запись и если надо - записали. В таком случае просто лучше держать все время файлы открытыми (делать все внутри блока with open). Так мне даже чуть больше нравится

gc_filter(record, lower_gc_bound, upper_gc_bound) and \
quality_filter(record, quality_threshold):
SeqIO.write(record, output, 'fastq')

Choose a reason for hiding this comment

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

Нет проверки корректности диапазонов для lower_gc_bound, upper_gc_bound, lower_length_bound, upper_length_bound и quality_threshold


class SequenceFunction(BiologicalSequence):
alphabet = ()

Choose a reason for hiding this comment

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

Стоило бы настроить алфавит

return self.seq[item]
else:
raise IndexError('Your index is incorrect')

Choose a reason for hiding this comment

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

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

Suggested change
def __getitem__(self, item: int) -> str:
return self.seq[item]

Copy link
Member Author

Choose a reason for hiding this comment

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

Верно

def gc_content(self) -> Union[int, float]:
gc_count = self.seq.count('C') + self.seq.count('G')
return gc_count/len(self.seq)*100

Choose a reason for hiding this comment

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

Аннотация типа возвращает Union[int, float], но на практике это всегда будет float

Suggested change
def gc_content(self) -> float:

def check_alphabet(self):
return super().check_alphabet()

def count_molecular_weight(self, amino_acid_weights: dict) -> int:

Choose a reason for hiding this comment

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

В аннотации указано, что метод count_molecular_weight возвращает int, но на самом деле результатом будет float

return super().check_alphabet()

def count_molecular_weight(self, amino_acid_weights: dict) -> int:
molecular_weight = sum(self.amino_acid_weights.get(aa, 0) for aa in self.seq)

Choose a reason for hiding this comment

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

Метод count_molecular_weight принимает параметр amino_acid_weights, который не используется. Вместо этого, используется атрибут класса

Choose a reason for hiding this comment

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

Suggested change
molecular_weight = sum(self.amino_acid_weights.get(aa, 0) for aa in self.seq)
def count_molecular_weight(self) -> int:
molecular_weight = sum(self.amino_acid_weights.get(aa, 0) for aa in self.seq)
return molecular_weight

Copy link

@LinaWhite15 LinaWhite15 left a comment

Choose a reason for hiding this comment

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

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


def length_filter(record, lower_length_bound: int, upper_length_bound: int):
if lower_length_bound <= len(record.seq) <= upper_length_bound:
return record

Choose a reason for hiding this comment

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

Немного избыточно, можно либо True, либо False

def filter_fastq(file_path: str, output_file: str = 'input_fasta_name_filter.fastq',
lower_gc_bound: int = 0, upper_gc_bound: int = 30,
lower_length_bound: int = 0, upper_length_bound: int = 2**32,
quality_threshold: Union[int, float] = 0) -> None:

Choose a reason for hiding this comment

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

Хорошее использование модулей

SeqIO.write(record, output, 'fastq')


class BiologicalSequence(ABC):

Choose a reason for hiding this comment

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

Реализованы все необходимые абстрактные методы

pass


class SequenceFunction(BiologicalSequence):

Choose a reason for hiding this comment

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

Крутое решение с введением вспомогательного класса

self.length = len(self.seq)
return self.length

def __getitem__(self, item: int) -> str:

Choose a reason for hiding this comment

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

Работающее решение, но было бы здорово, если бы пользователь мог получить не только отдельный элемент, но целый срез

super().__init__(seq)

def complement(self) -> str:
complement_seq = self.seq.translate(str.maketrans(self.complement_dict))

Choose a reason for hiding this comment

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

Интересное решение!



class DNASequence(NucleicAcidSequence):
alphabet = ('A', 'T', 'G', 'C')

Choose a reason for hiding this comment

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

Регистрозависимость, на вход будут приниматься только записи большими буквами, возможно, стоит добавить upper в check_alphabet

super().__init__(seq)

def check_alphabet(self):
return super().check_alphabet()

Choose a reason for hiding this comment

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

Опять же, возможно, стоит добавить переведение последовательности в верхний регистр

Copy link

@sofiyaga57 sofiyaga57 left a comment

Choose a reason for hiding this comment

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

Очень классная работа! Было понятно и удобно читать код. Возможно, для человека, который впервые столкнулся бы с модулем, без докстринги было бы сложно понять, что происходит. Есть классные решения, взяла на заметку:)

@@ -0,0 +1,142 @@
from typing import Union

Choose a reason for hiding this comment

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

Классно, что есть Union! Мне самое порой лениво его использовать, так что респект

from abc import ABC, abstractmethod


def length_filter(record, lower_length_bound: int, upper_length_bound: int):

Choose a reason for hiding this comment

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

Немножко не хватило аннотации, как-то было не очевидно сразу, что он может выдавать и record, и None

def filter_fastq(file_path: str, output_file: str = 'input_fasta_name_filter.fastq',
lower_gc_bound: int = 0, upper_gc_bound: int = 30,
lower_length_bound: int = 0, upper_length_bound: int = 2**32,
quality_threshold: Union[int, float] = 0) -> None:

Choose a reason for hiding this comment

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

Like!

Comment on lines +66 to +70
def __getitem__(self, item: int) -> str:
if 0 <= item < len(self.seq):
return self.seq[item]
else:
raise IndexError('Your index is incorrect')

Choose a reason for hiding this comment

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

Немножко странно, что ошибка по индексу будет вылезать при отрицательных значениях индекса: это же ок для питона, мы можем обращаться к [-1] и тд

return complement_seq

def gc_content(self) -> Union[int, float]:
gc_count = self.seq.count('C') + self.seq.count('G')

Choose a reason for hiding this comment

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

Suggested change
gc_count = self.seq.count('C') + self.seq.count('G')
gc_count = sum(1 for base in self.seq if base in 'GC')

Можно попробовать так, как разбирали недавно на паре. Мне кажется, так будет более эффективно по скорости, потому что мы считаем один раз, а не два.

Comment on lines +23 to +24
lower_gc_bound: int = 0, upper_gc_bound: int = 30,
lower_length_bound: int = 0, upper_length_bound: int = 2**32,

Choose a reason for hiding this comment

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

Возможно, стоило бы вынести эти параметры как константы вне функции

Comment on lines +94 to +95
alphabet = ('A', 'T', 'G', 'C')
complement_dict = {'A': 'T', 'T': 'A', 'G': 'C', 'C': 'G'}

Choose a reason for hiding this comment

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

Я бы добавила строчные буквы или тогда делала бы upper для входной строки

super().__init__(seq)

def complement(self) -> str:
complement_seq = self.seq.translate(str.maketrans(self.complement_dict))

Choose a reason for hiding this comment

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

Прикольно с maketrans:)) не знала о существовании этой функции

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