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

Separate unprofiled samples into separate group in comparison tab #3760

Merged
merged 1 commit into from
Nov 19, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
212 changes: 142 additions & 70 deletions src/pages/resultsView/ResultsViewPageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,9 @@ export type ModifyQueryParams = {

interface IResultsViewExclusionSettings {
setExcludeGermlineMutations: (value: boolean) => void;
setHideUnprofiledSamples: (value: boolean) => void;
setHideUnprofiledSamples: (
value: IAnnotationFilterSettings['hideUnprofiledSamples']
) => void;
}

/* fields and methods in the class below are ordered based on roughly
Expand Down Expand Up @@ -703,17 +705,24 @@ export class ResultsViewPageStore

@computed
public get hideUnprofiledSamples() {
return this.urlWrapper.query.hide_unprofiled_samples === 'true';
const value = this.urlWrapper.query.hide_unprofiled_samples;
if (value === 'any' || value === 'totally') {
return value;
} else {
return false;
}
}

@action.bound
public setHideUnprofiledSamples(e: boolean) {
public setHideUnprofiledSamples(
e: IAnnotationFilterSettings['hideUnprofiledSamples']
) {
this.urlWrapper.updateURL({
hide_unprofiled_samples: e.toString(),
hide_unprofiled_samples: (e || false).toString(),
});
}

public set hideUnprofiledSamples(include: boolean) {
public set hideUnprofiledSamples(include: 'any' | 'totally' | false) {
this.setHideUnprofiledSamples(include);
}

Expand Down Expand Up @@ -930,9 +939,8 @@ export class ResultsViewPageStore
await: () => [
this.studyIds,
this.filteredAlteredSamples,
this.filteredUnalteredSamples,
this.filteredAlteredPatients,
this.filteredUnalteredPatients,
this.filteredUnalteredAndProfiledSamples,
this.totallyUnprofiledSamples,
this.filteredSamples,
this.oqlFilteredCaseAggregatedDataByUnflattenedOQLLine,
this.defaultOQLQuery,
Expand All @@ -946,8 +954,10 @@ export class ResultsViewPageStore
this.usePatientLevelEnrichments,
this.studyIds.result!,
this.filteredAlteredSamples.result!,
this.filteredUnalteredSamples.result!,
this.queryContainsOql
this.filteredUnalteredAndProfiledSamples.result!,
this.totallyUnprofiledSamples.result!,
this.queryContainsOql,
this.hideUnprofiledSamples
)
);

Expand Down Expand Up @@ -2208,52 +2218,25 @@ export class ResultsViewPageStore
),
});

readonly filteredUnalteredSampleKeys = remoteData({
await: () => [this.filteredSamples, this.oqlFilteredCaseAggregatedData],
invoke: () => {
const caseAggregatedData = this.oqlFilteredCaseAggregatedData
.result!;
return Promise.resolve(
this.filteredSamples
.result!.map(s => s.uniqueSampleKey)
.filter(
sampleKey =>
!caseAggregatedData.samples[sampleKey].length
)
);
},
});

readonly filteredUnalteredSamples = remoteData<Sample[]>(
{
await: () => [
this.sampleKeyToSample,
this.filteredUnalteredSampleKeys,
],
invoke: () => {
const unalteredSamples: Sample[] = [];
this.filteredUnalteredSampleKeys.result!.forEach(a =>
unalteredSamples.push(this.sampleKeyToSample.result![a])
);
return Promise.resolve(unalteredSamples);
},
},
[]
);

readonly filteredUnalteredPatients = remoteData({
readonly filteredUnalteredAndProfiledSamples = remoteData({
await: () => [
this.filteredPatients,
this.filteredSamples,
this.oqlFilteredCaseAggregatedData,
this.totallyUnprofiledSamples,
],
invoke: () => {
const caseAggregatedData = this.oqlFilteredCaseAggregatedData
.result!;
const unprofiledSamples = _.keyBy(
this.totallyUnprofiledSamples.result!,
s => s.uniqueSampleKey
);
return Promise.resolve(
this.filteredPatients.result!.filter(
patient =>
!caseAggregatedData.patients[patient.uniquePatientKey]
.length
this.filteredSamples.result!.filter(
sample =>
!caseAggregatedData.samples[sample.uniqueSampleKey]
.length &&
!(sample.uniqueSampleKey in unprofiledSamples)
)
);
},
Expand Down Expand Up @@ -3853,38 +3836,127 @@ export class ResultsViewPageStore
readonly filteredSamples = remoteData({
await: () => [
this.samples,
this.coverageInformation,
this.genes,
this.selectedMolecularProfiles,
this.unprofiledSampleKeyToSample,
this.totallyUnprofiledSamples,
],
invoke: () => {
if (this.hideUnprofiledSamples) {
// only show samples that are profiled in every gene in every selected profile
const genes = this.genes.result!;
const coverageInfo = this.coverageInformation.result!;
const queryProfileIds = this.selectedMolecularProfiles.result!.map(
p => p.molecularProfileId
);
let unprofiledSampleKeys: { [key: string]: Sample };
if (this.hideUnprofiledSamples === 'any') {
unprofiledSampleKeys = this.unprofiledSampleKeyToSample
.result!;
} else if (this.hideUnprofiledSamples === 'totally') {
unprofiledSampleKeys = _.keyBy(
this.totallyUnprofiledSamples.result!,
s => s.uniqueSampleKey
);
}
return Promise.resolve(
this.samples.result!.filter(sample => {
return _.every(genes, gene => {
return _.every(
isSampleProfiledInMultiple(
sample.uniqueSampleKey,
queryProfileIds,
coverageInfo,
gene.hugoGeneSymbol
)
);
});
})
this.samples.result!.filter(
s => !(s.uniqueSampleKey in unprofiledSampleKeys)
)
);
} else {
return Promise.resolve(this.samples.result!);
}
},
});

readonly unprofiledSamples = remoteData({
await: () => [
this.samples,
this.coverageInformation,
this.genes,
this.selectedMolecularProfiles,
],
invoke: () => {
// Samples that are unprofiled for at least one (gene, profile)
const genes = this.genes.result!;
const coverageInfo = this.coverageInformation.result!;
const studyToSelectedMolecularProfileIds = _.mapValues(
_.groupBy(
this.selectedMolecularProfiles.result!,
p => p.studyId
),
profiles => profiles.map(p => p.molecularProfileId)
);

return Promise.resolve(
this.samples.result!.filter(sample => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this condition could use a comment. like, i don't understand why we use "isSampleProfiledInMultiple" why multiple?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, seems like a very expensive operation, right? samples x genes x more . maybe no way around it.

Copy link
Contributor Author

@adamabeshouse adamabeshouse May 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSampleProfiledInMultiple is a method that checks whether a sample is profiled in multiple profiles, in a way that's more efficient than checking a single profile multiple times. the logic here (I'll add a comment because it is really hard to read) is we want to only consider a sample unprofiled if it is unprofiled in every gene in every profile. yes, it is expensive - samples x genes x profiles. the worst case is when everything is profiled, otherwise the _.every and the _.some would both return early. I cant think of a better way to check it unfortunately, let me know if you can

// Only look at profiles for this sample's study - doesn't
// make sense to look at profiles for other studies, which
// the sample certainly is not part of.
const profileIds =
studyToSelectedMolecularProfileIds[sample.studyId];

// Sample that is unprofiled for some gene
return _.some(genes, gene => {
// for some profile
return !_.every(
isSampleProfiledInMultiple(
sample.uniqueSampleKey,
profileIds,
coverageInfo,
gene.hugoGeneSymbol
)
);
});
})
);
},
});

readonly totallyUnprofiledSamples = remoteData({
await: () => [
this.unprofiledSamples,
this.coverageInformation,
this.genes,
this.selectedMolecularProfiles,
],
invoke: () => {
const genes = this.genes.result!;
const coverageInfo = this.coverageInformation.result!;
const studyToSelectedMolecularProfileIds = _.mapValues(
_.groupBy(
this.selectedMolecularProfiles.result!,
p => p.studyId
),
profiles => profiles.map(p => p.molecularProfileId)
);

return Promise.resolve(
this.unprofiledSamples.result!.filter(sample => {
// Only look at profiles for this sample's study - doesn't
// make sense to look at profiles for other studies, which
// the sample certainly is not part of.
const profileIds =
studyToSelectedMolecularProfileIds[sample.studyId];

// Among unprofiled samples, pick out samples that are unprofiled for EVERY gene ...(gene x profile)
return _.every(genes, gene => {
// for EVERY profile
return !_.some(
isSampleProfiledInMultiple(
sample.uniqueSampleKey,
profileIds,
coverageInfo,
gene.hugoGeneSymbol
)
);
});
})
);
},
});

readonly unprofiledSampleKeyToSample = remoteData({
await: () => [this.unprofiledSamples],
invoke: () =>
Promise.resolve(
_.keyBy(this.unprofiledSamples.result!, s => s.uniqueSampleKey)
),
});

readonly filteredSampleKeyToSample = remoteData({
await: () => [this.filteredSamples],
invoke: () =>
Expand Down
46 changes: 37 additions & 9 deletions src/pages/resultsView/comparison/ResultsViewComparisonUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ComplexKeyMap from '../../../shared/lib/complexKeyDataStructures/ComplexK
import { SingleGeneQuery } from '../../../shared/lib/oql/oql-parser';
import oql_parser from '../../../shared/lib/oql/oql-parser';
import _ from 'lodash';
import { DEFAULT_NA_COLOR } from 'shared/lib/Colors';
import { SessionGroupData } from 'shared/api/session-service/sessionServiceModels';

export type ResultsViewComparisonGroup = ComparisonGroup & {
Expand All @@ -28,6 +29,7 @@ export const ALTERED_COLOR = '#dc3912';
export const UNALTERED_COLOR = '#3366cc';
export const ALTERED_GROUP_NAME = 'Altered group';
export const UNALTERED_GROUP_NAME = 'Unaltered group';
export const UNPROFILED_GROUP_NAME = 'Unprofiled group';

// compute/add members to SessionGroupData to make them
// into complete ComparisonGroup objects
Expand Down Expand Up @@ -69,11 +71,14 @@ export function getAlteredVsUnalteredGroups(
patientLevel: boolean,
studyIds: string[],
alteredSamples: Sample[],
unalteredSamples: Sample[],
queryContainsOql: boolean
unalteredAndProfiledSamples: Sample[],
totallyUnprofiledSamples: Sample[],
queryContainsOql: boolean,
hideUnprofiledSamples: 'any' | 'totally' | false
): SessionGroupData[] {
return [
{
const ret = [];
if (alteredSamples.length > 0) {
ret.push({
name: ALTERED_GROUP_NAME,
description: `${
patientLevel ? 'Patients' : 'Samples'
Expand All @@ -83,19 +88,42 @@ export function getAlteredVsUnalteredGroups(
studies: getStudiesAttr(alteredSamples, alteredSamples),
origin: studyIds,
color: ALTERED_COLOR,
},
{
});
}
if (unalteredAndProfiledSamples.length > 0) {
ret.push({
name: UNALTERED_GROUP_NAME,
description: `${
patientLevel ? 'Patients' : 'Samples'
} without any alterations in ${
queryContainsOql ? 'the OQL specification for ' : ''
}your queried genes in the selected profiles.`,
studies: getStudiesAttr(unalteredSamples, unalteredSamples),
studies: getStudiesAttr(
unalteredAndProfiledSamples,
unalteredAndProfiledSamples
),
origin: studyIds,
color: UNALTERED_COLOR,
},
];
});
}
if (
totallyUnprofiledSamples.length > 0 &&
hideUnprofiledSamples !== 'totally'
) {
ret.push({
name: UNPROFILED_GROUP_NAME,
description: `${
patientLevel ? 'Patients' : 'Samples'
} not profiled in any queried genes in any selected profiles.`,
studies: getStudiesAttr(
totallyUnprofiledSamples,
totallyUnprofiledSamples
),
origin: studyIds,
color: DEFAULT_NA_COLOR,
});
}
return ret;
}

export function getAlteredByOncoprintTrackGroups(
Expand Down
Loading