Skip to content

Commit 8118bc3

Browse files
committed
Gene-specific chart finalized
Optimization to how groupsamples are created; by using the sample unique id we can create sampleidentifier
1 parent 130c98c commit 8118bc3

File tree

4 files changed

+212
-8
lines changed

4 files changed

+212
-8
lines changed

src/pages/studyView/StudyViewPageStore.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ import {
186186
transformSampleDataToSelectedSampleClinicalData,
187187
updateCustomIntervalFilter,
188188
invokeGenomicDataCount,
189+
invokeGenomicDataCountIncludeSampleid,
190+
groupSamplesByMutationStatus,
189191
invokeMutationDataCount,
190192
invokeNamespaceDataCount,
191193
invokeGenericAssayDataCount,
@@ -1635,6 +1637,81 @@ export class StudyViewPageStore
16351637
});
16361638
}
16371639

1640+
private createGeneSpecificComparisonSession(
1641+
chartMeta: ChartMeta,
1642+
clinicalAttributeValues: ClinicalDataCountSummary[],
1643+
statusCallback: (phase: LoadingPhase) => void
1644+
): Promise<string> {
1645+
statusCallback(LoadingPhase.DOWNLOADING_GROUPS);
1646+
1647+
return new Promise<string>(resolve => {
1648+
onMobxPromise([this.selectedSamples], async () => {
1649+
const chartInfo = this._geneSpecificChartMap.get(
1650+
chartMeta.uniqueKey
1651+
);
1652+
if (!chartInfo || !this.hasFilteredSamples) return;
1653+
const includeSampleIds = true;
1654+
1655+
const data: any[] = await invokeGenomicDataCountIncludeSampleid(
1656+
chartInfo,
1657+
this.filters,
1658+
includeSampleIds
1659+
);
1660+
1661+
const lcValueToColor = _.keyBy(clinicalAttributeValues, d =>
1662+
d.value.toLowerCase()
1663+
);
1664+
1665+
/**
1666+
* Step 1:
1667+
* Normalize counts into { group, SampleIdentifier } pairs
1668+
*/
1669+
const groupedSamples = groupSamplesByMutationStatus(
1670+
data,
1671+
this.studyIds
1672+
);
1673+
1674+
/**
1675+
* Step 2:
1676+
* Build SessionGroupData objects for each group, using the sample identifiers from step 1
1677+
*/
1678+
const groups: SessionGroupData[] = _.chain(
1679+
clinicalAttributeValues
1680+
)
1681+
.map(attrVal => {
1682+
const sampleIdentifiers: SampleIdentifier[] =
1683+
groupedSamples[attrVal.value];
1684+
if (!sampleIdentifiers?.length) return null;
1685+
1686+
return getGroupParameters(
1687+
attrVal.value,
1688+
sampleIdentifiers,
1689+
this.studyIds,
1690+
lcValueToColor[attrVal.value.toLowerCase()]?.color
1691+
);
1692+
})
1693+
.compact()
1694+
.slice(
1695+
0,
1696+
doesChartHaveComparisonGroupsLimit(chartMeta)
1697+
? MAX_GROUPS_IN_SESSION
1698+
: undefined
1699+
)
1700+
.value();
1701+
statusCallback(LoadingPhase.CREATING_SESSION);
1702+
1703+
// create session and get id
1704+
const { id } = await comparisonClient.addComparisonSession({
1705+
groups,
1706+
clinicalAttributeName: chartMeta.displayName,
1707+
origin: this.studyIds,
1708+
});
1709+
1710+
resolve(id);
1711+
});
1712+
});
1713+
}
1714+
16381715
private createCnaGeneComparisonSession(
16391716
chartMeta: ChartMeta,
16401717
hugoGeneSymbols: string[],
@@ -2069,13 +2146,9 @@ export class StudyViewPageStore
20692146
chartMeta.uniqueKey
20702147
);
20712148
if (isGeneSpecificChart) {
2072-
const chartInfo = this._geneSpecificChartMap.get(
2073-
chartMeta.uniqueKey
2074-
)!;
2075-
2076-
comparisonId = await this.createCnaGeneComparisonSession(
2149+
comparisonId = await this.createGeneSpecificComparisonSession(
20772150
chartMeta,
2078-
[chartInfo.hugoGeneSymbol],
2151+
params.clinicalAttributeValues!,
20792152
statusCallback
20802153
);
20812154
} else {

src/pages/studyView/StudyViewUtils.spec.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
updateCustomIntervalFilter,
6767
updateGeneQuery,
6868
updateSavedUserPreferenceChartIds,
69+
groupSamplesByMutationStatus,
6970
} from 'pages/studyView/StudyViewUtils';
7071
import {
7172
CancerStudy,
@@ -3656,6 +3657,64 @@ describe('StudyViewUtils', () => {
36563657
});
36573658
});
36583659

3660+
describe.only('groupSamplesByMutationStatus', () => {
3661+
const data = [
3662+
{
3663+
counts: [
3664+
{
3665+
value: 'NOT_PROFILED',
3666+
sampleIds: ['ignore_me_001'],
3667+
},
3668+
],
3669+
},
3670+
{
3671+
counts: [
3672+
{
3673+
value: 'Missense_Mutation',
3674+
sampleIds: ['study_ABC_123', 'study_DEF_456'],
3675+
},
3676+
],
3677+
},
3678+
{
3679+
counts: [
3680+
{
3681+
value: 'NOT_MUTATED',
3682+
sampleIds: ['study_GHI_789'],
3683+
},
3684+
],
3685+
},
3686+
] as any;
3687+
3688+
const studyId: string[] = ['study_ABC', 'study_DEF', 'study_GHI'];
3689+
3690+
it('groups and creates SampleIdeifier by mutation status correctly', () => {
3691+
const result = groupSamplesByMutationStatus(data, studyId);
3692+
assert.deepEqual(result, {
3693+
MUTATED: [
3694+
{
3695+
studyId: 'study_ABC',
3696+
sampleId: '123',
3697+
},
3698+
{
3699+
studyId: 'study_DEF',
3700+
sampleId: '456',
3701+
},
3702+
],
3703+
NOT_MUTATED: [
3704+
{
3705+
studyId: 'study_GHI',
3706+
sampleId: '789',
3707+
},
3708+
],
3709+
});
3710+
});
3711+
3712+
it('handles missing or empty counts safely', () => {
3713+
const emptyData = [{}, { counts: [] }];
3714+
expect(groupSamplesByMutationStatus(emptyData, [])).toEqual({});
3715+
});
3716+
});
3717+
36593718
describe('getFilteredStudiesWithSamples', () => {
36603719
const samples: Sample[] = [
36613720
{

src/pages/studyView/StudyViewUtils.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ import { MultiSelectionTableRow } from './table/MultiSelectionTable';
125125
import Survival from 'pages/groupComparison/Survival';
126126
import { StructVarMultiSelectionTableRow } from './table/StructuralVariantMultiSelectionTable';
127127
import { ObservableMap } from 'mobx';
128+
import { boolean } from 'yargs';
128129

129130
// Cannot use ClinicalDataTypeEnum here for the strong type. The model in the type is not strongly typed
130131
export enum ClinicalDataTypeEnum {
@@ -4252,6 +4253,78 @@ export async function invokeGenericAssayDataCount(
42524253
return undefined;
42534254
}
42544255

4256+
export async function invokeGenomicDataCountIncludeSampleid(
4257+
chartInfo: GenomicChart,
4258+
filters: StudyViewFilter,
4259+
includeSampleIds: boolean
4260+
) {
4261+
let result = [];
4262+
let params = {
4263+
genomicDataCountFilter: {
4264+
genomicDataFilters: [
4265+
{
4266+
hugoGeneSymbol: chartInfo.hugoGeneSymbol,
4267+
profileType: chartInfo.profileType,
4268+
} as GenomicDataFilter,
4269+
],
4270+
studyViewFilter: filters,
4271+
},
4272+
$queryParameters: {
4273+
projection: 'DETAILED',
4274+
includeSampleIds,
4275+
},
4276+
} as any;
4277+
4278+
result = await getInternalClient().fetchMutationDataCountsUsingPOST(params);
4279+
4280+
return result;
4281+
}
4282+
4283+
export function groupSamplesByMutationStatus(data: any[], studyIds: string[]) {
4284+
const SKIP_VALUES = new Set(['NOT_PROFILED']);
4285+
const NON_MUTATION_VALUES = new Set(['NOT_MUTATED']);
4286+
4287+
return _.chain(data)
4288+
.flatMap(d => d.counts ?? [])
4289+
.filter(c => !SKIP_VALUES.has(c.value) && c.sampleIds?.length > 0) // avoid NOT_PROFILED samples and any bins without samples
4290+
.flatMap(c => {
4291+
const group = NON_MUTATION_VALUES.has(c.value)
4292+
? c.value
4293+
: 'MUTATED';
4294+
4295+
return c.sampleIds
4296+
.map((rawId: string) => {
4297+
// sampleIds are in the format of {studyId}_{sampleId}, need to be split before grouping
4298+
const matchedStudyId = studyIds.find(studyId =>
4299+
rawId.startsWith(studyId + '_')
4300+
);
4301+
4302+
if (!matchedStudyId) return null;
4303+
// create an object with group, studyId and sampleId for each sampleId, where group is MUTATED or NOT_MUTATED based on the value of the bin it belongs to
4304+
return {
4305+
group,
4306+
studyId: matchedStudyId,
4307+
sampleId: rawId.substring(matchedStudyId.length + 1),
4308+
};
4309+
})
4310+
.filter(Boolean);
4311+
})
4312+
.groupBy('group')
4313+
.mapValues(items =>
4314+
_.uniqBy(
4315+
// cretaes a sampleidentifier{sampleId, studyId} using the matched studyId and the rest of the rawId as sampleId
4316+
items.map(
4317+
({ studyId, sampleId }): SampleIdentifier => ({
4318+
studyId,
4319+
sampleId,
4320+
})
4321+
),
4322+
i => i.sampleId // if sample IDs aren't globally unique across studies, we may want to use both studyId and sampleId to determine uniqueness
4323+
)
4324+
)
4325+
.value();
4326+
}
4327+
42554328
export async function invokeGenomicDataCount(
42564329
chartInfo: GenomicChart,
42574330
filters: StudyViewFilter

src/pages/studyView/charts/ChartContainer.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,7 @@ export class ChartContainer extends React.Component<IChartContainerProps, {}> {
406406
(this.props.promise.result.treatments ??
407407
this.props.promise.result.patientTreatments ??
408408
this.props.promise.result)!.length > 1 &&
409-
COMPARISON_CHART_TYPES.indexOf(this.props.chartType) > -1 &&
410-
!this.props.chartMeta.mutationOptionType
409+
COMPARISON_CHART_TYPES.indexOf(this.props.chartType) > -1
411410
);
412411
}
413412

0 commit comments

Comments
 (0)