Repository: biopython/biopython

Issue:
How to delete sequences from an alignment (Bio.Align.MultipleSeqAlignment)
### Setup

Is there a way to delete specific sequences from a Bio.Align.MultipleSeqAlignment object? It could be done either by its rec.id (ideal) or its index.

If there is not a _method_ for this, is it possible to do it 'manually' by creating a new object, e.g.,

```python
recs = recs[0:2] + recs[3:]
```

where I'm trying to remove the third (index 2) record? I know THIS doesn't work — it gives an error, **ValueError: When adding two alignments they must have the same length (i.e. same number or rows)**. But, is something like it possible?

Thanks very much!
Chase


Discussion:
Ah, I think I've found a solution with **.extend()**. Still, would love feedback about whether this is the best way! For example, is there a way to do it without making an extra copy of some of the records in memory?

```python
recs_new = recs[0:2]
recs_new.extend(recs[3:])
```

Thanks so much!
Chase
I guess deleting would be the more natural syntax:

```
>>> x = [1,4,2,3,45]
>>> del x[4]
>>> x
[1, 4, 2, 3]
```
Thanks so much, Peter! Unfortunately, the above attempt was necessary because that more natural approach gives an error:

```python
del recs[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'MultipleSeqAlignment' object doesn't support item deletion
```

Likewise if I specify both indices:

```python
del recs[1,:]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'MultipleSeqAlignment' object does not support item deletion
```

Would love to know if there is indeed a more straightforward way to accomplish this!

With gratitude,
Chase
Right now with ``align = MultipleSeqAlignment(...)`` you could use ``del align._records[1]`` but that's accessing a private attribute of the object. i.e. Use with caution and expect this not to work forever, comment the code about why you're doing this, and link to this issue.

We would need to define the ``__delitem__`` "Magic Method" on the class in order for ``del align[1]`` to work (*edited to fix example*), but this seems well defined for a single integer argument.

Do you think the list ``.pop(...)`` would also make sense for the MultipleSeqAlignment objects?
Thank you so much, Peter! I am still quite new to Python, so thank you for this incredibly helpful explanation — I will use this method for now. 

My own purpose is to allow the user to specify at the command line if they want certain record IDs excluded from the analysis. Right now I'm deleting the indices in reverse order, but it would also help to be able to delete them all simultaneously, e.g., by providing a **list** of **non-consecutive** indices — is such a thing possible or straightforward in Python?

A pop method seems great to me, e.g., for instances where the user would like to "gobble" through the sequences and discard them to clear memory as soon as they're processed. However, I'm not sure how much it would be used and the same thing could be accomplished with del? Your call!

Thanks a ton!
If you have a list/tuple/set of unwanted IDs to exclude, you could do something like this:

```python
from Bio.Align import MultipleSeqAlignment
# Assuming align = AlignIO.read(...) or similar
unwanted = {"BAD1", "BAD2", "BAD3"}  # supplied by user
sub_alignment = MultipleSeqAlignment(record for record in align if record.id not in unwanted)
```

Even though you'd have the full and sub-alignment in memory at the same time, the memory overhead would be minimal as they're essentially two alternative lists of the same record objects.
This works PERFECTLY — thank you so much, Peter!

With gratitude,
Chase
Hello! I'm Anika and I would love to contribute. I already have some background on basic bio-informatics algorithms like sequence alignment. Could you guide me how to get started?
@Anika-Roy You would need to define the ``__delitem__`` "Magic Method" on the class to accept integers only, so that the example ``del align[1]`` etc would work.

This isn't beginner level Python, but ought not to be too tricky.
Okay sir, I'll try
Thank you

Resolving patch:
diff --git a/Bio/Align/__init__.py b/Bio/Align/__init__.py
--- a/Bio/Align/__init__.py
+++ b/Bio/Align/__init__.py
@@ -791,6 +791,13 @@ def __getitem__(self, index):
                     new.column_annotations[k] = v[col_index]
             return new
 
+    def __delitem__(self, index):
+        """Delete SeqRecord by index or multiple SeqRecords by slice."""
+        if not isinstance(index, int) and not isinstance(index, slice):
+            raise TypeError("Invalid index type.")
+
+        del self._records[index]
+
     def sort(self, key=None, reverse=False):
         """Sort the rows (SeqRecord objects) of the alignment in place.
 


Test patch:
diff --git a/Tests/test_align.py b/Tests/test_align.py
--- a/Tests/test_align.py
+++ b/Tests/test_align.py
@@ -176,6 +176,8 @@ def test_basic_alignment(self):
         alignment.append(SeqRecord(Seq(letters), id="mixed"))
         alignment.append(SeqRecord(Seq(letters.lower()), id="lower"))
         alignment.append(SeqRecord(Seq(letters.upper()), id="upper"))
+        alignment.append(SeqRecord(Seq(letters), id="duplicate"))
+        del alignment[3]
         self.assertEqual(alignment.get_alignment_length(), 26)
         self.assertEqual(len(alignment), 3)
         self.assertEqual(alignment[0].seq, letters)