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

Demo rfc80 density plot #10837

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cbioportal.service;

import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.DensityPlotData;

import java.math.BigDecimal;
import java.util.List;

public interface ClinicalDataDensityPlotService {
DensityPlotData getDensityPlotData(List<ClinicalData> filteredClinicalData,
uklineale marked this conversation as resolved.
Show resolved Hide resolved
String xAxisAttributeId,
String yAxisAttributeId,
Boolean xAxisLogScale,
Boolean yAxisLogScale,
Integer xAxisBinCount,
Integer yAxisBinCount,
BigDecimal xAxisStart,
BigDecimal yAxisStart,
BigDecimal xAxisEnd,
BigDecimal yAxisEnd);

List<ClinicalData> filterClinicalData(List<ClinicalData> clinicalDataList);
uklineale marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package org.cbioportal.service.impl;

import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
import org.apache.commons.math3.stat.correlation.SpearmansCorrelation;
import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.DensityPlotBin;
import org.cbioportal.model.DensityPlotData;
import org.cbioportal.service.ClinicalDataDensityPlotService;
import org.cbioportal.web.columnar.StudyViewColumnStoreController;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ClinicalDataDensityPlotServiceImpl implements ClinicalDataDensityPlotService {
@Override
public DensityPlotData getDensityPlotData(List<ClinicalData> filteredClinicalData,
String xAxisAttributeId,
String yAxisAttributeId,
Boolean xAxisLogScale,
Boolean yAxisLogScale,
Integer xAxisBinCount,
Integer yAxisBinCount,
BigDecimal xAxisStart,
BigDecimal yAxisStart,
BigDecimal xAxisEnd,
BigDecimal yAxisEnd) {
DensityPlotData result = new DensityPlotData();
result.setBins(new ArrayList<>());

if (filteredClinicalData.isEmpty()) {
return result;
}

Map<Boolean, List<ClinicalData>> partition = filteredClinicalData.stream().collect(
Collectors.partitioningBy(c -> c.getAttrId().equals(xAxisAttributeId)));

boolean useXLogScale = xAxisLogScale && ClinicalDataDensityPlotServiceImpl.isLogScalePossibleForAttribute(xAxisAttributeId);
boolean useYLogScale = yAxisLogScale && ClinicalDataDensityPlotServiceImpl.isLogScalePossibleForAttribute(yAxisAttributeId);

double[] xValues = partition.get(true).stream().mapToDouble(
useXLogScale ? ClinicalDataDensityPlotServiceImpl::parseValueLog : ClinicalDataDensityPlotServiceImpl::parseValueLinear
).toArray();
double[] yValues = partition.get(false).stream().mapToDouble(
useYLogScale ? ClinicalDataDensityPlotServiceImpl::parseValueLog : ClinicalDataDensityPlotServiceImpl::parseValueLinear
).toArray();
double[] xValuesCopy = Arrays.copyOf(xValues, xValues.length);
double[] yValuesCopy = Arrays.copyOf(yValues, yValues.length); // Why copy these?
Arrays.sort(xValuesCopy);
Arrays.sort(yValuesCopy);

double xAxisStartValue = xAxisStart == null ? xValuesCopy[0] :
(useXLogScale ? ClinicalDataDensityPlotServiceImpl.logScale(xAxisStart.doubleValue()) : xAxisStart.doubleValue());
double xAxisEndValue = xAxisEnd == null ? xValuesCopy[xValuesCopy.length - 1] :
(useXLogScale ? ClinicalDataDensityPlotServiceImpl.logScale(xAxisEnd.doubleValue()) : xAxisEnd.doubleValue());
double yAxisStartValue = yAxisStart == null ? yValuesCopy[0] :
(useYLogScale ? ClinicalDataDensityPlotServiceImpl.logScale(yAxisStart.doubleValue()) : yAxisStart.doubleValue());
double yAxisEndValue = yAxisEnd == null ? yValuesCopy[yValuesCopy.length - 1] :
(useYLogScale ? ClinicalDataDensityPlotServiceImpl.logScale(yAxisEnd.doubleValue()) : yAxisEnd.doubleValue());
double xAxisBinInterval = (xAxisEndValue - xAxisStartValue) / xAxisBinCount;
double yAxisBinInterval = (yAxisEndValue - yAxisStartValue) / yAxisBinCount;
List<DensityPlotBin> bins = result.getBins();
for (int i = 0; i < xAxisBinCount; i++) {
for (int j = 0; j < yAxisBinCount; j++) {
DensityPlotBin densityPlotBin = new DensityPlotBin();
densityPlotBin.setBinX(BigDecimal.valueOf(xAxisStartValue + (i * xAxisBinInterval)));
densityPlotBin.setBinY(BigDecimal.valueOf(yAxisStartValue + (j * yAxisBinInterval)));
densityPlotBin.setCount(0);
bins.add(densityPlotBin);
}
}

for (int i = 0; i < xValues.length; i++) {
double xValue = xValues[i];
double yValue = yValues[i];
int xBinIndex = (int) ((xValue - xAxisStartValue) / xAxisBinInterval);
int yBinIndex = (int) ((yValue - yAxisStartValue) / yAxisBinInterval);
int index = (int) (((xBinIndex - (xBinIndex == xAxisBinCount ? 1 : 0)) * yAxisBinCount) +
(yBinIndex - (yBinIndex == yAxisBinCount ? 1 : 0)));
DensityPlotBin densityPlotBin = bins.get(index);
densityPlotBin.setCount(densityPlotBin.getCount() + 1);
BigDecimal xValueBigDecimal = BigDecimal.valueOf(xValue);
BigDecimal yValueBigDecimal = BigDecimal.valueOf(yValue);
if (densityPlotBin.getMinX() != null) {
if (densityPlotBin.getMinX().compareTo(xValueBigDecimal) > 0) {
densityPlotBin.setMinX(xValueBigDecimal);
}
} else {
densityPlotBin.setMinX(xValueBigDecimal);
}
if (densityPlotBin.getMaxX() != null) {
if (densityPlotBin.getMaxX().compareTo(xValueBigDecimal) < 0) {
densityPlotBin.setMaxX(xValueBigDecimal);
}
} else {
densityPlotBin.setMaxX(xValueBigDecimal);
}
if (densityPlotBin.getMinY() != null) {
if (densityPlotBin.getMinY().compareTo(yValueBigDecimal) > 0) {
densityPlotBin.setMinY(yValueBigDecimal);
}
} else {
densityPlotBin.setMinY(yValueBigDecimal);
}
if (densityPlotBin.getMaxY() != null) {
if (densityPlotBin.getMaxY().compareTo(yValueBigDecimal) < 0) {
densityPlotBin.setMaxY(yValueBigDecimal);
}
} else {
densityPlotBin.setMaxY(yValueBigDecimal);
}
}

if (xValues.length > 1) {
// need at least 2 entries in each to compute correlation
result.setPearsonCorr(new PearsonsCorrelation().correlation(xValues, yValues));
result.setSpearmanCorr(new SpearmansCorrelation().correlation(xValues, yValues));
} else {
// if less than 1 entry, just set 0 correlation
result.setSpearmanCorr(0.0);
result.setPearsonCorr(0.0);
}

// filter out empty bins
result.setBins(result.getBins().stream().filter((bin)->(bin.getCount() > 0)).collect(Collectors.toList()));
return result;
}

@Override
public List<ClinicalData> filterClinicalData(List<ClinicalData> clinicalDataList) {
uklineale marked this conversation as resolved.
Show resolved Hide resolved
Map<String, List<ClinicalData>> clinicalDataGroupedBySampleId = clinicalDataList.stream().
collect(Collectors.groupingBy(ClinicalData::getSampleId));

return clinicalDataGroupedBySampleId.entrySet().stream()
.filter(entry -> entry.getValue().size() == 2 &&
NumberUtils.isCreatable(entry.getValue().get(0).getAttrValue()) &&
NumberUtils.isCreatable(entry.getValue().get(1).getAttrValue())
).flatMap(entry -> entry.getValue().stream())
.toList();
}

private static boolean isLogScalePossibleForAttribute(String clinicalAttributeId) {
return clinicalAttributeId.equals("MUTATION_COUNT");
}

private static double logScale(double val) {
return Math.log(1+val);
}

private static double parseValueLog(ClinicalData c) {
return ClinicalDataDensityPlotServiceImpl.logScale(Double.parseDouble(c.getAttrValue()));
}

private static double parseValueLinear(ClinicalData c) {
return Double.parseDouble(c.getAttrValue());
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
package org.cbioportal.web.columnar;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
import org.apache.commons.math3.stat.correlation.SpearmansCorrelation;
import org.cbioportal.model.AlterationCountByGene;
import org.cbioportal.model.AlterationFilter;
import org.cbioportal.model.ClinicalAttribute;
import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.ClinicalDataBin;
import org.cbioportal.model.ClinicalDataCountItem;
import org.cbioportal.model.DensityPlotBin;
import org.cbioportal.model.DensityPlotData;
import org.cbioportal.model.Patient;
import org.cbioportal.model.Sample;
import org.cbioportal.persistence.mybatisclickhouse.StudyViewMapper;
import org.cbioportal.service.ClinicalAttributeService;
import org.cbioportal.service.ClinicalDataDensityPlotService;
import org.cbioportal.service.StudyViewColumnarService;
import org.cbioportal.service.StudyViewService;
import org.cbioportal.service.exception.StudyNotFoundException;
import org.cbioportal.service.impl.ClinicalAttributeServiceImpl;
import org.cbioportal.service.impl.PatientServiceImpl;
import org.cbioportal.service.impl.SampleServiceImpl;
import org.cbioportal.service.util.ClinicalAttributeUtil;
import org.cbioportal.web.StudyViewController;
import org.cbioportal.web.columnar.util.NewStudyViewFilterUtil;
import org.cbioportal.web.config.annotation.InternalApi;
import org.cbioportal.web.parameter.ClinicalDataBinCountFilter;
import org.cbioportal.web.parameter.ClinicalDataCountFilter;
import org.cbioportal.web.parameter.ClinicalDataFilter;
import org.cbioportal.web.parameter.DataBinMethod;
import org.cbioportal.web.parameter.Projection;
import org.cbioportal.web.parameter.StudyViewFilter;
import org.cbioportal.web.util.ClinicalDataBinUtil;
import org.cbioportal.web.util.ClinicalDataFetcher;
import org.cbioportal.web.util.StudyViewFilterApplier;
import org.cbioportal.web.util.StudyViewFilterUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -29,9 +57,12 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@InternalApi
Expand All @@ -41,16 +72,39 @@
public class StudyViewColumnStoreController {

private final StudyViewColumnarService studyViewColumnarService;
private final StudyViewService studyViewService;
private final ClinicalDataBinner clinicalDataBinner;
private final StudyViewService studyViewService;
@Autowired
private final ClinicalAttributeService clinicalAttributeService;
@Autowired
private ClinicalAttributeUtil clinicalAttributeUtil;
@Autowired
private PatientServiceImpl patientService;
@Autowired
private StudyViewFilterApplier studyViewFilterApplier;
@Autowired
private StudyViewFilterUtil studyViewFilterUtil;
@Autowired
private SampleServiceImpl sampleService;
@Autowired
private ClinicalDataFetcher clinicalDataFetcher;
uklineale marked this conversation as resolved.
Show resolved Hide resolved
@Autowired
private ClinicalDataDensityPlotService clinicalDataDensityPlotService;
uklineale marked this conversation as resolved.
Show resolved Hide resolved

@Autowired
public StudyViewColumnStoreController(StudyViewColumnarService studyViewColumnarService, StudyViewService studyViewService, ClinicalDataBinner clinicalDataBinner) {
public StudyViewColumnStoreController(StudyViewColumnarService studyViewColumnarService, StudyViewService studyViewService,
ClinicalDataBinner clinicalDataBinner, ClinicalAttributeService clinicalAttributeService,
ClinicalDataDensityPlotService clinicalDataDensityPlotService
) {
this.studyViewColumnarService = studyViewColumnarService;
this.studyViewService = studyViewService;
this.clinicalDataBinner = clinicalDataBinner;
this.clinicalAttributeService = clinicalAttributeService;
this.clinicalDataDensityPlotService = clinicalDataDensityPlotService;
}



@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PostMapping(value = "/column-store/filtered-samples/fetch",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Expand Down Expand Up @@ -118,4 +172,50 @@ public ResponseEntity<List<ClinicalDataBin>> fetchClinicalDataBinCounts(
);
return new ResponseEntity<>(clinicalDataBins, HttpStatus.OK);
}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@RequestMapping(value = "/column-store/clinical-data-density-plot/fetch", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Fetch clinical data density plot bins by study view filter")
@ApiResponse(responseCode = "200", description = "OK",
content = @Content(schema = @Schema(implementation = DensityPlotData.class)))
@Validated
public ResponseEntity<DensityPlotData> fetchClinicalDataDensityPlot(
@Parameter(required = true, description = "Clinical Attribute ID of the X axis")
@RequestParam String xAxisAttributeId,
@Parameter(description = "Number of the bins in X axis")
@RequestParam(defaultValue = "50") Integer xAxisBinCount,
@Parameter(description = "Starting point of the X axis, if different than smallest value")
@RequestParam(required = false) BigDecimal xAxisStart,
@Parameter(description = "Starting point of the X axis, if different than largest value")
@RequestParam(required = false) BigDecimal xAxisEnd,
@Parameter(required = true, description = "Clinical Attribute ID of the Y axis")
@RequestParam String yAxisAttributeId,
@Parameter(description = "Number of the bins in Y axis")
@RequestParam(defaultValue = "50") Integer yAxisBinCount,
@Parameter(description = "Starting point of the Y axis, if different than smallest value")
@RequestParam(required = false) BigDecimal yAxisStart,
@Parameter(description = "Starting point of the Y axis, if different than largest value")
@RequestParam(required = false) BigDecimal yAxisEnd,
@Parameter(description="Use log scale for X axis")
@RequestParam(required = false, defaultValue = "false") Boolean xAxisLogScale,
@Schema(defaultValue = "false")
@Parameter(description="Use log scale for Y axis")
@RequestParam(required = false, defaultValue = "false") Boolean yAxisLogScale,
@Parameter(hidden = true) // prevent reference to this attribute in the swagger-ui interface
@RequestAttribute(required = false, value = "involvedCancerStudies") Collection<String> involvedCancerStudies,
@Parameter(hidden = true) // prevent reference to this attribute in the swagger-ui interface. this attribute is needed for the @PreAuthorize tag above.
@Valid @RequestAttribute(required = false, value = "interceptedStudyViewFilter") StudyViewFilter interceptedStudyViewFilter,
@Parameter(required = true, description = "Study view filter")
@RequestBody(required = false) StudyViewFilter studyViewFilter) {

List<String> xyAttributeId = new ArrayList<>(Arrays.asList(xAxisAttributeId, yAxisAttributeId));
List<ClinicalData> sampleClinicalDataList = studyViewColumnarService.getSampleClinicalData(interceptedStudyViewFilter, xyAttributeId);

List<ClinicalData> filteredClinicalDataList = clinicalDataDensityPlotService.filterClinicalData(sampleClinicalDataList);
DensityPlotData result = clinicalDataDensityPlotService.getDensityPlotData(filteredClinicalDataList, xAxisAttributeId,
yAxisAttributeId, xAxisLogScale, yAxisLogScale, xAxisBinCount, yAxisBinCount, xAxisStart, yAxisStart, xAxisEnd, yAxisEnd);
uklineale marked this conversation as resolved.
Show resolved Hide resolved

return new ResponseEntity<>(result, HttpStatus.OK);
}
}
Loading