fasta_lake.entrapment#
fasta_lake.entrapment
#
Entrapment generation and conditional FDP estimates.
For N_E entrapment and N_T original-target discoveries, the combined estimate is N_E * (1 + 1/r) / (N_T + N_E), where r is the effective entrapment-to-target search-space ratio at the counting level. Under the equal-chance-type assumptions in Wen et al. (2025), this is an upper estimate in expectation. It is not a per-experiment confidence bound. A zero observed count does not prove that the underlying error rate is zero.
The ratio N_E / (N_T + N_E) is a lower bound and cannot validate successful FDR control. The paired estimator requires an explicit one-to-one pairing of unique target and entrapment entities, scored on a comparable scale. Holding K/R positions while shuffling whole proteins does not establish such a peptide pairing, nor does partitioning an ordinary reference into two random halves establish that one half is absent from the sample.
Empty target or entrapment partitions and zero total discoveries are
undefined. Callers must inspect evaluable before quoting an estimate.
The injection stage, counting unit, conserved matches, ratio definition,
and null comparability must accompany any biological interpretation.
Reference: Wen B, Freestone J, Riffle M, MacCoss MJ, Noble WS, Keich U. Assessment of false discovery rate control in tandem mass spectrometry analysis using entrapment. Nature Methods (2025). https://doi.org/10.1038/s41592-025-02719-x
generate_shuffled_entrapment
#
generate_shuffled_entrapment(source_proteins: dict[str, str], n_proteins: int = 1000, seed: int = 42, prefix: str = 'ENTRAP_SHUFFLED', preserve_cleavage_sites: bool = True) -> dict[str, str]
Generate shuffled entrapment proteins from source sequences.
Each shuffled sequence preserves the amino acid composition of its source protein but randomizes the order. This ensures:
- Same mass distribution as real proteins
- A reproducible sequence perturbation, conditional on seed and input order
This does not guarantee distinct or known-absent peptides. Target overlap, tryptic constraints, homology, and null comparability require validation.
.. note::
preserve_cleavage_sites defaults to True, which holds K and R
in place so each entrapment protein's tryptic peptide lengths match
its source protein's K/R position pattern. This matches the reference
implementation used for the manuscript
(projects/masters_circularity_F1/scripts/make_shuffled_entrapment.py).
Earlier versions of this function did a naive full shuffle. That
preserves the K/R count, so the aggregate peptide-length
distribution is about right, but not the per-protein pairing:
measured on the 512-protein P. furiosus panel, the naive shuffle
reproduced a protein's exact tryptic length multiset for 1 of 512
proteins (0.2%) against 512 of 512 (100%) when K/R are held.
Pass preserve_cleavage_sites=False for the old behaviour.
For lake-scale work use
:func:fasta_lake.entrapment_workflow.generate_entrapment_fasta, which
streams instead of building a dict in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_proteins
|
dict[str, str]
|
Source proteins to shuffle (protein_id -> sequence). |
required |
n_proteins
|
int
|
Number of entrapment proteins to generate. If larger than source pool, all source proteins are used. Default: 1000. |
1000
|
seed
|
int
|
Random seed for reproducibility. Default: 42. |
42
|
prefix
|
str
|
Prefix for entrapment protein IDs. Default: |
'ENTRAP_SHUFFLED'
|
preserve_cleavage_sites
|
bool
|
Hold K/R residues in place while shuffling. Default: |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Entrapment proteins ( |
Source code in fasta_lake/entrapment.py
load_entrapment_fasta
#
Load user-supplied entrapment proteins from a FASTA file.
Optionally prefixes protein IDs with prefix to mark them as
entrapment in downstream analysis.
.. warning::
This builds the whole file into a dict and keys it on the first
header token, so descriptions are discarded and memory scales with
the input. It is fine for an entrapment panel of a few thousand
proteins; it is not usable at lake scale. For streaming, header-
preserving work use
:func:fasta_lake.entrapment_workflow.generate_entrapment_fasta and
:func:fasta_lake.entrapment_workflow.iter_fasta.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fasta_path
|
str or Path
|
Path to entrapment FASTA file. |
required |
prefix
|
str
|
Prefix to add to protein IDs. Set to empty string to keep
original IDs. Default: |
'ENTRAP'
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Entrapment proteins (optionally prefixed ID -> sequence). |
Source code in fasta_lake/entrapment.py
generate_noble_paired
#
generate_noble_paired(proteins: dict[str, str], seed: int = 42) -> tuple[dict[str, str], dict[str, str]]
Noble paired entrapment: split proteins into target and entrapment sets.
The Noble method randomly partitions proteins into two equal-sized groups. Search both against the sample. The entrapment group hit rate estimates FDR for the target group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proteins
|
dict[str, str]
|
Full protein database to partition. |
required |
seed
|
int
|
Random seed for reproducibility. |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
target |
dict[str, str]
|
Target proteins (half of input). |
entrapment |
dict[str, str]
|
Entrapment proteins (other half, prefixed with |
Source code in fasta_lake/entrapment.py
estimate_fdp
#
estimate_fdp(entrapment_hits: int, target_hits: int, target_db_size: int, entrapment_db_size: int, method: str = 'combined') -> dict
Estimate False Discovery Proportion using entrapment hits.
Implements the methods from Wen, Keich & Noble (2025) Nature Methods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entrapment_hits
|
int
|
Number of PSMs matching entrapment proteins (N_E). |
required |
target_hits
|
int
|
Number of PSMs matching target proteins (N_T). |
required |
target_db_size
|
int
|
Number of proteins in the target database. |
required |
entrapment_db_size
|
int
|
Number of proteins in the entrapment database. |
required |
method
|
str
|
FDP estimation method:
- |
'combined'
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Source code in fasta_lake/entrapment.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | |
estimate_fdp_paired
#
estimate_fdp_paired(n_target: int, n_entrapment: int, n_entrap_target_undiscovered: int, n_entrap_gt_target: int) -> dict
Wen 2025 Eq 4 paired FDP estimator — a tighter valid upper bound.
The paper's claim is that this is tighter than the COMBINED method, not that it is the tightest bound available: it also introduces a "k-matched" generalization (r = k) and proves both valid. Verbatim, §2.2 heading: "The paired estimation method yields a tighter upper bound on the FDP".
Implements the paired estimation method of Wen, Freestone, Riffle, MacCoss, Noble & Keich (2025), Nature Methods 22, 1454-1463::
FDP* = (N_E + N_{E>=s>T} + 2 * N_{E>T>=s}) / (N_T + N_E)
Unlike the combined method (:func:estimate_fdp), the paired method uses
the score-order relationship between each entrapment peptide and its
unique paired target peptide to reduce conservative bias while remaining
an upper estimate in expectation under the paper's assumptions. It requires
r = 1 and a verified one-to-one pairing of distinct peptide entities.
A whole-protein K/R-preserving shuffle alone does not establish this pairing.
Target and entrapment scores must be comparable and obtained in the
declared common search experiment.
Partitioning of the N_E discovered entrapment peptides:
n_entrap_target_undiscovered=N_{E>=s>T}: entrapment peptide discovered, its paired target peptide NOT discovered (target score < s).n_entrap_gt_target=N_{E>T>=s}: entrapment peptide discovered AND its paired target also discovered, but the entrapment scored higher.- the remaining discovered entrapment peptides (paired target discovered and scored at least as high) contribute only their base count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_target
|
int
|
|
required |
n_entrapment
|
int
|
|
required |
n_entrap_target_undiscovered
|
int
|
|
required |
n_entrap_gt_target
|
int
|
|
required |
Returns:
| Type | Description |
|---|---|
dict
|
|
Notes
Undefined is not zero: when nothing was discovered (N_T + N_E == 0)
the estimate is None and evaluable is False — the test could
not run, which is not a pass. See the module docstring.
Source code in fasta_lake/entrapment.py
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
validate_entrapment_fdr
#
validate_entrapment_fdr(entrapment_hits: int, target_hits: int, target_db_size: int, entrapment_db_size: int, expected_fdr: float = 0.01) -> dict
Validate FDR calibration using entrapment analysis.
Uses the combined method (Eq 1, Keich & Noble 2025) which provides a valid upper bound on FDP. If this upper bound is at or below the nominal FDR threshold, the FDR control is validated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entrapment_hits
|
int
|
Number of PSMs matching entrapment proteins. |
required |
target_hits
|
int
|
Number of PSMs matching target proteins. |
required |
target_db_size
|
int
|
Number of proteins in the target database. |
required |
entrapment_db_size
|
int
|
Number of proteins in the entrapment database. |
required |
expected_fdr
|
float
|
Nominal FDR threshold to validate against. Default: 0.01. |
0.01
|
Returns:
| Type | Description |
|---|---|
dict
|
Includes |
Source code in fasta_lake/entrapment.py
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 | |
prepare_entrapment
#
prepare_entrapment(config: EntrapmentConfig, source_proteins: dict[str, str] | None = None) -> dict[str, str]
Prepare entrapment proteins based on configuration.
Dispatcher that calls the appropriate generation method based on
config.method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
EntrapmentConfig
|
Entrapment configuration. |
required |
source_proteins
|
dict[str, str] or None
|
Source proteins for shuffled method. Required for |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Entrapment proteins ready to merge into the search database. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is not recognized or required inputs are missing. |