eff-len 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eff_len/__init__.py +2 -0
- eff_len/div.py +262 -0
- eff_len/utils.py +93 -0
- eff_len-0.1.0.dist-info/METADATA +91 -0
- eff_len-0.1.0.dist-info/RECORD +6 -0
- eff_len-0.1.0.dist-info/WHEEL +4 -0
eff_len/__init__.py
ADDED
eff_len/div.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.lib.stride_tricks import as_strided
|
|
6
|
+
from .utils import msa_to_oh
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def average_dist(X):
|
|
10
|
+
N, L, k = X.shape
|
|
11
|
+
X = X.reshape(N, -1)
|
|
12
|
+
mat_dist = L - X @ X.T
|
|
13
|
+
avg_dist = mat_dist[np.triu_indices(N, k=1)].mean()
|
|
14
|
+
return avg_dist
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def kmers_from_id_matrix(ids, kmer_len):
|
|
18
|
+
ids = np.asarray(ids)
|
|
19
|
+
N, L = ids.shape
|
|
20
|
+
|
|
21
|
+
n_kmers = L - kmer_len + 1
|
|
22
|
+
sN, sL = ids.strides
|
|
23
|
+
|
|
24
|
+
kmers = as_strided(ids, shape=(N, n_kmers, kmer_len), strides=(sN, sL, sL))
|
|
25
|
+
return kmers.copy()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def kmer_entropy(ids, kmer_len):
|
|
29
|
+
kmers = kmers_from_id_matrix(ids, kmer_len) # (N, n_kmers, kmer_len)
|
|
30
|
+
flat = kmers.reshape(-1, kmer_len) # all k-mers in one list
|
|
31
|
+
|
|
32
|
+
unique_kmers, counts = np.unique(flat, axis=0, return_counts=True)
|
|
33
|
+
p = counts / counts.sum()
|
|
34
|
+
H = -np.sum(p * np.log(p + 1e-15)) # numerical safety
|
|
35
|
+
return np.exp(H)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def pos_entropy(X: np.ndarray, pos: bool = False, leff: bool=False, neff: bool = False, eps: float = 1e-12):
|
|
39
|
+
assert len(X.shape) == 3, "MSA must be NxLxk, where N is the number of sequences, L the length, and k is 5 or 21"
|
|
40
|
+
N, L, k = X.shape
|
|
41
|
+
p = X.mean(axis=0) # frequency per symbol
|
|
42
|
+
p = np.clip(p, eps, 1.0) # avoid log(0)
|
|
43
|
+
H = -(p * np.log(p)).sum(axis=-1)
|
|
44
|
+
if neff:
|
|
45
|
+
return np.exp(H.sum())
|
|
46
|
+
elif pos:
|
|
47
|
+
return np.exp(H)
|
|
48
|
+
elif leff:
|
|
49
|
+
return H.sum()/np.log(k)
|
|
50
|
+
else:
|
|
51
|
+
return np.exp(H.mean())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def neff_seq(X: np.ndarray, thres: float = 0.8, wei: bool = False):
|
|
55
|
+
N, L, k = X.shape
|
|
56
|
+
X = X.reshape(N, -1)
|
|
57
|
+
C = X @ X.T / L
|
|
58
|
+
|
|
59
|
+
S = (C > thres).astype(float) # similarity matrix
|
|
60
|
+
counts = S.sum(axis=1)
|
|
61
|
+
|
|
62
|
+
w = 1.0 / counts
|
|
63
|
+
|
|
64
|
+
if wei:
|
|
65
|
+
return w / w.sum() # normalized weights
|
|
66
|
+
else:
|
|
67
|
+
return w.sum() # effective number of sequences
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def helmert_basis(k: int) -> np.ndarray:
|
|
71
|
+
# k x (k-1), columns span {v : 1^T v = 0}, columns orthonormal
|
|
72
|
+
H = np.zeros((k, k-1), float)
|
|
73
|
+
for j in range(k-1):
|
|
74
|
+
H[:j+1, j] = 1.0
|
|
75
|
+
H[j+1, j] = -(j+1)
|
|
76
|
+
H[:j+2, j] /= np.sqrt((j+1)*(j+2))
|
|
77
|
+
return H
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def to_zero_sum(X: np.ndarray, L: int, k: int) -> np.ndarray:
|
|
81
|
+
# X: N x (kL) one-hot, L positions, k symbols
|
|
82
|
+
Q = helmert_basis(k) # k x (k-1)
|
|
83
|
+
T = np.kron(np.eye(L), Q) # (kL) x (L*(k-1))
|
|
84
|
+
return X @ T # N x L*(k-1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def weighted_covariance(Z, w):
|
|
88
|
+
# Z: N×M, w: N, sum(w)=1
|
|
89
|
+
Zt = Z * np.sqrt(w[:, None]) # weighted data: diag(√w) Z
|
|
90
|
+
mu = (w @ Z) # μ_w ∈ ℝ^{M×1}
|
|
91
|
+
C = Zt.T @ Zt - np.outer(mu, mu) # Zᵀ diag(w) Z − μ_w μ_wᵀ
|
|
92
|
+
return C, mu, Zt
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_center_data(X, w):
|
|
96
|
+
mu = w @ X # (M,)
|
|
97
|
+
Zx = X - mu # center
|
|
98
|
+
Zw = np.sqrt(w)[:, None] * Zx # sqrt(W) Zc
|
|
99
|
+
return Zw
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def effective_length(
|
|
103
|
+
Z, w=None, tol=1e-12, signal_frac=None,
|
|
104
|
+
zs=True, neff=False, reg=0., svd=True
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Estimate the effective dimensional length of a dataset via the entropy of
|
|
108
|
+
its variance spectrum.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
Z : array_like, shape (N, L, k)
|
|
113
|
+
Input tensor of N samples with L positions and k features per position.
|
|
114
|
+
w : array_like, optional
|
|
115
|
+
Sample weights of length N. If None, uniform weights are used.
|
|
116
|
+
tol : float, optional
|
|
117
|
+
Absolute numerical eigenvalue threshold. Components below this value
|
|
118
|
+
are discarded before any signal-fraction truncation.
|
|
119
|
+
signal_frac : float or None, optional
|
|
120
|
+
Fraction of total retained variance to preserve. If in (0, 1], the
|
|
121
|
+
spectrum is sorted in decreasing order and truncated to the smallest
|
|
122
|
+
number of components whose cumulative variance is at least
|
|
123
|
+
`signal_frac`. For example, `signal_frac=0.99` retains components
|
|
124
|
+
explaining 99% of the variance.
|
|
125
|
+
zs : bool, optional
|
|
126
|
+
If True, apply zero-sum mean-removal constraint across features.
|
|
127
|
+
neff : bool, optional
|
|
128
|
+
If True, return a support estimate of the sequence space spanned by
|
|
129
|
+
the MSA.
|
|
130
|
+
reg : float, optional
|
|
131
|
+
Diagonal regularization added to covariance in eigenvalue mode.
|
|
132
|
+
svd : bool, optional
|
|
133
|
+
If True, or when N < 5M, use SVD of centered data; otherwise use
|
|
134
|
+
weighted covariance eigenvalues.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
float
|
|
139
|
+
Entropy-based effective rank of the feature space, optionally scaled
|
|
140
|
+
to an effective alphabet-size support when `neff=True`.
|
|
141
|
+
|
|
142
|
+
Notes
|
|
143
|
+
-----
|
|
144
|
+
The method computes the Shannon entropy of normalized singular values or
|
|
145
|
+
covariance eigenvalues and converts it to an effective dimension called
|
|
146
|
+
`L_eff`.
|
|
147
|
+
"""
|
|
148
|
+
N, L, k = Z.shape
|
|
149
|
+
Z = Z.reshape(N, -1)
|
|
150
|
+
|
|
151
|
+
if zs:
|
|
152
|
+
Z = to_zero_sum(Z, L, k)
|
|
153
|
+
|
|
154
|
+
N, M = Z.shape
|
|
155
|
+
|
|
156
|
+
if w is None:
|
|
157
|
+
w = np.ones(N) / N
|
|
158
|
+
|
|
159
|
+
if svd or N < 5 * M:
|
|
160
|
+
Zw = get_center_data(Z, w)
|
|
161
|
+
vals = np.linalg.svd(Zw, full_matrices=False, compute_uv=False)
|
|
162
|
+
vals = (vals ** 2) / Zw.shape[0]
|
|
163
|
+
else:
|
|
164
|
+
C, mu, Zt = weighted_covariance(Z, w)
|
|
165
|
+
vals = np.linalg.eigvalsh((C + C.T) / 2.0 + reg * np.eye(M))
|
|
166
|
+
|
|
167
|
+
vals = vals[vals > tol]
|
|
168
|
+
|
|
169
|
+
if vals.size == 0:
|
|
170
|
+
return 0.0
|
|
171
|
+
|
|
172
|
+
vals = np.sort(vals)[::-1]
|
|
173
|
+
|
|
174
|
+
if signal_frac is not None:
|
|
175
|
+
if not (0.0 < signal_frac <= 1.0):
|
|
176
|
+
raise ValueError("signal_frac must be in (0, 1].")
|
|
177
|
+
|
|
178
|
+
frac = np.cumsum(vals) / np.sum(vals)
|
|
179
|
+
n_keep = np.searchsorted(frac, signal_frac) + 1
|
|
180
|
+
vals = vals[:n_keep]
|
|
181
|
+
|
|
182
|
+
p = vals / vals.sum()
|
|
183
|
+
H = -np.sum(p * np.log(p))
|
|
184
|
+
|
|
185
|
+
denom = k - 1 if zs else k
|
|
186
|
+
Leff = np.exp(H) / denom
|
|
187
|
+
|
|
188
|
+
if neff:
|
|
189
|
+
return k ** Leff
|
|
190
|
+
return float(Leff)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def spectral_entropy(C, tol=1e-12):
|
|
194
|
+
lam_full, U_full = np.linalg.eigh(C) # M eigenvalues, M×M eigenvectors
|
|
195
|
+
mask = lam_full > tol
|
|
196
|
+
lam = lam_full[mask] # r
|
|
197
|
+
U = U_full[:, mask] # M×r
|
|
198
|
+
|
|
199
|
+
S = lam.sum()
|
|
200
|
+
p = lam / S
|
|
201
|
+
H = -np.sum(p * np.log(p))
|
|
202
|
+
return H, lam, U
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def cross_effective_length(X, Y, wx=None, wy=None, tol=1e-12, zs=True, alpha=1.):
|
|
206
|
+
"""
|
|
207
|
+
Cross isotropy between two *independent* sample sets.
|
|
208
|
+
X: (Nx, Lx, k)
|
|
209
|
+
Y: (Ny, Ly, k)
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
Nx, Lx, k = X.shape
|
|
213
|
+
Ny, Ly, k = Y.shape
|
|
214
|
+
|
|
215
|
+
X = to_zero_sum(X.reshape(Nx, -1), Lx, k) if zs else X
|
|
216
|
+
Y = to_zero_sum(Y.reshape(Ny, -1), Ly, k) if zs else Y
|
|
217
|
+
|
|
218
|
+
if wx is None: wx = np.ones(Nx) / Nx
|
|
219
|
+
if wy is None: wy = np.ones(Ny) / Ny
|
|
220
|
+
wx = wx / wx.sum()
|
|
221
|
+
wy = wy / wy.sum()
|
|
222
|
+
|
|
223
|
+
Xc, Yc = get_center_data(X, wx), get_center_data(Y, wy)
|
|
224
|
+
# independent cross moment
|
|
225
|
+
Cxy = Xc @ Yc.T
|
|
226
|
+
|
|
227
|
+
# singular spectrum
|
|
228
|
+
s = np.linalg.svd(Cxy, compute_uv=False)
|
|
229
|
+
s = s[s > tol]
|
|
230
|
+
if s.size == 0:
|
|
231
|
+
return 0.0
|
|
232
|
+
|
|
233
|
+
p = s / s.sum()
|
|
234
|
+
|
|
235
|
+
if alpha == 1:
|
|
236
|
+
H = -np.sum(p * np.log(p))
|
|
237
|
+
eff_rank = np.exp(H)
|
|
238
|
+
else:
|
|
239
|
+
eff_rank = np.sum(p**alpha) ** (1.0 / (1.0 - alpha))
|
|
240
|
+
|
|
241
|
+
return float(eff_rank / (k - 1))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def average_min_dist(Y, X):
|
|
245
|
+
if len(X.shape) == 3:
|
|
246
|
+
Xf, Yf = X.reshape(len(X), -1), Y.reshape(len(Y), -1)
|
|
247
|
+
else:
|
|
248
|
+
Xf, Yf = X, Y
|
|
249
|
+
|
|
250
|
+
XY = Xf @ Yf.T
|
|
251
|
+
XX = Xf @ Xf.T
|
|
252
|
+
np.fill_diagonal(XX, 0)
|
|
253
|
+
return (XY.max(1)[XX.max(1)>0] / XX.max(1)[XX.max(1)>0]).mean()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def leff(msa: list, seq_type: str, **kwargs) -> float:
|
|
257
|
+
return effective_length(msa_to_oh(msa, seq_type=seq_type), **kwargs)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def cross_leff(msa_x: list, msa_y: list, seq_type: str, **kwargs) -> float:
|
|
261
|
+
return cross_effective_length(msa_to_oh(msa_x, seq_type=seq_type), msa_to_oh(msa_y, seq_type=seq_type), **kwargs)
|
|
262
|
+
|
eff_len/utils.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
# alphabets
|
|
8
|
+
NUC = {'-': 0, 'A': 1, 'U': 2, 'C': 3, 'G': 4}
|
|
9
|
+
INDEX_TO_NUC = {v: k for k, v in NUC.items()}
|
|
10
|
+
|
|
11
|
+
AA = {'-': 0, 'A': 1, 'R': 2, 'N': 3, 'D': 4, 'C': 5, 'Q': 6, 'E': 7, 'G': 8,
|
|
12
|
+
'H': 9, 'I': 10, 'L': 11, 'K': 12, 'M': 13, 'F': 14, 'P': 15, 'S': 16,
|
|
13
|
+
'T': 17, 'W': 18, 'Y': 19, 'V': 20}
|
|
14
|
+
INDEX_TO_AA = {v: k for k, v in AA.items()}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def hamming_distance(s1, s2):
|
|
18
|
+
return sum(a != b for a, b in zip(s1, s2))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def seq_to_indices(sequence, seq_type="nuc"):
|
|
22
|
+
if seq_type == "nuc":
|
|
23
|
+
alphabet = NUC
|
|
24
|
+
elif seq_type == "prot":
|
|
25
|
+
alphabet = AA
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError("seq_type must be 'nuc' or 'prot'")
|
|
28
|
+
return np.array([alphabet[x] for x in sequence], dtype=int)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_one_hot(data, num_classes):
|
|
32
|
+
N, L = data.shape
|
|
33
|
+
oh = np.zeros((N, L, num_classes), dtype=float)
|
|
34
|
+
oh[np.arange(N)[:, None], np.arange(L), data] = 1.0
|
|
35
|
+
return oh
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def read_fasta(infile, seq_type="prot"):
|
|
39
|
+
results = {}
|
|
40
|
+
with open(infile, 'r') as f:
|
|
41
|
+
name = None
|
|
42
|
+
for l in f:
|
|
43
|
+
l = l.strip()
|
|
44
|
+
if l.startswith(">"):
|
|
45
|
+
name = l[1:]
|
|
46
|
+
results[name] = ""
|
|
47
|
+
elif name:
|
|
48
|
+
if seq_type == "nuc":
|
|
49
|
+
cleaned = re.sub(r'[^ACGUT]', '-', l.upper()).replace("T", "U")
|
|
50
|
+
elif seq_type == "prot":
|
|
51
|
+
cleaned = re.sub(r'[^ACDEFGHIKLMNPQRSTVWY]', '-', l.upper())
|
|
52
|
+
else:
|
|
53
|
+
cleaned = l.upper()
|
|
54
|
+
results[name] += cleaned
|
|
55
|
+
return results
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def msa_to_oh(msa, seq_type="nuc"):
|
|
59
|
+
if seq_type == "nuc":
|
|
60
|
+
k = len(NUC) # 5
|
|
61
|
+
elif seq_type == "prot":
|
|
62
|
+
k = len(AA) # 21
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("seq_type must be 'nuc' or 'prot'")
|
|
65
|
+
|
|
66
|
+
if isinstance(msa, dict):
|
|
67
|
+
msa_l = [seq_to_indices(seq, seq_type) for seq in msa.values()]
|
|
68
|
+
else:
|
|
69
|
+
msa_l = [seq_to_indices(seq, seq_type) for seq in msa]
|
|
70
|
+
msa_arr = np.stack(msa_l) # (N, L)
|
|
71
|
+
return get_one_hot(msa_arr, num_classes=k) # (N, L, k)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def oh_to_msa(msa_oh, seq_type="nuc"):
|
|
75
|
+
if seq_type == "nuc":
|
|
76
|
+
idx_to_sym = INDEX_TO_NUC
|
|
77
|
+
elif seq_type == "prot":
|
|
78
|
+
idx_to_sym = INDEX_TO_AA
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError("seq_type must be 'nuc' or 'prot'")
|
|
81
|
+
|
|
82
|
+
idxs = np.argmax(msa_oh, axis=-1) # (N, L)
|
|
83
|
+
return [''.join(idx_to_sym[i] for i in row) for row in idxs]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def msa_to_ind(msa, seq_type="nuc"):
|
|
87
|
+
if type(msa) is dict:
|
|
88
|
+
msa = msa.values()
|
|
89
|
+
return np.stack([seq_to_indices(seq, seq_type) for seq in msa])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def ind_to_msa(ind_array):
|
|
93
|
+
return ["".join(INDEX_TO_NUC[i] for i in row) for row in ind_array]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: eff-len
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Spectral measure of diversity for multiple sequence alignments
|
|
5
|
+
Project-URL: Homepage, https://github.com/vaiteaopuu/effective_length
|
|
6
|
+
Project-URL: Issues, https://github.com/vaiteaopuu/effective_length/issues
|
|
7
|
+
Author-email: Vaitea Opuu <vaitea.opuu@cnrs.fr>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: bioinformatics,diversity,msa,sequence-alignment
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# eff_len
|
|
18
|
+
|
|
19
|
+
Spectral measure of diversity for multiple sequence alignments.
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
This repo contains the code to reproduce the results in *"A spectral framework for measuring diversity in multiple sequence alignments"*. It introduces a measure, `L_eff`, that estimates the diversity (or amount of information) contained in a multiple sequence alignment. `L_eff` allows a faithful comparison between MSAs, as well as between generated datasets.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Requirements: NumPy.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install eff_len
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
From source:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/vaiteaopuu/effective_length
|
|
37
|
+
cd effective_length
|
|
38
|
+
pip install .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Python
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from eff_len import read_fasta, msa_to_oh, effective_length
|
|
47
|
+
|
|
48
|
+
msa = read_fasta("data/RF00028.fa", seq_type="nuc")
|
|
49
|
+
|
|
50
|
+
msa_oh = msa_to_oh(msa, seq_type="nuc")
|
|
51
|
+
N, L, k = msa_oh.shape
|
|
52
|
+
L_eff = effective_length(msa_oh)
|
|
53
|
+
|
|
54
|
+
print(N, L, L_eff, L_eff / L)
|
|
55
|
+
# 2611 251 35.88477058938092 0.14296721350350963
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`cross_effective_length` and `leff` are also exported, for comparing two alignments and for the convenience wrapper respectively.
|
|
59
|
+
|
|
60
|
+
## Repository content
|
|
61
|
+
|
|
62
|
+
| Path | Description |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `src/eff_len/` | The package itself |
|
|
65
|
+
| `analysis/` | Notebooks and scripts |
|
|
66
|
+
| `data/` | Example alignments |
|
|
67
|
+
| `reproducibility.org` | Code snippets reproducing the figures in the paper |
|
|
68
|
+
|
|
69
|
+
## Data sources
|
|
70
|
+
|
|
71
|
+
The data used in these analyses were extracted from:
|
|
72
|
+
|
|
73
|
+
- C. Lambert *et al.* (2025) *Nat. Commun.*
|
|
74
|
+
- F. Calvanese *et al.* (2024) *NAR*
|
|
75
|
+
- M. Mirdita *et al.* (2027) *NAR*
|
|
76
|
+
|
|
77
|
+
## Citation
|
|
78
|
+
|
|
79
|
+
If you use this code, please cite:
|
|
80
|
+
|
|
81
|
+
```bibtex
|
|
82
|
+
@article{opuu_spectral,
|
|
83
|
+
title = {A spectral framework for measuring diversity in multiple sequence alignments},
|
|
84
|
+
author = {Opuu, Vaitea},
|
|
85
|
+
year = {2026}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
eff_len/__init__.py,sha256=qqpx4c0Vgdu_7ScKZHgWVmk-kFXI6OscP6u_vwKQZl0,40
|
|
2
|
+
eff_len/div.py,sha256=-dpj75TqvG360XODJKPiV6W7U2MRzIIFo87ztGgyZmY,7642
|
|
3
|
+
eff_len/utils.py,sha256=FN81ScLplmNptDItm7et36jaEKRyKrWUSr1SBC5d6f4,2703
|
|
4
|
+
eff_len-0.1.0.dist-info/METADATA,sha256=_LjcQH53z9I4HHZIdXZ31Dgmp4GtDNcYg69vFw7nQpA,2425
|
|
5
|
+
eff_len-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
6
|
+
eff_len-0.1.0.dist-info/RECORD,,
|