Skip to content

Commit e697a53

Browse files
authored
Merge pull request #3 from NIB-SI/fix/allele_specific_structure_testing_ratio_error
Fix/allele specific structure testing ratio error
2 parents 790ed6a + 21d6f28 commit e697a53

1 file changed

Lines changed: 35 additions & 130 deletions

File tree

polyase/stats.py

Lines changed: 35 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,14 +1480,6 @@ def _match_all_isoforms_for_plotting(
14801480
minor_isoform,
14811481
min_similarity_for_matching=0.4
14821482
):
1483-
"""
1484-
Match ALL reference isoforms (including zero-expressed ones) to isoforms in other haplotypes.
1485-
1486-
Returns
1487-
-------
1488-
dict
1489-
Key: (haplotype, ref_isoform_id), Value: {'transcript_idx', 'transcript_id', 'similarity'}
1490-
"""
14911483
import numpy as np
14921484

14931485
matches = {}
@@ -1499,52 +1491,55 @@ def _match_all_isoforms_for_plotting(
14991491
if len(hap_indices) == 0:
15001492
continue
15011493

1502-
# For each reference isoform, find best match in this haplotype
1503-
for ref_iso in all_ref_isoforms:
1504-
ref_iso_id = ref_iso['transcript_id']
1505-
ref_exon_struct = ref_iso['exon_structure']
1506-
ref_intron_struct = ref_iso['intron_structure']
1507-
1508-
# If this is the reference haplotype, use direct match
1509-
if hap == reference_haplotype:
1510-
matches[(hap, ref_iso_id)] = {
1494+
if hap == reference_haplotype:
1495+
for ref_iso in all_ref_isoforms:
1496+
matches[(hap, ref_iso['transcript_id'])] = {
15111497
'transcript_idx': ref_iso['transcript_idx'],
1512-
'transcript_id': ref_iso_id,
1498+
'transcript_id': ref_iso['transcript_id'],
15131499
'similarity': 1.0
15141500
}
1515-
continue
1516-
1517-
# Find best matching transcript in this haplotype
1518-
best_match_idx = None
1519-
best_similarity = 0.0
1520-
1501+
continue
1502+
1503+
# Build full similarity matrix: ref_isoform x hap_transcript
1504+
similarity_matrix = {}
1505+
for ref_iso in all_ref_isoforms:
1506+
ref_iso_id = ref_iso['transcript_id']
15211507
for idx in hap_indices:
15221508
tid = transcript_ids[idx]
15231509
exon_struct = exon_lengths_dict.get(tid, [])
15241510
intron_struct = intron_lengths_dict.get(tid, [])
15251511

15261512
similarity = _calculate_combined_structure_similarity(
1527-
ref_exon_struct, exon_struct,
1528-
ref_intron_struct, intron_struct
1513+
ref_iso['exon_structure'], exon_struct,
1514+
ref_iso['intron_structure'], intron_struct
15291515
)
1530-
1531-
if similarity > best_similarity:
1532-
best_similarity = similarity
1533-
best_match_idx = idx
1516+
similarity_matrix[(ref_iso_id, idx)] = similarity
1517+
1518+
# Greedy assignment: best similarity pair first, no reuse of hap transcripts
1519+
assigned_hap_transcripts = set()
1520+
assigned_ref_isoforms = set()
1521+
1522+
# Sort all pairs by similarity descending
1523+
sorted_pairs = sorted(similarity_matrix.items(), key=lambda x: x[1], reverse=True)
1524+
1525+
for (ref_iso_id, idx), similarity in sorted_pairs:
1526+
if ref_iso_id in assigned_ref_isoforms:
1527+
continue
1528+
if idx in assigned_hap_transcripts:
1529+
continue
1530+
if similarity < min_similarity_for_matching:
1531+
continue # remaining pairs will only be worse
15341532

1535-
# Only add if similarity meets threshold
1536-
if best_match_idx is not None and best_similarity >= min_similarity_for_matching:
1537-
matches[(hap, ref_iso_id)] = {
1538-
'transcript_idx': best_match_idx,
1539-
'transcript_id': transcript_ids[best_match_idx],
1540-
'similarity': best_similarity
1541-
}
1533+
matches[(hap, ref_iso_id)] = {
1534+
'transcript_idx': idx,
1535+
'transcript_id': transcript_ids[idx],
1536+
'similarity': similarity
1537+
}
1538+
assigned_hap_transcripts.add(idx)
1539+
assigned_ref_isoforms.add(ref_iso_id)
15421540

15431541
return matches
15441542

1545-
1546-
1547-
15481543
def _identify_major_minor_isoforms(
15491544
synt_indices, haplotypes, transcript_ids,
15501545
exon_lengths_dict, intron_lengths_dict,
@@ -1864,93 +1859,3 @@ def _calculate_length_based_similarity(lengths1, lengths2, tolerance=10):
18641859

18651860
# Ensure the result is bounded between 0 and 1
18661861
return max(0.0, min(1.0, final_similarity))
1867-
1868-
1869-
1870-
def _calculate_length_based_similarity(lengths1, lengths2, tolerance=10):
1871-
"""
1872-
Calculate similarity between two genomic structures based on element counts and lengths.
1873-
1874-
Parameters
1875-
----------
1876-
lengths1, lengths2 : list of int
1877-
Lengths of genomic elements (exons or introns)
1878-
tolerance : int, default=10
1879-
Base pair tolerance for length comparison
1880-
1881-
Returns
1882-
-------
1883-
float
1884-
Similarity score between 0 and 1
1885-
"""
1886-
if not lengths1 or not lengths2:
1887-
return 0.0
1888-
1889-
# Ensure we're working with lists
1890-
if not isinstance(lengths1, list):
1891-
lengths1 = [lengths1]
1892-
if not isinstance(lengths2, list):
1893-
lengths2 = [lengths2]
1894-
1895-
# Component 1: Check if the number of elements is the same
1896-
count1 = len(lengths1)
1897-
count2 = len(lengths2)
1898-
1899-
# Penalize heavily for different counts
1900-
if count1 != count2:
1901-
count_similarity = 1.0 - abs(count1 - count2) / max(count1, count2)
1902-
# If counts differ significantly, return low similarity
1903-
if count_similarity < 0.5:
1904-
return count_similarity * 0.5 # Max 0.25 if counts differ a lot
1905-
else:
1906-
count_similarity = 1.0
1907-
1908-
# Component 2: Calculate length similarity for corresponding elements
1909-
# Sort lengths to compare corresponding elements by size
1910-
lengths1_sorted = sorted(lengths1)
1911-
lengths2_sorted = sorted(lengths2)
1912-
1913-
# Compare corresponding lengths (pair shortest with shortest, etc.)
1914-
n_comparisons = min(len(lengths1_sorted), len(lengths2_sorted))
1915-
1916-
if n_comparisons == 0:
1917-
return 0.0
1918-
1919-
length_similarities = []
1920-
for i in range(n_comparisons):
1921-
len1 = lengths1_sorted[i]
1922-
len2 = lengths2_sorted[i]
1923-
1924-
# Calculate absolute difference
1925-
diff = abs(len1 - len2)
1926-
1927-
# Apply tolerance: differences within tolerance have no penalty
1928-
if diff <= tolerance:
1929-
similarity = 1.0
1930-
else:
1931-
# Calculate similarity based on relative difference beyond tolerance
1932-
adjusted_diff = diff - tolerance
1933-
avg_length = (len1 + len2) / 2
1934-
# Use exponential decay for penalty to avoid harsh drops
1935-
similarity = max(0.0, 1.0 - (adjusted_diff / avg_length))
1936-
1937-
length_similarities.append(similarity)
1938-
1939-
# Average length similarity
1940-
avg_length_similarity = sum(length_similarities) / n_comparisons
1941-
1942-
# Penalize for extra elements (if counts differ)
1943-
if count1 != count2:
1944-
extra_elements = abs(count1 - count2)
1945-
penalty = extra_elements / max(count1, count2) * 0.2 # 20% penalty per extra element
1946-
avg_length_similarity *= (1 - penalty)
1947-
1948-
# Combine count and length similarity
1949-
# Give more weight to length similarity if counts match
1950-
if count1 == count2:
1951-
final_similarity = 0.2 * count_similarity + 0.8 * avg_length_similarity
1952-
else:
1953-
final_similarity = 0.5 * count_similarity + 0.5 * avg_length_similarity
1954-
1955-
# Ensure the result is bounded between 0 and 1
1956-
return max(0.0, min(1.0, final_similarity))

0 commit comments

Comments
 (0)