Robustness
Robustness in fitness landscapes is the flip side of evolvability: it asks not "how can a population improve?" but "how little does fitness change when a population is perturbed?". A landscape with many neutral mutations and large flat regions is robust; one where every mutation is consequential is fragile. Mutational robustness shapes the speed and direction of adaptation: under high mutation rates, selection often favors variants that occupy flat regions of the landscape (a phenomenon termed "survival of the flattest").
This page documents the robustness diagnostics in GraphFLA. They fall into three groups:
- Global neutrality —
neutrality, summarizing how widespread near-neutral mutations are across the entire landscape. - Per-mutation effect distributions —
single_mutation_effectsandall_mutation_effects, which test the significance of specific or all mutations across genetic backgrounds. - Evolvability-enhancing mutations —
evolvability_enhancing_fractionandevolvability_effects, which summarize statistically supported changes in the opportunities for subsequent adaptation.
Overview¶
| API | Purpose |
|---|---|
neutrality |
Fraction of represented neighbor pairs with \|Δf\| ≤ threshold. |
single_mutation_effects |
Per-position binomial test of all allele-pair fitness effects. |
all_mutation_effects |
single_mutation_effects aggregated across every position. |
evolvability_enhancing_fraction |
Fraction of observed directed mutations with a statistically supported EE effect. |
evolvability_effects |
Per-mutation EE statistics and significance. |
Neutrality¶
Calculates the overall neutrality of the landscape.
Landscape neutrality describes the presence of regions where distinct genotypes exhibit equivalent or very similar fitness levels. Mutations that result in negligible or no change in fitness are known as neutral mutations, and interconnected sets of such genotypes form neutral networks (or "effectively neutral" regions). These neutral pathways enable populations to explore a wide array of genetic variations without incurring significant fitness penalties.
This function measures the overall neutrality of the landscape by calculating the proportion of single-point mutations whose absolute fitness effect falls below the specified threshold. A higher neutrality value suggests larger neutral networks or areas where mutations have minimal impact on fitness.
graphfla.analysis.neutrality(
landscape, threshold: float = 0.01
) -> floatReturn the fraction of neighbor pairs within a fitness-difference threshold.
Parameters
-
landscape: Landscape Built fitness landscape, including any retained neutral adjacency.
-
threshold: float, default=0.01 Maximum absolute fitness difference counted as neutral, in input fitness units. This analysis threshold is independent of construction epsilon.
Returns
-
fraction: float Fraction in [0, 1] of represented neighbor pairs satisfying the threshold. Graph and retained neutral neighbors are combined without double-counting an adjacency. Returns NaN if there are no neighbor pairs.
Examples
>>> from graphfla.landscape import BooleanLandscape
>>> from graphfla.analysis import neutrality
>>> landscape = BooleanLandscape().build_from_data(
... ["00", "01", "10", "11"], [0, 1, 2, 4], verbose=False)
>>> neutrality(landscape, threshold=1.0)
0.25
When the landscape has retained neutral adjacency, neutral neighbors stored during construction are included alongside the graph-based neighbors. This ensures that equal-fitness pairs — which have no directed edge — are still counted toward the neutrality metric.
Mutational Robustness¶
On the individual genotype level, mutational robustness refers to the ability of a genotype to preserve its phenotype (fitness) when subjected to mutations. Genotypes exhibiting high mutational robustness can endure a greater proportion of mutational changes with minimal or no adverse effects. Such genotypes often occupy "flatter" areas of the fitness landscape, and under conditions of high mutation rates, selection might favor these robust genotypes through "survival of the flattest".
Per-position significance test¶
Assess the fitness effects of all possible mutations at a single position across all genetic backgrounds.
For every pair of distinct alleles \((A, B)\) at the given position, the function pairs up genotypes sharing the same background and reports the median absolute effect size (normalized by the landscape's fitness standard deviation), the mean effect, and the \(p\)-value of a one-sided binomial test of whether the effect is positive (i.e., \(f(\text{B-background}) > f(\text{A-background})\)) or negative.
graphfla.analysis.single_mutation_effects(
landscape, position: str, test_type: str = "positive"
) -> pd.DataFrameReturn fitness-effect summaries for allele pairs at one position.
Parameters
-
landscape: Landscape Built fitness landscape.
-
position: str Configuration-column label from
landscape.data_types, not a positional column index.-
test_type: (positive, negative), default="positive" The type of significance test to perform. Must be 'positive' or 'negative', i.e. whether a majority of backgrounds show a fitness increase or a decrease under the mutation.
Returns
-
effects: pandas.DataFrame One row per unordered pair of observed alleles, oriented from the earlier to the later sorted allele. Columns are
mutation_from,mutation_to,median_abs_effect(median absolute effect divided by landscape sample SD),mean_effect(signed, in fitness units),p_value(one-sided binomial test),significant(p < 0.05), andposition(column label). An empty result retains these columns; missing matched backgrounds give NaN statistics andsignificant=False.
Examples
>>> from graphfla.landscape import BooleanLandscape
>>> from graphfla.analysis import single_mutation_effects
>>> landscape = BooleanLandscape().build_from_data(
... ["00", "01", "10", "11"], [0, 1, 2, 4], verbose=False)
>>> single_mutation_effects(landscape, "bit_0").mean_effect.tolist()
[2.5]
Notes
Effects are signed as mutation_to minus mutation_from in each shared
genetic background, the same convention as
fitness_effect_distribution.
All-position summary¶
Apply single_mutation_effects across all positions of the landscape, returning a concatenated DataFrame indexed by position and allele pair.
graphfla.analysis.all_mutation_effects(
landscape, test_type: str = "positive"
) -> pd.DataFrameReturn fitness-effect summaries for allele pairs at every position.
Parameters
-
landscape: Landscape Built fitness landscape.
-
test_type: (positive, negative), default="positive" The type of significance test to perform. Must be 'positive' or 'negative', i.e. whether a majority of backgrounds show a fitness increase or a decrease under the mutation.
Returns
-
effects: pandas.DataFrame One row per allele pair per variable position, in configuration-column order, with a RangeIndex. Columns and pair orientation are the same as
single_mutation_effects, includingposition. An empty result retains the same schema.
Examples
>>> from graphfla.landscape import BooleanLandscape
>>> from graphfla.analysis import all_mutation_effects
>>> landscape = BooleanLandscape().build_from_data(
... ["00", "01", "10", "11"], [0, 1, 2, 4], verbose=False)
>>> all_mutation_effects(landscape)[["position", "mean_effect"]].to_dict("list")
{'position': ['bit_0', 'bit_1'], 'mean_effect': [2.5, 1.5]}
Notes
Effects use the same signed target-minus-source convention and tests as
single_mutation_effects; optimization direction does not reverse them.
Evolvability-enhancing Mutations¶
Background dependence can also change the opportunities for subsequent adaptation. A mutation may make other mutations more favorable, even when its own fitness effect is small or negative. Wagner (2023) describes such mutations as evolvability-enhancing (EE).
GraphFLA compares the mean fitness of the two mutational neighborhoods, excluding changes at the mutated position. For a beneficial mutation, the neighborhood increase must exceed its own fitness gain; for a neutral or deleterious mutation, it must exceed zero. The EE fraction counts statistically supported cases among all observed directed mutations. It measures local opportunities for adaptation, without guaranteeing that evolution will reach a fitter peak.
EE fraction¶
graphfla.analysis.evolvability_enhancing_fraction(
landscape, *, fdr=0.01, effect_type="all"
) -> floatReturn the fraction of evolvability-enhancing directed mutations.
Parameters
-
landscape: Landscape Built landscape with unique, nonmissing configurations, finite fitness values and one-site neighbor pairs. Both orientations of each graph pair and retained neutral pair are evaluated once. Discarded vertices and neighbor pairs are not reconstructed. Fitness is negated when
landscape.maximize=Falseso positive effects indicate improvement.-
fdr: float, default=0.01 Benjamini-Hochberg false discovery rate, strictly between 0 and 1. Corrections use all ordered pairs, before selecting an effect type.
-
effect_type: (all, beneficial, deleterious, neutral), default="all" Effects to include in the numerator, classified by the sign of the unrounded fitness change. The denominator always includes every represented ordered neighbor pair. "all" combines all three classes.
Returns
-
fraction: float Significant EE mutations of the selected type divided by all ordered neighbor pairs. Untestable pairs remain in the denominator and are not counted as EE. The value lies in [0, 1] when defined; return NaN if no pair is testable. A type with no EE mutations returns zero if at least one pair in the full landscape is testable.
Examples
An additive landscape has no EE mutations: the neighborhood increase equals the focal mutation's own fitness benefit.
>>> from itertools import product
>>> from graphfla.landscape import BooleanLandscape
>>> from graphfla.analysis import evolvability_enhancing_fraction
>>> X = list(product([0, 1], repeat=3))
>>> landscape = BooleanLandscape()
>>> _ = landscape.build_from_data(X, [sum(x) for x in X], verbose=False)
>>> evolvability_enhancing_fraction(landscape)
0.0
>>> evolvability_enhancing_fraction(landscape, effect_type="beneficial")
0.0
A mutation is evolvability-enhancing (EE) when the increase in mean non-focal neighbor fitness significantly exceeds the larger of zero and its own fitness effect 1. A mutation may be any one-variable change represented in the landscape, including changes in nonbiological data.
References
[1] Wagner, A. Evolvability-enhancing mutations in the fitness landscapes of an RNA and a protein. Nat. Commun. 14, 3624 (2023). https://doi.org/10.1038/s41467-023-39321-8
Raises
-
graphfla.exceptions.NotBuiltError If the landscape has not been built.
-
ValueError If fdr or effect_type is invalid, configurations are missing or duplicated, fitness is nonfinite, or a pair does not differ at one site.
Warns
-
RuntimeWarning If no pair has at least two non-focal neighbors at each endpoint.
Per-mutation EE results¶
graphfla.analysis.evolvability_effects(
landscape, *, fdr=0.01
) -> pd.DataFrameReturn EE statistics for each directed one-site mutation.
Parameters
-
landscape: Landscape Built landscape satisfying the input requirements of
evolvability_enhancing_fraction. The existing graph and retained neutral adjacency define which changes are evaluated.-
fdr: float, default=0.01 Benjamini-Hochberg false discovery rate, strictly between 0 and 1. This changes the EE decisions, not the raw or adjusted p-values.
Returns
-
effects: pandas.DataFrame One row per ordered neighbor pair, sorted by source_id and target_id, with a RangeIndex. An empty result retains the same column schema:
-
source_id,target_id: int Vertex IDs matchinglandscape.get_data().index. -
position,source_allele,target_allele: object Configuration column name and original allele labels. -
effect_type: str "beneficial", "deleterious" or "neutral", using the sign of the unrounded fitness effect in the landscape's optimization direction. -
delta_fitness,delta_neighbor_fitness,excess: float Focal effect, non-focal neighborhood mean difference anddelta_neighbor_fitness - max(0, delta_fitness), in fitness units. -
n_source_neighbors,n_target_neighbors: int Non-focal neighborhood sizes. Both must be at least two for a test. -
p_effect,p_zero: float Two-sided p-values for neighborhood difference equal to the focal effect or zero, respectively. Untestable pairs have NaN values. -
q_effect,q_zero: float BH adjusted p-values, corrected separately over all ordered pairs. Untestable pairs enter each family as p=1 but retain NaN in output. -
is_ee: pandas nullable boolean EE classification; NA for an untestable pair. Positive focal effects use q_effect; other effects use q_zero. A rejection must also have positive excess, allowing for floating-point roundoff. -
status: str "ok" or "insufficient_neighbors".
-
Examples
>>> from itertools import product
>>> from graphfla.landscape import BooleanLandscape
>>> from graphfla.analysis import evolvability_effects
>>> X = list(product([0, 1], repeat=3))
>>> landscape = BooleanLandscape()
>>> _ = landscape.build_from_data(X, [sum(x) for x in X], verbose=False)
>>> effects = evolvability_effects(landscape)
>>> len(effects)
24
>>> bool(effects["is_ee"].any())
False
Each row represents a change between two configurations in a particular background. The reverse change occupies a separate row. Exclude all changes at the focal position from both endpoints' neighborhoods.
Notes
Two-sided one-sample t tests use the sum of both neighborhood population variances (ddof=0), n=min(k_source, k_target) and df=n-1. The strict EE criterion is delta_neighbor_fitness > max(0, delta_fitness).
Neighborhood variability describes differences among configurations, not repeated-measurement uncertainty. This interface does not model experimental errors. Applying the same screening procedure to other domains does not establish statistical calibration for their sampling or dependence structure.
Raises
-
graphfla.exceptions.NotBuiltError If the landscape has not been built.
-
ValueError If fdr or the landscape inputs are invalid.