Repository: biopython/biopython

Issue:
GC content for RNA sequences
### Setup

I am reporting a problem with:
-  Biopython version: 1.81
- Python version: 3.9.0
- System: macOS Sonoma

### Expected behaviour

```
from Bio.SeqUtils import gc_fraction
gr_fraction("GGAUCUUCGGAUCU") 
# expected result: 0.5
```

### Actual behaviour

```
from Bio.SeqUtils import gc_fraction
gr_fraction("GGAUCUUCGGAUCU") 
# result: 0.7777777777777779
```

### What to fix
Please add U to the non-ambiguous nucleotides to support RNA sequences (currently, the non-ambiguous nucleotides are ATCGSW). The previous GC content function (now deprecated) was supporting RNA sequences too


Discussion:
The old function didn't explicitly handle U either:

```python
def GC(seq):
    """Calculate G+C content (DEPRECATED).
    Use Bio.SeqUtils.gc_fraction instead.
    """
    warnings.warn(
        "GC is deprecated; please use gc_fraction instead.",
        BiopythonDeprecationWarning,
    )

    gc = sum(seq.count(x) for x in ["G", "C", "g", "c", "S", "s"])
    try:
        return gc * 100.0 / len(seq)
    except ZeroDivisionError:
        return 0.0
```

(See 960a5e2c9e98ce3b1b207cffcd818c3ffcf60bde which removed it)

To mimic the old behaviour, use the ignore mode rather than the default remove mode:

```pycon
>>> from Bio.SeqUtils import gc_fraction
>>> gc_fraction("GGAUCUUCGGAUCU", "ignore") 
0.5
```

That said, explicitly handling `U` for RNA support makes sense. I think it would be a one line change to add `U` to the dictionary here:

https://github.com/biopython/biopython/blob/biopython-183/Bio/SeqUtils/__init__.py#L32

This would also need a new test case (e.g. your example). Would you like to make a pull request?
Sure, I'm on it!

Resolving patch:
diff --git a/Bio/SeqUtils/__init__.py b/Bio/SeqUtils/__init__.py
--- a/Bio/SeqUtils/__init__.py
+++ b/Bio/SeqUtils/__init__.py
@@ -27,6 +27,7 @@
     "C": 1.000,
     "A": 0.000,
     "T": 0.000,
+    "U": 0.000,
     "S": 1.000,  # Strong interaction (3 H bonds) (G or C)
     "W": 0.000,  # Weak interaction (2 H bonds) (A or T)
     "M": 0.500,  # Amino (A or C)
@@ -46,10 +47,10 @@ def gc_fraction(seq, ambiguous="remove"):
     """Calculate G+C percentage in seq (float between 0 and 1).
 
     Copes with mixed case sequences. Ambiguous Nucleotides in this context are
-    those different from ATCGSW (S is G or C, and W is A or T).
+    those different from ATCGSWU (S is G or C, and W is A or T).
 
     If ambiguous equals "remove" (default), will only count GCS and will only
-    include ACTGSW when calculating the sequence length. Equivalent to removing
+    include ACTGSWU when calculating the sequence length. Equivalent to removing
     all characters in the set BDHKMNRVXY before calculating the GC content, as
     each of these ambiguous nucleotides can either be in (A,T) or in (C,G).
 
@@ -70,6 +71,12 @@ def gc_fraction(seq, ambiguous="remove"):
     >>> print(f"GC content of {seq} : {gc_fraction(seq):.2f}")
     GC content of ACTG : 0.50
 
+    Example with an RNA sequence:
+
+    >>> seq = "GGAUCUUCGGAUCU"
+    >>> print(f"GC content of {seq} : {gc_fraction(seq):.2f}")
+    GC content of GGAUCUUCGGAUCU : 0.50
+
     S and W are ambiguous for the purposes of calculating the GC content.
 
     >>> seq = "ACTGSSSS"
@@ -118,7 +125,7 @@ def gc_fraction(seq, ambiguous="remove"):
     gc = sum(seq.count(x) for x in "CGScgs")
 
     if ambiguous == "remove":
-        length = gc + sum(seq.count(x) for x in "ATWatw")
+        length = gc + sum(seq.count(x) for x in "ATWUatwu")
     else:
         length = len(seq)
 


Test patch:
diff --git a/Tests/test_SeqUtils.py b/Tests/test_SeqUtils.py
--- a/Tests/test_SeqUtils.py
+++ b/Tests/test_SeqUtils.py
@@ -367,6 +367,13 @@ def test_gc_fraction(self):
         self.assertAlmostEqual(gc_fraction(seq, "weighted"), 0.75, places=3)
         self.assertAlmostEqual(gc_fraction(seq, "remove"), 0.75, places=3)
 
+        # Test RNA sequence
+
+        seq = "GGAUCUUCGGAUCU"
+        self.assertAlmostEqual(gc_fraction(seq, "ignore"), 0.5, places=3)
+        self.assertAlmostEqual(gc_fraction(seq, "weighted"), 0.5, places=3)
+        self.assertAlmostEqual(gc_fraction(seq, "remove"), 0.5, places=3)
+
         # Test ambiguous nucleotide behaviour
 
         seq = "CCTGNN"