This site is represents a re-writing and consolidation of several of the z-score calculators at parameterz.blogspot.com. The backstory behind the parameterz.com domain is that I was unsatisfied with the available z-score equations that were supplied with our professional-grade echo reporting software, and I thought I could do something better. I had originally set up a few calculators as a test to: a) see if I could write a web-based z-score calculator, and b) compare the calculated z-scores among the available references. I thought others might find the calculators useful for their own similar purpose.
I have tried to make the references and code available (see below) should you like to incorporate these calculations into your own echo reporting software. If, on the other hand, you find yourself using the site as the sole source for reporting your z-scores (which is fine, provided you understand the disclaimer), please consider making a donation:
Each of the references cited (Boston, Detroit, Halifax, Paris, and Wessex) has it's own module. The modules are basically containers for classes, each providing a similar interface for accessing methods for the z-score, mean, and range calculations.
(click to show/hide the code samples)
#!/usr/bin/env python '''boston - A module for calculating z-scores based on data published from Boston Childrens Hospital Validation and re-evaluation of a discriminant model predicting anatomic suitability for biventricular repair in neonates with aortic stenosis. Colan SD, McElhinney DB, Crawford EC, Keane JF, Lock JE. J Am Coll Cardiol. 2006 May 2;47(9):1858-65. ''' from __future__ import division #required to avoid random 'classic'(integer) division import math class Base( object ): '''this is the basic form for calculating many of the Boston z-scores: one regression (based on an allometric relationship with bsa) for the mean and a separate regression for the sd ''' def __init__(self, d): self.slope = d['slope'] self.exponent = d['exponent'] self.sd_int = d['sd_int'] self.sd_slope = d['sd_slope'] self.site = d['site'] self.bsaMethod = 'Haycock' def zscore( self, pt ): try: score = getattr(pt, self.site) #the incoming score is in 'mm' and needs to be converted to expected 'cm' score /= 10 return ( ( score ) - self._mean( pt.bsa(self.bsaMethod) ) ) / self.sd( pt.bsa(self.bsaMethod) ) except: return None #property of object 'pt' does not exist def mean( self, pt ): return 10 * self._mean( pt.bsa(self.bsaMethod) ) def _mean( self, bsa ): #this is a 'private' method that allows for the calculation in expected units (cm) return self.slope * math.pow( bsa, self.exponent ) def sd( self, bsa ): return self.sd_int + ( self.sd_slope * bsa ) def range( self, pt, limit = 1.96 ): lower = -limit * self.sd( pt.bsa(self.bsaMethod) ) + self._mean( pt.bsa(self.bsaMethod) ) upper = limit * self.sd( pt.bsa(self.bsaMethod) ) + self._mean( pt.bsa(self.bsaMethod) ) return '%.2f - %.2f' %( 10 * lower, 10 * upper ) aov = Base( { 'site': 'aov', 'slope': 1.55, 'exponent': 0.5, 'sd_int': 0.060, 'sd_slope': 0.083 } ) sov = Base( { 'site': 'sov', 'slope': 2.02, 'exponent': 0.5, 'sd_int': 0.098, 'sd_slope': 0.120 } ) sites ={ 'aov': aov, 'sov': sov }
#!/usr/bin/env python '''detroit - A module for calculating z-scores based on data published from Detroit Med Ctr Regression equations for calculation of z scores of cardiac structures in a large cohort of healthy infants, children, and adolescents: an echocardiographic study. Pettersen MD, Du W, Skeens ME, Humes RA. J Am Soc Echocardiogr. 2008 Aug;21(8):922-34. ''' from __future__ import division #required to avoid random classic/integer division import math class Base(object): '''this is the basic form for the Detroit equations: a third-order polynomial equation, log transforming only the measurement (and not bsa) ''' def __init__(self, d): self.b1 = d['b1'] self.b2 = d['b2'] self.b3 = d['b3'] self.int = d['int'] self.mse = d['mse'] self.site = d['site'] self.bsaMethod = 'Dubois' def _mean( self, bsa ): #this is a 'private' method that allows calculation in the expected units, 'cm' return self.int + ( self.b1 * bsa ) + ( self.b2 * math.pow( bsa, 2 ) ) + ( self.b3 * math.pow( bsa, 3 ) ) def mean( self, pt ): bsa = pt.bsa(self.bsaMethod) return 10 * math.exp( self._mean( bsa ) ) def sd( self ): return math.sqrt( self.mse ) def zscore( self, pt ): bsa = pt.bsa(self.bsaMethod) try: score = getattr(pt, self.site) #the incoming score is in 'mm' and needs to be converted to expected 'cm' score /= 10 return ( math.log( score ) - self._mean( bsa ) ) / self.sd( ) except: return None def range( self, pt, limit = 1.96 ): bsa = pt.bsa(self.bsaMethod) lower = math.exp( -limit * self.sd() + self._mean( bsa ) ) upper = math.exp( limit * self.sd() + self._mean( bsa ) ) return '%.2f - %.2f' %( 10 * lower, 10 * upper ) aov = Base( { 'site': 'aov', 'int': -0.874, 'b1': 2.708, 'b2': -1.841, 'b3': 0.452, 'mse': 0.010 } ) sov = Base( { 'site': 'sov', 'int': -0.500, 'b1': 2.537, 'b2': -1.707, 'b3': 0.420, 'mse': 0.012 } ) stj = Base( { 'site': 'stj', 'int': -0.759, 'b1': 2.643, 'b2': -1.797, 'b3': 0.442, 'mse': 0.018 } ) sites = { 'aov': aov, 'sov': sov, 'stj': stj }
#!/usr/bin/env python '''halifax - A module for calculating aortic root z-scores based on data published from Dalhousie University, Halifax, Nova Scotia, Canada Dilatation of the ascending aorta in paediatric patients with bicuspid aortic valve: frequency, rate of progression and risk factors. Warren AE, Boyd ML, OConnell C, Dodds L. Heart. 2006 Oct;92(10):1496-500. Epub 2006 Mar 17 ''' import math class Base( object ): '''this is the basic form for the Halifax equations: a log-log transformation and "stablized" variance ''' def __init__( self, d): self.slope = d['slope'] self.int = d['int'] self.mse = d['mse'] self.site = d['site'] self.bsaMethod = 'Boyd' def _mean( self, bsa ): #returns the log-transformed mean value return self.int + self.slope * math.log( bsa ) def mean( self, pt ): #returns the back-transformed mean value bsa = pt.bsa(self.bsaMethod) return math.exp( self._mean( bsa ) ) def sd( self ): return self.mse def zscore( self, pt ): bsa = pt.bsa(self.bsaMethod) try: score = getattr(pt, self.site) return ( math.log( score ) - self._mean( bsa ) ) / self.sd() except: return None def range( self, pt, limit = 1.96 ): '''returns a string representing the "lln - uln" ''' bsa = pt.bsa(self.bsaMethod) lower = math.exp( -limit * self.sd() + self._mean( bsa ) ) upper = math.exp( limit * self.sd() + self._mean( bsa ) ) return '%.2f - %.2f' %( lower, upper ) aov = Base( { 'site': 'aov', 'slope': 0.426, 'int': 2.732, 'mse': 0.10392 }) sov = Base( { 'site': 'sov', 'slope': 0.443, 'int': 3.021, 'mse': 0.10173 }) stj = Base( { 'site': 'stj', 'slope': 0.434, 'int': 2.819, 'mse': 0.10961 }) aao = Base( { 'site': 'aao', 'slope': 0.421, 'int': 2.898, 'mse': 0.09111 }) sites ={ 'aov': aov, 'sov': sov, 'stj': stj, 'aao': aao }
#!/usr/bin/env python '''paris - A module for calculating aortic root z-scores based on data published from Hopital Bichat, AP-HP, Paris, France; Nomograms for aortic root diameters in children using two-dimensional echocardiography. Gautier M, Detaint D, Fermanian C, Aegerter P, Delorme G, Arnoult F, Milleron O, Raoux F, Stheneur C, Boileau C, Vahanian A, Jondeau G. Am J Cardiol. 2010 Mar 15;105(6):888-94. ''' import math class Gender( object ): pass #an empty object for 'boys' and 'girls' aortic root data class Base( object ): def __init__( self, d ): self.int = d['int'] self.slope = d['slope'] self.mse = d['mse'] self.site = d['site'] self.bsaMethod = 'Dubois' def __mean( self, bsa ): #'private' method for returning log-transformed mean value #note the DOUBLE UNDERSCORE return self.int + self.slope * math.log(bsa); def mean( self, pt ): bsa = pt.bsa(self.bsaMethod) return math.exp( self.__mean( bsa ) ) def sd( self ): return self.mse def zscore( self, pt ): bsa = pt.bsa(self.bsaMethod) try: score = getattr(pt, self.site) return ( math.log( score ) - self.__mean( bsa ) ) / self.sd() except: return None def range( self, pt, limit = 1.96 ): bsa = pt.bsa(self.bsaMethod) lower = math.exp( -limit * self.sd() + self.__mean( bsa ) ) upper = math.exp( limit * self.sd() + self.__mean( bsa ) ) return '%.2f - %.2f' %( lower, upper ) boys = Gender() girls = Gender() boys.aov = Base( { 'site': 'aov', 'int': 2.78, 'slope': 0.47, 'mse': 0.10 } ) boys.sov = Base( { 'site': 'sov', 'int': 3.10, 'slope': 0.49, 'mse': 0.10 } ) boys.stj = Base( { 'site': 'stj', 'int': 2.90, 'slope': 0.47, 'mse': 0.10 } ) boys.aao = Base( { 'site': 'aao', 'int': 2.90, 'slope': 0.46, 'mse': 0.11 } ) girls.aov = Base( { 'site': 'aov', 'int': 2.75, 'slope': 0.44, 'mse': 0.10 } ) girls.sov = Base( { 'site': 'sov', 'int': 3.10, 'slope': 0.44, 'mse': 0.09 } ) girls.stj = Base( { 'site': 'stj', 'int': 2.90, 'slope': 0.42, 'mse': 0.09 } ) girls.aao = Base( { 'site': 'aao', 'int': 2.90, 'slope': 0.46, 'mse': 0.10 } ) #to do: re-work entire class to handle 'gender' as a property of 'pt'
#!/usr/bin/env python '''wessex - A module for calculating z-scores based on data published from Wessex Cardiothoracic Unit, Southampton General Hospital, UK Relationship of the dimension of cardiac structures to body size: an echocardiographic study in normal infants and children. Daubeney PE, Blackstone EH, Weintraub RG, Slavik Z, Scanlon J, Webber SA. Cardiol Young. 1999 Jul;9(4):402-10 ''' from __future__ import division #required to avoid random classic/integer division import math class Base( object ): '''this is the basic form for the Wessex equations: a log-log transformation and "stablized" variance ''' def __init__( self, d): self.slope = d['slope'] self.int = d['int'] self.mse = d['mse'] self.site = d['site'] self.bsaMethod = 'Boyd' self.reference = "Wessex" def _mean( self, bsa ): return self.int + self.slope * math.log( bsa ) def mean( self, pt ): bsa = pt.bsa(self.bsaMethod) return 10 * math.exp( self._mean(bsa) ) def sd( self ): return self.mse def zscore( self, pt ): bsa = pt.bsa(self.bsaMethod) try: score = getattr(pt, self.site) score /= 10 return ( math.log( score ) - self._mean( bsa ) ) / self.sd() except: return None def range( self, pt, limit = 1.96 ): bsa = pt.bsa(self.bsaMethod) lower = math.exp( -limit * self.sd() + self._mean( bsa ) ) upper = math.exp( limit * self.sd() + self._mean( bsa ) ) return '%.2f - %.2f' %( 10* lower, 10 * upper ) aov = Base( { 'site': 'aov', 'slope': 0.5347, 'int': 0.5183, 'mse': 0.06726 }) sov = Base( { 'site': 'sov', 'slope': 0.5082, 'int': 0.7224, 'mse': 0.07284 }) stj = Base( { 'site': 'stj', 'slope': 0.5490, 'int': 0.5417, 'mse': 0.08656 }) sites = { 'aov': aov, 'sov': sov, 'stj': stj }
The data for the charts are built using a method that returns a list of lists, one each for the upper limit and lower limit based on the following basic form:
(click to show/hide the code samples)
def plotData( self ): '''returns an object with the bsa + uln/lln over a range of values for client-side plotting''' bsaData = [x * 0.1 for x in range(1, 21)] lln = [[x, 10 * math.exp( -1.96 * self.sd() + self._mean( x ) )] for x in bsaData] uln = [[x, 10 * math.exp( 1.96 * self.sd() + self._mean( x ) )] for x in bsaData] return {'uln': uln, 'lln': lln}
The lists are built with Python's list comprehension technique, saving a whole bunch of for: loop
code. The actual charts themselves, and links to the charts, are built with client-side JavaScript- if your browser does not have JavaScript enabled, you won't be able to view the charts.
Sorry.