Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce unnormalized frequencies when data is lacking #1278

Merged
merged 4 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/actions/frequencies.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { debounce } from 'lodash';
import * as types from "./types";
import { timerStart, timerEnd } from "../util/perf";
import { computeMatrixFromRawData, processFrequenciesJSON } from "../util/processFrequencies";
import { computeMatrixFromRawData, checkIfNormalizableFromRawData, processFrequenciesJSON } from "../util/processFrequencies";

export const loadFrequencies = (json) => (dispatch, getState) => {
const { tree, controls } = getState();
const { data, pivots, matrix, projection_pivot, normalizeFrequencies } = processFrequenciesJSON(json, tree, controls);
dispatch({
type: types.LOAD_FREQUENCIES,
frequencies: {loaded: true, version: 1, ...processFrequenciesJSON(json, tree, controls)}
frequencies: {loaded: true, version: 1, data, pivots, matrix, projection_pivot},
normalizeFrequencies
});
};

Expand All @@ -22,17 +24,21 @@ const updateFrequencyData = (dispatch, getState) => {
console.error("Race condition in updateFrequencyData. Frequencies data not in state. Matrix can't be calculated.");
return;
}

const normalizeFrequencies = controls.normalizeFrequencies &&
checkIfNormalizableFromRawData(frequencies.data, frequencies.pivots, tree.nodes, tree.visibility);

const matrix = computeMatrixFromRawData(
frequencies.data,
frequencies.pivots,
tree.nodes,
tree.visibility,
controls.colorScale,
controls.colorBy,
controls.normalizeFrequencies
normalizeFrequencies
);
timerEnd("updateFrequencyData");
dispatch({type: types.FREQUENCY_MATRIX, matrix});
dispatch({type: types.FREQUENCY_MATRIX, matrix, normalizeFrequencies});
};

/* debounce works better than throttle, as it _won't_ update while events are still coming in (e.g. dragging the date slider) */
Expand Down
14 changes: 13 additions & 1 deletion src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { treeJsonToState } from "../util/treeJsonProcessing";
import { entropyCreateState } from "../util/entropyCreateStateFromJsons";
import { determineColorByGenotypeMutType, calcNodeColor } from "../util/colorHelpers";
import { calcColorScale, createVisibleLegendValues } from "../util/colorScale";
import { computeMatrixFromRawData } from "../util/processFrequencies";
import { computeMatrixFromRawData, checkIfNormalizableFromRawData } from "../util/processFrequencies";
import { applyInViewNodesToTree } from "../actions/tree";
import { isColorByGenotype, decodeColorByGenotype, decodeGenotypeFilters, encodeGenotypeFilters } from "../util/getGenotype";
import { getTraitFromNode, getDivFromNode, collectGenotypeStates } from "../util/treeMiscHelpers";
Expand Down Expand Up @@ -829,6 +829,18 @@ export const createStateFromQueryOrJSONs = ({
/* update frequencies if they exist (not done for new JSONs) */
if (frequencies && frequencies.loaded) {
frequencies.version++;

const allowNormalization = checkIfNormalizableFromRawData(
frequencies.data,
frequencies.pivots,
tree.nodes,
tree.visibility
);

if (!allowNormalization) {
controls.normalizeFrequencies = false;
}

frequencies.matrix = computeMatrixFromRawData(
frequencies.data,
frequencies.pivots,
Expand Down
18 changes: 17 additions & 1 deletion src/components/controls/frequency-normalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { withTranslation } from "react-i18next";
import Toggle from "./toggle";
import { controlsWidth } from "../../util/globals";
import { FREQUENCY_MATRIX } from "../../actions/types";
import { computeMatrixFromRawData } from "../../util/processFrequencies";
import { computeMatrixFromRawData, checkIfNormalizableFromRawData } from "../../util/processFrequencies";
import { SidebarSubtitle } from "./styles";

@connect((state) => {
return {
Expand All @@ -17,13 +18,28 @@ import { computeMatrixFromRawData } from "../../util/processFrequencies";
class NormalizeFrequencies extends React.Component {
render() {
const { t } = this.props;

const allowNormalization = this.props.frequencies.loaded && this.props.tree.loaded &&
checkIfNormalizableFromRawData(
this.props.frequencies.data,
this.props.frequencies.pivots,
this.props.tree.nodes,
this.props.tree.visibility
);
if (!allowNormalization) {
return (
<SidebarSubtitle>(Frequencies cannot be normalized)</SidebarSubtitle>
);
}

return (
<div style={{marginBottom: 10, width: controlsWidth, fontSize: 14}}>
<Toggle
display
on={this.props.controls.normalizeFrequencies}
callback={() => {
const normalizeFrequencies = !this.props.controls.normalizeFrequencies;

const matrix = computeMatrixFromRawData(
this.props.frequencies.data,
this.props.frequencies.pivots,
Expand Down
1 change: 1 addition & 0 deletions src/components/controls/miscInfoText.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export const PanelOptionsInfo = (
export const FrequencyInfo = (
<>
<em>Normalize frequencies</em> controls whether the vertical axis represents the entire dataset or only the samples currently visible (e.g. due to filtering).
This option is not available when data is limited to prevent numerical issues.
</>
);
4 changes: 1 addition & 3 deletions src/components/frequencies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'd3-transition';
import { connect } from "react-redux";
import Card from "../framework/card";
import { calcXScale, calcYScale, drawXAxis, drawYAxis, drawProjectionInfo,
areListsEqual, drawStream, processMatrix, parseColorBy, normString } from "./functions";
drawStream, processMatrix, parseColorBy, normString } from "./functions";
import "../../css/entropy.css";

@connect((state) => {
Expand Down Expand Up @@ -50,8 +50,6 @@ class Frequencies extends React.Component {
/* we don't have to check width / height changes here - that's done in componentDidUpdate */
const data = processMatrix({...newProps});
const maxYChange = oldState.maxY !== data.maxY;
const catChange = !areListsEqual(oldState.categories, data.categories);
if (!maxYChange && !catChange) return false;
const chartGeom = this.calcChartGeom(newProps.width, newProps.height);
/* should the y scale be updated? */
let newScales;
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ const Controls = (state = getDefaultControlsState(), action) => {
case types.TOGGLE_TRANSMISSION_LINES:
return Object.assign({}, state, { showTransmissionLines: action.data });

case types.LOAD_FREQUENCIES:
return {...state, normalizeFrequencies: action.normalizeFrequencies};
case types.FREQUENCY_MATRIX: {
if (Object.hasOwnProperty.call(action, "normalizeFrequencies")) {
return Object.assign({}, state, { normalizeFrequencies: action.normalizeFrequencies });
Expand Down
26 changes: 24 additions & 2 deletions src/util/processFrequencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ const assignCategory = (colorScale, categories, node, colorBy, isGenotype) => {
return unassigned_label;
};

// Returns a boolean specifying if frequencies are allowed to be normalized
// Only normalize if minimum frequency is above 0.1%
export const checkIfNormalizableFromRawData = (data, pivots, nodes, visibility) => {
const pivotsLen = pivots.length;
const pivotTotals = new Array(pivotsLen).fill(0);
data.forEach((d) => {
if (visibility[d.idx] === NODE_VISIBLE) {
for (let i = 0; i < pivotsLen; i++) {
pivotTotals[i] += d.values[i];
}
}
});
const minFrequency = Math.min(...pivotTotals);
const allowNormalization = minFrequency > 0.001;
return allowNormalization;
};

export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorScale, colorBy, normalizeFrequencies) => {
/* color scale domain forms the categories in the stream graph */
const categories = colorScale.legendValues.filter((d) => d !== undefined);
Expand Down Expand Up @@ -98,19 +115,24 @@ export const processFrequenciesJSON = (rawJSON, tree, controls) => {
weight: rawJSON[n.name].weight
});
});

const normalizeFrequencies = controls.normalizeFrequencies &&
checkIfNormalizableFromRawData(data, pivots, tree.nodes, tree.visibility);

const matrix = computeMatrixFromRawData(
data,
pivots,
tree.nodes,
tree.visibility,
controls.colorScale,
controls.colorBy,
controls.normalizeFrequencies
normalizeFrequencies
);
return {
data,
pivots,
matrix,
projection_pivot
projection_pivot,
normalizeFrequencies
};
};