Skip to content

Commit

Permalink
2 numerical attributes works
Browse files Browse the repository at this point in the history
  • Loading branch information
richardwolfmayr committed Sep 14, 2023
1 parent 0b923b8 commit c8a121a
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 50 deletions.
41 changes: 21 additions & 20 deletions coral/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -1011,45 +1011,46 @@ def recommendSplit():

@app.route("/createAutomatically", methods=["GET", "POST"])
# TODO add login_required
def createAutomatically():
def create_automatically():
# error msg is wrong, it is based on the cohortData route
# cohortData?cohortId=2&attribute=gender
# cohortData?cohortId=2&attribute=genderdata
error_msg = """Paramerter missing or wrong!
For the {route} query the following parameter is needed:
- cohortId: id of the cohort
There is also one optional parameter:
There are also optional parameters:
- attribute1: one column of the entity table, used when called for 2 attributes
- attribute2: one column of the entity table, used when called for 2 attributes
- attribute: one column of the entity table""".format(
route="cohortData"
)

_log.debug("request.values %s", request.values)

# TODO: base on createUseNumFilter and create cohorts based on hdbscan grouping
# based on createUseNumFilter and create cohorts with hdbscan clustering
try:
##########
# query = QueryElements()
# cohort = query.get_cohort_from_db(request.values, error_msg) # get parent cohort
# new_cohort = query.create_cohort_automatically(request.values, cohort,
# error_msg) # get filtered cohort from args and cohort
# return query.add_cohort_to_db(new_cohort) # save new cohort into DB
# return jsonify("not implemented yet")
############

query = QueryElements()
cohort = query.get_cohort_from_db(request.values, error_msg) # get parent cohort
sql_text = query.get_cohort_data_sql(request.values, cohort) # get sql statement to retrieve data
query_results = query.execute_sql_query(sql_text, cohort.entity_database)
# _log.debug("query_results %s ", query_results.get_json())

tissues = [item for item in query_results.get_json() if item[request.values['attribute']] is not None]
_log.debug("tissues[0] %s", tissues[0])
tissues_df = pd.DataFrame(tissues)
tissues_attribute_df = tissues_df.iloc[:, 0].values.reshape(-1, 1) # get only the first column of tissues_df (e.g. attribute age)
_log.debug("tissues_attribute_df[0] %s", tissues_attribute_df[0])
_log.debug("tissues_attribute_df shape %s", tissues_attribute_df.shape)
# if there are 2 attributes, use both, if there is just 1 attribute, use one
if 'attribute1' in request.values:
attribute1 = request.values['attribute1']
attribute2 = request.values['attribute2']
tissues = [item for item in query_results.get_json() if item[attribute1] is not None and item[attribute2] is not None]
tissues_df = pd.DataFrame(tissues)
# get only the values of the attributes
tissues_attribute_df = tissues_df[[attribute1, attribute2]].values
else:
attribute = request.values['attribute']
tissues = [item for item in query_results.get_json() if item[attribute] is not None]
tissues_df = pd.DataFrame(tissues)
tissues_attribute_df = tissues_df[attribute].values.reshape(-1, 1)


# hdbscan
clusterer = hdbscan.HDBSCAN(min_cluster_size=round(tissues_attribute_df.shape[0]/20), gen_min_span_tree=True) # one tenth of the number of tissues, to get a reasonable amount of clusters
clusterer = hdbscan.HDBSCAN(min_cluster_size=round(tissues_attribute_df.shape[0]/50), gen_min_span_tree=True) # one tenth of the number of tissues, to get a reasonable amount of clusters
clusterer.fit(tissues_attribute_df)
# get the labels of the clusters
labels = clusterer.labels_
Expand Down
8 changes: 6 additions & 2 deletions coral/sql_query_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,10 +661,14 @@ def create_cohort_automatically_from_tissue_names(self, args, cohort, error_msg)
if name is None:
raise RuntimeError(error_msg)


attribute = args.get("attribute")
if attribute is None:
raise RuntimeError(error_msg)
attribute1 = args.get("attribute1")
attribute2 = args.get("attribute2")
if attribute1 is None or attribute2 is None:
raise RuntimeError(error_msg)



sql_text = "SELECT p.* FROM ({entities}) p".format(entities=cohort.statement)
_log.debug("sql_text_create_cohort_automatically_from_tissue_names: %s", sql_text)
Expand Down
6 changes: 4 additions & 2 deletions src/Taskview/visualizations/AVegaVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {

import {
recommendSplit,
createAutomatically
createAutomatically, createDBCohortAutomatically
} from './../../base/rest';

export const MISSING_VALUES_LABEL = 'Missing Values';
Expand Down Expand Up @@ -256,6 +256,7 @@ export abstract class AVegaVisualization implements IVegaVisualization {
abstract show(container: HTMLDivElement, attributes: IAttribute[], cohorts: ICohort[]);
abstract filter(): void;
abstract split(): void;
abstract createAutomatically(): void;
abstract showImpl(chart: HTMLDivElement, data: Array<IdValuePair>); // probably the method impl from SingleAttributeVisualization can be moved here

destroy() {
Expand Down Expand Up @@ -731,7 +732,7 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
cohortId: cohort.dbId,
attribute: this.attribute.dataKey
};
newCohortIds = await createAutomatically(params, false)
newCohortIds = await createDBCohortAutomatically(params)
console.log("createAutomatically data", newCohortIds);
}

Expand All @@ -756,6 +757,7 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {

let cohortDescs: INewCohortDesc[];
cohortDescs = [];
// for every selected cohort
for (const cohort of this.cohorts) {
// for every newCohort create a filter (for now... the filter is actually not needed, will be changed in the future)
for (const newCohort of newCohortIds){
Expand Down
10 changes: 10 additions & 0 deletions src/Taskview/visualizations/MultiAttributeVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export abstract class MultiAttributeVisualization extends AVegaVisualization {
</div>
<div class="d-grid gap-2">
<button type="button" class="btn btn-coral-prime btn-block applyBtn">Apply</button>
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
</div>
`,
);
Expand All @@ -147,6 +148,15 @@ export abstract class MultiAttributeVisualization extends AVegaVisualization {
this.addIntervalControls(attr, axis);
}
}

select(this.controls)
.select('button.createAutomaticallyBtn')
.on('click', () => {
console.log("createAutomaticallyBtn clicked");
this.createAutomatically();
});


select(this.controls)
.select('button.applyBtn')
.on('click', () => {
Expand Down
48 changes: 45 additions & 3 deletions src/Taskview/visualizations/Scatterplot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ import { TopLevel, LayerSpec } from 'vega-lite/build/src/spec';
import { Field } from 'vega-lite/build/src/channeldef';
import { ICohort } from '../../app/interfaces';
import { AttributeType, IAttribute, IdValuePair, ServerColumnAttribute } from '../../data';
import { NumRangeOperators } from '../../base';
import { IFilterDesc, inRange } from '../../util';
import { FilterEvent } from '../../base/events';
import {
ICohortDBDataParams,
ICohortMultiAttrDBDataParams,
NumRangeOperators
} from '../../base';
import {IFilterDesc, INewCohortDesc, inRange} from '../../util';
import {AutoSplitEvent, FilterEvent} from '../../base/events';
import { DATA_LABEL } from './constants';
import { AxisType, MultiAttributeVisualization } from './MultiAttributeVisualization';

import {
createDBCohortAutomatically,
createAutomatically
} from '../../base/rest';

export class Scatterplot extends MultiAttributeVisualization {
static readonly NAME: string = 'Scatterplot';

Expand Down Expand Up @@ -869,6 +878,37 @@ export class Scatterplot extends MultiAttributeVisualization {
this.container.dispatchEvent(new FilterEvent(filterDescs));
}

async createAutomatically() {
console.log("createAutomatically scatterplot");

let newCohortIds = [];
for (const cht of this.cohorts) {
const params: ICohortMultiAttrDBDataParams = {
cohortId: cht.dbId,
attribute1: "age",
attribute2: "bmi"
};
newCohortIds = await createDBCohortAutomatically(params)
console.log("createAutomatically scatterplot data", newCohortIds);
}

let cohortDescs: INewCohortDesc[];
cohortDescs = [];
// for every selected cohort
for (const cohort of this.cohorts) {
// for every newCohort create a filter (for now... the filter is actually not needed, will be changed in the future)
for (const newCohort of newCohortIds){
cohortDescs.push({
cohort: cohort,
newCohortId: newCohort,
attr:[this.attributes[0], this.attributes[1]]
});
}
}

this.container.dispatchEvent(new AutoSplitEvent(cohortDescs));
}

split() {
const filterDescs: IFilterDesc[] = [];
for (const cht of this.cohorts) {
Expand Down Expand Up @@ -1259,3 +1299,5 @@ export class TsneScatterplot extends Scatterplot {
this.container.dispatchEvent(new FilterEvent(filterDescs));
}
}


7 changes: 7 additions & 0 deletions src/base/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface ICohortDBWithNumFilterParams extends ICohortNumFilterParams {

export interface ICohortDBWithAutoSplitParams extends ICohortAutoSplitParams {
name: string;

}

export const valueListDelimiter = '&#x2e31;';
Expand Down Expand Up @@ -87,6 +88,12 @@ export interface ICohortDBDataParams extends IParams {
attribute?: string;
}

export interface ICohortMultiAttrDBDataParams extends IParams {
cohortId: number;
attribute1?: string;
attribute2?: string;
}

export interface ICohortDBSizeParams extends IParams {
cohortId: number;
}
Expand Down
49 changes: 31 additions & 18 deletions src/base/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ICohortDBPanelAnnotationParams,
ICohortDBParams,
ICohortDBSizeParams,
ICohortDBUpdateName,
ICohortDBUpdateName, ICohortDBWithAutoSplitParams,
ICohortDBWithDepletionScoreFilterParams,
ICohortDBWithEqualsFilterParams,
ICohortDBWithGeneEqualsFilterParams,
Expand All @@ -23,7 +23,7 @@ import {
ICohortDepletionScoreFilterParams,
ICohortEqualsFilterParams,
ICohortGeneEqualsFilterParams,
ICohortGeneNumFilterParams,
ICohortGeneNumFilterParams, ICohortMultiAttrDBDataParams,
ICohortNumFilterParams,
ICohortPanelAnnotationFilterParams,
ICohortRow,
Expand Down Expand Up @@ -149,15 +149,15 @@ export function createDBCohortWithNumFilter(params: ICohortDBWithNumFilterParams
return getCohortDataImpl(CohortRoutes.createUseNumFilter, newParams, assignIds);
}

export function createDBCohortAutomatically(params: ICohortDBWithNumFilterParams, assignIds = false): Promise<IRow[]> {
const newParams: IParams = {
cohortId: params.cohortId,
name: params.name,
attribute: params.attribute,
ranges: convertNumRanges(params.ranges),
};
return getCohortDataImpl(CohortRoutes.createAutomatically, newParams, assignIds);
}
// export function createDBCohortAutomatically(params: ICohortDBWithNumFilterParams, assignIds = false): Promise<IRow[]> {
// const newParams: IParams = {
// cohortId: params.cohortId,
// name: params.name,
// attribute: params.attribute,
// ranges: convertNumRanges(params.ranges),
// };
// return getCohortDataImpl(CohortRoutes.createAutomatically, newParams, assignIds);
// }

export function recommendSplit(params: ICohortDBDataParams, assignIds = false): Promise<IRow[]> {
const url = `/cohortdb/db/${'recommendSplit'}`;
Expand All @@ -169,13 +169,26 @@ export function recommendSplit(params: ICohortDBDataParams, assignIds = false):
return AppContext.getInstance().getAPIJSON(url, params);
}

export function createAutomatically(params: ICohortDBDataParams, assignIds = false): Promise<IRow[]> {
const newParams: IParams = {
cohortId: params.cohortId,
name: "TODO: create name",
attribute: params.attribute,
ranges: "TODO: remove ranges since they are not needed",
};
// TODO: remove this? not used?
export function createDBCohortAutomatically(params: ICohortDBDataParams | ICohortMultiAttrDBDataParams, assignIds = false): Promise<IRow[]> {
let newParams: IParams = {};
// check if params is ICohortDBDataParams
if ("attribute" in params) {
newParams = {
cohortId: params.cohortId,
name: "TODO: create name", // this will be used to create a name for the cohort in the database afaik TODO: check this and implement
attribute: params.attribute,
};
} else {
if ("attribute1" in params) {
newParams = {
cohortId: params.cohortId,
name: "TODO: create name",
attribute1: params.attribute1,
attribute2: params.attribute2,
};
}
}
return getCohortDataImpl(CohortRoutes.createAutomatically, newParams, assignIds);
}

Expand Down
10 changes: 5 additions & 5 deletions src/data/Attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,15 @@ export async function multiAttributeGetAutoCohort(baseCohort: ICohort, attr: IAt

// when only one filter is used the labels don't have to be set again
// minimizes the number of time a cohort in the DB has to be updated
// if (filters.length > 1) {
// newCohort.setLabels(labelOne.join(', '), labelTwo.join(', '));
// newCohort.values = values;
// }
if (attr.length > 1) {
newCohort.setLabels(labelOne.join(', '), labelTwo.join(', '));
newCohort.values = values;
}

return newCohort;
}

export async function multiAttributeFilter(baseCohort: ICohort, filters: IAttributeFilter[], autofilter?: boolean): Promise<ICohort> {
export async function multiAttributeFilter(baseCohort: ICohort, filters: IAttributeFilter[]): Promise<ICohort> {
let newCohort = baseCohort;

const labelOne = [];
Expand Down

0 comments on commit c8a121a

Please sign in to comment.