diff --git a/CHANGELOG.md b/CHANGELOG.md index d07c664..2f14871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +7/13/18: Add 2D histogram option for ScatterPlotSysTest (issue #94) 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/sys_tests.py b/stile/sys_tests.py index 276dbb9..020ca92 100644 --- a/stile/sys_tests.py +++ b/stile/sys_tests.py @@ -1856,7 +1856,8 @@ class BaseScatterPlotSysTest(SysTest): short_name = 'scatterplot' def __call__(self, array, x_field, y_field, yerr_field, z_field=None, residual=False, per_ccd_stat=None, xlabel=None, ylabel=None, zlabel=None, color="", - lim=None, equal_axis=False, linear_regression=False, reference_line=None): + lim=None, equal_axis=False, linear_regression=False, reference_line=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): """ Draw a scatter plot and return a :class:`matplotlib.figure.Figure` object. This method has a bunch of options for controlling appearance of a plot, which is @@ -1902,6 +1903,15 @@ def __call__(self, array, x_field, y_field, yerr_field, z_field=None, residual=F ``x=y`` is drawn. If ``reference_line == 'zero'``, ``y=0`` is drawn. A user-specific function can be used by passing an object which has an attribute :func:`__call__` and returns a 1-d Numpy array. + :param histogram: Plot a 2-d histogram (instead of plotting each point individually) + for crowded plots. Setting `histogram=True` will cause any data + given for `z` to be ignored, and any uncertainty on `y` will not be + plotted (though it will still be used to compute a trendline). + [default: False] + :param histogram_n_bins: The number of bins along *each* axis for the histogram; ignored if + `histogram=False`. [default: 40] + :param histogram_cmap: The matplotlib colormap used for the histogram; ignored if + `histogram=False`. [default: 'Blues'] :returns: a :class:`matplotlib.figure.Figure` object """ if per_ccd_stat: @@ -1946,7 +1956,9 @@ def __call__(self, array, x_field, y_field, yerr_field, z_field=None, residual=F return self.scatterPlot(x, y, yerr, z, xlabel=xlabel, ylabel=ylabel, color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line=reference_line) + linear_regression=True, reference_line=reference_line, + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) def getData(self): """ @@ -1958,7 +1970,8 @@ def getData(self): return self.data def scatterPlot(self, x, y, yerr=None, z=None, xlabel=None, ylabel=None, zlabel=None, color="", - lim=None, equal_axis=False, linear_regression=False, reference_line=None): + lim=None, equal_axis=False, linear_regression=False, reference_line=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): """ Draw a scatter plot and return a :class:`matplotlib.figure.Figure` object. This method has a bunch of options for controlling appearance of a plot, which is @@ -1998,11 +2011,19 @@ def scatterPlot(self, x, y, yerr=None, z=None, xlabel=None, ylabel=None, zlabel= A user-specific function can be used by passing an object which has an attribute :func:`__call__` and returns a 1-d Numpy array. [default: False] + :param histogram: Plot a 2-d histogram (instead of plotting each point individually) + for crowded plots. Setting `histogram=True` will cause any data + given for `z` to be ignored, and any uncertainty on `y` will not be + plotted (though it will still be used to compute a trendline). + [default: False] + :param histogram_n_bins: The number of bins along *each* axis for the histogram; ignored if + `histogram=False`. [default: 40] + :param histogram_cmap: The matplotlib colormap used for the histogram; ignored if + `histogram=False`. [default: 'Blues'] :returns: a :class:`matplotlib.figure.Figure` object """ fig = plt.figure() ax = fig.add_subplot(1, 1, 1) - # mask data with nan. Emit a warning if an array has nan in it. x_isnan = numpy.isnan(x) y_isnan = numpy.isnan(y) @@ -2019,6 +2040,8 @@ def scatterPlot(self, x, y, yerr=None, z=None, xlabel=None, ylabel=None, zlabel= % (numpy.sum(yerr_isnan), len(yerr_isnan))) sel = numpy.logical_and(sel, numpy.invert(yerr_isnan)) if z is not None: + if histogram: + warnings.warn('Plotting a histogram - z (color) data will be ignored.') z_isnan = numpy.isnan(z) if numpy.sum(z_isnan) != 0: warnings.warn('There are %s nans in z, out of %s.' @@ -2057,19 +2080,24 @@ def scatterPlot(self, x, y, yerr=None, z=None, xlabel=None, ylabel=None, zlabel= ylim = None # plot - if z is None: - if yerr is None: - p = ax.plot(x, y, ".%s" % color) + if not histogram: + if z is None: + if yerr is None: + p = ax.plot(x, y, ".%s" % color) + else: + p = ax.errorbar(x, y, yerr, fmt=".%s" % color) + # store color for latter use + used_color = p[0].get_color() else: - p = ax.errorbar(x, y, yerr, fmt=".%s" % color) - # store color for latter use - used_color = p[0].get_color() + if yerr is not None: + plt.errorbar(x, y, yerr=yerr, linestyle="None", color="k", zorder=0) + plt.scatter(x, y, c=z, zorder=1) + cb = plt.colorbar() + used_color = "b" else: - if yerr is not None: - plt.errorbar(x, y, yerr=yerr, linestyle="None", color="k", zorder=0) - plt.scatter(x, y, c=z, zorder=1) + plt.hist2d(x, y, bins=histogram_n_bins, cmap=histogram_cmap) cb = plt.colorbar() - used_color = "b" + used_color = color # make axes ticks equal to each other if specified if equal_axis: @@ -2139,7 +2167,7 @@ def scatterPlot(self, x, y, yerr=None, z=None, xlabel=None, ylabel=None, zlabel= ax.set_xlabel(xlabel) if ylabel is not None: ax.set_ylabel(ylabel) - if zlabel is not None: + if zlabel is not None and not histogram: cb.set_label(zlabel) fig.tight_layout() @@ -2238,13 +2266,16 @@ class ScatterPlotStarVsPSFG1SysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('g1', 'g1_err', 'psf_g1')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotStarVsPSFG1SysTest, self).__call__(array, 'psf_g1', 'g1', 'g1_err', residual=False, per_ccd_stat=per_ccd_stat, xlabel=r'$g^{\rm PSF}_1$', ylabel=r'$g^{\rm star}_1$', color=color, lim=lim, equal_axis=False, linear_regression=True, - reference_line='one-to-one') + reference_line='one-to-one', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotStarVsPSFG2SysTest(BaseScatterPlotSysTest): @@ -2256,13 +2287,16 @@ class ScatterPlotStarVsPSFG2SysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('g2', 'g2_err', 'psf_g2')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotStarVsPSFG2SysTest, self).__call__(array, 'psf_g2', 'g2', 'g2_err', residual=False, per_ccd_stat=per_ccd_stat, xlabel=r'$g^{\rm PSF}_2$', ylabel=r'$g^{\rm star}_2$', color=color, lim=lim, equal_axis=False, linear_regression=True, - reference_line='one-to-one') + reference_line='one-to-one', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotStarVsPSFSigmaSysTest(BaseScatterPlotSysTest): @@ -2274,14 +2308,17 @@ class ScatterPlotStarVsPSFSigmaSysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('sigma', 'sigma_err', 'psf_sigma')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotStarVsPSFSigmaSysTest, self).__call__(array, 'psf_sigma', 'sigma', 'sigma_err', residual=False, per_ccd_stat=per_ccd_stat, xlabel=r'$\sigma^{\rm PSF}$ [arcsec]', ylabel=r'$\sigma^{\rm star}$ [arcsec]', color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line='one-to-one') + linear_regression=True, reference_line='one-to-one', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotResidualVsPSFG1SysTest(BaseScatterPlotSysTest): @@ -2293,13 +2330,16 @@ class ScatterPlotResidualVsPSFG1SysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('g1', 'g1_err', 'psf_g1')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotResidualVsPSFG1SysTest, self).__call__(array, 'psf_g1', 'g1', 'g1_err', residual=True, per_ccd_stat=per_ccd_stat, xlabel=r'$g^{\rm PSF}_1$', ylabel=r'$g^{\rm star}_1 - g^{\rm PSF}_1$', color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line='zero') + linear_regression=True, reference_line='zero', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotResidualVsPSFG2SysTest(BaseScatterPlotSysTest): @@ -2311,13 +2351,16 @@ class ScatterPlotResidualVsPSFG2SysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('g2', 'g2_err', 'psf_g2')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotResidualVsPSFG2SysTest, self).__call__(array, 'psf_g2', 'g2', 'g2_err', residual=True, per_ccd_stat=per_ccd_stat, xlabel=r'$g^{\rm PSF}_2$', ylabel=r'$g^{\rm star}_2 - g^{\rm PSF}_2$', color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line='zero') + linear_regression=True, reference_line='zero', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotResidualVsPSFSigmaSysTest(BaseScatterPlotSysTest): @@ -2329,14 +2372,17 @@ class ScatterPlotResidualVsPSFSigmaSysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('sigma', 'sigma_err', 'psf_sigma')] - def __call__(self, array, per_ccd_stat=None, color='', lim=None): + def __call__(self, array, per_ccd_stat=None, color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): return super(ScatterPlotResidualVsPSFSigmaSysTest, self).__call__(array, 'psf_sigma', 'sigma', 'sigma_err', residual=True, per_ccd_stat=per_ccd_stat, xlabel=r'$\sigma^{\rm PSF}$ [arcsec]', ylabel=r'$\sigma^{\rm star} - \sigma^{\rm PSF}$ [arcsec]', color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line='zero') + linear_regression=True, reference_line='zero', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) class ScatterPlotResidualSigmaVsPSFMagSysTest(BaseScatterPlotSysTest): @@ -2348,7 +2394,8 @@ class ScatterPlotResidualSigmaVsPSFMagSysTest(BaseScatterPlotSysTest): objects_list = ['star PSF'] required_quantities = [('sigma', 'sigma_err', 'psf_sigma', 'mag_inst')] - def __call__(self, array, per_ccd_stat='None', color='', lim=None): + def __call__(self, array, per_ccd_stat='None', color='', lim=None, + histogram=False, histogram_n_bins=40, histogram_cmap='Blues'): self.per_ccd_stat = None if per_ccd_stat == 'None' else per_ccd_stat import numpy.lib.recfunctions use_array = numpy.copy(array) @@ -2367,5 +2414,7 @@ def __call__(self, array, per_ccd_stat='None', color='', lim=None): ylabel= r'$(\sigma^{\rm star} - \sigma^{\rm PSF})/\sigma^{\rm PSF}$', color=color, lim=lim, equal_axis=False, - linear_regression=True, reference_line='zero') + linear_regression=True, reference_line='zero', + histogram=histogram, histogram_n_bins=histogram_n_bins, + histogram_cmap=histogram_cmap) diff --git a/tests/test_sys_tests.py b/tests/test_sys_tests.py new file mode 100644 index 0000000..afb8dfb --- /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_histogram_scatterplot(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, + source_data['g1']*0.05, source_data['g2']*0.05, source_data['g1']*0.05, + source_data['g2']*0.05], + names = ['x', 'y', 'g1', 'g2', 'psf_g1', 'psf_g2', 'sigma', 'psf_sigma', + 'g1_err', 'g2_err', 'psf_g1_err', 'psf_g2_err']) + obj = stile.ScatterPlotSysTest('StarVsPSFG1') + obj(data, color='g') + obj(data, histogram=True, color='g') + + +if __name__=='__main__': + unittest.main() +