diff --git a/client/package.json b/client/package.json index 5067806..1c138ba 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "HealthPrior", - "version": "0.3.0", + "version": "0.4.0", "description": "HealthPrior GUI", "author": "HealthPrior Working Group ", "private": true, @@ -15,7 +15,6 @@ "dependencies": { "axios": "^0.16.2", "babel-preset-es2015": "^6.24.1", - "bokehjs": "0.12.14", "bootstrap": "^3.3.7", "chartist": "^0.11.0", "crypto-api": "^0.7.3", diff --git a/client/src/components/app/DiseaseBurdenPage.vue b/client/src/components/app/DiseaseBurdenPage.vue index 7c65ade..9737e57 100644 --- a/client/src/components/app/DiseaseBurdenPage.vue +++ b/client/src/components/app/DiseaseBurdenPage.vue @@ -112,7 +112,6 @@ Last update: 2018-05-02
-
@@ -122,7 +121,6 @@ Last update: 2018-05-02 - @@ -183,7 +181,7 @@ Last update: 2018-05-02 -
- @@ -404,6 +402,8 @@ Last update: 2018-05-02 }, applyNameFilter(sets) { + console.log('CK TEST1') + console.log(sets) return sets.filter(theSet => theSet.burdenset.name.toLowerCase().indexOf(this.filterText.toLowerCase()) !== -1) }, @@ -450,6 +450,9 @@ Last update: 2018-05-02 // Reset the bottom table sorting state. this.sortColumn2 = 'name' this.sortReverse2 = false + + // Plot graphs + this.makeGraph(burdenSet) }) this.$notifications.notify({ @@ -461,6 +464,31 @@ Last update: 2018-05-02 }); }, + makeGraph(burdenSet) { + console.log('makeGraph() called for ' + burdenSet.burdenset.name) + + // Set the active project to the matched project. + this.activeBurdenSet = burdenSet + + // Go to the server to get the diseases from the burden set. + rpcservice.rpcProjectCall('get_project_burden_plots', + [this.$store.state.activeProject.project.id, this.activeBurdenSet.burdenset.numindex]) + .then(response => { + this.serverresponse = response.data // Pull out the response data. + let theFig = response.data.graph1 // Extract hack info. + mpld3.draw_figure('fig01', response.data.graph1) // Draw the figure. + mpld3.draw_figure('fig02', response.data.graph2) // Draw the figure. + mpld3.draw_figure('fig03', response.data.graph3) // Draw the figure. + }) + .catch(error => { + // Pull out the error message. + this.serverresponse = 'There was an error: ' + error.message + + // Set the server error. + this.servererror = error.message + }) + }, + copyBurdenSet(burdenSet) { console.log('copyBurdenSet() called for ' + burdenSet.burdenset.name) @@ -529,67 +557,6 @@ Last update: 2018-05-02 }) }, - makeGraph(burdenSet) { - console.log('makeGraph() called for ' + burdenSet.burdenset.name) - - // Set the active project to the matched project. - this.activeBurdenSet = burdenSet - - // Go to the server to get the diseases from the burden set. - rpcservice.rpcProjectCall('get_project_burden_plots', - [this.$store.state.activeProject.project.id, this.activeBurdenSet.burdenset.numindex]) - .then(response => { - // Pull out the response data. - this.serverresponse = response.data - - // mpld3 drawing code - - // Extract hack info. - let theFig = response.data.graph1 - - - // Run the script passed in with the graph. -// eval(response.data.graph1.script) - - // Draw the figure. - mpld3.draw_figure('fig01', response.data.graph1) - - - // Run the script passed in with the graph. -// eval(response.data.graph2.script) - - // Draw the figure. - mpld3.draw_figure('fig02', response.data.graph2) - - - // Run the script passed in with the graph. -// eval(response.data.graph3.script) - - // Draw the figure. - mpld3.draw_figure('fig03', response.data.graph3) - - - // bokeh drawing code - - // Draw the figure in the 'fig01' div tag. -/* console.log('About to replace'); - document.getElementById("fig01").innerHTML = response.data.graph1.div; - console.log('About to eval'); - console.log(response.data.graph1.script); - eval(response.data.graph1.script); */ - -/* console.log('TEMP complete') - document.getElementById("fig02").innerHTML = response.data.graph2.div; - eval(response.data.graph2.script); */ - }) - .catch(error => { - // Pull out the error message. - this.serverresponse = 'There was an error: ' + error.message - - // Set the server error. - this.servererror = error.message - }) - }, updateSorting2(sortColumn) { console.log('updateSorting2() called') @@ -628,34 +595,34 @@ Last update: 2018-05-02 } ) }, - + updateDisease(disease) { console.log('Update to be made') - console.log('Index: ', disease.numindex) + console.log('Index: ', disease.numindex) console.log('Active?: ', disease.active) console.log('Cause: ', disease.cause) console.log('DALYs: ', disease.dalys) console.log('Deaths: ', disease.deaths) console.log('Prevalence: ', disease.prevalence) - + // Do format filtering to prepare the data to pass to the RPC. let filterActive = disease.active ? 1 : 0 - + // Go to the server to update the disease from the burden set. // Note: filter out commas in the numeric fields. rpcservice.rpcProjectCall('update_burden_set_disease', - [this.$store.state.activeProject.project.id, - this.activeBurdenSet.burdenset.numindex, - disease.numindex, - [filterActive, disease.cause, - disease.dalys.replace(/,/g, ''), - disease.deaths.replace(/,/g, ''), + [this.$store.state.activeProject.project.id, + this.activeBurdenSet.burdenset.numindex, + disease.numindex, + [filterActive, disease.cause, + disease.dalys.replace(/,/g, ''), + disease.deaths.replace(/,/g, ''), disease.prevalence.replace(/,/g, '')]]) .then(response => { - // Update the display of the disease list by rerunning the active + // Update the display of the disease list by rerunning the active // burden set. this.viewBurdenSet(this.activeBurdenSet) - }) + }) } } diff --git a/client/src/components/app/HealthPackagesPage.vue b/client/src/components/app/HealthPackagesPage.vue index 1385033..6e6011d 100644 --- a/client/src/components/app/HealthPackagesPage.vue +++ b/client/src/components/app/HealthPackagesPage.vue @@ -1,272 +1,609 @@ diff --git a/client/src/components/app/InterventionsPage.vue b/client/src/components/app/InterventionsPage.vue index 2017cbd..faba51a 100644 --- a/client/src/components/app/InterventionsPage.vue +++ b/client/src/components/app/InterventionsPage.vue @@ -133,10 +133,10 @@ Last update: 2018-05-02
- + theSet.intervset.name.toLowerCase().indexOf(this.filterText.toLowerCase()) !== -1) }, @@ -390,8 +392,8 @@ export default { this.interventionList[ind].icer = Number(this.interventionList[ind][5]).toLocaleString() this.interventionList[ind].unitcost = Number(this.interventionList[ind][6]).toLocaleString() this.interventionList[ind].equity = this.interventionList[ind][8] - this.interventionList[ind].frp = this.interventionList[ind][7] - } + this.interventionList[ind].frp = this.interventionList[ind][7] + } }) this.$notifications.notify({ @@ -470,39 +472,39 @@ export default { this.updateIntervSets() }) }, - + updateInterv(interv) { console.log('Update to be made') - console.log('Index: ', interv.numindex) + console.log('Index: ', interv.numindex) console.log('Active?: ', interv.active) console.log('Name: ', interv.name) console.log('Platform: ', interv.platform) console.log('Type: ', interv.type) console.log('ICER: ', interv.icer) console.log('Unit cost: ', interv.unitcost) - console.log('FRP: ', interv.frp) + console.log('FRP: ', interv.frp) console.log('Equity: ', interv.equity) - + // Do format filtering to prepare the data to pass to the RPC. let filterActive = interv.active ? 1 : 0 - + // Go to the server to update the intervention from the intervention set. // Note: filter out commas in the numeric fields. rpcservice.rpcProjectCall('update_interv_set_interv', - [this.$store.state.activeProject.project.id, - this.activeIntervSet.intervset.numindex, - interv.numindex, - [filterActive, interv.name, interv.platform, interv.type, - interv.icer.replace(/,/g, ''), - interv.unitcost.replace(/,/g, ''), - interv.frp.replace(/,/g, ''), + [this.$store.state.activeProject.project.id, + this.activeIntervSet.intervset.numindex, + interv.numindex, + [filterActive, interv.name, interv.platform, interv.type, + interv.icer.replace(/,/g, ''), + interv.unitcost.replace(/,/g, ''), + interv.frp.replace(/,/g, ''), interv.equity.replace(/,/g, '')]]) .then(response => { - // Update the display of the intervention list by rerunning the active + // Update the display of the intervention list by rerunning the active // intervention set. this.viewSet(this.activeIntervSet) - }) - }, + }) + }, intervAllCategoryClick() { this.showCancerIntervs = false diff --git a/data/dcp-data-afg-v1.xlsx b/data/dcp-data-afg-v1.xlsx new file mode 100755 index 0000000..1464918 Binary files /dev/null and b/data/dcp-data-afg-v1.xlsx differ diff --git a/data/ihme-gbd.xlsx b/data/ihme-gbd.xlsx index ed13c78..e963d8e 100755 Binary files a/data/ihme-gbd.xlsx and b/data/ihme-gbd.xlsx differ diff --git a/hptool/__init__.py b/hptool/__init__.py index 5033ea8..30c7e7e 100644 --- a/hptool/__init__.py +++ b/hptool/__init__.py @@ -85,8 +85,11 @@ def __init__(self, errormsg, *args, **kwargs): ##################################################################################################################### # Core functions +default_key = -1 # Define what the default key is -- WARNING, move + from .burden import Burden from .interventions import Interventions +from .healthpackage import HealthPackage from .project import Project # Webapp diff --git a/hptool/burden.py b/hptool/burden.py index a2f621f..77a45d2 100644 --- a/hptool/burden.py +++ b/hptool/burden.py @@ -63,8 +63,8 @@ def plottopcauses(self, which=None, n=None, axsize=None, figsize=None, engine=No # Handle options if which is None: which = 'dalys' if n is None: n = 10 - if axsize is None: axsize = (0.15, 0.15, 0.8, 0.8) - if figsize is None: figsize = (5,5) + if axsize is None: axsize = (0.45, 0.15, 0.5, 0.8) + if figsize is None: figsize = (12,5) if engine is None: engine = 'matplotlib' # Choices are bokeh or matplotlib barw = 0.8 barcolor = (0.7,0,0.3) @@ -84,17 +84,12 @@ def plottopcauses(self, which=None, n=None, axsize=None, figsize=None, engine=No raise HPException(errormsg) # Pull out data - burdendata = dcp(self.data) burdendata.sort(col=which, reverse=True) topdata = burdendata[:n] barlabels = topdata['cause'].tolist() barvals = topdata[which] - # Truncate the bar labels (remove this soon). -# firstThreeLetters = lambda theString: theString[0:3] -# barlabels = map(firstThreeLetters, barlabels) - largestval = barvals[0] if largestval>1e6: barvals /= 1e6 @@ -106,40 +101,16 @@ def plottopcauses(self, which=None, n=None, axsize=None, figsize=None, engine=No unitstr = '' # Create plot + fig = figure(facecolor='none', figsize=figsize) + ax = fig.add_axes(axsize) + ax.set_facecolor('none') + yaxis = arange(len(barvals), 0, -1) + barh(yaxis, barvals, height=barw, facecolor=barcolor, edgecolor='none') + ax.set_yticks(arange(10, 0, -1)) + ax.set_yticklabels(barlabels) - if engine=='matplotlib': - fig = figure(facecolor='w', figsize=figsize) - ax = fig.add_axes(axsize) - yaxis = arange(len(barvals), 0, -1) - barh(yaxis, barvals, height=barw, facecolor=barcolor, edgecolor='none') - - # This way of setting the ticks works for the present mpld3 code. -# ax.set_yticks(arange(1, 11)) -# ax.set_yticklabels(barlabels[::-1]) # need to reverse bar labels order - - # This way of setting the ticks does NOT work for the release mpld3 code - # because the descending order of the ticks fouls things up. - ax.set_yticks(arange(10, 0, -1)) - ax.set_yticklabels(barlabels) - - SIticks(ax=ax,axis='x') - ax.set_xlabel(thisxlabel+unitstr) - ax.set_title(thistitle) - boxoff() - return fig - elif engine=='bokeh': - barlabelsr = barlabels[::-1] - barvalsr = barvals[::-1] - yaxis = arange(len(barvals)) - p = bkfigure(y_range=barlabelsr) - p.hbar(y=yaxis+0.5, height=0.5, left=0, right=barvalsr, color="navy") - p.xaxis[0].axis_label = thisxlabel+unitstr - p.title.text = thistitle - - script, div = bkcomponents(p) - output = {'script':script, 'div':div} - return output - else: - raise HPException('Engine %s not found' % engine) - return None - \ No newline at end of file + SIticks(ax=ax,axis='x') + ax.set_xlabel(thisxlabel+unitstr) + ax.set_title(thistitle) + boxoff() + return fig \ No newline at end of file diff --git a/hptool/healthpackage.py b/hptool/healthpackage.py new file mode 100644 index 0000000..e75b7c6 --- /dev/null +++ b/hptool/healthpackage.py @@ -0,0 +1,153 @@ +""" +Version: +""" + +import hptool as hp +import pylab as pl +import sciris.core as sc + +class HealthPackage(object): + ''' + Class to hold the results from the analysis. + ''' + + def __init__(self, name='default', project=None): + self.name = name # Name of the parameter set, e.g. 'default' + self.uid = sc.uuid() # ID + self.projectref = hp.Link(project) # Store pointer for the project, if available + self.created = sc.today() # Date created + self.modified = sc.today() # Date modified + self.results = None + + def __repr__(self): + ''' Print out useful information when called''' + output = sc.defaultrepr(self) + output += 'Health packages name: %s\n' % self.name + output += ' Date created: %s\n' % sc.getdate(self.created) + output += ' Date modified: %s\n' % sc.getdate(self.modified) + output += ' UID: %s\n' % self.uid + output += '============================================================\n' + return output + + def make_package(self, burdenset=None, interset=None): + ''' Make results ''' + burdenset = self.projectref().burden(key=burdenset) + interset = self.projectref().inter(key=interset) + + # Data cleaning: remove if missing: cause, icer, unitcost, spending + origdata = sc.dcp(interset.data) + critical_cols = ['cause', 'unitcost', 'spend', 'icer'] + for col in critical_cols: + origdata.filter_out(key='', col=col, verbose=True) + origdata.replace(col='spend', old='', new=0.0) + nrows = origdata.nrows() + + # Create new dataframe + df = sc.dataframe(cols=['active'], data=pl.ones(nrows)) + for col in ['shortname']+critical_cols: # Copy columns over + df[col] = origdata[col] + + # Calculate people covered (spending/unitcost) + df['coverage'] = df['spend']/df['unitcost'] + + # Pull out DALYS and prevalence + df.addcol('total_dalys') + df.addcol('total_prevalence') + for r in range(df.nrows()): + key = df.get(rows=r, cols='cause') + try: + tmp_burden = burdenset.data.findrow(key=key, col='cause', asdict=True) + except: + raise hp.HPException('Burden "%s" not found' % key) + df['total_dalys',r] = tmp_burden['dalys'] + df['total_prevalence',r] = tmp_burden['prevalence'] + + # Calculate 80% coverage + print('Not calculating 80% coverage since denominators are wrong') + + # Current DALYs averted (spend/icer) + df['dalys_averted'] = df['spend']/df['icer'] + + # Current % of DALYs averted (dalys_averted/total_dalys) + df['frac_averted'] = df['dalys_averted']/df['total_dalys'] # To list large fractions: df['shortname'][ut.findinds(df['frac_averted']>0.2)] + + self.results = df # Store it + return None + + def export(self, cols=None, rows=None, header=None): + ''' Export to a JSON-friendly representation ''' + output = self.results.jsonify(cols=cols, rows=rows, header=header) + return output + + def plot_dalys(self): + df = self.results + fig = pl.figure(figsize=(10,6)) + max_entries = 11 + colors = sc.gridcolors(ncolors=max_entries+2)[2:] + df.sort(col='dalys_averted', reverse=True) + DA_data = df['dalys_averted'] + plot_data = list(DA_data[:max_entries-1]) + plot_data.append(sum(DA_data[max_entries:])) + plot_data = pl.array(plot_data)/1e3 + plot_data = plot_data.round() + total_averted = (plot_data.sum()/1e3) + data_labels = ['%i'%datum for datum in plot_data] + DA_labels = df['shortname'] + plot_labels = list(DA_labels[:max_entries-1]) + plot_labels.append('Other') + pl.axes([0.1,0.1,0.5,0.8]) + pl.pie(plot_data, labels=data_labels, colors=colors, startangle=90, counterclock=False, radius=0.5, labeldistance=1.03) + pl.gca().axis('equal') + pl.title("Current DALYs averted by health intervention\n('000s; total: %0.2f million)" % total_averted) + pl.legend(plot_labels, bbox_to_anchor=(1,0.8)) + pl.gca().set_facecolor('none') + return fig + + def plot_cascade(self, vertical=True): + if vertical: + fig_size = (12,12) + ax_size = [0.45,0.05,0.5,0.9] + else: + fig_size = (16,8) + ax_size = [0.05,0.45,0.9,0.5] + df = self.results + cutoff = 200e3 + fig = pl.figure(figsize=fig_size) + df.sort(col='icer', reverse=False) + DA_data = df['spend'] + inds = sc.findinds(DA_data>cutoff) + DA_data = DA_data[inds] + DA_data /= 1e6 + DA_labels = df['shortname'][inds] + npts = len(DA_data) + colors = sc.gridcolors(npts, limits=(0.25,0.75)) + x = pl.arange(len(DA_data)) + pl.axes(ax_size) + for pt in range(npts): + loc = x[pt:] + this = DA_data[pt] + start = sum(DA_data[:pt]) + prop = 0.9 + color = colors[pt] + amount = sum(DA_data[:pt+1]) + amountstr = '%0.1f' % amount + if vertical: + pl.barh(loc, width=this, left=start, height=prop, color=color) + pl.text(amount, x[pt], amountstr, verticalalignment='center', color=colors[pt]) + else: + pl.bar(loc, height=this, bottom=start, width=prop, color=color) + pl.text(x[pt], amount+1, amountstr, horizontalalignment='center', color=colors[pt]) + if vertical: + pl.xlabel('Spending (US$ millions)') + pl.gca().set_yticks(x) + ticklabels = pl.gca().set_yticklabels(DA_labels) + else: + pl.ylabel('Spending (US$ millions)') + pl.gca().set_xticks(x) + ticklabels = pl.gca().set_xticklabels(DA_labels, rotation=90) + for t,tl in enumerate(ticklabels): + tl.set_color(colors[t]) + + pl.gca().set_facecolor('none') + pl.title('Investment cascade for Afghanistan') + return fig \ No newline at end of file diff --git a/hptool/project.py b/hptool/project.py index 4770f0a..7deaab2 100644 --- a/hptool/project.py +++ b/hptool/project.py @@ -1,6 +1,9 @@ -from hptool import odict, uuid, today, version, gitinfo, objrepr, getdate -from hptool import printv, makefilepath, saveobj, dcp -from hptool import Burden, Interventions +####################################################################################################### +## Imports and setup +####################################################################################################### + +import hptool as hp +import sciris.core as sc ####################################################################################################### @@ -14,10 +17,9 @@ class Project(object): The main HealthPrior project class. Almost all functionality is provided by this class. An HP project is based around 4 major lists: - 1. burdens -- an odict of burden data - 2. intervs -- an odict of program sets - 3. optims -- an odict of optimization structures - 4. results -- an odict of results associated with the choices above + 1. burdensets -- an odict of burden data + 2. intersets -- an odict of intervention sets + 3. packagesets -- an odict of health packages Methods for structure lists: @@ -35,56 +37,59 @@ class Project(object): ### Built-in methods -- initialization, and the thing to print if you call a project ####################################################################################################### - def __init__(self, name='default', burdenfile=None, interventionsfile=None, country=None, verbose=2, **kwargs): + def __init__(self, name='default', burdenfile=None, interventionsfile=None, country=None, make_package=True, verbose=2, **kwargs): ''' Initialize the project ''' ## Define the structure sets - self.burdensets = odict() - self.intersets = odict() - self.optims = odict() - self.results = odict() + self.burdensets = sc.odict() + self.intersets = sc.odict() + self.packagesets = sc.odict() ## Define other quantities self.name = name self.country = country - self.uid = uuid() - self.created = today() - self.modified = today() - self.spreadsheetdate = 'Spreadsheet never loaded' - self.version = version - self.gitinfo = gitinfo(__file__) + self.uid = sc.uuid() + self.created = sc.today() + self.modified = sc.today() + self.version = hp.version + self.gitinfo = sc.gitinfo(__file__) self.filename = None # File path, only present if self.save() is used self.warnings = None # Place to store information about warnings (mostly used during migrations) ## Load burden spreadsheet, if available if burdenfile: - burden = Burden(project=self) + burden = hp.Burden(project=self) burden.loaddata(filename=burdenfile) self.burdensets['default'] = burden ## Load interventions spreadsheet, if available if interventionsfile: - interventions = Interventions(project=self) + interventions = hp.Interventions(project=self) interventions.loaddata(filename=interventionsfile) self.intersets['default'] = interventions + + ## Combine into health package, if available + if make_package and burdenfile and interventionsfile: + package = hp.HealthPackage(project=self) + package.make_package() + self.packagesets['default'] = package return None def __repr__(self): ''' Print out useful information when called ''' - output = objrepr(self) + output = sc.objrepr(self) output += ' Project name: %s\n' % self.name output += ' Country: %s\n' % self.country output += '\n' output += ' Burden sets: %i\n' % len(self.burdensets) output += ' Intervention sets: %i\n' % len(self.intersets) - output += ' Optimizations: %i\n' % len(self.optims) - output += ' Results sets: %i\n' % len(self.results) + output += ' Health packages: %i\n' % len(self.packagesets) output += '\n' output += ' HP version: %s\n' % self.version - output += ' Date created: %s\n' % getdate(self.created) - output += ' Date modified: %s\n' % getdate(self.modified) + output += ' Date created: %s\n' % sc.getdate(self.created) + output += ' Date modified: %s\n' % sc.getdate(self.modified) output += ' Git branch: %s\n' % self.gitinfo['branch'] output += ' Git hash: %s\n' % self.gitinfo['hash'] output += ' UID: %s\n' % self.uid @@ -95,7 +100,7 @@ def __repr__(self): def getinfo(self): ''' Return an odict with basic information about the project''' - info = odict() + info = sc.odict() for attr in ['name', 'version', 'created', 'modified', 'gitbranch', 'gitversion', 'uid']: info[attr] = getattr(self, attr) # Populate the dictionary # info['parsetkeys'] = self.parsets.keys() @@ -123,18 +128,11 @@ def getwarnings(self, doprint=True): return output - def save(self, filename=None, folder=None, saveresults=False, verbose=2): + def save(self, filename=None, folder=None, verbose=2): ''' Save the current project, by default using its name, and without results ''' - fullpath = makefilepath(filename=filename, folder=folder, default=[self.filename, self.name], ext='prj', sanitize=True) + fullpath = sc.makefilepath(filename=filename, folder=folder, default=[self.filename, self.name], ext='prj', sanitize=True) self.filename = fullpath # Store file path - if saveresults: - saveobj(fullpath, self, verbose=verbose) - else: - tmpproject = dcp(self) # Need to do this so we don't clobber the existing results -# tmpproject.restorelinks() # Make sure links are restored - tmpproject.cleanresults() # Get rid of all results - saveobj(fullpath, tmpproject, verbose=verbose) # Save it to file - del tmpproject # Don't need it hanging around any more + sc.saveobj(fullpath, self, verbose=verbose) return fullpath @@ -142,20 +140,22 @@ def save(self, filename=None, folder=None, saveresults=False, verbose=2): ### Utilities ####################################################################################################### - def burden(self, key=-1, verbose=2): + def burden(self, key=None, verbose=2): ''' Shortcut for getting the latest active burden set, i.e. self.burdensets[-1] ''' + if key is None: key = hp.default_key try: return self.burdensets[key] - except: return printv('Warning, burden set not found!', 1, verbose) # Returns None + except: return sc.printv('Warning, burden set not found!', 1, verbose) # Returns None - def inter(self, key=-1, verbose=2): + def inter(self, key=None, verbose=2): ''' Shortcut for getting the latest active interventions set, i.e. self.intersets[-1] ''' + if key is None: key = hp.default_key try: return self.intersets[key] - except: return printv('Warning, interventions set not found!', 1, verbose) # Returns None + except: return sc.printv('Warning, interventions set not found!', 1, verbose) # Returns None - def cleanresults(self): - ''' Remove all results ''' - for key,result in self.results.items(): - self.results.pop(key) - return None + def package(self, key=None, verbose=2): + ''' Shortcut for getting the latest active health packages set, i.e. self.intersets[-1] ''' + if key is None: key = hp.default_key + try: return self.packagesets[key] + except: return sc.printv('Warning, interventions set not found!', 1, verbose) # Returns None diff --git a/hptool/version.py b/hptool/version.py index b456453..ed8a425 100644 --- a/hptool/version.py +++ b/hptool/version.py @@ -1,2 +1,2 @@ -version = '0.3.1' -versiondate = '2018-04-16' \ No newline at end of file +version = '0.4.0' +versiondate = '2018-05-18' \ No newline at end of file diff --git a/hptool/webapp/main.py b/hptool/webapp/main.py index 13ae0c9..3470242 100644 --- a/hptool/webapp/main.py +++ b/hptool/webapp/main.py @@ -20,9 +20,9 @@ import sciris.datastore as ds import sciris.user as user import sciris.project as project -import hptool from hptool.webapp import config -from hptool import uuid, dcp, Burden, Interventions +import sciris.core as sc +import hptool as hp # @@ -116,7 +116,7 @@ def loadFromCopy(self, otherObject): super(ProjectSO, self).loadFromCopy(otherObject) # Copy the Project object itself. - self.theProject = dcp(otherObject.theProject) + self.theProject = sc.dcp(otherObject.theProject) # Copy the owner UID. self.ownerUID = otherObject.ownerUID @@ -131,7 +131,6 @@ def show(self): print 'Project Name: %s' % self.theProject.name print 'Creation Time: %s' % self.theProject.created print 'Update Time: %s' % self.theProject.modified - print 'Data Upload Time: %s' % self.theProject.spreadsheetdate def getUserFrontEndRepr(self): objInfo = { @@ -141,7 +140,6 @@ def getUserFrontEndRepr(self): 'userId': self.ownerUID, 'creationTime': self.theProject.created, 'updatedTime': self.theProject.modified, - 'dataUploadTime': self.theProject.spreadsheetdate } } return objInfo @@ -224,10 +222,10 @@ def saveAsFile(self, loadDir): # self.uid = validUID # # Otherwise, generate a new random UUID using uuid4(). # else: -# self.uid = uuid() +# self.uid = sc.uuid() # # Otherwise, generate a new random UUID using uuid4(). # else: -# self.uid = uuid() +# self.uid = sc.uuid() # # # Set the project name. # self.name = name @@ -460,15 +458,15 @@ def init_projects(theApp): # Else (no match)... else: # Load the data path holding the Excel files. - dataPath = hptool.HPpath('data') + dataPath = hp.HPpath('data') print '>> Creating a new ProjectCollection.' project.theProjCollection.addToDataStore() print '>> Starting a demo project.' - theProject = hptool.Project(name='Afghanistan test 1', + theProject = hp.Project(name='Afghanistan test 1', burdenfile=dataPath + 'ihme-gbd.xlsx', - interventionsfile=dataPath + 'dcp-data.xlsx') + interventionsfile=dataPath + 'dcp-data-afg-v1.xlsx') theProjectSO = ProjectSO(theProject, user.get_scirisdemo_user()) # theProjectSO = ProjectSO('Afghanistan test 1', # user.get_scirisdemo_user(), @@ -476,7 +474,7 @@ def init_projects(theApp): project.theProjCollection.addObject(theProjectSO) print '>> Starting a second demo project.' - theProject = hptool.Project(name='Afghanistan HBP equity') + theProject = hp.Project(name='Afghanistan HBP equity') theProjectSO = ProjectSO(theProject, user.get_scirisdemo_user()) # theProjectSO = ProjectSO('Afghanistan HBP equity', # user.get_scirisdemo_user(), @@ -484,7 +482,7 @@ def init_projects(theApp): project.theProjCollection.addObject(theProjectSO) print '>> Starting a third demo project.' - theProject = hptool.Project(name='Final Afghanistan HBP') + theProject = hp.Project(name='Final Afghanistan HBP') theProjectSO = ProjectSO(theProject, user.get_scirisdemo_user()) # theProjectSO = ProjectSO('Final Afghanistan HBP', # user.get_scirisdemo_user(), @@ -492,7 +490,7 @@ def init_projects(theApp): project.theProjCollection.addObject(theProjectSO) print '>> Starting a fourth demo project.' - theProject = hptool.Project(name='Pakistan test 1') + theProject = hp.Project(name='Pakistan test 1') theProjectSO = ProjectSO(theProject, user.get_scirisdemo_user()) # theProjectSO = ProjectSO('Pakistan test 1', # user.get_scirisdemo_user(), @@ -503,7 +501,7 @@ def init_projects(theApp): project.theProjCollection.show() def init_main(theApp): - print('-- Welcome to the HealthPrior webapp, version %s (%s) --' % (hptool.version, hptool.versiondate)) + print('-- Welcome to the HealthPrior webapp, version %s (%s) --' % (hp.version, hp.versiondate)) return None # @@ -694,10 +692,10 @@ def json_sanitize_result(theResult): def get_version_info(): ''' Return the informatino about the project. ''' - gitinfo = sciris.utils.gitinfo(__file__) + gitinfo = sc.gitinfo(__file__) version_info = { - 'version': hptool.version, - 'date': hptool.versiondate, + 'version': hp.version, + 'date': hp.versiondate, 'gitbranch': gitinfo['branch'], 'githash': gitinfo['hash'], 'gitdate': gitinfo['date'], @@ -720,7 +718,7 @@ def save_project(theProj): project_record = project.load_project_record(theProj.uid) # Copy the project, only save what we want... - new_project = dcp(theProj) + new_project = sc.dcp(theProj) # Create the new project entry and enter it into the ProjectCollection. # Note: We don't need to pass in project.uid as a 3rd argument because @@ -754,7 +752,7 @@ def save_project_as_new(theProj, user_id): """ # Set a new project UID, so we aren't replicating the UID passed in. - theProj.uid = uuid() + theProj.uid = sc.uuid() # Create the new project entry and enter it into the ProjectCollection. theProjSO = ProjectSO(theProj, user_id) @@ -790,6 +788,17 @@ def get_interv_set_fe_repr(theIntervSet): } return objInfo +def get_package_set_fe_repr(packageset): + objInfo = { + 'packageset': { + 'name': packageset.name, + 'uid': packageset.uid, + 'creationTime': packageset.created, + 'updateTime': packageset.modified + } + } + return objInfo + # # RPC functions # @@ -813,15 +822,15 @@ def create_new_project(user_id): return {'error': 'Unauthorized RPC'} # Load the data path holding the Excel files. - dataPath = hptool.HPpath('data') + dataPath = hp.HPpath('data') # Get a unique name for the project to be added. newProjName = project.get_unique_name('New project', other_names=None) # Create the project, loading in the desired spreadsheets. - theProj = hptool.Project(name=newProjName, + theProj = hp.Project(name=newProjName, burdenfile=dataPath + 'ihme-gbd.xlsx', - interventionsfile=dataPath + 'dcp-data.xlsx') + interventionsfile=dataPath + 'dcp-data-afg-v1.xlsx') # Set the burden population size. theProj.burden().popsize = 36373.176 # From UN population division @@ -874,7 +883,7 @@ def copy_project(project_id): theProj = project_record.theProject # Make a copy of the project loaded in to work with. - new_project = dcp(theProj) + new_project = sc.dcp(theProj) # Just change the project name, and we have the new version of the # Project object to be saved as a copy. @@ -974,12 +983,12 @@ def update_project_fn(theProj): other_names=list(theProj.burdensets)) # Create a new (empty) burden set. - newBurdenSet = Burden(project=theProj, name=uniqueName) + newBurdenSet = hp.Burden(project=theProj, name=uniqueName) # Load data from the Excel spreadsheet. # NOTE: We may want to take this out later in favor leaving the # new sets empty to start. - dataPath = hptool.HPpath('data') + dataPath = hp.HPpath('data') newBurdenSet.loaddata(dataPath+'ihme-gbd.xlsx') # Put the new burden set in the dictionary. @@ -1018,7 +1027,7 @@ def update_project_fn(theProj): other_names=list(theProj.burdensets)) # Create a new burdenset which is a copy of the old one. - newBurdenSet = dcp(theProj.burdensets[burdenset_numindex]) + newBurdenSet = sc.dcp(theProj.burdensets[burdenset_numindex]) # Overwrite the old name with the new. newBurdenSet.name = uniqueName @@ -1073,9 +1082,6 @@ def update_project_fn(theProj): frontendfigsize = (5.5, 2) frontendpositionnolegend = [[0.19, 0.12], [0.85, 0.85]] -from matplotlib.transforms import Bbox -from numpy import array -from pylab import subplots class HelloWorld(mpld3.plugins.PluginBase): # inherit from PluginBase """Hello World plugin""" @@ -1103,15 +1109,15 @@ def __init__(self): def make_mpld3_graph_dict(theFig): # Handle figure size - zoom = 1.0 - figsize = (frontendfigsize[0] * zoom, frontendfigsize[1] * zoom) - theFig.set_size_inches(figsize) - - if len(theFig.axes) == 1: - ax = theFig.axes[0] - legend = ax.get_legend() - if legend is None: - ax.set_position(Bbox(array(frontendpositionnolegend))) +# zoom = 1.0 +# figsize = (frontendfigsize[0] * zoom, frontendfigsize[1] * zoom) +# theFig.set_size_inches(figsize) + +# if len(theFig.axes) == 1: +# ax = theFig.axes[0] +# legend = ax.get_legend() +# if legend is None: +# ax.set_position(Bbox(array(frontendpositionnolegend))) mpld3_dict = mpld3.fig_to_dict(theFig) @@ -1222,13 +1228,13 @@ def update_project_fn(theProj): other_names=list(theProj.intersets)) # Create a new (empty) intervention set. - newIntervSet = Interventions(project=theProj, name=uniqueName) + newIntervSet = hp.Interventions(project=theProj, name=uniqueName) # Load data from the Excel spreadsheet. # NOTE: We may want to take this out later in favor leaving the # new sets empty to start. - dataPath = hptool.HPpath('data') - newIntervSet.loaddata(dataPath+'dcp-data.xlsx') + dataPath = hp.HPpath('data') + newIntervSet.loaddata(dataPath+'dcp-data-afg-v1.xlsx') # Put the new intervention set in the dictionary. theProj.intersets[uniqueName] = newIntervSet @@ -1266,7 +1272,7 @@ def update_project_fn(theProj): other_names=list(theProj.intersets)) # Create a new intervention set which is a copy of the old one. - newIntervSet = dcp(theProj.intersets[intervset_numindex]) + newIntervSet = sc.dcp(theProj.intersets[intervset_numindex]) # Overwrite the old name with the new. newIntervSet.name = uniqueName @@ -1321,11 +1327,178 @@ def update_project_fn(theProj): # Do the project update using the internal function. update_project_with_fn(project_id, update_project_fn) + + + +## +## package set RPCs +## + +def get_project_package_sets(project_id): + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Get the Project object. + theProj = project.load_project(project_id) + + # Get a list of the package objects. + packageSets = [theProj.packagesets[ind] for ind in range(len(theProj.packagesets))] + + # Return the JSON-friendly result. + return {'packagesets': map(get_package_set_fe_repr, packageSets)} + +def get_project_package_set_results(project_id, packageset_numindex): + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Get the Project object. + theProj = project.load_project(project_id) + + # Get the package set that matches packageset_numindex. + packageSet = theProj.package(key=packageset_numindex) + + # Return an empty list if no data is present. + if packageSet.results is None: + return { 'results': [] } + + # Gather the list for all of the diseases. + resultData = packageSet.export(cols=['active','shortname','cause','coverage','dalys_averted'], header=False) + + # Return success. + return { 'results': resultData } + +def create_package_set(project_id, new_package_set_name): + + def update_project_fn(theProj): + # Get a unique name (just in case the one provided collides with an + # existing one). + uniqueName = project.get_unique_name(new_package_set_name, + other_names=list(theProj.packagesets)) + + # Create a new (empty) package set. + newpackageSet = hp.HealthPackage(project=theProj, name=uniqueName) + + # Put the new package set in the dictionary. + theProj.packagesets[uniqueName] = newpackageSet + + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Do the project update using the internal function. + update_project_with_fn(project_id, update_project_fn) + + # Return the new package sets. + return get_project_package_sets(project_id) + +def delete_package_set(project_id, packageset_numindex): + + def update_project_fn(theProj): + theProj.packagesets.pop(packageset_numindex) + + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Do the project update using the internal function. + update_project_with_fn(project_id, update_project_fn) + +def copy_package_set(project_id, packageset_numindex): + + def update_project_fn(theProj): + # Get a unique name (just in case the one provided collides with an + # existing one). + uniqueName = project.get_unique_name(theProj.packagesets[packageset_numindex].name, + other_names=list(theProj.packagesets)) + + # Create a new packageset which is a copy of the old one. + newpackageSet = sc.dcp(theProj.packagesets[packageset_numindex]) + + # Overwrite the old name with the new. + newpackageSet.name = uniqueName + + # Put the new package set in the dictionary. + theProj.packagesets[uniqueName] = newpackageSet + + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Do the project update using the internal function. + update_project_with_fn(project_id, update_project_fn) + + # Return the new package sets. + return get_project_package_sets(project_id) + +def rename_package_set(project_id, packageset_numindex, new_package_set_name): + + def update_project_fn(theProj): + # Overwrite the old name with the new. + theProj.packagesets[packageset_numindex].name = new_package_set_name + + # Check (for security purposes) that the function is being called by the + # correct endpoint, and if not, fail. + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Do the project update using the internal function. + update_project_with_fn(project_id, update_project_fn) + + +def get_project_package_plots(project_id, packageset_numindex): + ''' Plot the health packages ''' + + if request.endpoint != 'normalProjectRPC': + return {'error': 'Unauthorized RPC'} + + # Get the Project object. + theProj = project.load_project(project_id) + + # Get the package set that matches packageset_numindex. + packageSet = theProj.package(key=packageset_numindex) + + figs = [] + fig1 = packageSet.plot_dalys() + fig2 = packageSet.plot_cascade() + figs.append(fig1) + figs.append(fig2) + + # Gather the list for all of the diseases. + graphs = [] + for fig in figs: + graph_dict = make_mpld3_graph_dict(fig) + graphs.append(graph_dict) + + # Return success. + return {'graph1': graphs[0], + 'graph2': graphs[1],} + + + + + + + ## ## Temporary (development) RPCs ## + + + + + + + + def tester_func_main(project_id): theProjRecord = project.load_project_record(project_id) print theProjRecord diff --git a/scripts/afghanistan.py b/scripts/afghanistan.py new file mode 100644 index 0000000..0bef95a --- /dev/null +++ b/scripts/afghanistan.py @@ -0,0 +1,109 @@ +# Create results for Afghanistan + +#%% Setup + +# Create project +import hptool as hp +import sciris.core as sc +import pylab as pl +import numpy as np + +# Set things +plot_dalys = True +plot_cascade = False +export = False +name = 'Afghanistan' +burdenfile = hp.HPpath('data')+'ihme-gbd.xlsx' +interventionsfile = hp.HPpath('data')+'dcp-data-afg-v1.xlsx' + +P = hp.Project(name=name, burdenfile=burdenfile, interventionsfile=interventionsfile) + +#%% Calculations + +# Data cleaning: remove if missing: cause, icer, unitcost, spending +df = hp.dcp(P.inter().data) +for col in ['icer', 'unitcost', 'cause']: + df.filter_out(key='', col=col, verbose=True) +df.replace(col='spend', old='', new=0.0) + +for col in [u'Intervention Number', u'fullname', u'frp', u'equity', u'Level 1 cause', u'Level 1 cause name', u'Level 2 cause ', u'Level 3 cause ', u'DCP3 Packages', u'Package Number', u'Urgency', u'Code', u'Codes for interventions that appear in multiple packages', u'Volume(s) intervention included in', u'Platform in Volume', u'Platform in EUHC']: + df.rmcol(col) + +# Calculate people covered (spending/unitcost) +df['coverage'] = df['spend']/df['unitcost'] + +# Pull out DALYS and prevalence +df.addcol('total_dalys') +df.addcol('total_prevalence') +for r in range(df.nrows()): + key = df.get(rows=r, cols='cause') + tmp_burden = P.burden().data.findrow(key=key, col='cause', asdict=True) + df['total_dalys',r] = tmp_burden['dalys'] + df['total_prevalence',r] = tmp_burden['prevalence'] + +# Calculate 80% coverage +print('Not calculating 80% coverage since denominators are wrong') + +# Current DALYs averted (spend/icer) +df['dalys_averted'] = df['spend']/df['icer'] + +# Current % of DALYs averted (dalys_averted/total_dalys) +df['frac_averted'] = df['dalys_averted']/df['total_dalys'] # To list large fractions: df['shortname'][ut.findinds(df['frac_averted']>0.2)] + +#%% Make plots + +# Current DALYs averted +if plot_dalys: + pl.figure(figsize=(10,6)) + max_entries = 11 + colors = sc.gridcolors(ncolors=max_entries+2)[2:] + df.sort(col='dalys_averted', reverse=True) + DA_data = df['dalys_averted'] + plot_data = list(DA_data[:max_entries-1]) + plot_data.append(sum(DA_data[max_entries:])) + plot_data = np.array(plot_data)/1e3 + plot_data = plot_data.round() + total_averted = (plot_data.sum()/1e3) + data_labels = ['%i'%datum for datum in plot_data] + DA_labels = df['shortname'] + plot_labels = list(DA_labels[:max_entries-1]) + plot_labels.append('Other') + pl.axes([0.1,0.1,0.5,0.8]) + pl.pie(plot_data, labels=data_labels, colors=colors, startangle=90, counterclock=False, radius=0.5, labeldistance=1.03) + pl.gca().axis('equal') + pl.title("Current DALYs averted by health intervention\n('000s; total: %0.2f million)" % total_averted) + pl.legend(plot_labels, bbox_to_anchor=(1,0.8)) + pl.savefig('hptool_afghanistan_DALYS_averted_2018-05-14.png') + + +# Investment cascade +if plot_cascade: + cutoff = 200e3 + pl.figure(figsize=(16,8)) + df.sort(col='icer', reverse=False) + DA_data = df['spend'] + inds = sc.findinds(DA_data>cutoff) + DA_data = DA_data[inds] + DA_data /= 1e6 + DA_labels = df['shortname'][inds] + npts = len(DA_data) + colors = sc.gridcolors(npts, limits=(0.25,0.75)) + x = np.arange(len(DA_data)) + pl.axes([0.05,0.45,0.9,0.5]) + for pt in range(npts): + pl.bar(x[pt:], height=DA_data[pt], bottom=sum(DA_data[:pt]), width=0.9, color=colors[pt]) + amount = sum(DA_data[:pt+1])+1 + amountstr = '%0.1f' % amount + pl.text(x[pt], amount, amountstr, horizontalalignment='center', color=colors[pt]) + pl.gca().set_xticks(x) + ticklabels = pl.gca().set_xticklabels(DA_labels, rotation=90) + for t,tl in enumerate(ticklabels): + tl.set_color(colors[t]) + pl.ylabel('Spending (US$ millions)') + pl.title('Investment cascade for Afghanistan') + pl.savefig('hptool_afghanistan_investment_cascade_2018-05-14.png') + +if export: + df.export(filename='hptool_afghanistan_data_2018-05-14.xlsx') + +print('Done.') \ No newline at end of file diff --git a/scripts/example.py b/scripts/example.py index 522b4e2..cf7994a 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -8,7 +8,7 @@ from pylab import show dp = HPpath('data') -P = Project(burdenfile=dp+'ihme-gbd.xlsx', interventionsfile=dp+'dcp-data.xlsx') +P = Project(burdenfile=dp+'ihme-gbd.xlsx', interventionsfile=dp+'dcp-data-afg-v1.xlsx') P.burden().popsize = 36373.176 # From UN population division @@ -16,7 +16,8 @@ print('\n\nExample interventions entry:\n\n%s' % P.intersets[0].data[37]) -P.burden().plottopcauses(axsize=(0.55, 0.15, 0.4, 0.8), figsize=(15,5)) +#P.burden().plottopcauses() +P.package().plot_cascade() #P.burden().plottopcauses(which='prevalence', n=15) # dd = P.burden().export(cols=['cause','dalys','deaths','prevalence'])