Convert the supplied multiple sequence alignment to an ordinary array so that reducing over rows returns one value per column. With nine sequences, fewer than 30% gaps means at most two gaps in a column:
from collections import Counter
A = np.asarray(msa_matrix)
gap_fraction = np.mean(A == ".", axis=0)
match_mask = gap_fraction < .30
match_columns = np.flatnonzero(match_mask)
print(match_columns + 1)  # human-readable, one-based alignment columns
There are 72 match states: alignment columns 1–21 and 29–79, using one-based numbering. The seven remaining columns, 22–28, form one contiguous insertion region. The strict inequality is applied to the actual fraction, rather than to rounded percentages.
For each match state, count the nongap amino acids and divide by the number of nongap observations in that column. A gap is not one of the twenty emitted amino acids; including it in the denominator would make the emission probabilities sum to less than one:
def emission_counts(column):
    counts = Counter(str(x) for x in column if x != ".")
    n = sum(counts.values())
    if n == 0:
        raise ValueError("an all-gap column has no empirical emission distribution")
    return counts, n

match_emissions = {}
for state, j in enumerate(match_columns, 1):
    counts, n = emission_counts(A[:, j])
    match_emissions[state] = {aa: count/n for aa, count in counts.items()}
    print(state, j+1, match_emissions[state])
The table gives an exact compact representation of every emission probability: divide each displayed count by the denominator in its row; all unlisted amino acids have probability zero. The state number follows the order of match states, while the column refers to the original multiple sequence alignment.
StateAlignment columnNongap counts
119I:1, K:1, R:1, S:2, V:3, Y:1
229D:1, E:2, G:1, I:1, L:1, Q:2, S:1
339E:2, I:1, K:1, Q:1, R:3, T:1
449L:8, M:1
559E:3, G:1, L:2, N:1, Q:1, S:1
669A:1, D:2, G:1, K:1, N:1, S:2, T:1
779A:1, E:3, I:2, L:1, M:1, V:1
889I:2, L:7
999D:3, E:1, K:1, L:2, N:1, Q:1
10109D:1, E:3, G:1, N:1, P:1, S:2
11119A:5, D:1, G:1, P:1, T:1
12129I:1, L:7, Y:1
13139E:1, F:1, G:1, H:2, M:1, Q:2, T:1
14149F:9
15159F:1, L:8
16169E:1, I:1, L:1, N:3, Q:1, R:1, Y:1
17179D:6, E:2, S:1
18189C:7, F:1, S:1
19199K:1, N:6, R:2
20209E:1, F:1, I:5, L:1, V:1
21219K:3, P:1, Q:1, S:4
22297K:4, P:1, R:1, S:1
23307E:2, F:1, G:1, I:1, K:1, V:1
24317D:3, E:1, G:1, K:1, S:1
25328D:1, E:1, G:1, K:1, N:1, R:3
26338D:7, N:1
27348A:1, P:6, S:1
28359C:1, H:1, I:1, K:1, M:1, R:1, T:3
29369E:1, F:1, G:1, L:1, Q:3, W:1, Y:1
30379I:5, L:1, T:2, Y:1
31389A:1, G:1, P:3, S:4
32399I:1, K:6, L:1, R:1
33409F:1, N:1, Q:4, T:2, V:1
34419A:1, F:1, I:1, V:6
35429I:1, L:7, V:1
36439A:1, C:1, E:1, N:1, R:1, S:3, V:1
37449D:3, E:5, Q:1
38459A:1, C:7, I:1
39469K:2, L:3, R:4
40479A:3, E:1, L:1, N:3, T:1
41489I:4, L:1, T:1, V:3
42499L:9
43509D:1, E:1, H:1, Q:1, S:1, T:1, V:1, Y:2
44519E:1, K:1, Q:2, R:1, V:4
45529L:9
46539G:9
47549P:8, T:1
48559W:9
49569A:2, C:7
50579A:6, C:1, L:1, T:1
51589A:1, D:3, F:1, S:2, V:1, Y:1
52599K:4, R:2, S:3
53609A:2, F:1, I:2, L:2, V:2
54619A:7, G:1, Y:1
55629E:3, G:3, Q:3
56639I:3, M:3, T:1, Y:2
57649F:3, L:2, M:3, N:1
58659H:1, I:3, L:1, V:4
59669K:1, M:1, P:1, R:4, S:1, T:1
60679D:1, E:3, Q:4, Y:1
61689I:1, L:7, M:1
62699D:1, E:4, Q:4
63709K:7, R:2
64719A:1, I:2, M:1, N:1, R:1, Y:3
65729A:1, C:1, D:1, E:2, I:2, V:2
66739K:9
67749H:4, L:1, Q:2, S:1, V:1
68759E:8, Q:1
69769Q:3, R:1, S:2, T:3
70779C:2, E:3, G:1, R:1, S:1, V:1
71789A:1, D:2, E:3, I:1, K:1, Y:1
72799I:2, L:3, M:1, N:1, Q:1, V:1
These are unregularized maximum-likelihood estimates of the emission probabilities. No additional artificial observations are added. Although only emissions are requested here, a full profile hidden Markov model would also require transitions and deletion mechanisms to represent the gaps that remain in some match columns.
For each alignment column, ignore gaps, form the empirical distribution of its amino acids, and calculate its Shannon entropy in bits. The requested alignment column information content is
Thus it is the relative entropy from the column distribution to the uniform distribution on twenty amino acids, not the ordinary Shannon entropy itself. A column containing one residue type has the maximum value bits.
information = []
for j in range(A.shape[1]):
    counts, n = emission_counts(A[:, j])
    p = np.array(list(counts.values()), dtype=float) / n
    H = -np.sum(p * np.log2(p))
    information.append(float(np.log2(20) - H))
print(np.round(information, 4))
The values for all 79 alignment columns are as follows, in bits. Each line lists its one-based column interval followed by the corresponding values in order:
01–10: 1.9025, 1.5964, 1.9025, 3.8187, 1.9025, 1.5964, 1.9025, 3.5577, 1.9025, 1.9025
11–20: 2.4420, 3.3355, 1.5964, 4.3219, 3.8187, 1.6803, 3.0975, 3.3355, 3.0975, 2.4420
21–30: 2.5692, 2.8219, 3.5106, 3.3219, 4.3219, 4.3219, 2.8000, 2.0703, 2.6574, 1.8003
31–40: 2.1938, 1.9163, 3.7784, 3.2606, 1.6803, 1.6803, 2.6642, 2.5692, 2.8753, 2.2631
41–50: 2.8753, 3.3355, 1.6803, 2.9703, 3.3355, 2.7914, 2.2086, 2.5692, 4.3219, 1.3742
51–60: 2.2631, 4.3219, 4.3219, 3.8187, 4.3219, 3.5577, 2.8753, 1.9025, 2.7914, 2.0409
61–70: 3.3355, 2.7370, 2.4309, 2.4309, 2.5692, 2.0409, 2.5692, 3.3355, 2.9298, 3.5577
71–79: 1.9025, 1.8187, 4.3219, 2.2631, 3.8187, 2.4309, 1.9025, 1.9025, 1.9025
A high score indicates conservation among the observed nongap letters, but does not itself imply low gap fraction. For example, an insertion column represented by only one observed nongap residue has maximal information by this calculation; its tiny sample size still makes that estimate uncertain.
Columns 22–28 form one run between alignment columns 21 and 29, corresponding to match states and . A self-repeating insertion state can emit a variable number of residues, so these seven alignment columns require one populated insertion state, not seven successive mandatory states.
Pool all nongap residues in this run to estimate its emission probabilities:
insert_columns = np.flatnonzero(~match_mask)
runs = np.split(insert_columns, np.flatnonzero(np.diff(insert_columns) > 1) + 1)
insert_emissions = []
for run in runs:
    if len(run):
        counts, n = emission_counts(A[:, run].ravel())
        insert_emissions.append({aa: count/n for aa, count in counts.items()})
        print(run+1, n, insert_emissions[-1])
There are 24 emitted residues, giving
All other amino acids have probability zero in this empirical emission probabilities. A conventional full architecture may provide an optional insertion state at every boundary, including the two ends, giving possible insertion positions; only one has an observed insertion here and is required by this alignment's populated insertion region.

Articles by others on the same topic (0)

There are currently no matching articles.