degenerate-input-filtering — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited degenerate-input-filtering (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Degenerate inputs are data points that carry no statistical information: constant-value features, all-NaN columns, single-sequence alignments, empty files, and similar edge cases. When these reach a statistical test or model, the result is meaningless -- a correlation of NaN, a p-value of 1.0, a score of 0.0, or an outright crash. This guide establishes the mandatory practice of detecting and removing such inputs before any analysis, and of reporting every removal so that downstream consumers know the effective sample size.
A data point is degenerate when it cannot contribute to the statistic being computed. The root cause is always the same: the input lacks the variation or completeness that the method requires.
| Type | Example | Why It Fails |
|---|---|---|
| Constant-value feature | Gene with identical expression across all samples | Variance = 0; correlation, t-test, fold-change are all undefined |
| All-NaN feature | Column with no valid observations | Every aggregation returns NaN |
| Single-sequence alignment | BLAST result with one sequence | Score = 0.0; no pairwise comparison is possible |
| Empty file | 0-byte FASTA or CSV | Parser crashes or returns an empty frame |
| Single value after grouping | One sample in a treatment group | Within-group variance is undefined; group comparison is meaningless |
| Zero-length sequence | Empty FASTA entry | Alignment and k-mer tools fail or produce nonsense |
| Near-constant feature | Gene with one outlier and N-1 identical values | Technically non-zero variance but correlation is dominated by a single point |
Many numerical libraries do not raise errors on degenerate input. Instead they return sentinel values:
numpy.corrcoef returns nan for constant columns without warning.scipy.stats.spearmanr returns (nan, nan) when one array is constant.scipy.stats.ttest_ind returns (nan, nan) for zero-variance groups.These NaN values propagate silently through pipelines, contaminating aggregated statistics, heatmaps, volcano plots, and ranked gene lists. By the time a researcher notices the problem, it may be unclear which upstream step introduced the NaN.
Filtering without reporting is nearly as harmful as not filtering. When items are silently dropped, the effective sample or feature count differs from what the user expects, leading to incorrect power calculations and misleading summary statistics. Every filtering step must print:
Use this tree to determine which filtering checks apply to your data before running a statistical analysis:
Is the input tabular (DataFrame)?
├── Yes
│ ├── Are there columns with zero unique values (all NaN)? → Remove, report count
│ ├── Are there columns with exactly one unique value (constant)? → Remove, report count
│ ├── Are there columns with fewer than N valid observations? → Remove, report count
│ └── Are there near-constant columns (1 outlier, rest identical)? → Flag or remove
└── No (file list, sequence set, etc.)
├── Are there empty files (0 bytes)? → Skip, report count
├── Are there single-entry collections (e.g., 1-sequence alignment)? → Skip, report count
└── Are there zero-length entries within a file? → Filter entries, report count| Scenario | Recommended Check | Threshold |
|---|---|---|
| Gene expression matrix before DE analysis | Remove zero-variance genes, remove genes detected in fewer than N samples | N = max(3, 10% of samples) |
| Correlation analysis between two feature sets | Remove features constant in either set; require >= 10 shared non-NaN pairs | nunique >= 2 in both vectors; shared observations >= 10 |
| Multiple sequence alignment scoring | Skip alignments with < 2 sequences | Sequence count >= 2 |
| Survival analysis with grouped covariates | Remove groups with < 2 events | Events per group >= 2 |
| PCA or clustering on a feature matrix | Remove zero-variance and all-NaN features | Variance > 0 and at least 1 non-NaN value |
| Batch correction (e.g., ComBat) | Remove features absent in any batch | Feature detected in all batches |
nunique() >= 2 for every column before computing correlations. Use the reference implementation below.max(3, int(0.1 * n_samples)) as a floor. Adjust upward for small studies where even 10% is fewer than 3.var() returns NaN, which is not > 0, so the column is dropped -- but the reason logged is "zero variance" rather than "all NaN," which is misleading.The following sequential process should be applied before any statistical analysis.
nunique() <= 1 (after excluding NaN).import pandas as pd
import numpy as np
def filter_degenerate(data, context="features"):
"""Filter degenerate data points and report what was removed.
Always call this BEFORE statistical analysis.
"""
n_before = len(data)
# Filter based on data type
if isinstance(data, pd.DataFrame):
# Remove constant columns (zero variance)
non_constant = data.loc[:, data.nunique() > 1]
# Remove all-NaN columns
non_nan = non_constant.dropna(axis=1, how='all')
filtered = non_nan
elif isinstance(data, list):
# Remove None, empty, and zero-length items
filtered = [x for x in data if x is not None and len(x) > 0]
else:
filtered = data
n_after = len(filtered) if not isinstance(filtered, pd.DataFrame) else filtered.shape[1]
n_removed = n_before if not isinstance(data, pd.DataFrame) else data.shape[1]
n_removed = n_removed - n_after
# MANDATORY: Print the count
print(f"Degenerate {context} filtered: {n_removed} / {n_before} removed")
print(f"Remaining {context}: {n_after}")
return filtered# Filter single-sequence alignments (score=0.0 is meaningless)
alignment_scores = {}
for aln_file in alignment_files:
n_seqs = count_sequences(aln_file)
if n_seqs < 2:
print(f"Skipping {aln_file}: only {n_seqs} sequence(s)")
continue
score = compute_alignment_score(aln_file)
alignment_scores[aln_file] = score
print(f"Filtered {len(alignment_files) - len(alignment_scores)} single-sequence alignments")# Filter genes with zero variance (constant expression)
gene_vars = expression_df.var(axis=1)
zero_var = (gene_vars == 0).sum()
print(f"Genes with zero variance: {zero_var}")
expression_filtered = expression_df.loc[gene_vars > 0]
# Filter genes detected in too few samples
min_samples = max(3, int(0.1 * expression_df.shape[1])) # At least 10% of samples
detected = (expression_df > 0).sum(axis=1)
low_detection = (detected < min_samples).sum()
print(f"Genes detected in < {min_samples} samples: {low_detection}")
expression_filtered = expression_filtered.loc[detected >= min_samples]from scipy.stats import spearmanr
# Filter features before computing correlations
valid_correlations = {}
skipped_constant = 0
skipped_few_values = 0
for feature in features:
x = data_x[feature].dropna()
y = data_y[feature].dropna()
# Check for constant values
if x.nunique() < 2 or y.nunique() < 2:
skipped_constant += 1
continue
# Check for sufficient data points
common = x.index.intersection(y.index)
if len(common) < 10:
skipped_few_values += 1
continue
rho, pval = spearmanr(x[common], y[common])
valid_correlations[feature] = rho
print(f"Skipped (constant values): {skipped_constant}")
print(f"Skipped (< 10 valid pairs): {skipped_few_values}")
print(f"Valid correlations computed: {len(valid_correlations)}")A well-formatted filtering report looks like this:
Input: 18500 genes
Filtered: 230 genes with zero variance
Filtered: 45 genes with < 3 valid samples
Remaining: 18225 genes for analysisnan-safe-correlation -- Techniques for computing correlations in the presence of missing values; complements this guide's pre-filtering step for correlation inputs.statistical-analysis -- Broader guidance on choosing and applying statistical tests; assumes degenerate inputs have already been removed per this guide.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.