From 50a69a220952ffe83dfb7cf6fce098469ad064ef Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Sun, 18 Dec 2016 21:58:03 -0800 Subject: [PATCH 1/7] Add BinnedWhiskerPlots (#96) --- stile/sys_tests.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/stile/sys_tests.py b/stile/sys_tests.py index 62c126f..f9001f2 100644 --- a/stile/sys_tests.py +++ b/stile/sys_tests.py @@ -1102,6 +1102,10 @@ def WhiskerPlotSysTest(type=None): - Star: whisker plot of shapes of PSF stars - PSF: whisker plot of PSF shapes at the location of PSF stars - Residual: whisker plot of (star shape-PSF shape) + - BinnedStar: whisker plot of shapes of PSF stars, based on averages in pixels + - BinnedPSF: whisker plot of PSF shapes at the location of PSF stars, based on averages in + pixels + - BinnedResidual: whisker plot of (star shape-PSF shape), based on averages in pixels - None: an empty BaseWhiskerPlotSysTest class instance, which can be used for multiple types of whisker plots. See the documentation for BaseWhiskerPlotSysTest (especially the method whiskerPlot) for more details. Note that this type has a different call signature than @@ -1114,6 +1118,12 @@ def WhiskerPlotSysTest(type=None): return WhiskerPlotPSFSysTest() elif type=='Residual': return WhiskerPlotResidualSysTest() + elif type=='BinnedStar': + return BinnedWhiskerPlotStarSysTest() + elif type=='BinnedPSF': + return BinnedWhiskerPlotPSFSysTest() + elif type=='BinnedResidual': + return BinnedWhiskerPlotResidualSysTest() elif type is None: return BaseWhiskerPlotSysTest() else: @@ -1284,6 +1294,69 @@ def __call__(self, array, linewidth=0.01, scale=None, figsize=None, size_label=r'$\sigma$ [pixel]', xlim=xlim, ylim=ylim, equal_axis=True) +class BinnedWhiskerPlotSysTest(BaseWhiskerPlotSysTest): + def whiskerPlot(self, x, y, g1, g2, sigma, n_x=30, n_y=30, split_by_objects=True, + **kwargs): + """ + A method for making whisker plots where the whiskers represent the average value in pixels. + Most of the arguments and keywords are the same as for + :func:`BaseWhiskerPlotSysTest.whiskerPlot`, with the following additional keywords: + + @param n_x Number of pixels to use in the x-coordinate [default: 30] + @param n_y Number of pixels to use in the y-coordinate [default: 30] + @param split_by_objects If True, make the pixels so there are an equal number of objects + in each column or row. (Note this doesn't guarantee equal number + in each pixel, just each column or row of pixels in total.) If + False, instead use equal binning in the x- or y-coordinate, eg + (0, 10, 20, 30...) for the bin endpoints. [default: True] + """ + new_x = [] + new_y = [] + new_g1 = [] + new_g2 = [] + new_sigma = [] + if split_by_objects: + bin_x = numpy.percentile(x, numpy.linspace(0, 100, num=n_x+1, endpoint=True)) + bin_y = numpy.percentile(y, numpy.linspace(0, 100, num=n_y+1, endpoint=True)) + else: + bin_x = numpy.linspace(numpy.min(x), numpy.max(x), num=n_x, endpoint=True) + bin_y = numpy.linspace(numpy.min(y), numpy.max(y), num=n_y, endpoint=True) + for lo_x, hi_x in zip(bin_x[:-1], bin_x[1:]): + for lo_y, hi_y in zip(bin_y[:-1], bin_y[1:]): + mask = (x>lo_x) & (x<=hi_x) & (y>lo_y) & (y<=hi_y) + if numpy.any(mask): # guard against objectless pixells + new_x.append(numpy.mean(x[mask])) + new_y.append(numpy.mean(y[mask])) + new_g1.append(numpy.mean(g1[mask])) + new_g2.append(numpy.mean(g2[mask])) + new_sigma.append(numpy.mean(sigma[mask])) + new_x = numpy.asarray(new_x) + new_y = numpy.asarray(new_y) + new_g1 = numpy.asarray(new_g1) + new_g2 = numpy.asarray(new_g2) + new_sigma = numpy.asarray(new_sigma) + return super(BinnedWhiskerPlotSysTest, self).whiskerPlot( + new_x, new_y, new_g1, new_g2, new_sigma, **kwargs) + +class BinnedWhiskerPlotStarSysTest(BinnedWhiskerPlotSysTest, WhiskerPlotStarSysTest): + short_name = 'binned_whiskerplot_star' + long_name = 'Make a binned whisker plot of stars' + objects_list = ['star PSF'] + required_quantities = [('x', 'y', 'g1', 'g2', 'sigma')] + +class BinnedWhiskerPlotPSFSysTest(BinnedWhiskerPlotSysTest, WhiskerPlotPSFSysTest): + short_name = 'binned_whiskerplot_psf' + long_name = 'Make a binned whisker plot of PSFs' + objects_list = ['star PSF'] + required_quantities = [('x', 'y', 'psf_g1', 'psf_g2', 'psf_sigma')] + +class BinnedWhiskerPlotResidualSysTest(BinnedWhiskerPlotSysTest, WhiskerPlotResidualSysTest): + short_name = 'whiskerplot_residual' + long_name = 'Make a Whisker plot of residuals' + objects_list = ['star PSF'] + required_quantities = [('x', 'y', 'g1', 'g2', 'sigma', 'psf_g1', 'psf_g2', 'psf_sigma')] + + class HistogramSysTest(SysTest): """ A base class for Stile systematics tests that generate histograms. From 7e7ba1512980285a2838b57953047369b9468d93 Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Thu, 23 Feb 2017 20:49:04 -0800 Subject: [PATCH 2/7] Updates to binning module (#96) --- stile/binning.py | 80 +++++++++++++++++++++++++++++++++---------- tests/test_binning.py | 2 +- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/stile/binning.py b/stile/binning.py index 4194ac4..f7b00e2 100644 --- a/stile/binning.py +++ b/stile/binning.py @@ -22,8 +22,8 @@ class BinList: """ def __init__(self, field, bin_list): - if not isinstance(field, str): - raise TypeError('Field description must be a string. Passed value: '+str(field)+ + if not isinstance(field, str) and field is not None: + raise TypeError('Field description must be a string or None. Passed value: '+str(field)+ 'of type'+type(field)) if not bin_list: raise TypeError('Must pass a non-empty bin_list') @@ -44,7 +44,11 @@ def __call__(self): Returns a list of SingleBin objects following the definitions provided when the class was created. """ - return_list = [SingleBin(field=self.field, low=low, high=high, short_name=str(i)) + if self.field is not None: + bin_type = SingleBin + else: + bin_type = SingleBinFieldless + return_list = [bin_type(field=self.field, low=low, high=high, short_name=str(i)) for i, (low, high) in enumerate(zip(self.bin_list[:-1], self.bin_list[1:]))] if self.reverse: return_list.reverse() @@ -139,12 +143,16 @@ def __init__(self, field, low=None, high=None, step=None, n_bins=None, use_log=F self.reverse = False def __call__(self): + if self.field is not None: + bin_type = SingleBin + else: + bin_type = SingleBinFieldless if self.use_log: - return_list = [SingleBin(field=self.field, low=numpy.exp(self.low+i*self.step), + return_list = [bin_type(field=self.field, low=numpy.exp(self.low+i*self.step), high=numpy.exp(self.low+(i+1)*self.step), short_name=str(i)) for i in range(self.n_bins)] else: - return_list = [SingleBin(field=self.field, low=self.low+i*self.step, + return_list = [bin_type(field=self.field, low=self.low+i*self.step, high=self.low+(i+1)*self.step, short_name=str(i)) for i in range(self.n_bins)] if self.reverse: @@ -152,31 +160,28 @@ def __call__(self): return return_list -class SingleBin: +class SingleBinFieldless(object): """ A class that contains the information for one particular bin generated from one of the Bin* - classes. The attributes can be accessed directly for DataHandlers that read in the data - selectively. The class can also be called with a data array to bin it to the correct data - range: SingleBin(array) will return only the data within the bounds of the particular instance - of the class. The endpoints are assumed to be [low,high), that is, low <= data < high, with - defined relational operators. + classes. This version is for cases where you want to operate on a single array without defined + fields, unlike e.g. a numpy.recarray. The class can be called with a data array to bin it to + the correct data range: SingleBin(array) will return only the data within the bounds of the + particular instance of the class. The endpoints are assumed to be [low,high), that is, + low <= data < high, with defined relational operators. + + See :class:`SingleBin` for the version that operates on formatted arrays with fields defined. - @param field The index of the field containing the data to be binned (must be a string). @param low The lower edge of the bin (inclusive). @param high The upper edge of the bin (exclusive). @param short_name A string denoting this bin in filenames. @param long_name A string denoting this bin in program text outputs/plots. [default: "low-high"] """ - def __init__(self, field, low, high, short_name, long_name=None): - if not isinstance(field, str): - raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+type(field)) + def __init__(self, low, high, short_name, long_name=None): if high <= low: raise ValueError("High ("+str(high)+") must be greater than low ("+str(low)+")") if not isinstance(short_name, str) or (long_name and not isinstance(long_name, str)): raise TypeError("Short_name and long_name must be strings") - self.field = field self.low = low self.high = high self.short_name = short_name @@ -194,7 +199,46 @@ def __call__(self, data): @returns A NumPy array corresponding to the input data, restricted to the bin described by this object. """ - return data[numpy.logical_and(data[self.field] >= self.low, data[self.field] < self.high)] + return data[numpy.logical_and(data >= self.low, data < self.high)] + + +class SingleBin(SingleBinFieldless): + """ + A class that contains the information for one particular bin generated from one of the Bin* + classes. The attributes can be accessed directly for DataHandlers that read in the data + selectively. The class can also be called with a data array to bin it to the correct data + range: SingleBin(array) will return only the data within the bounds of the particular instance + of the class. The endpoints are assumed to be [low,high), that is, low <= data < high, with + defined relational operators. + + This version is for data arrays with defined fields such as numpy.recarrays; see + :class:`SingleBinFieldless` for the version that operates on raw arrays. + + @param field The index of the field containing the data to be binned (must be a string). + @param low The lower edge of the bin (inclusive). + @param high The upper edge of the bin (exclusive). + @param short_name A string denoting this bin in filenames. + @param long_name A string denoting this bin in program text outputs/plots. + [default: "low-high"] + """ + def __init__(self, field, low, high, short_name, long_name=None): + if not isinstance(field, str): + raise TypeError('Field description must be a string. Passed value: '+str(field)+ + 'of type'+type(field)) + self.field = field + super(SingleBin, self).__init__(low, high, short_name, long_name) + + def __call__(self, data): + """ + Given data, returns only the data with data[self.field] within the bounds + [self.low, self.high). + + @param data A NumPy array of data that can be indexed by self.field. + @returns A NumPy array corresponding to the input data, restricted to the bin + described by this object. + """ + return super(SingleBin, self).__call__(data[self.field]) + class BinFunction: diff --git a/tests/test_binning.py b/tests/test_binning.py index 57cc344..a6299df 100644 --- a/tests/test_binning.py +++ b/tests/test_binning.py @@ -338,7 +338,7 @@ def test_singlebin_input_errors(self): sfb = stile.binning.SingleFunctionBin(binfunction, 1) self.assertIsNotNone(sb.long_name) # check that this was made properly self.assertRaises(TypeError, sb, [1, 2, 3, 4]) - self.assertRaises(ValueError, sb, numpy.array([1, 2, 3, 4])) + self.assertRaises(IndexError, sb, numpy.array([1, 2, 3, 4])) self.assertRaises(ValueError, sb, numpy.array([(1, ), (2, ), (3, ), (4, )], dtype=[('field_1', int)])) self.assertRaises(TypeError, sb, 3) From bbb00c1677eee8265fee088c73a17af9e8ffdc40 Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Fri, 22 Jun 2018 18:28:57 -0600 Subject: [PATCH 3/7] Add a test for the BinnedWhiskerPlots --- tests/test_sys_tests.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_sys_tests.py diff --git a/tests/test_sys_tests.py b/tests/test_sys_tests.py new file mode 100644 index 0000000..b7cb3d6 --- /dev/null +++ b/tests/test_sys_tests.py @@ -0,0 +1,28 @@ +import numpy +import unittest + +try: + import stile +except ImportError: + sys.path.append('..') + import stile + +class TestSysTests(unittest.TestCase): + def test_binned_whisker_plots(self): + source_data = stile.ReadASCIITable('../examples/example_source_catalog.dat', + fields={'id': 0, 'ra': 1, 'dec': 2, 'z': 3, 'g1': 4, 'g2': 5}) + data = numpy.rec.fromarrays([source_data['ra'], source_data['dec'], source_data['g1'], + source_data['g2'], source_data['g1']+0.1*source_data['ra'], source_data['g2']+0.05*source_data['dec'], + numpy.ones_like(source_data['ra']), numpy.ones_like(source_data['ra'])+0.02], + names = ['x', 'y', 'g1', 'g2', 'psf_g1', 'psf_g2', 'sigma', 'psf_sigma']) + print data.dtype.names + obj = stile.WhiskerPlotSysTest("BinnedStar") + obj(data) + obj = stile.WhiskerPlotSysTest("BinnedPSF") + obj(data) + obj = stile.WhiskerPlotSysTest("BinnedResidual") + obj(data) + +if __name__=='__main__': + unittest.main() + From 23bcbe7a58a7895d240ab82d239f82b0efad5eaa Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Fri, 22 Jun 2018 18:42:08 -0600 Subject: [PATCH 4/7] Pass kwargs through correctly, and check that they work for BinnedWhiskerPlotSysTests (#96) --- stile/sys_tests.py | 15 ++++++++------- tests/test_sys_tests.py | 8 +++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/stile/sys_tests.py b/stile/sys_tests.py index a2cd596..cc03b11 100644 --- a/stile/sys_tests.py +++ b/stile/sys_tests.py @@ -1324,7 +1324,7 @@ class WhiskerPlotStarSysTest(BaseWhiskerPlotSysTest): required_quantities = [('x', 'y', 'g1', 'g2', 'sigma')] def __call__(self, array, linewidth=0.01, scale=None, figsize=None, - xlim=None, ylim=None): + xlim=None, ylim=None, **kwargs): if 'CCD' in array.dtype.names: fields = list(self.required_quantities[0]) + ['CCD'] else: @@ -1334,7 +1334,7 @@ def __call__(self, array, linewidth=0.01, scale=None, figsize=None, linewidth=linewidth, scale=scale, figsize=figsize, xlabel=r'$x$ [pixel]', ylabel=r'$y$ [pixel]', size_label=r'$\sigma$ [pixel]', - xlim=xlim, ylim=ylim, equal_axis=True) + xlim=xlim, ylim=ylim, equal_axis=True, **kwargs) class WhiskerPlotPSFSysTest(BaseWhiskerPlotSysTest): @@ -1347,7 +1347,7 @@ class WhiskerPlotPSFSysTest(BaseWhiskerPlotSysTest): required_quantities = [('x', 'y', 'psf_g1', 'psf_g2', 'psf_sigma')] def __call__(self, array, linewidth=0.01, scale=None, figsize=None, - xlim=None, ylim=None): + xlim=None, ylim=None, **kwargs): if 'CCD' in array.dtype.names: fields = list(self.required_quantities[0]) + ['CCD'] else: @@ -1357,7 +1357,7 @@ def __call__(self, array, linewidth=0.01, scale=None, figsize=None, array['psf_sigma'], linewidth=linewidth, scale=scale, figsize=figsize, xlabel=r'$x$ [pixel]', ylabel=r'$y$ [pixel]', size_label=r'$\sigma$ [pixel]', - xlim=xlim, ylim=ylim, equal_axis=True) + xlim=xlim, ylim=ylim, equal_axis=True, **kwargs) class WhiskerPlotResidualSysTest(BaseWhiskerPlotSysTest): @@ -1369,7 +1369,7 @@ class WhiskerPlotResidualSysTest(BaseWhiskerPlotSysTest): required_quantities = [('x', 'y', 'g1', 'g2', 'sigma', 'psf_g1', 'psf_g2', 'psf_sigma')] def __call__(self, array, linewidth=0.01, scale=None, figsize=None, - xlim=None, ylim=None): + xlim=None, ylim=None, **kwargs): data = [array['x'], array['y'], array['g1'] - array['psf_g1'], array['g2'] - array['psf_g2'], array['sigma'] - array['psf_sigma']] fields = ['x', 'y', 'g1-psf_g1', 'g2-psf_g2', 'sigma-psf_sigma'] @@ -1382,7 +1382,7 @@ def __call__(self, array, linewidth=0.01, scale=None, figsize=None, linewidth=linewidth, scale=scale, figsize=figsize, xlabel=r'$x$ [pixel]', ylabel=r'$y$ [pixel]', size_label=r'$\sigma$ [pixel]', - xlim=xlim, ylim=ylim, equal_axis=True) + xlim=xlim, ylim=ylim, equal_axis=True, **kwargs) class BinnedWhiskerPlotSysTest(BaseWhiskerPlotSysTest): def whiskerPlot(self, x, y, g1, g2, sigma, n_x=30, n_y=30, split_by_objects=True, @@ -1426,7 +1426,8 @@ def whiskerPlot(self, x, y, g1, g2, sigma, n_x=30, n_y=30, split_by_objects=True new_g2 = numpy.asarray(new_g2) new_sigma = numpy.asarray(new_sigma) return super(BinnedWhiskerPlotSysTest, self).whiskerPlot( - new_x, new_y, new_g1, new_g2, new_sigma, **kwargs) + new_x, new_y, new_g1, new_g2, new_sigma, **kwargs) + class BinnedWhiskerPlotStarSysTest(BinnedWhiskerPlotSysTest, WhiskerPlotStarSysTest): short_name = 'binned_whiskerplot_star' diff --git a/tests/test_sys_tests.py b/tests/test_sys_tests.py index b7cb3d6..f3b1301 100644 --- a/tests/test_sys_tests.py +++ b/tests/test_sys_tests.py @@ -15,13 +15,19 @@ def test_binned_whisker_plots(self): source_data['g2'], source_data['g1']+0.1*source_data['ra'], source_data['g2']+0.05*source_data['dec'], numpy.ones_like(source_data['ra']), numpy.ones_like(source_data['ra'])+0.02], names = ['x', 'y', 'g1', 'g2', 'psf_g1', 'psf_g2', 'sigma', 'psf_sigma']) - print data.dtype.names obj = stile.WhiskerPlotSysTest("BinnedStar") obj(data) obj = stile.WhiskerPlotSysTest("BinnedPSF") obj(data) obj = stile.WhiskerPlotSysTest("BinnedResidual") obj(data) + data['x'] = numpy.log10(1+data['x']) + obj = stile.WhiskerPlotSysTest("BinnedStar") + obj(data) + obj = stile.WhiskerPlotSysTest("BinnedStar") + obj(data, split_by_objects=False, n_x=50, n_y=50) + + if __name__=='__main__': unittest.main() From 3d542f074e043018ff0f00b32733f279f8bf43ae Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Fri, 13 Jul 2018 17:50:09 -0700 Subject: [PATCH 5/7] Fix string formatting bug & update changelog (#96) --- CHANGELOG.md | 1 + stile/binning.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d07c664..90fc228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +7/13/18: Add BinnedScatterPlot systematics test type (issue #96) 7/11/18: Update code to python3 3/17/16: Update documentation to Sphinx standard and add documentation build files (issue #17) 2/17/16: Changes to correlation function plots & documentation (issue #77) diff --git a/stile/binning.py b/stile/binning.py index bc7faf2..f2651e8 100644 --- a/stile/binning.py +++ b/stile/binning.py @@ -26,7 +26,7 @@ class BinList: def __init__(self, field, bin_list): if not isinstance(field, str) and field is not None: raise TypeError('Field description must be a string or None. Passed value: '+str(field)+ - 'of type'+type(field)) + 'of type'+str(type(field))) if not bin_list: raise TypeError('Must pass a non-empty bin_list') self.field = field From 3e8e5ecca0ec693a6ec9c62a7a6aadbaa5f9d7c1 Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Fri, 13 Jul 2018 17:54:44 -0700 Subject: [PATCH 6/7] Update other places string formatting bug existed in binning.py (#96) --- stile/binning.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stile/binning.py b/stile/binning.py index f2651e8..dd53dd5 100644 --- a/stile/binning.py +++ b/stile/binning.py @@ -86,7 +86,7 @@ class BinStep: def __init__(self, field, low=None, high=None, step=None, n_bins=None, use_log=False): if not isinstance(field, str): raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+type(field)) + 'of type'+str(type(field))) self.field = field n_none = (low is None) + (high is None) + (step is None) + (n_bins is None) if n_none > 1: @@ -184,7 +184,7 @@ class SingleBinFieldless(object): def __init__(self, field, low, high, short_name, long_name=None): if not isinstance(field, str): raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+type(field)) + 'of type'+str(type(field))) if high <= low: raise ValueError("High ("+str(high)+") must be greater than low ("+str(low)+")") if not isinstance(short_name, str) or (long_name and not isinstance(long_name, str)): @@ -231,7 +231,7 @@ class SingleBin(SingleBinFieldless): def __init__(self, field, low, high, short_name, long_name=None): if not isinstance(field, str): raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+type(field)) + 'of type'+str(type(field))) self.field = field super(SingleBin, self).__init__(low, high, short_name, long_name) From 1fd4f850e3d07d335b87d68cfb18992004f624e9 Mon Sep 17 00:00:00 2001 From: Melanie Simet Date: Fri, 13 Jul 2018 18:17:00 -0700 Subject: [PATCH 7/7] Fix ordering of field kwarg in binning interface, and fix testing to work properly with field kwargs (#96) --- stile/binning.py | 15 ++-- tests/test_binning.py | 170 +++++++++++++++++++++++------------------- 2 files changed, 100 insertions(+), 85 deletions(-) diff --git a/stile/binning.py b/stile/binning.py index dd53dd5..4d9c8b0 100644 --- a/stile/binning.py +++ b/stile/binning.py @@ -23,10 +23,10 @@ class BinList: :returns: A list of :class:`SingleBin` objects determined by the input criteria. """ - def __init__(self, field, bin_list): + def __init__(self, bin_list, field=None): if not isinstance(field, str) and field is not None: raise TypeError('Field description must be a string or None. Passed value: '+str(field)+ - 'of type'+str(type(field))) + ' of type'+str(type(field))) if not bin_list: raise TypeError('Must pass a non-empty bin_list') self.field = field @@ -83,10 +83,10 @@ class BinStep: :returns: A list of :class:`SingleBin` objects determined by the input criteria. """ - def __init__(self, field, low=None, high=None, step=None, n_bins=None, use_log=False): + def __init__(self, field=None, low=None, high=None, step=None, n_bins=None, use_log=False): if not isinstance(field, str): raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+str(type(field))) + ' of type'+str(type(field))) self.field = field n_none = (low is None) + (high is None) + (step is None) + (n_bins is None) if n_none > 1: @@ -181,10 +181,7 @@ class SingleBinFieldless(object): [default: ``"low-high"``]. """ - def __init__(self, field, low, high, short_name, long_name=None): - if not isinstance(field, str): - raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+str(type(field))) + def __init__(self, low, high, short_name, long_name=None): if high <= low: raise ValueError("High ("+str(high)+") must be greater than low ("+str(low)+")") if not isinstance(short_name, str) or (long_name and not isinstance(long_name, str)): @@ -231,7 +228,7 @@ class SingleBin(SingleBinFieldless): def __init__(self, field, low, high, short_name, long_name=None): if not isinstance(field, str): raise TypeError('Field description must be a string. Passed value: '+str(field)+ - 'of type'+str(type(field))) + ' of type'+str(type(field))) self.field = field super(SingleBin, self).__init__(low, high, short_name, long_name) diff --git a/tests/test_binning.py b/tests/test_binning.py index 30df879..160dcb0 100644 --- a/tests/test_binning.py +++ b/tests/test_binning.py @@ -40,18 +40,18 @@ def test_BinStep_SingleBin_creation(self): various inputs.""" # All of these should return the same objects (the expected_obj_list), except the final one, # which should return them in the reverse order. - lhs = stile.BinStep('field_0', low=0, high=6, step=1) - lhn = stile.BinStep('field_0', low=0, high=6, n_bins=6) - lsn = stile.BinStep('field_0', low=0, step=1, n_bins=6) - hsn = stile.BinStep('field_0', high=6, step=1, n_bins=6) - reverse_lhs = stile.BinStep('field_0', low=6, high=0, step=-1) - - expected_obj_list = [stile.binning.SingleBin('field_0', low=0, high=1, short_name='b'), - stile.binning.SingleBin('field_0', low=1, high=2, short_name='b'), - stile.binning.SingleBin('field_0', low=2, high=3, short_name='b'), - stile.binning.SingleBin('field_0', low=3, high=4, short_name='b'), - stile.binning.SingleBin('field_0', low=4, high=5, short_name='b'), - stile.binning.SingleBin('field_0', low=5, high=6, short_name='b')] + lhs = stile.BinStep(field='field_0', low=0, high=6, step=1) + lhn = stile.BinStep(field='field_0', low=0, high=6, n_bins=6) + lsn = stile.BinStep(field='field_0', low=0, step=1, n_bins=6) + hsn = stile.BinStep(field='field_0', high=6, step=1, n_bins=6) + reverse_lhs = stile.BinStep(field='field_0', low=6, high=0, step=-1) + + expected_obj_list = [stile.binning.SingleBin(field='field_0', low=0, high=1, short_name='b'), + stile.binning.SingleBin(field='field_0', low=1, high=2, short_name='b'), + stile.binning.SingleBin(field='field_0', low=2, high=3, short_name='b'), + stile.binning.SingleBin(field='field_0', low=3, high=4, short_name='b'), + stile.binning.SingleBin(field='field_0', low=4, high=5, short_name='b'), + stile.binning.SingleBin(field='field_0', low=5, high=6, short_name='b')] names = ["passed low, high, and step", "passed low, high, and n_bins", @@ -70,18 +70,18 @@ def test_BinStep_SingleBin_creation(self): msg='BinStep ('+name+') created incorrect SingleBins!') # As above, but using logarithmic bins. - lhs = stile.BinStep('field_0', low=0.25, high=8, step=numpy.log(2.), use_log=True) - lhn = stile.BinStep('field_0', low=0.25, high=8, n_bins=5, use_log=True) - lsn = stile.BinStep('field_0', low=0.25, step=numpy.log(2.), n_bins=5, use_log=True) - hsn = stile.BinStep('field_0', high=8, step=numpy.log(2.), n_bins=5, use_log=True) - reverse_lhs = stile.BinStep('field_0', low=8, high=0.25, step=-numpy.log(2.), + lhs = stile.BinStep(field='field_0', low=0.25, high=8, step=numpy.log(2.), use_log=True) + lhn = stile.BinStep(field='field_0', low=0.25, high=8, n_bins=5, use_log=True) + lsn = stile.BinStep(field='field_0', low=0.25, step=numpy.log(2.), n_bins=5, use_log=True) + hsn = stile.BinStep(field='field_0', high=8, step=numpy.log(2.), n_bins=5, use_log=True) + reverse_lhs = stile.BinStep(field='field_0', low=8, high=0.25, step=-numpy.log(2.), use_log=True) - expected_obj_list = [stile.binning.SingleBin('field_0', low=0.25, high=0.5, short_name='b'), - stile.binning.SingleBin('field_0', low=0.5, high=1., short_name='b'), - stile.binning.SingleBin('field_0', low=1., high=2., short_name='b'), - stile.binning.SingleBin('field_0', low=2., high=4., short_name='b'), - stile.binning.SingleBin('field_0', low=4., high=8., short_name='b')] + expected_obj_list = [stile.binning.SingleBin(field='field_0', low=0.25, high=0.5, short_name='b'), + stile.binning.SingleBin(field='field_0', low=0.5, high=1., short_name='b'), + stile.binning.SingleBin(field='field_0', low=1., high=2., short_name='b'), + stile.binning.SingleBin(field='field_0', low=2., high=4., short_name='b'), + stile.binning.SingleBin(field='field_0', low=4., high=8., short_name='b')] names = ["passed low, high, and step", "passed low, high, and n_bins", @@ -101,7 +101,7 @@ def test_BinStep_SingleBin_creation(self): def test_BinList_SingleBin_creation(self): """Test that the creation of a SingleBin exhibits appropriate behavior.""" - obj = stile.BinList('field_0', [0, 1.1, 1.9, 3.0, 4.0, 5.0, 6.5]) + obj = stile.BinList([0, 1.1, 1.9, 3.0, 4.0, 5.0, 6.5], 'field_0') expected_obj_list = [stile.binning.SingleBin('field_0', low=0, high=1.1, short_name='b'), stile.binning.SingleBin('field_0', low=1.1, high=1.9, short_name='b'), @@ -118,7 +118,7 @@ def test_BinList_SingleBin_creation(self): self.assertTrue(compare_single_bin(obj_list[i], expected_obj_list[i]), msg='BinList created incorrect SingleBins!') - obj = stile.BinList('field_0', [6.5, 5.0, 4.0, 3.0, 1.9, 1.1, 0]) + obj = stile.BinList([6.5, 5.0, 4.0, 3.0, 1.9, 1.1, 0], field='field_0') obj_list = obj() obj_list.reverse() self.assertEqual(len(obj_list), 6, @@ -126,7 +126,7 @@ def test_BinList_SingleBin_creation(self): for i in range(len(obj_list)): self.assertTrue(compare_single_bin(obj_list[i], expected_obj_list[i]), msg='Reversed BinList created incorrect SingleBins!') - self.assertRaises(TypeError, stile.BinList, [0.5, 1.5, 1.0]) + self.assertRaises(ValueError, stile.BinList, [0.5, 1.5, 1.0]) def test_BinStep_linear(self): """Test that BinStep objects with linear spacing behave appropriately.""" @@ -147,18 +147,24 @@ def test_BinStep_linear(self): # Formatted arrays don't compare properly to non-formatted arrays, so we use slices of the # original array to ensure the formatting matches properly even for empty (formatted) # arrays. - expected_bin_array_1 = [self.bin_array_1[0], self.bin_array_1[1], self.bin_array_1[2], - self.bin_array_1[3], self.bin_array_1[4], self.bin_array_1[:0]] - expected_bin_array_2 = [self.bin_array_2[:0], self.bin_array_2[0], self.bin_array_2[1], - self.bin_array_2[2], self.bin_array_2[3], self.bin_array_2[4]] - expected_bin_array_3 = [self.bin_array_3[0:2], self.bin_array_3[:0], self.bin_array_3[:0], - self.bin_array_3[4], self.bin_array_3[3], self.bin_array_3[2]] - expected_bin_array_4 = [self.bin_array_4[0], self.bin_array_4[:0], self.bin_array_4[:0], - self.bin_array_4[:0], self.bin_array_4[:0], self.bin_array_4[:0]] - expected_bin_array_5 = [self.bin_array_5[:0], self.bin_array_5[0], self.bin_array_5[:0], - self.bin_array_5[1], self.bin_array_5[:0], self.bin_array_5[2]] - expected_bin_array_6 = [self.bin_array_6[:0], self.bin_array_6[1], self.bin_array_6[:0], - self.bin_array_6[:0], self.bin_array_6[:0], self.bin_array_6[:0]] + expected_bin_array_1 = [self.bin_array_1['field_0'][0], self.bin_array_1['field_0'][1], + self.bin_array_1['field_0'][2], self.bin_array_1['field_0'][3], + self.bin_array_1['field_0'][4], self.bin_array_1['field_0'][:0]] + expected_bin_array_2 = [self.bin_array_2['field_0'][:0], self.bin_array_2['field_0'][0], + self.bin_array_2['field_0'][1], self.bin_array_2['field_0'][2], + self.bin_array_2['field_0'][3], self.bin_array_2['field_0'][4]] + expected_bin_array_3 = [self.bin_array_3['field_0'][0:2], self.bin_array_3['field_0'][:0], + self.bin_array_3['field_0'][:0], self.bin_array_3['field_0'][4], + self.bin_array_3['field_0'][3], self.bin_array_3['field_0'][2]] + expected_bin_array_4 = [self.bin_array_4['field_0'][0], self.bin_array_4['field_0'][:0], + self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][:0], + self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][:0]] + expected_bin_array_5 = [self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][0], + self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][1], + self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][2]] + expected_bin_array_6 = [self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][1], + self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][:0], + self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][:0]] for obj, name in zip(objs, names): err_msg = ("BinStep test ("+name+ @@ -192,11 +198,11 @@ def test_BinStep_linear(self): def test_BinStep_log(self): """Test that BinStep objects with logarithmic spacing behave appropriately.""" - lhs = stile.BinStep('field_0', low=0.25, high=8, step=numpy.log(2.), use_log=True) - lhn = stile.BinStep('field_0', low=0.25, high=8, n_bins=5, use_log=True) - lsn = stile.BinStep('field_0', low=0.25, step=numpy.log(2.), n_bins=5, use_log=True) - hsn = stile.BinStep('field_0', high=8, step=numpy.log(2.), n_bins=5, use_log=True) - reverse_lhs = stile.BinStep('field_0', low=8, high=0.25, step=-numpy.log(2.), + lhs = stile.BinStep(field='field_0', low=0.25, high=8, step=numpy.log(2.), use_log=True) + lhn = stile.BinStep(field='field_0', low=0.25, high=8, n_bins=5, use_log=True) + lsn = stile.BinStep(field='field_0', low=0.25, step=numpy.log(2.), n_bins=5, use_log=True) + hsn = stile.BinStep(field='field_0', high=8, step=numpy.log(2.), n_bins=5, use_log=True) + reverse_lhs = stile.BinStep(field='field_0', low=8, high=0.25, step=-numpy.log(2.), use_log=True) names = ["passed low, high, and step", "passed low, high, and n_bins", @@ -206,18 +212,24 @@ def test_BinStep_log(self): objs = [lhs, lhn, lsn, hsn, reverse_lhs] - expected_bin_array_1 = [self.bin_array_1[:0], self.bin_array_1[0], self.bin_array_1[1], - self.bin_array_1[2:4], self.bin_array_1[4]] - expected_bin_array_2 = [self.bin_array_2[:0], self.bin_array_2[:0], self.bin_array_2[0], - self.bin_array_2[1:3], self.bin_array_2[3:]] - expected_bin_array_3 = [self.bin_array_3[:0], self.bin_array_3[:2], self.bin_array_3[:0], - self.bin_array_3[4], self.bin_array_3[2:4]] - expected_bin_array_4 = [self.bin_array_4[:0], self.bin_array_4[0], self.bin_array_4[:0], - self.bin_array_4[:0], self.bin_array_4[:0]] - expected_bin_array_5 = [self.bin_array_5[:0], self.bin_array_5[:0], self.bin_array_5[0], - self.bin_array_5[1], self.bin_array_5[2]] - expected_bin_array_6 = [self.bin_array_6[:0], self.bin_array_6[:0], self.bin_array_6[1], - self.bin_array_6[:0], self.bin_array_6[:0]] + expected_bin_array_1 = [self.bin_array_1['field_0'][:0], self.bin_array_1['field_0'][0], + self.bin_array_1['field_0'][1], self.bin_array_1['field_0'][2:4], + self.bin_array_1['field_0'][4]] + expected_bin_array_2 = [self.bin_array_2['field_0'][:0], self.bin_array_2['field_0'][:0], + self.bin_array_2['field_0'][0], self.bin_array_2['field_0'][1:3], + self.bin_array_2['field_0'][3:]] + expected_bin_array_3 = [self.bin_array_3['field_0'][:0], self.bin_array_3['field_0'][:2], + self.bin_array_3['field_0'][:0], self.bin_array_3['field_0'][4], + self.bin_array_3['field_0'][2:4]] + expected_bin_array_4 = [self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][0], + self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][:0], + self.bin_array_4['field_0'][:0]] + expected_bin_array_5 = [self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][:0], + self.bin_array_5['field_0'][0], self.bin_array_5['field_0'][1], + self.bin_array_5['field_0'][2]] + expected_bin_array_6 = [self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][:0], + self.bin_array_6['field_0'][1], self.bin_array_6['field_0'][:0], + self.bin_array_6['field_0'][:0]] for obj, name in zip(objs, names): err_msg = ("Logarithmic BinStep test ("+name+ @@ -250,25 +262,31 @@ def test_BinStep_log(self): def test_BinList(self): """Test that BinList objects behave appropriately with respect to SingleBin behavior.""" - obj_forward = stile.BinList('field_0', [0, 1., 1.9, 3.0, 4.0, 5.0, 6.5]) - obj_reverse = stile.BinList('field_0', [6.5, 5.0, 4.0, 3.0, 1.9, 1., 0]) + obj_forward = stile.BinList([0, 1., 1.9, 3.0, 4.0, 5.0, 6.5], field='field_0') + obj_reverse = stile.BinList([6.5, 5.0, 4.0, 3.0, 1.9, 1., 0], field='field_0') names = [" ", " (reversed) "] objs = [obj_forward, obj_reverse] # Expected results; each item of the list is the result of the n-th SingleBin - expected_bin_array_1 = [self.bin_array_1[0], self.bin_array_1[1], self.bin_array_1[2], - self.bin_array_1[3], self.bin_array_1[4], self.bin_array_1[:0]] - expected_bin_array_2 = [self.bin_array_2[:0], self.bin_array_2[0], self.bin_array_2[1], - self.bin_array_2[2], self.bin_array_2[3], self.bin_array_2[4]] - expected_bin_array_3 = [self.bin_array_3[0:2], self.bin_array_3[:0], self.bin_array_3[:0], - self.bin_array_3[4], self.bin_array_3[3], self.bin_array_3[2]] - expected_bin_array_4 = [self.bin_array_4[0], self.bin_array_4[:0], self.bin_array_4[:0], - self.bin_array_4[:0], self.bin_array_4[:0], self.bin_array_4[:0]] - expected_bin_array_5 = [self.bin_array_5[:0], self.bin_array_5[0], self.bin_array_5[:0], - self.bin_array_5[1], self.bin_array_5[:0], self.bin_array_5[2]] - expected_bin_array_6 = [self.bin_array_6[:0], self.bin_array_6[1], self.bin_array_6[:0], - self.bin_array_6[:0], self.bin_array_6[:0], self.bin_array_6[:0]] + expected_bin_array_1 = [self.bin_array_1['field_0'][0], self.bin_array_1['field_0'][1], + self.bin_array_1['field_0'][2], self.bin_array_1['field_0'][3], + self.bin_array_1['field_0'][4], self.bin_array_1['field_0'][:0]] + expected_bin_array_2 = [self.bin_array_2['field_0'][:0], self.bin_array_2['field_0'][0], + self.bin_array_2['field_0'][1], self.bin_array_2['field_0'][2], + self.bin_array_2['field_0'][3], self.bin_array_2['field_0'][4]] + expected_bin_array_3 = [self.bin_array_3['field_0'][0:2], self.bin_array_3['field_0'][:0], + self.bin_array_3['field_0'][:0], self.bin_array_3['field_0'][4], + self.bin_array_3['field_0'][3], self.bin_array_3['field_0'][2]] + expected_bin_array_4 = [self.bin_array_4['field_0'][0], self.bin_array_4['field_0'][:0], + self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][:0], + self.bin_array_4['field_0'][:0], self.bin_array_4['field_0'][:0]] + expected_bin_array_5 = [self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][0], + self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][1], + self.bin_array_5['field_0'][:0], self.bin_array_5['field_0'][2]] + expected_bin_array_6 = [self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][1], + self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][:0], + self.bin_array_6['field_0'][:0], self.bin_array_6['field_0'][:0]] for obj, name in zip(objs, names): err_msg = ("BinList"+name+"failed to produce correct binning for array %s in bin # %i") @@ -325,16 +343,16 @@ def test_bin_creation_errors(self): self.assertRaises(ValueError, stile.BinStep, 'c', low=1, high=2, n_bins=-1) self.assertRaises(TypeError, stile.BinStep, 0, low=0, high=5, step=1) # Wrong arguments to BinList - self.assertRaises(TypeError, stile.BinList, 'c', [1, 2, 3], n_bins=1) - self.assertRaises(ValueError, stile.BinList, 'c', [1, 3, 2]) - self.assertRaises(TypeError, stile.BinList, 0, [1, 3]) - self.assertRaises(TypeError, stile.BinList, 'c', []) - self.assertRaises(TypeError, stile.BinList, [1, 3, 2]) + self.assertRaises(TypeError, stile.BinList, [1, 2, 3], 'c', n_bins=1) + self.assertRaises(ValueError, stile.BinList, [1, 3, 2], 'c') + self.assertRaises(TypeError, stile.BinList, [1, 3], 0) + self.assertRaises(TypeError, stile.BinList, [], 'c') + self.assertRaises(ValueError, stile.BinList, [1, 3, 2]) self.assertRaises(TypeError, stile.BinList, 'c') def test_singlebin_input_errors(self): """Test that SingleBin objects appropriately object to strange input.""" - sb = stile.binning.SingleBin('field_0', low=0, high=10, short_name='boo') + sb = stile.binning.SingleBin(field='field_0', low=0, high=10, short_name='boo') sfb = stile.binning.SingleFunctionBin(binfunction, 1) self.assertIsNotNone(sb.long_name) # check that this was made properly self.assertRaises(TypeError, sb, [1, 2, 3, 4]) @@ -365,8 +383,8 @@ def func(): ('2a', '1b', '1c'), ('2a', '1b', '2c'), ('2a', '1b', '3c')]) numpy.testing.assert_equal(stile.ExpandBinList(None), []) numpy.testing.assert_equal(stile.ExpandBinList([]), []) - bin_obj0 = stile.BinStep('column_0', low=0, high=6, n_bins=2) - bin_obj1 = stile.BinList('column_1', [0, 2, 4]) + bin_obj0 = stile.BinStep(field='column_0', low=0, high=6, n_bins=2) + bin_obj1 = stile.BinList([0, 2, 4], 'column_1') results = stile.ExpandBinList([bin_obj0, bin_obj1]) expected_results = [(stile.binning.SingleBin('column_0', low=0, high=3, short_name='b'), stile.binning.SingleBin('column_1', low=0, high=2, short_name='b')),