Add condition-specific mutation names to get_mutations_df
Summary
Add mutation_{condition} columns to Model.get_mutations_df() that re-express each mutation relative to the wildtype sequence of each condition. Currently, all mutations are named relative to the reference condition only, which makes downstream analyses difficult when users need to know what each mutation means in a non-reference condition's native coordinates.
Background
In multidms, all mutations are encoded relative to the reference condition's wildtype sequence. For example, if the reference condition has G at site 3 and condition b has P at site 3, then:
- Mutation
G3R in reference coordinates corresponds to P3R in condition b coordinates
- Mutation
G3P in reference coordinates is an identity (wildtype) for condition b
The current get_mutations_df() output uses reference-relative names exclusively. This is correct for the model internals, but users analyzing specific conditions need to map these back to the condition's native sequence — e.g., to compare predicted mutational effects with independently measured data expressed in that condition's coordinates, or to find Usher tree founder sequences for each homolog.
Motivated by: https://github.com/matsengrp/data-central/pull/84#issuecomment-3974093633
Proposed Approach
Add a mutation_{condition} column for each condition to the DataFrame returned by get_mutations_df(). The mapping uses the existing Data.site_map property, which already stores the wildtype amino acid at each site for every condition.
Key design decisions:
- Per-condition columns in wide format — Keeps the existing wide-format output structure consistent (parallels
beta_{condition}, shift_{condition}, predicted_func_score_{condition} columns).
- Identity mutations are included (e.g.,
P3P) — When a reference mutation targets the wildtype residue of a non-reference condition, show the identity mutation rather than dropping the row. Users can filter these trivially.
- Implemented at
Model.get_mutations_df() level — The mutation-to-condition mapping logic belongs in the model output, not in Data, since it's a presentation concern.
User Interface / API
Programmatic API
model.fit()
mut_df = model.get_mutations_df()
# New columns alongside existing ones:
# mutation_a, mutation_b (one per condition)
# Example row: mutation='G3R', mutation_a='G3R', mutation_b='P3R'
# Example row: mutation='G3P', mutation_a='G3P', mutation_b='P3P'
# Reference condition column is always identical to the index
assert (mut_df["mutation_a"] == mut_df.index).all() # if 'a' is reference
# Filter out identity mutations for a specific condition
non_identity = mut_df[
mut_df["mutation_b"].str[0] != mut_df["mutation_b"].str[-1]
]
Proposed Changes
1. Model.get_mutations_df() in multidms/model.py
File: multidms/model.py (existing)
Add mutation_{condition} columns after beta/shift columns and before predicted functional score columns. The mapping logic:
# For each condition, remap mutation names using site_map
site_map = self._data.site_map
for condition in self._data.conditions:
def remap_mutation(mut_str):
wt, site, aa = self._data._mutparser.parse_mut(mut_str)
cond_wt = site_map.loc[site, condition]
return f"{cond_wt}{site}{aa}"
mutations_df[f"mutation_{condition}"] = [
remap_mutation(m) for m in self._data.mutations
]
For reference-sequence conditions, mutation_{condition} will be identical to the index (since the wildtype is the same). For non-reference conditions, the wildtype character in the mutation string is replaced with the condition's actual wildtype at that site.
2. Column ordering
Place mutation_{condition} columns immediately after the index, before beta_{condition} columns:
mutation (index) | wts | sites | muts | times_seen_* |
mutation_a | mutation_b | beta_a | beta_b | shift_b |
predicted_func_score_a | predicted_func_score_b
Implementation Details
Mutation Remapping Algorithm
For a mutation string like G3R:
- Parse into
(wt='G', site='3', mut='R') using Data._mutparser.parse_mut()
- Look up
site_map.loc['3', condition] to get the condition's wildtype at that site
- Construct
f"{cond_wt}{site}{mut}" → e.g., P3R if condition wildtype is P
Edge cases:
- Reference condition:
cond_wt == wt always, so output matches input
- Identity mutation: When
mut == cond_wt (e.g., G3P where condition has P at site 3), output is P3P. Included by design.
- Sites with identical wildtype across conditions: Output matches input for those sites
Data Dependencies
The mapping relies only on:
Data.site_map — wildtype amino acids per site per condition (already computed during Data.__init__)
Data._mutparser.parse_mut() — existing mutation string parser
No new dependencies required.
Testing Strategy
- Doctest in
get_mutations_df: Add a doctest showing mutation_{condition} columns for a minimal two-condition example with at least one non-identical site.
- Unit test edge cases:
- Reference condition column equals the mutation index
- Non-reference condition correctly remaps wildtype character
- Identity mutations (e.g.,
P3P) appear when expected
- All-identical-wildtype conditions produce identical mutation names
- Integration: Verify that the existing
tests/test_data.py and doctests continue to pass unchanged.
Documentation Updates
Dependencies
None — uses only existing Data properties.
Alternatives Considered
Alternative 1: Long-format output with a single conditional_mutation column
Would require restructuring get_mutations_df() from wide to long format.
Why not chosen: Would break existing downstream code that expects the wide format. The wide format is consistent with how beta_, shift_, and predicted_func_score_ columns are already structured.
Alternative 2: Implement as a standalone Data method
Add Data.mutations_for_condition(condition) that returns remapped mutation names.
Why not chosen: Users expect this information alongside the model output (betas, shifts, predictions). A separate method would require manual joining. However, a utility on Data could be added later if needed independently of the model.
Alternative 3: Exclude identity mutations (show empty string / NaN)
Why not chosen: User preference is to include them. This also avoids ambiguity about what an empty value means (unseen vs. identity).
Success Criteria
Future Work
- A standalone
Data.remap_mutation(mutation, condition) utility for one-off lookups
- Optional filtering of identity mutations via parameter
- Per-condition CSV export method for external tool compatibility
Add condition-specific mutation names to
get_mutations_dfSummary
Add
mutation_{condition}columns toModel.get_mutations_df()that re-express each mutation relative to the wildtype sequence of each condition. Currently, all mutations are named relative to the reference condition only, which makes downstream analyses difficult when users need to know what each mutation means in a non-reference condition's native coordinates.Background
In multidms, all mutations are encoded relative to the reference condition's wildtype sequence. For example, if the reference condition has
Gat site 3 and conditionbhasPat site 3, then:G3Rin reference coordinates corresponds toP3Rin conditionbcoordinatesG3Pin reference coordinates is an identity (wildtype) for conditionbThe current
get_mutations_df()output uses reference-relative names exclusively. This is correct for the model internals, but users analyzing specific conditions need to map these back to the condition's native sequence — e.g., to compare predicted mutational effects with independently measured data expressed in that condition's coordinates, or to find Usher tree founder sequences for each homolog.Motivated by: https://github.com/matsengrp/data-central/pull/84#issuecomment-3974093633
Proposed Approach
Add a
mutation_{condition}column for each condition to the DataFrame returned byget_mutations_df(). The mapping uses the existingData.site_mapproperty, which already stores the wildtype amino acid at each site for every condition.Key design decisions:
beta_{condition},shift_{condition},predicted_func_score_{condition}columns).P3P) — When a reference mutation targets the wildtype residue of a non-reference condition, show the identity mutation rather than dropping the row. Users can filter these trivially.Model.get_mutations_df()level — The mutation-to-condition mapping logic belongs in the model output, not inData, since it's a presentation concern.User Interface / API
Programmatic API
Proposed Changes
1.
Model.get_mutations_df()inmultidms/model.pyFile:
multidms/model.py(existing)Add
mutation_{condition}columns after beta/shift columns and before predicted functional score columns. The mapping logic:For reference-sequence conditions,
mutation_{condition}will be identical to the index (since the wildtype is the same). For non-reference conditions, the wildtype character in the mutation string is replaced with the condition's actual wildtype at that site.2. Column ordering
Place
mutation_{condition}columns immediately after the index, beforebeta_{condition}columns:Implementation Details
Mutation Remapping Algorithm
For a mutation string like
G3R:(wt='G', site='3', mut='R')usingData._mutparser.parse_mut()site_map.loc['3', condition]to get the condition's wildtype at that sitef"{cond_wt}{site}{mut}"→ e.g.,P3Rif condition wildtype isPEdge cases:
cond_wt == wtalways, so output matches inputmut == cond_wt(e.g.,G3Pwhere condition hasPat site 3), output isP3P. Included by design.Data Dependencies
The mapping relies only on:
Data.site_map— wildtype amino acids per site per condition (already computed duringData.__init__)Data._mutparser.parse_mut()— existing mutation string parserNo new dependencies required.
Testing Strategy
get_mutations_df: Add a doctest showingmutation_{condition}columns for a minimal two-condition example with at least one non-identical site.P3P) appear when expectedtests/test_data.pyand doctests continue to pass unchanged.Documentation Updates
get_mutations_dfdocstring to document new columnsDependencies
None — uses only existing
Dataproperties.Alternatives Considered
Alternative 1: Long-format output with a single
conditional_mutationcolumnWould require restructuring
get_mutations_df()from wide to long format.Why not chosen: Would break existing downstream code that expects the wide format. The wide format is consistent with how
beta_,shift_, andpredicted_func_score_columns are already structured.Alternative 2: Implement as a standalone
DatamethodAdd
Data.mutations_for_condition(condition)that returns remapped mutation names.Why not chosen: Users expect this information alongside the model output (betas, shifts, predictions). A separate method would require manual joining. However, a utility on
Datacould be added later if needed independently of the model.Alternative 3: Exclude identity mutations (show empty string / NaN)
Why not chosen: User preference is to include them. This also avoids ambiguity about what an empty value means (unseen vs. identity).
Success Criteria
get_mutations_df()returnsmutation_{condition}columns for every conditionP3Pare includedFuture Work
Data.remap_mutation(mutation, condition)utility for one-off lookups