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

Refactor analysis, Add Wilcoxon, Add ANOVA #212

Merged
merged 80 commits into from Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
000815c
start adding analzye step
sgratzl Sep 11, 2019
645c1a7
play with submenu
sgratzl Sep 11, 2019
54b31fc
start with base component for analyze
sgratzl Sep 12, 2019
33578fa
fix left menu
sgratzl Sep 12, 2019
6c929ba
fix expandsion of menu
sgratzl Sep 12, 2019
a5dbe44
list tests as cards
sgratzl Sep 12, 2019
bbfb546
fix layout
sgratzl Sep 12, 2019
b6178c0
rename test
sgratzl Sep 12, 2019
4aed4a0
style playing
sgratzl Sep 12, 2019
9bb7bd9
selectable zero method
sgratzl Sep 12, 2019
e3ad032
selectable alternatives
sgratzl Sep 12, 2019
22637cf
start with server side computation
sgratzl Sep 12, 2019
204f87a
first wilcoxon test version
sgratzl Sep 12, 2019
be64169
don't use base layout
sgratzl Sep 12, 2019
b07e7d7
first running version
sgratzl Sep 12, 2019
c642c1e
hide button if not needed
sgratzl Sep 12, 2019
ed9844c
fix linting
sgratzl Sep 12, 2019
d1d3ff0
fix linting
sgratzl Sep 12, 2019
b9cf8ee
fix linting
sgratzl Sep 12, 2019
46937f1
renaming
sgratzl Sep 12, 2019
0f11dee
use webargs
sgratzl Sep 12, 2019
9b112ad
auto compute when created
sgratzl Sep 12, 2019
4b56f13
fix style and use different icon
sgratzl Sep 12, 2019
b07071b
use small-tile
sgratzl Sep 12, 2019
e1f591f
Merge remote-tracking branch 'origin/master' into sgratzl/analyze
sgratzl Sep 12, 2019
742bd69
merge master
sgratzl Sep 12, 2019
3059f29
centralize analysis definition
sgratzl Sep 12, 2019
6c8c621
fix linting
sgratzl Sep 12, 2019
ac02ff0
start with anova
sgratzl Sep 13, 2019
d2c06f1
migrate R code
sgratzl Sep 13, 2019
70189aa
auto install new package dependencies
sgratzl Sep 13, 2019
84436e2
add auto delete of container
sgratzl Sep 13, 2019
399019c
running data version
sgratzl Sep 13, 2019
ed5a61e
encode nan
sgratzl Sep 13, 2019
fe4c48a
listen to ready for initial compute
sgratzl Sep 13, 2019
8b78ed5
dump data
sgratzl Sep 13, 2019
c45eaa6
fix linting
sgratzl Sep 13, 2019
a34f9e9
simple table representation
sgratzl Sep 16, 2019
52afc55
fix linting
sgratzl Sep 16, 2019
6a9258c
extract wrapper and use mixin
sgratzl Sep 16, 2019
7fc6177
extract toolbar option
sgratzl Sep 16, 2019
4a492a8
merge refactor
sgratzl Sep 16, 2019
736fd1a
apply refactoring
sgratzl Sep 16, 2019
4e7f8f4
fix linting
sgratzl Sep 16, 2019
ea52d1a
Merge pull request #191 from girder/sgratzl/analyzerefactor
sgratzl Sep 16, 2019
3d2f852
rename and throw proper error
sgratzl Sep 16, 2019
dd4cfb6
use Vue.extend to have better autocomplete support
sgratzl Sep 16, 2019
2eba961
also for declaring the mixin
sgratzl Sep 16, 2019
99cdef4
no Vue.extend for mixins
sgratzl Sep 16, 2019
28d24e9
more typings
sgratzl Sep 16, 2019
5bfe19d
use integrated validator
sgratzl Sep 16, 2019
5b6209a
Merge branch 'sgratzl/analyze' into sgratzl/anova
sgratzl Sep 16, 2019
ef32f7c
more typings
sgratzl Sep 16, 2019
4b035b5
apply some Vue best practices
sgratzl Sep 16, 2019
e58fff2
merge sgratzl/analyze
sgratzl Sep 16, 2019
da3c636
rename file to follow conventions
sgratzl Sep 16, 2019
ed625ac
Finish rebase
subdavis Sep 17, 2019
19be8a7
loading
subdavis Sep 17, 2019
c8581fd
Merge branch 'sgratzl/anova' into one-plot-pattern-to-rule-them-all
subdavis Sep 17, 2019
b6c291d
WIP refactor analysis
subdavis Sep 17, 2019
e6b3084
Wip
subdavis Sep 17, 2019
f508566
Add vis tile large
subdavis Sep 17, 2019
cf8cef7
layout fix
subdavis Sep 17, 2019
6f706f6
Adds anovaTableTile
subdavis Sep 17, 2019
cc729a0
Specific validators
subdavis Sep 17, 2019
cddc73e
lint fixes
subdavis Sep 17, 2019
bc0f41a
Merge branch 'master' into one-plot-pattern-to-rule-them-all
subdavis Sep 17, 2019
2740487
Move analyses constant
subdavis Sep 18, 2019
4bb354a
Merge branch 'one-plot-pattern-to-rule-them-all' of github.com:girder…
subdavis Sep 18, 2019
14b8d59
Merge branch 'master' into one-plot-pattern-to-rule-them-all
subdavis Sep 18, 2019
fb25bfa
named function
subdavis Sep 18, 2019
8fd6cbe
add new analyses file
subdavis Sep 18, 2019
8ea888d
move to analyses.js
subdavis Sep 18, 2019
59ce657
Fix path resolution
subdavis Sep 18, 2019
71606e4
move analysis.R
subdavis Sep 18, 2019
1fb70e5
Remove comments about key
subdavis Sep 18, 2019
d36b418
resolve plot warnings
subdavis Sep 18, 2019
a05aa93
Record updated Pipfile.lock
waxlamp Sep 18, 2019
af4fc1b
remove key
subdavis Sep 18, 2019
add2ed7
Merge branch 'one-plot-pattern-to-rule-them-all' of github.com:girder…
subdavis Sep 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -60,7 +60,7 @@ docker build -t metabulo .
and start the instance

```sh
docker run -it -p 8004:8004 metabulo
docker run -it --rm -p 8004:8004 metabulo
subdavis marked this conversation as resolved.
Show resolved Hide resolved
```

You may find that changes to the position or size of the window in which the
Expand Down
2 changes: 1 addition & 1 deletion devops/Dockerfile
@@ -1,6 +1,6 @@
FROM metabulo/opencpu-base

ADD metabulo /metabulo
RUN R -e 'library(devtools); library(roxygen2); setwd("/metabulo"); document(); setwd("/"); install("metabulo")'
RUN R -e 'library(devtools); library(roxygen2); setwd("/metabulo"); devtools::update_packages(devtools::dev_package_deps()$package, dependencies=NA, quiet=TRUE, upgrade="never"); document(); setwd("/");install("metabulo", dependencies=NA, quiet=TRUE, upgrade="never")'
waxlamp marked this conversation as resolved.
Show resolved Hide resolved

CMD service cron start && /usr/lib/rstudio-server/bin/rserver && apachectl -DFOREGROUND
1 change: 1 addition & 0 deletions devops/metabulo/DESCRIPTION
Expand Up @@ -4,6 +4,7 @@ Version: 0.0.0.9000
Authors@R: person("First", "Last", email = "first.last@example.com", role = c("aut", "cre"))
Description: The package contains functions that are exposed via RPC for the Metabolomics web application.
Depends: R (>= 3.5.2)
Imports: car, emmeans
License: Apache 2.0
Encoding: UTF-8
LazyData: true
Expand Down
35 changes: 35 additions & 0 deletions devops/metabulo/R/analyses.R
@@ -0,0 +1,35 @@
#' Basic PCA plot
#'
#' Uses MetaboAnalystR's PlotPCAPairSummary method

anova_tukey_adjustment <- function(measurements, groups) {
Metab = read.csv(measurements, row.names=1)
groups = read.csv(groups, row.names=1)

# take the first column
Group = as.factor(groups[, 1])

library(car) #For Type III ANOVA
library(emmeans) #For pairwise comparisons https://cran.r-project.org/web/packages/emmeans/vignettes/comparisons.html

# based on the given R code
OUT <- NULL
for (n in colnames(Metab)){
mod <- lm(Metab[[n]] ~ Group)
a <- Anova(mod, type="III")
x1 <- cbind(n,t(a[,"Pr(>F)"] ))

emm.mod <- emmeans(mod, "Group")
b <- pairs(emm.mod)
x2 <- summary(b)$p.value
x2 <- t(x2)
colnames(x2) <- summary(b)$contrast

x <- cbind(x1,x2)
OUT <- rbind(OUT, x)
colnames(OUT) <- c("Metabolite", row.names(a), colnames(x2))
rm(mod,a,b,x1,x2,x)
}

OUT
}
48 changes: 48 additions & 0 deletions metabulo/analyses.py
@@ -0,0 +1,48 @@
from itertools import combinations
from typing import Any, Dict, List, Optional, Union

import pandas as pd
from scipy.stats import wilcoxon

from .opencpu import opencpu_request


def wilcoxon_test(measurements: pd.DataFrame, zero_method: Optional[str] = None,
alternative: Optional[str] = None) -> Dict[str, Union[List[str], List[Dict]]]:

if zero_method is None:
zero_method = 'wilcox'
if alternative is None:
alternative = 'two-sided'
waxlamp marked this conversation as resolved.
Show resolved Hide resolved

indices = list(measurements.index)
data = []

# test all possible combinations of rows
for (x, y) in combinations(measurements.iterrows(), 2):
x_index, x_data = x
y_index, y_data = y
(score, p) = wilcoxon(x_data, y_data, zero_method=zero_method, alternative=alternative)
data.append(dict(x=x_index, y=y_index, score=score, p=p))

return {
'indices': indices,
'data': data
}


def anova_test(measurements: pd.DataFrame, groups: pd.Series) -> Dict[str, Any]:
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
files = {
'measurements': measurements.to_csv().encode(),
'groups': groups.to_csv(header=True).encode()
}

data = opencpu_request('anova_tukey_adjustment', files, {})

data = data.rename(columns={'(Intercept)': 'Intercept'})

return {
'groups': list(set(groups)),
'pairs': list(data)[4:],
'data': data.replace({pd.np.nan: None}).to_dict(orient='records')
}
39 changes: 38 additions & 1 deletion metabulo/views.py
Expand Up @@ -3,7 +3,7 @@
import json
import math
from pathlib import PurePath
from typing import cast, Dict
from typing import cast, Dict, Optional
from uuid import uuid4

from flask import Blueprint, current_app, jsonify, request, Response, send_file
Expand All @@ -13,6 +13,7 @@
from werkzeug import FileStorage

from metabulo import opencpu
from metabulo.analyses import anova_test, wilcoxon_test
from metabulo.cache import csv_file_cache
from metabulo.imputation import IMPUTE_MCAR_METHODS, IMPUTE_MNAR_METHODS
from metabulo.models import AXIS_NAME_TYPES, CSVFile, CSVFileSchema, db, \
Expand Down Expand Up @@ -529,3 +530,39 @@ def get_pca_overview(validated_table):
png_content = opencpu.generate_image('/metabulo/R/pca_overview_plot',
validated_table.measurements)
return Response(png_content, mimetype='image/png')


@csv_bp.route('/csv/<uuid:csv_id>/analyses/wilcoxon', methods=['GET'])
@use_kwargs({
'zero_method': fields.Str(missing='wilcox',
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
validate=validate.OneOf(['wilcox', 'pratt', 'zsplit'])),
'alternative': fields.Str(missing='two-sided',
subdavis marked this conversation as resolved.
Show resolved Hide resolved
validate=validate.OneOf(['two-sided', 'greater', 'less']))
subdavis marked this conversation as resolved.
Show resolved Hide resolved
})
@load_validated_csv_file
def get_wilcoxon_test(validated_table: ValidatedMetaboliteTable,
zero_method: str, alternative: str):
subdavis marked this conversation as resolved.
Show resolved Hide resolved
table = validated_table.measurements

data = wilcoxon_test(table, zero_method, alternative)

return jsonify(data), 200


@csv_bp.route('/csv/<uuid:csv_id>/analyses/anova', methods=['GET'])
@use_kwargs({
'group_column': fields.Str()
})
@load_validated_csv_file
def get_anova_test(validated_table: ValidatedMetaboliteTable, group_column: Optional[str] = None):
measurements = validated_table.measurements
groups = validated_table.groups

group = groups.iloc[:, 0] if group_column is None else groups.loc[:, group_column]
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
if group is None:
raise ValidationError(
'invalid group column', field_name='group_column', data=group_column)

data = anova_test(measurements, group)

return jsonify(data), 200
4 changes: 4 additions & 0 deletions web/src/common/api.service.js
Expand Up @@ -51,6 +51,10 @@ export const CSVService = {
return ApiService.get('csv', `${slug}/plot/${type}`, params);
},

getAnalysis(slug, key, params = {}) {
return ApiService.get('csv', `${slug}/analyses/${key}`, params);
},

post(data) {
return ApiService.post('csv', data);
},
Expand Down
35 changes: 35 additions & 0 deletions web/src/components/AnalyzeData.vue
@@ -0,0 +1,35 @@
<script>
import Vue from 'vue';
import { analyses } from './vis';

export default Vue.extend({
subdavis marked this conversation as resolved.
Show resolved Hide resolved
props: {
id: {
type: String,
required: true,
},
},
data() {
return {
analyses,
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
};
},
});
</script>

<template lang="pug">
v-layout.analyze-component(row, fill-height)
v-container.grow-overflow.ma-0(grid-list-lg, fluid)
v-container.pa-2(fluid)
v-layout(row)
v-flex(v-for="card in analyses", :key="card.path", md3)
v-card
v-card-title(primary-title)
div
.headline(v-text="card.name")
| {{card.description}}
v-card-actions
v-btn(text,
@click="$router.push({ path: `/pretreatment/${id}/analyze/${card.path}` })")
| Analyze
</template>
28 changes: 27 additions & 1 deletion web/src/components/Pretreatment.vue
@@ -1,6 +1,7 @@
<script>
import { SET_SELECTION } from '@/store/mutations.type';
import { loadDataset } from '@/utils/mixins';
import { analyses } from './vis';

export default {
mixins: [loadDataset],
Expand All @@ -13,6 +14,7 @@ export default {
data() {
return {
datasets: this.$store.state.datasets,
analyses,
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
};
},
methods: {
Expand Down Expand Up @@ -42,6 +44,9 @@ export default {
});
}
},
isSubRoute(dataset, start) {
return this.$router.currentRoute.path.startsWith(`/pretreatment/${dataset.id}/${start}`);
},
},
};
</script>
Expand Down Expand Up @@ -95,6 +100,26 @@ v-layout.pretreatment-component(row, fill-height)
v-list-tile-title.pl-2
v-icon.pr-1.middle {{ $vuetify.icons.bubbles }}
| Transform Table

v-list-group.ml-2.link-group(
:append-icon="null",
:value="isSubRoute(dataset, `analyze/`)",
:disabled="!valid(dataset)",
@click="$router.push({ path: `/pretreatment/${dataset.id}/analyze` })")
template(v-slot:activator)
v-list-tile(
:class="{ active: $router.currentRoute.name === 'Analyze Data' }")
v-list-tile-title.pl-2
v-icon.pr-1.middle {{ $vuetify.icons.cogs }}
| Analyze Table
v-list-tile.ml-2.small-tile(
v-for="a in analyses", :key="a.path",
:class="{ active: $router.currentRoute.name === a.shortName }",
@click="$router.push({ path: `/pretreatment/${dataset.id}/analyze/${a.path}` })")
v-list-tile-title.pl-2
v-icon.pr-1.middle {{ $vuetify.icons.compare }}
| {{a.shortName}}

keep-alive
router-view
</template>
Expand Down Expand Up @@ -125,7 +150,8 @@ v-layout.pretreatment-component(row, fill-height)
}
}

.view-list .v-list__tile--link {
.view-list .v-list__tile--link,
.view-list .link-group > .v-list__group__header {
transition: none;

.v-list__tile__title {
Expand Down
31 changes: 31 additions & 0 deletions web/src/components/ToolbarOption.vue
@@ -0,0 +1,31 @@
<script>
waxlamp marked this conversation as resolved.
Show resolved Hide resolved
export default {
props: {
title: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
options: {
type: Array,
required: true,
},
},
};
</script>

<template lang="pug">
div
v-toolbar.darken-3(color="primary", dark, flat, dense, :card="false")
v-toolbar-title {{title}}
v-card.mx-3(flat)
v-card-actions
v-radio-group.my-0(:value="value",
hide-details,
@change="$emit('change', $event)")
v-radio(v-for="m in options", :label="m.label",
:value="m.value", :key="m.value")
</template>
47 changes: 4 additions & 43 deletions web/src/components/Transform.vue
@@ -1,6 +1,6 @@
<script>
import { mapState } from 'vuex';
import { MUTEX_TRANSFORM_TABLE, LOAD_PLOT } from '@/store/actions.type';
import { MUTEX_TRANSFORM_TABLE } from '@/store/actions.type';
import {
normalize_methods,
scaling_methods,
Expand Down Expand Up @@ -39,46 +39,8 @@ export default {
norm_arg() { return this.$store.getters.txType(this.id, 'normalization_argument'); },
trans() { return this.$store.getters.txType(this.id, 'transformation'); },
scaling() { return this.$store.getters.txType(this.id, 'scaling'); },
pcaData() { return this.$store.getters.plotData(this.id, 'pca'); },
pcaValid() { return this.$store.getters.plotValid(this.id, 'pca'); },
loadingsData() { return this.$store.getters.plotData(this.id, 'loadings'); },
loadingsValid() { return this.$store.getters.plotValid(this.id, 'loadings'); },
},
watch: {
pcaValid: {
immediate: true,
handler(valid) { this.pcaLoader(valid); },
},
loadingsValid: {
immediate: true,
handler(valid) { this.loadingsLoader(valid); },
},
id() {
this.pcaLoader(this.pcaValid);
this.loadingsLoader(this.loadingsValid);
},
loading() {
this.pcaLoader(this.pcaValid);
this.loadingsLoader(this.loadingsValid);
},
},
methods: {
pcaLoader(valid) {
if (valid === false) {
this.$store.dispatch(LOAD_PLOT, {
dataset_id: this.id,
name: 'pca',
});
}
},
loadingsLoader(valid) {
if (valid === false) {
this.$store.dispatch(LOAD_PLOT, {
dataset_id: this.id,
name: 'loadings',
});
}
},
async transformTable(value, category, argument, methods) {
/* If there's no argument and there should be, pick the firt from the list */
const method = methods.find(v => v.value === value);
Expand Down Expand Up @@ -157,16 +119,15 @@ v-layout.transform-component(row, fill-height)
score-plot-tile(
:width="600",
:height="600",
:raw-points="pcaData",
:dataset="dataset")
:id="id")
loadings-plot-tile(
:width="600",
:height="600",
:points="loadingsData")
:id="id")
scree-plot-tile(
:width="600",
:height="600",
:eigenvalues="pcaData.sdev")
:id="id")
v-container(v-else-if="ready", fill-height)
v-layout(column)
.display-2 Error: Cannot show transform table
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/stepper/Stepper.vue
Expand Up @@ -18,7 +18,7 @@ const step3 = {
};
const step4 = {
name: 'Analysis',
description: '',
description: 'Analyze your data using statistical tests and visualizations',
icon: 'checkedFlag',
};
const steps = [step1, step2, step3, step4];
Expand Down