FScanpy 1.0.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.
@@ -0,0 +1,4 @@
1
+ FS_period,399bp,fs_position,DNA_seqid,label,source,FS_type,dataset
2
+ gtgtgaacacaatagtgagtgacatactaaacg,ggatatgtaacatggacaagtcattgtgtaggtatccaagaccaatagcctttactttcaaagggaaagaagaattcaggaccttgtttggaggactcatctcgatgtcgattcaggtggtcattgtgctctatgcttatattatgctaaagataatgatagaacgtaatgacacatcaaaaagtgtgaacacaatagtgagtgacatactaaacgacaaatctccagtatctctcaatacaacagatttctcgtttgcatttgatgcttttattcttggcgatgataatttcgatttcaacaataaccaatacttcggaattgagctacttcaatggattaagcagccagatactggagaactatcatccactaatattccatatgaaagatgtggaa,16.0,MSTRG.18491.1,0,EUPLOTES,negative,EUPLOTES
3
+ gtctcagaagagtctgaggaatatctccaagga,caaattaataacaaatatgaattccatcaacaacttttatggagacgagaacttatcagatgaacttctgagtgaagatgtcgtgtcttgagaagtaagaggatcagaaaagatcttgcataacatggggagaaagtctctcagtaataagaagcctttaagcggagtggagttggactgagagtctcagaagagtctgaggaatatctccaaggataaaatttgttcgcaaggaagatctatctttaggcagaagaagtcaaaatcttgtgatcaagtagaagaacctcttagtagtcttaaagataacatgagtcactttaatgacatagacttgcaagctagtaagcctctaaaatcagagattagcaatctttttgggtactcaactcagcccaa,16.0,MSTRG.4662.1,0,EUPLOTES,negative,EUPLOTES
4
+ cttacttgcaaacatgaatctaataaattagag,ttaagaaggcataagagttttgctaaaaataaagatttgaagaatattactactaagtttggcaagagtaaacagagaagaagtaccatttctggctctccgacaaaatcagtcagatgcccttctgcaaaaaagagcctaacagatagaccaagaagaggaggtatccttgccaggaagaatcttacttgcaaacatgaatctaataaattagagatgctgatgaacctcatctatcgtacaccgaatgtagacctgattgaaaataggatcgatggactgataagaagtaactctatattgaacaaagtcgagaagagagtagctcactccggcattaagacttacaggttttctcctaatttactgaagaagataattccaaagaagataaaattc,16.0,MSTRG.14742.1,0,EUPLOTES,negative,EUPLOTES
@@ -0,0 +1,4 @@
1
+ from .sequence import SequenceFeatureExtractor
2
+ from .cnn_input import CNNInputProcessor
3
+
4
+ __all__ = ['SequenceFeatureExtractor', 'CNNInputProcessor']
@@ -0,0 +1,79 @@
1
+ import numpy as np
2
+ from typing import List, Union
3
+
4
+ class CNNInputProcessor:
5
+ """CNN模型输入数据处理器"""
6
+
7
+ def __init__(self, max_length: int = 399):
8
+ self.max_length = max_length
9
+ self.base_to_num = {'A': 0, 'T': 1, 'C': 2, 'G': 3, 'N': 4}
10
+
11
+ def trim_sequence(self, seq, target_length):
12
+ """
13
+ 从序列两端等量截取,使其达到目标长度,保持中心位置不变
14
+
15
+ 参数:
16
+ seq: 原始序列
17
+ target_length: 目标长度
18
+
19
+ 返回:
20
+ 截取后的序列
21
+ """
22
+ if len(seq) <= target_length:
23
+ return seq
24
+
25
+ # 计算需要从每端截取的长度
26
+ excess = len(seq) - target_length
27
+ trim_each_side = excess // 2
28
+
29
+ # 从两端等量截取,保持中心位置不变
30
+ return seq[trim_each_side:len(seq)-trim_each_side]
31
+
32
+ def prepare_sequence(self, sequence: str) -> np.ndarray:
33
+ """
34
+ 处理单个序列
35
+
36
+ Args:
37
+ sequence: DNA序列
38
+
39
+ Returns:
40
+ np.ndarray: 处理后的序列数组
41
+ """
42
+ try:
43
+ # 序列验证和预处理
44
+ if not isinstance(sequence, str):
45
+ sequence = str(sequence)
46
+
47
+ sequence = sequence.upper().replace('U', 'T')
48
+
49
+ # 如果序列长度不等于目标长度,进行截取
50
+ if len(sequence) > self.max_length:
51
+ sequence = self.trim_sequence(sequence, self.max_length)
52
+
53
+ # 使用与训练时相同的编码方式
54
+ self.base_to_num = {'A': 0, 'T': 1, 'C': 2, 'G': 3, 'N': 4} # 与SemiBilstmCnn.py中保持一致
55
+
56
+ # 序列转换为数字
57
+ seq_numeric = []
58
+ for base in sequence:
59
+ seq_numeric.append(self.base_to_num.get(base, 4)) # 未知碱基用4表示
60
+
61
+ # 填充序列
62
+ if len(seq_numeric) < self.max_length:
63
+ seq_numeric.extend([4] * (self.max_length - len(seq_numeric)))
64
+
65
+ # 重塑数据为三维数组 (samples, timesteps, features)
66
+ result = np.array(seq_numeric).reshape(1, self.max_length, 1)
67
+
68
+ # 检查结果维度
69
+ if result.ndim != 3:
70
+ print(f"警告: CNN输入维度异常 - {result.ndim},应为3")
71
+ # 强制修正为正确的维度
72
+ result = result.reshape(1, self.max_length, 1)
73
+
74
+ return result
75
+
76
+ except Exception as e:
77
+ print(f"CNN序列处理失败: {str(e)}")
78
+ # 出错时返回全零的三维数组
79
+ return np.zeros((1, self.max_length, 1))
@@ -0,0 +1,283 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import itertools
4
+ from typing import List, Dict, Union
5
+
6
+ class SequenceFeatureExtractor:
7
+ """DNA序列特征提取器"""
8
+
9
+ def __init__(self, seq_length=33):
10
+ """初始化特征提取器"""
11
+ self.bases = ['A', 'T', 'G', 'C']
12
+ self.valid_bases = set('ATGCN')
13
+ self.seq_length = seq_length # 添加序列长度配置
14
+ self.feature_names = self._get_feature_names()
15
+
16
+ def _get_feature_names(self) -> List[str]:
17
+ """
18
+ 返回特征名称列表,包含所有可能的碱基特征
19
+
20
+ Returns:
21
+ features: 特征名称列表
22
+ """
23
+ features = []
24
+
25
+ # 基础特征 (包含N)
26
+ bases = ['A', 'T', 'G', 'C', 'N']
27
+ features.extend(bases)
28
+
29
+ # 3-mer特征
30
+ kmers_3 = [''.join(p) for p in itertools.product(bases, repeat=3)] # 125个特征
31
+ features.extend(kmers_3)
32
+
33
+ # 密码子特征
34
+ codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
35
+ n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
36
+ for i in range(n_codons):
37
+ for codon in codons:
38
+ features.append(f'codon_pos_{i}_{codon}')
39
+
40
+ # GC含量特征
41
+ features.append('gc_content')
42
+
43
+ # 序列复杂度特征
44
+ features.append('sequence_complexity')
45
+
46
+ return features
47
+
48
+ def trim_sequence(self, seq, target_length):
49
+ """
50
+ 从序列两端等量截取,使其达到目标长度
51
+
52
+ Args:
53
+ seq: 原始序列
54
+ target_length: 目标长度
55
+
56
+ Returns:
57
+ 截取后的序列
58
+ """
59
+ if len(seq) <= target_length:
60
+ return seq
61
+
62
+ # 计算需要从每端截取的长度
63
+ excess = len(seq) - target_length
64
+ trim_each_side = excess // 2
65
+
66
+ # 从两端等量截取,保持中心位置不变
67
+ return seq[trim_each_side:len(seq)-trim_each_side]
68
+
69
+ def _preprocess_sequence(self, sequence):
70
+ """
71
+ 将DNA序列转换为特征向量
72
+
73
+ Args:
74
+ sequence: DNA序列
75
+
76
+ Returns:
77
+ feature_vector: 特征向量
78
+ """
79
+ try:
80
+ feature_names = self.feature_names
81
+
82
+ if pd.isna(sequence) or not isinstance(sequence, str):
83
+ sequence = str(sequence)
84
+ sequence = sequence.upper().replace('U', 'T') # 统一为大写字母
85
+
86
+ # 如果序列长度不等于目标长度,进行截取或填充
87
+ if len(sequence) > self.seq_length:
88
+ sequence = self.trim_sequence(sequence, self.seq_length)
89
+ else:
90
+ sequence = sequence[:self.seq_length].ljust(self.seq_length, 'N')
91
+
92
+ # 初始化特征字典
93
+ features = {
94
+ 'A': 0,
95
+ 'T': 0,
96
+ 'G': 0,
97
+ 'C': 0,
98
+ 'N': 0
99
+ }
100
+ kmer_features = {}
101
+
102
+ # 碱基组成
103
+ for base in ['A', 'T', 'G', 'C', 'N']:
104
+ features[base] = sequence.count(base) / self.seq_length
105
+
106
+ # 3-mer特征
107
+ for kmer in [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C', 'N'], repeat=3)]:
108
+ kmer_count = 0
109
+ for i in range(self.seq_length - 2):
110
+ if sequence[i:i+3] == kmer:
111
+ kmer_count += 1
112
+ kmer_features[kmer] = kmer_count / max(1, self.seq_length - 2)
113
+
114
+ # 密码子特征
115
+ codon_features = {}
116
+ codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
117
+ n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
118
+ for i in range(n_codons):
119
+ pos_start = i * 3
120
+ current_codon = sequence[pos_start:pos_start+3]
121
+ for codon in codons:
122
+ codon_features[f'codon_pos_{i}_{codon}'] = 1 if current_codon == codon and 'N' not in current_codon else 0
123
+
124
+ # GC含量
125
+ valid_bases = [b for b in sequence if b != 'N']
126
+ gc_content = (valid_bases.count('G') + valid_bases.count('C')) / len(valid_bases) if valid_bases else 0
127
+
128
+ # 序列复杂度(Shannon熵)
129
+ from collections import Counter
130
+ valid_counts = Counter(valid_bases)
131
+ total_valid = sum(valid_counts.values())
132
+ entropy = 0
133
+ for cnt in valid_counts.values():
134
+ p = cnt / total_valid
135
+ entropy += -p * np.log2(p)
136
+ entropy /= np.log2(4) # 归一化到0-1
137
+
138
+ # 合并所有特征
139
+ all_features = {**features, **kmer_features, **codon_features}
140
+ all_features['gc_content'] = gc_content
141
+ all_features['sequence_complexity'] = entropy
142
+
143
+ # 确保特征顺序一致
144
+ feature_vector = [all_features.get(f, 0.0) for f in feature_names]
145
+
146
+ return feature_vector
147
+ except Exception as e:
148
+ raise ValueError(f"特征提取失败: {str(e)}")
149
+
150
+ def extract_features_batch(self, sequences: List[Union[str, float]]) -> np.ndarray:
151
+ """
152
+ 批量提取特征
153
+
154
+ Args:
155
+ sequences: DNA序列列表
156
+
157
+ Returns:
158
+ np.ndarray: 特征矩阵
159
+ """
160
+ try:
161
+ return np.array([self.extract_features(seq) for seq in sequences])
162
+ except Exception as e:
163
+ raise ValueError(f"批量特征提取失败: {str(e)}")
164
+
165
+ def predict_region_batch(self, data: pd.DataFrame, gb_threshold: float = 0.1) -> pd.DataFrame:
166
+ """
167
+ 批量预测区域序列
168
+
169
+ Args:
170
+ data: DataFrame包含'33bp'和'399bp'列
171
+ gb_threshold: GB模型概率阈值(默认为0.1)
172
+
173
+ Returns:
174
+ DataFrame: 包含预测结果的DataFrame
175
+ """
176
+ results = []
177
+ for idx, row in data.iterrows():
178
+ try:
179
+ # 确保序列是字符串
180
+ seq_33bp = str(row['33bp'])
181
+ seq = str(row['399bp'])
182
+
183
+ # 确保序列长度正确
184
+ seq_33bp = self._preprocess_sequence(seq_33bp)
185
+ seq = self._preprocess_sequence(seq)
186
+
187
+ # 预测
188
+ result = self.predict_region(seq_33bp, seq, gb_threshold)
189
+
190
+ # 添加原始数据的其他列
191
+ for col in data.columns:
192
+ if col not in ['33bp', '399bp']:
193
+ result[col] = row[col]
194
+
195
+ results.append(result)
196
+
197
+ except Exception as e:
198
+ print(f"处理索引 {idx} 的序列时出错: {str(e)}")
199
+ continue
200
+
201
+ return pd.DataFrame(results)
202
+
203
+ def extract_features(self, sequence: str) -> list:
204
+ """
205
+ 提取序列特征
206
+
207
+ Args:
208
+ sequence: DNA序列
209
+
210
+ Returns:
211
+ list: 特征向量
212
+ """
213
+ try:
214
+ # 确保输入是字符串
215
+ if not isinstance(sequence, str):
216
+ sequence = str(sequence)
217
+
218
+ # 大写并替换U为T
219
+ sequence = sequence.upper().replace('U', 'T')
220
+
221
+ # 如果序列长度不等于目标长度,进行截取
222
+ if len(sequence) != self.seq_length:
223
+ sequence = self.trim_sequence(sequence, self.seq_length)
224
+
225
+ # 初始化特征列表
226
+ features = []
227
+
228
+ try:
229
+ # 基础特征 (碱基频率)
230
+ for base in ['A', 'T', 'G', 'C', 'N']:
231
+ features.append(sequence.count(base) / len(sequence))
232
+
233
+ # 3-mer特征
234
+ for kmer in [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C', 'N'], repeat=3)]:
235
+ count = 0
236
+ for i in range(len(sequence) - 2):
237
+ if sequence[i:i+3] == kmer:
238
+ count += 1
239
+ features.append(count / max(1, len(sequence) - 2))
240
+
241
+ # 密码子特征
242
+ codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)]
243
+ n_codons = len(sequence) // 3
244
+ for i in range(n_codons):
245
+ pos_start = i * 3
246
+ current_codon = sequence[pos_start:pos_start+3]
247
+ for codon in codons:
248
+ features.append(1 if current_codon == codon and 'N' not in current_codon else 0)
249
+
250
+ # GC含量
251
+ valid_bases = [b for b in sequence if b != 'N']
252
+ gc_content = (valid_bases.count('G') + valid_bases.count('C')) / len(valid_bases) if valid_bases else 0
253
+ features.append(gc_content)
254
+
255
+ # 序列复杂度
256
+ from collections import Counter
257
+ valid_counts = Counter(valid_bases)
258
+ total_valid = sum(valid_counts.values())
259
+ entropy = 0
260
+ if total_valid > 0: # 避免除零错误
261
+ for cnt in valid_counts.values():
262
+ if cnt > 0: # 避免log(0)
263
+ p = cnt / total_valid
264
+ entropy += -p * np.log2(p)
265
+ entropy /= np.log2(4) if len(valid_counts) > 0 else 1 # 归一化到0-1,避免除零
266
+ features.append(entropy)
267
+
268
+ # 确保返回的是一维列表或数组
269
+ if isinstance(features, np.ndarray) and features.ndim > 1:
270
+ features = features.flatten()
271
+
272
+ return features
273
+
274
+ except Exception as e:
275
+ print(f"特征计算过程出错: {str(e)}")
276
+ # 如果计算过程出错,返回正确长度的全零特征向量
277
+ expected_length = 5 + 125 + (len(sequence) // 3) * 64 + 2 # 根据特征提取逻辑计算特征向量长度
278
+ return [0.0] * expected_length
279
+
280
+ except Exception as e:
281
+ print(f"特征提取失败: {str(e)}")
282
+ # 返回一个空列表,调用方需处理这种情况
283
+ return []