def display_photometry_model_magnitudes(analysis, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted with linear interpolation between each bin :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = list(analysis.data_list.keys()) else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in list(analysis.data_list.keys()): if isinstance(analysis.data_list[key], photolike.PhotometryLike): new_data_keys.append(key) else: custom_warnings.warn( "Dataset %s is not of the Photometery kind. Cannot be plotted by " "display_photometry_model_magnitudes" % key) if not new_data_keys: RuntimeError( "There were no valid Photometry data requested for plotting. Please use the detector names in the data list" ) data_keys = new_data_keys # Default is to show the model with steps step = threeML_config.plugins.photo.fit_plot.step data_cmap = threeML_config.plugins.photo.fit_plot.data_cmap.value # plt.cm.rainbow model_cmap = threeML_config.plugins.photo.fit_plot.model_cmap.value # Legend is on by default show_legend = True # Default colors data_colors = cmap_intervals(len(data_keys), data_cmap) model_colors = cmap_intervals(len(data_keys), model_cmap) # Now override defaults according to the optional keywords, if present if "show_legend" in kwargs: show_legend = bool(kwargs.pop("show_legend")) if "step" in kwargs: step = bool(kwargs.pop("step")) if "data_cmap" in kwargs: data_cmap = plt.get_cmap(kwargs.pop("data_cmap")) data_colors = cmap_intervals(len(data_keys), data_cmap) if "model_cmap" in kwargs: model_cmap = kwargs.pop("model_cmap") model_colors = cmap_intervals(len(data_keys), model_cmap) if "data_colors" in kwargs: data_colors = kwargs.pop("data_colors") if len(data_colors) < len(data_keys): log.error( "You need to provide at least a number of data colors equal to the " "number of datasets") raise ValueError() if "model_colors" in kwargs: model_colors = kwargs.pop("model_colors") if len(model_colors) < len(data_keys): log.error( "You need to provide at least a number of model colors equal to the " "number of datasets") raise ValueError() residual_plot = ResidualPlot(**kwargs) # go thru the detectors for key, data_color, model_color in zip(data_keys, data_colors, model_colors): data = analysis.data_list[key] # type: photolike # get the expected counts avg_wave_length = (data._filter_set.effective_wavelength.value ) # type: np.ndarray # need to sort because filters are not always in order sort_idx = avg_wave_length.argsort() expected_model_magnitudes = data._get_total_expectation()[sort_idx] magnitudes = data.magnitudes[sort_idx] mag_errors = data.magnitude_errors[sort_idx] avg_wave_length = avg_wave_length[sort_idx] residuals = old_div((expected_model_magnitudes - magnitudes), mag_errors) widths = data._filter_set.wavelength_bounds.widths[sort_idx] residual_plot.add_data( x=avg_wave_length, y=magnitudes, xerr=widths, yerr=mag_errors, residuals=residuals, label=data._name, color=data_color, ) residual_plot.add_model( avg_wave_length, expected_model_magnitudes, label="%s Model" % data._name, color=model_color, ) return residual_plot.finalize( xlabel="Wavelength\n(%s)" % data._filter_set.waveunits, ylabel="Magnitudes", xscale="linear", yscale="linear", invert_y=True, )
def display_posterior_model_counts(bayesian_analysis, result=None, thin=100, shade=True, q_level=68, gradient=0.6, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param data_color: (optional) color for all datasets :param model_color: (optional) color for all folded models :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted :param model_subplot: (optional) axe(s) to plot to for overplotting with linear interpolation between each bin :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = bayesian_analysis.data_list.keys() else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in bayesian_analysis.data_list.keys(): if isinstance(bayesian_analysis.data_list[key], threeML.plugins.SpectrumLike.SpectrumLike): new_data_keys.append(key) else: custom_warnings.warn( "Dataset %s is not of the SpectrumLike kind. Cannot be plotted by " "display_spectrum_model_counts" % key) if not new_data_keys: RuntimeError( 'There were no valid SpectrumLike data requested for plotting. Please use the detector names in the data list' ) data_keys = new_data_keys # default settings # Default is to show the model with steps step = True data_cmap = threeML_config['ogip']['data plot cmap'] # plt.cm.rainbow model_cmap = threeML_config['ogip'][ 'model plot cmap'] # plt.cm.nipy_spectral_r # Legend is on by default show_legend = False show_residuals = False # Default colors data_colors = cmap_intervals(len(data_keys), data_cmap) model_colors = cmap_intervals(len(data_keys), model_cmap) # Now override defaults according to the optional keywords, if present model_kwargs = dict(alpha=0.05, zorder=-5000) if 'model_kwargs' in kwargs: model_kwargs_tmp = kwargs.pop('model_kwargs') for k, v in model_kwargs_tmp.items(): model_kwargs[k] = v data_kwargs = dict( alpha=1., fmt=threeML_config['residual plot']['error marker'], markersize=threeML_config['residual plot']['error marker size'], linestyle='', elinewidth=threeML_config['residual plot']['error line width'], capsize=0, zorder=-1) if 'data_kwargs' in kwargs: data_kwargs_tmp = kwargs.pop('data_kwargs') for k, v in data_kwargs_tmp.items(): data_kwargs[k] = v if 'show_data' in kwargs: show_data = bool(kwargs.pop('show_data')) else: show_data = True if 'show_legend' in kwargs: show_legend = bool(kwargs.pop('show_legend')) if 'show_residuals' in kwargs: show_residuals = bool(kwargs.pop('show_residuals')) residual_storage = [] residual_err_storage = [] if 'step' in kwargs: step = bool(kwargs.pop('step')) if 'min_rate' in kwargs: min_rate = kwargs.pop('min_rate') # If min_rate is a floating point, use the same for all datasets, otherwise use the provided ones try: min_rate = float(min_rate) min_rates = [min_rate] * len(data_keys) except TypeError: min_rates = list(min_rate) assert len(min_rates) >= len( data_keys), "If you provide different minimum rates for each data set, you need" \ "to provide an iterable of the same length of the number of datasets" else: # This is the default (no rebinning) min_rates = [NO_REBIN] * len(data_keys) if 'data_cmap' in kwargs: data_cmap = plt.get_cmap(kwargs.pop('data_cmap')) data_colors = cmap_intervals(len(data_keys), data_cmap) if 'model_cmap' in kwargs: model_cmap = kwargs.pop('model_cmap') model_colors = cmap_intervals(len(data_keys), model_cmap) if 'data_colors' in kwargs: data_colors = kwargs.pop('data_colors') assert len(data_colors) >= len(data_keys), "You need to provide at least a number of data colors equal to the " \ "number of datasets" elif 'data_color' in kwargs: data_colors = [kwargs.pop('data_color')] * len(data_keys) if 'model_colors' in kwargs: model_colors = kwargs.pop('model_colors') assert len(model_colors) >= len( data_keys), "You need to provide at least a number of model colors equal to the " \ "number of datasets" ratio_residuals = False if 'ratio_residuals' in kwargs: ratio_residuals = bool(kwargs['ratio_residuals']) elif 'model_color' in kwargs: model_colors = [kwargs.pop('model_color')] * len(data_keys) if 'model_labels' in kwargs: model_labels = kwargs.pop('model_labels') assert len(model_labels) == len( data_keys ), 'you must have the same number of model labels as data sets' else: model_labels = [ '%s Model' % bayesian_analysis.data_list[key]._name for key in data_keys ] #fig, (ax, ax1) = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]}, **kwargs) residual_plot = ResidualPlot(show_residuals=show_residuals, **kwargs) if show_residuals: axes = [residual_plot.data_axis, residual_plot.residual_axis] else: axes = residual_plot.data_axis # extract the samples if result is None: samples = bayesian_analysis.results.samples.T[::thin] else: samples = result.samples.T[::thin] if shade: color_config = Colors() shade_y = [] shade_x = [] for params in samples: for i, (k, v) in enumerate( bayesian_analysis.likelihood_model.free_parameters.items()): v.value = params[i] # first with no data if shade: per_det_y = [] per_det_x = [] for key, data_color, model_color, min_rate, model_label in zip( data_keys, data_colors, model_colors, min_rates, model_labels): # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = bayesian_analysis.data_list[ key] # type: threeML.plugins.SpectrumLike.SpectrumLike if not shade: data.display_model(data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=False, show_data=False, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=None, model_subplot=axes, data_kwargs=data_kwargs, model_kwargs=model_kwargs) else: # this is private for now rebinned_quantities = data._construct_counts_arrays( min_rate, ratio_residuals) if step: pass else: y = (rebinned_quantities['expected_model_rate'] / rebinned_quantities['chan_width'])[data.mask] x = np.mean([ rebinned_quantities['energy_min'], rebinned_quantities['energy_max'] ], axis=0)[data.mask] per_det_y.append(y) per_det_x.append(x) if shade: shade_y.append(per_det_y) shade_x.append(per_det_x) if shade: # convert to per detector shade_y = np.array(shade_y).T shade_x = np.array(shade_x).T model_kwargs.pop('zorder') for key, data_color, model_color, min_rate, model_label, x, y, in zip( data_keys, data_colors, model_colors, min_rates, model_labels, shade_x, shade_y): # we have to do a little reshaping because... life y = np.array([yy.tolist() for yy in y]) q_levels = np.atleast_1d(q_level) q_levels.sort() scale = 1. zorder = -100 for level in q_levels: color = color_config.format(model_color) color_scale = color_config.scale_colour(color, scale) # first we need to get the quantiles along the energy axis low = np.percentile(y, 50 - level * 0.5, axis=0) high = np.percentile(y, 50 + level * 0.5, axis=0) residual_plot.data_axis.fill_between(x[0], low, high, color=color_scale, zorder=zorder, **model_kwargs) scale *= gradient zorder -= 1 for key, data_color, model_color, min_rate, model_label in zip( data_keys, data_colors, model_colors, min_rates, model_labels): # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = bayesian_analysis.data_list[ key] # type: threeML.plugins.SpectrumLike.SpectrumLike data.display_model( data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=False, show_data=show_data, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=model_label, model_subplot=axes, model_kwargs=dict(alpha=0.), # no model data_kwargs=data_kwargs) return residual_plot.figure
def display_spectrum_model_counts(analysis, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param data_color: (optional) color for all datasets :param model_color: (optional) color for all folded models :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted :param model_subplot: (optional) axe(s) to plot to for overplotting with linear interpolation between each bin :param data_per_plot: (optional) Can specify how many detectors should be plotted in one plot. If there are more detectors than this number it will split it up in several plots :param show_background: (optional) Also show the background :param source_only: (optional) Plot only source (total data - background) :param background_cmap: (str) (optional) the color map used to extract automatically the colors for the background :param background_colors: (optional) a tuple or list with the color for each background :param background_color: (optional) color for all backgrounds :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = list(analysis.data_list.keys()) else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in list(analysis.data_list.keys()): if isinstance(analysis.data_list[key], speclike.SpectrumLike): new_data_keys.append(key) else: log.warning( "Dataset %s is not of the SpectrumLike kind. Cannot be plotted by " "display_spectrum_model_counts" % key) if not new_data_keys: RuntimeError( "There were no valid SpectrumLike data requested for plotting. Please use the detector names in the data list" ) data_keys = new_data_keys # default settings _sub_menu: BinnedSpectrumPlot = threeML_config.plugins.ogip.fit_plot # Default is to show the model with steps step = _sub_menu.step data_cmap = _sub_menu.data_cmap.value model_cmap = _sub_menu.model_cmap.value background_cmap = _sub_menu.background_cmap.value # Legend is on by default show_legend = _sub_menu.show_legend show_residuals = _sub_menu.show_residuals show_background: bool = _sub_menu.show_background # Default colors _cmap_len = max(len(data_keys), _sub_menu.n_colors) data_colors = cmap_intervals(_cmap_len, data_cmap) model_colors = cmap_intervals(_cmap_len, model_cmap) background_colors = cmap_intervals(_cmap_len, background_cmap) # Now override defaults according to the optional keywords, if present if "show_data" in kwargs: show_data = bool(kwargs.pop("show_data")) else: show_data = True if "show_legend" in kwargs: show_legend = bool(kwargs.pop("show_legend")) if "show_residuals" in kwargs: show_residuals = bool(kwargs.pop("show_residuals")) if "step" in kwargs: step = bool(kwargs.pop("step")) if "min_rate" in kwargs: min_rate = kwargs.pop("min_rate") # If min_rate is a floating point, use the same for all datasets, otherwise use the provided ones try: min_rate = float(min_rate) min_rates = [min_rate] * len(data_keys) except TypeError: min_rates = list(min_rate) if len(min_rates) < len(data_keys): log.error( "If you provide different minimum rates for each data set, you need" "to provide an iterable of the same length of the number of datasets" ) raise ValueError() else: # This is the default (no rebinning) min_rates = [NO_REBIN] * len(data_keys) if "data_per_plot" in kwargs: data_per_plot = int(kwargs.pop("data_per_plot")) else: data_per_plot = len(data_keys) if "data_cmap" in kwargs: if len(data_keys) <= data_per_plot: _cmap_len = max(len(data_keys), _sub_menu.n_colors) data_colors = cmap_intervals(_cmap_len, kwargs.pop("data_cmap")) else: _cmap_len = max(data_per_plot, _sub_menu.n_colors) data_colors_base = cmap_intervals(_cmap_len, kwargs.pop("data_cmap")) data_colors = [] for i in range(len(data_keys)): data_colors.append(data_colors_base[i % data_per_plot]) elif "data_colors" in kwargs: data_colors = kwargs.pop("data_colors") if len(data_colors) < len(data_keys): log.error( "You need to provide at least a number of data colors equal to the " "number of datasets") raise ValueError() elif _sub_menu.data_color is not None: data_colors = [_sub_menu.data_color] * len(data_keys) # always override if ("data_color" in kwargs): data_colors = [kwargs.pop("data_color")] * len(data_keys) if "model_cmap" in kwargs: if len(data_keys) <= data_per_plot: _cmap_len = max(len(data_keys), _sub_menu.n_colors) model_colors = cmap_intervals(_cmap_len, kwargs.pop("model_cmap")) else: _cmap_len = max(data_per_plot, _sub_menu.n_colors) model_colors_base = cmap_intervals(_cmap_len, kwargs.pop("model_cmap")) model_colors = [] for i in range(len(data_keys)): model_colors.append(model_colors_base[i % data_per_plot]) elif "model_colors" in kwargs: model_colors = kwargs.pop("model_colors") if len(model_colors) < len(data_keys): log.error( "You need to provide at least a number of model colors equal to the " "number of datasets") raise ValueError() elif _sub_menu.model_color is not None: model_colors = [_sub_menu.model_color] * len(data_keys) # always overide if "model_color" in kwargs: model_colors = [kwargs.pop("model_color")] * len(data_keys) if "background_cmap" in kwargs: if len(data_keys) <= data_per_plot: background_colors = cmap_intervals(len(data_keys), kwargs.pop("background_cmap")) else: background_colors_base = cmap_intervals( data_per_plot, kwargs.pop("background_cmap")) background_colors = [] for i in range(len(data_keys)): background_colors.append(background_colors_base[i % data_per_plot]) elif "background_colors" in kwargs: background_colors = kwargs.pop("background_colors") if len(background_colors) < len(data_keys): log.error( "You need to provide at least a number of background colors equal to the " "number of datasets") raise ValueError() elif _sub_menu.background_color is not None: background_colors = [_sub_menu.background_color] * len(data_keys) # always override if "background_color" in kwargs: background_colors = [kwargs.pop("background_color")] * len(data_keys) ratio_residuals = False if "ratio_residuals" in kwargs: ratio_residuals = bool(kwargs["ratio_residuals"]) if "model_labels" in kwargs: model_labels = kwargs.pop("model_labels") if len(model_labels) != len(data_keys): log.error( "You must have the same number of model labels as data sets") raise ValueError() else: model_labels = [ "%s Model" % analysis.data_list[key]._name for key in data_keys ] if "background_labels" in kwargs: background_labels = kwargs.pop("background_labels") if len(background_labels) != len(data_keys): log.error( "You must have the same number of background labels as data sets" ) raise ValueError() else: background_labels = [ "%s Background" % analysis.data_list[key]._name for key in data_keys ] if "source_only" in kwargs: source_only = kwargs.pop("source_only") if type(source_only) != bool: log.error("source_only must be a boolean") raise TypeError() else: source_only = True if "show_background" in kwargs: show_background = kwargs.pop("show_background") if type(show_background) != bool: log.error("show_background must be a boolean") raise TypeError() if len(data_keys) <= data_per_plot: # If less than data_per_plot detectors need to be plotted, # just plot it in one plot residual_plot = ResidualPlot(show_residuals=show_residuals, **kwargs) if show_residuals: axes = [residual_plot.data_axis, residual_plot.residual_axis] else: axes = residual_plot.data_axis # go thru the detectors for key, data_color, model_color, background_color, min_rate, model_label, background_label in zip( data_keys, data_colors, model_colors, background_colors, min_rates, model_labels, background_labels): # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = analysis.data_list[key] # type: speclike data.display_model(data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=show_residuals, show_data=show_data, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=model_label, model_subplot=axes, show_background=show_background, source_only=source_only, background_color=background_color, background_label=background_label) return residual_plot.figure else: # Too many detectors to plot everything in one plot... Make indivi. # plots with data_per_plot dets per plot # How many plots do we need? n_plots = int(np.ceil(1. * len(data_keys) / data_per_plot)) plots = [] for i in range(n_plots): plots.append(ResidualPlot(show_residuals=show_residuals, **kwargs)) # go thru the detectors for j, (key, data_color, model_color, background_color, min_rate, model_label, background_label) in enumerate( zip(data_keys, data_colors, model_colors, background_colors, min_rates, model_labels, background_labels)): axes = [ plots[int(j / data_per_plot)].data_axis, plots[int(j / data_per_plot)].residual_axis ] # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = analysis.data_list[key] # type: speclike data.display_model(data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=show_residuals, show_data=show_data, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=model_label, model_subplot=axes, show_background=show_background, source_only=source_only, background_color=background_color, background_label=background_label) figs = [] for p in plots: figs.append(p.figure) return figs
def plot_spectra(*analysis_results, **kwargs): """ plotting routine for fitted point source spectra :param analysis_results: fitted JointLikelihood or BayesianAnalysis objects :param sources_to_use: (optional) list of PointSource string names to plot from the analysis :param energy_unit: (optional) astropy energy unit in string form (can also be frequency) :param flux_unit: (optional) astropy flux unit in string form :param confidence_level: (optional) confidence level to use (default: 0.68) :param ene_min: (optional) minimum energy to plot :param ene_max: (optional) maximum energy to plot :param num_ene: (optional) number of energies to plot :param use_components: (optional) True or False to plot the spectral components :param components_to_use: (optional) list of string names of the components to plot: including 'total' will also plot the total spectrum :param sum_sources: (optional) some all the MLE and Bayesian sources :param show_contours: (optional) True or False to plot the contour region :param plot_style_kwargs: (optional) dictionary of MPL plot styling for the best fit curve :param contour_style_kwargs: (optional) dictionary of MPL plot styling for the contour regions :param fit_cmap: MPL color map to iterate over for plotting multiple analyses :param contour_cmap: MPL color map to iterate over for plotting contours for multiple analyses :param subplot: subplot to use :param xscale: 'log' or 'linear' :param yscale: 'log' or 'linear' :param include_extended: True or False, also plot extended source spectra. :return: """ # allow matplotlib to plot quantities to the access quantity_support() _defaults = { 'fit_cmap': threeML_config['model plot']['point source plot']['fit cmap'], 'contour_cmap': threeML_config['model plot']['point source plot']['contour cmap'], 'contour_colors': None, 'fit_colors': None, 'confidence_level': 0.68, 'equal_tailed': True, 'best_fit': 'median', 'energy_unit': 'keV', 'flux_unit': '1/(keV s cm2)', 'ene_min': 10., 'ene_max': 1E4, 'num_ene': 100, 'use_components': False, 'components_to_use': [], 'sources_to_use': [], 'sum_sources': False, 'show_contours': True, 'plot_style_kwargs': threeML_config['model plot']['point source plot']['plot style'], 'contour_style_kwargs': threeML_config['model plot']['point source plot']['contour style'], 'show_legend': True, 'legend_kwargs': threeML_config['model plot']['point source plot']['legend style'], 'subplot': None, 'xscale': 'log', 'yscale': 'log', 'include_extended': False } for key, value in kwargs.items(): if key in _defaults: _defaults[key] = value if isinstance(_defaults['ene_min'], u.Quantity): assert isinstance( _defaults['ene_max'], u.Quantity), 'both energy arguments must be Quantities' if isinstance(_defaults['ene_max'], u.Quantity): assert isinstance( _defaults['ene_min'], u.Quantity), 'both energy arguments must be Quantities' if isinstance(_defaults['ene_max'], u.Quantity): energy_range = np.linspace(_defaults['ene_min'], _defaults['ene_max'], _defaults['num_ene']) # type: u.Quantity _defaults['energy_unit'] = energy_range.unit if _defaults['xscale'] == 'log': energy_range = np.logspace( np.log10(energy_range.min().value), np.log10(energy_range.max().value), _defaults['num_ene']) * energy_range.unit else: energy_range = np.logspace( np.log10(_defaults['ene_min']), np.log10(_defaults['ene_max']), _defaults['num_ene']) * u.Unit(_defaults['energy_unit']) mle_analyses, bayesian_analyses, num_sources_to_plot, duplicate_keys = _setup_analysis_dictionaries( analysis_results, energy_range, _defaults['energy_unit'], _defaults['flux_unit'], _defaults['use_components'], _defaults['components_to_use'], _defaults['confidence_level'], _defaults['equal_tailed'], differential=True, sources_to_use=_defaults['sources_to_use'], include_extended=_defaults['include_extended']) # we are now ready to plot. # all calculations have been made. # if we are not going to sum sources if not _defaults['sum_sources']: if _defaults['fit_colors'] is None: color_fit = cmap_intervals(num_sources_to_plot + 1, _defaults['fit_cmap']) else: # duck typing if isinstance(_defaults['fit_colors'], (str, str)): color_fit = [_defaults['fit_colors']] * num_sources_to_plot elif isinstance(_defaults['fit_colors'], list): assert len(_defaults['fit_colors']) == num_sources_to_plot, \ 'list of colors (%d) must be the same length as sources ot plot (%s)' % ( len(_defaults['fit_colors']), num_sources_to_plot) color_fit = _defaults['fit_colors'] else: raise ValueError('Can not setup color, wrong type:', type(_defaults['fit_colors'])) if _defaults['contour_colors'] is None: color_contour = cmap_intervals(num_sources_to_plot + 1, _defaults['contour_cmap']) else: # duck typing if isinstance(_defaults['contour_colors'], (str, str)): color_contour = [_defaults['contour_colors'] ] * num_sources_to_plot elif isinstance(_defaults['contour_colors'], list): assert len(_defaults['contour_colors']) == num_sources_to_plot, \ 'list of colors (%d) must be the same length as sources ot plot (%s)' % ( len(_defaults['contour_colors']), num_sources_to_plot) color_contour = _defaults['fit_colors'] else: raise ValueError('Can not setup contour color, wrong type:', type(_defaults['contour_colors'])) color_itr = 0 # go thru the mle analysis and plot spectra plotter = SpectralContourPlot( num_sources_to_plot, xscale=_defaults['xscale'], yscale=_defaults['yscale'], show_legend=_defaults['show_legend'], plot_kwargs=_defaults['plot_style_kwargs'], contour_kwargs=_defaults['contour_style_kwargs'], legend_kwargs=_defaults['legend_kwargs'], emin=_defaults['ene_min'], emax=_defaults['ene_max'], subplot=_defaults['subplot']) for key in list(mle_analyses.keys()): # we won't assume to plot the total until the end plot_total = False if _defaults['use_components']: # if this source has no components or none that we wish to plot # then we will plot the total spectrum after this if (not list(mle_analyses[key]['components'].keys())) or ( 'total' in _defaults['components_to_use']): plot_total = True for component in list(mle_analyses[key]['components'].keys()): positive_error = None negative_error = None # extract the information and plot it if _defaults['best_fit'] == 'average': best_fit = mle_analyses[key]['components'][ component].average else: best_fit = mle_analyses[key]['components'][ component].median if _defaults['show_contours']: positive_error = mle_analyses[key]['components'][ component].upper_error negative_error = mle_analyses[key]['components'][ component].lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 label = "%s: %s" % (key, component) # this is where we keep track of duplicates if key in duplicate_keys: label = "%s: MLE" % label if mle_analyses[key]['components'][ component].is_dimensionless: plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) color_itr += 1 else: plot_total = True if plot_total: # it ends up that we need to plot the total spectrum # which is just a repeat of the process if _defaults['best_fit'] == 'average': best_fit = mle_analyses[key]['fitted point source'].average else: best_fit = mle_analyses[key]['fitted point source'].median if _defaults['show_contours']: positive_error = mle_analyses[key][ 'fitted point source'].upper_error negative_error = mle_analyses[key][ 'fitted point source'].lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 else: positive_error = None negative_error = None label = "%s" % key if key in duplicate_keys: label = "%s: MLE" % label plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) color_itr += 1 # we will do the exact same thing for the bayesian analyses for key in list(bayesian_analyses.keys()): plot_total = False if _defaults['use_components']: if (not list(bayesian_analyses[key]['components'].keys())) or ( 'total' in _defaults['components_to_use']): plot_total = True for component in list( bayesian_analyses[key]['components'].keys()): positive_error = None negative_error = None if _defaults['best_fit'] == 'average': best_fit = bayesian_analyses[key]['components'][ component].average else: best_fit = bayesian_analyses[key]['components'][ component].median if _defaults['show_contours']: positive_error = bayesian_analyses[key]['components'][ component].upper_error negative_error = bayesian_analyses[key]['components'][ component].lower_error label = "%s: %s" % (key, component) if key in duplicate_keys: label = "%s: Bayesian" % label if bayesian_analyses[key]['components'][ component].is_dimensionless: plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) color_itr += 1 else: plot_total = True if plot_total: if _defaults['best_fit'] == 'average': best_fit = bayesian_analyses[key][ 'fitted point source'].average else: best_fit = bayesian_analyses[key][ 'fitted point source'].median positive_error = None negative_error = None if _defaults['show_contours']: positive_error = bayesian_analyses[key][ 'fitted point source'].upper_error negative_error = bayesian_analyses[key][ 'fitted point source'].lower_error label = "%s" % key if key in duplicate_keys: label = "%s: Bayesian" % label plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label) color_itr += 1 else: # now we sum sources instead # we keep MLE and Bayes apart because it makes no # sense to sum them together total_analysis_mle, component_sum_dict_mle, num_sources_to_plot = _collect_sums_into_dictionaries( mle_analyses, _defaults['use_components'], _defaults['components_to_use']) total_analysis_bayes, component_sum_dict_bayes, num_sources_to_plot_bayes = _collect_sums_into_dictionaries( bayesian_analyses, _defaults['use_components'], _defaults['components_to_use']) num_sources_to_plot += num_sources_to_plot_bayes plotter = SpectralContourPlot( num_sources_to_plot, xscale=_defaults['xscale'], yscale=_defaults['yscale'], show_legend=_defaults['show_legend'], plot_kwargs=_defaults['plot_style_kwargs'], contour_kwargs=_defaults['contour_style_kwargs'], legend_kwargs=_defaults['legend_kwargs'], emin=_defaults['ene_min'], emax=_defaults['ene_max'], subplot=_defaults['subplot']) color_fit = cmap_intervals(num_sources_to_plot, _defaults['fit_cmap']) color_contour = cmap_intervals(num_sources_to_plot, _defaults['contour_cmap']) color_itr = 0 if _defaults['use_components'] and list(component_sum_dict_mle.keys()): # we have components to plot for component, values in component_sum_dict_mle.items(): summed_analysis = sum(values) if _defaults['best_fit'] == 'average': best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults['show_contours']: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 if np.any([ c.is_dimensionless for c in component_sum_dict_mle[component] ]): plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: MLE" % component) else: plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: MLE" % component) color_itr += 1 if total_analysis_mle: # we will sum and plot the total # analysis summed_analysis = sum(total_analysis_mle) if _defaults['best_fit'] == 'average': best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults['show_contours']: positive_error = best_fit + summed_analysis.upper_error negative_error = best_fit - summed_analysis.lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="total: MLE") color_itr += 1 if _defaults['use_components'] and list( component_sum_dict_bayes.keys()): # we have components to plot for component, values in component_sum_dict_bayes.items(): summed_analysis = sum(values) if _defaults['best_fit'] == 'average': best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults['show_contours']: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error if np.any([ c.is_dimensionless for c in component_sum_dict_bayes[component] ]): plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: Bayesian" % component) else: plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: Bayesian" % component) color_itr += 1 if total_analysis_bayes: # we will sum and plot the total # analysis summed_analysis = sum(total_analysis_bayes) if _defaults['best_fit'] == 'average': best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults['show_contours']: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error plotter.add_model(energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="total: Bayesian") color_itr += 1 return plotter.finalize(_defaults)
def display_spectrum_model_counts(analysis, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param data_color: (optional) color for all datasets :param model_color: (optional) color for all folded models :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted :param model_subplot: (optional) axe(s) to plot to for overplotting with linear interpolation between each bin :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = list(analysis.data_list.keys()) else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in list(analysis.data_list.keys()): if isinstance(analysis.data_list[key], threeML.plugins.SpectrumLike.SpectrumLike): new_data_keys.append(key) else: custom_warnings.warn( "Dataset %s is not of the SpectrumLike kind. Cannot be plotted by " "display_spectrum_model_counts" % key) if not new_data_keys: RuntimeError( "There were no valid SpectrumLike data requested for plotting. Please use the detector names in the data list" ) data_keys = new_data_keys # default settings # Default is to show the model with steps step = True data_cmap = threeML_config["ogip"]["data plot cmap"] # plt.cm.rainbow model_cmap = threeML_config["ogip"][ "model plot cmap"] # plt.cm.nipy_spectral_r # Legend is on by default show_legend = True show_residuals = True # Default colors data_colors = cmap_intervals(len(data_keys), data_cmap) model_colors = cmap_intervals(len(data_keys), model_cmap) # Now override defaults according to the optional keywords, if present if "show_data" in kwargs: show_data = bool(kwargs.pop("show_data")) else: show_data = True if "show_legend" in kwargs: show_legend = bool(kwargs.pop("show_legend")) if "show_residuals" in kwargs: show_residuals = bool(kwargs.pop("show_residuals")) if "step" in kwargs: step = bool(kwargs.pop("step")) if "min_rate" in kwargs: min_rate = kwargs.pop("min_rate") # If min_rate is a floating point, use the same for all datasets, otherwise use the provided ones try: min_rate = float(min_rate) min_rates = [min_rate] * len(data_keys) except TypeError: min_rates = list(min_rate) assert len(min_rates) >= len(data_keys), ( "If you provide different minimum rates for each data set, you need" "to provide an iterable of the same length of the number of datasets" ) else: # This is the default (no rebinning) min_rates = [NO_REBIN] * len(data_keys) if "data_cmap" in kwargs: data_cmap = plt.get_cmap(kwargs.pop("data_cmap")) data_colors = cmap_intervals(len(data_keys), data_cmap) if "model_cmap" in kwargs: model_cmap = kwargs.pop("model_cmap") model_colors = cmap_intervals(len(data_keys), model_cmap) if "data_colors" in kwargs: data_colors = kwargs.pop("data_colors") assert len(data_colors) >= len(data_keys), ( "You need to provide at least a number of data colors equal to the " "number of datasets") elif "data_color" in kwargs: data_colors = [kwargs.pop("data_color")] * len(data_keys) if "model_colors" in kwargs: model_colors = kwargs.pop("model_colors") assert len(model_colors) >= len(data_keys), ( "You need to provide at least a number of model colors equal to the " "number of datasets") ratio_residuals = False if "ratio_residuals" in kwargs: ratio_residuals = bool(kwargs["ratio_residuals"]) elif "model_color" in kwargs: model_colors = [kwargs.pop("model_color")] * len(data_keys) if "model_labels" in kwargs: model_labels = kwargs.pop("model_labels") assert len(model_labels) == len( data_keys ), "you must have the same number of model labels as data sets" else: model_labels = [ "%s Model" % analysis.data_list[key]._name for key in data_keys ] # fig, (ax, ax1) = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]}, **kwargs) residual_plot = ResidualPlot(show_residuals=show_residuals, **kwargs) if show_residuals: axes = [residual_plot.data_axis, residual_plot.residual_axis] else: axes = residual_plot.data_axis # go thru the detectors for key, data_color, model_color, min_rate, model_label in zip( data_keys, data_colors, model_colors, min_rates, model_labels): # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = analysis.data_list[ key] # type: threeML.plugins.SpectrumLike.SpectrumLike data.display_model( data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=show_residuals, show_data=show_data, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=model_label, model_subplot=axes, ) return residual_plot.figure
def display_photometry_model_magnitudes(analysis, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted with linear interpolation between each bin :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = analysis.data_list.keys() else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in analysis.data_list.keys(): if isinstance(analysis.data_list[key], threeML.plugins.PhotometryLike.PhotometryLike): new_data_keys.append(key) else: custom_warnings.warn("Dataset %s is not of the Photometery kind. Cannot be plotted by " "display_photometry_model_magnitudes" % key) if not new_data_keys: RuntimeError( 'There were no valid Photometry data requested for plotting. Please use the detector names in the data list') data_keys = new_data_keys # Default is to show the model with steps step = True data_cmap = threeML_config['photo']['data plot cmap'] # plt.cm.rainbow model_cmap = threeML_config['photo']['model plot cmap'] # plt.cm.nipy_spectral_r # Legend is on by default show_legend = True # Default colors data_colors = cmap_intervals(len(data_keys), data_cmap) model_colors = cmap_intervals(len(data_keys), model_cmap) # Now override defaults according to the optional keywords, if present if 'show_legend' in kwargs: show_legend = bool(kwargs.pop('show_legend')) if 'step' in kwargs: step = bool(kwargs.pop('step')) if 'data_cmap' in kwargs: data_cmap = plt.get_cmap(kwargs.pop('data_cmap')) data_colors = cmap_intervals(len(data_keys), data_cmap) if 'model_cmap' in kwargs: model_cmap = kwargs.pop('model_cmap') model_colors = cmap_intervals(len(data_keys), model_cmap) if 'data_colors' in kwargs: data_colors = kwargs.pop('data_colors') assert len(data_colors) >= len(data_keys), "You need to provide at least a number of data colors equal to the " \ "number of datasets" if 'model_colors' in kwargs: model_colors = kwargs.pop('model_colors') assert len(model_colors) >= len( data_keys), "You need to provide at least a number of model colors equal to the " \ "number of datasets" residual_plot = ResidualPlot(**kwargs) # go thru the detectors for key, data_color, model_color in zip(data_keys, data_colors, model_colors): data = analysis.data_list[key] # type: threeML.plugins.PhotometryLike.PhotometryLike # get the expected counts avg_wave_length = data._filter_set.effective_wavelength.value #type: np.ndarray # need to sort because filters are not always in order sort_idx = avg_wave_length.argsort() expected_model_magnitudes = data._get_total_expectation()[sort_idx] magnitudes = data.magnitudes[sort_idx] mag_errors= data.magnitude_errors[sort_idx] avg_wave_length = avg_wave_length[sort_idx] residuals = (expected_model_magnitudes - magnitudes) / mag_errors widths = data._filter_set.wavelength_bounds.widths[sort_idx] residual_plot.add_data(x=avg_wave_length, y=magnitudes, xerr=widths, yerr=mag_errors, residuals=residuals, label=data._name, color=data_color) residual_plot.add_model(avg_wave_length, expected_model_magnitudes, label='%s Model' % data._name, color=model_color) return residual_plot.finalize(xlabel="Wavelength\n(%s)"%data._filter_set.waveunits, ylabel='Magnitudes', xscale='linear', yscale='linear', invert_y=True)
def display_spectrum_model_counts(analysis, data=(), **kwargs): """ Display the fitted model count spectrum of one or more Spectrum plugins NOTE: all parameters passed as keyword arguments that are not in the list below, will be passed as keyword arguments to the plt.subplots() constructor. So for example, you can specify the size of the figure using figsize = (20,10) :param args: one or more instances of Spectrum plugin :param min_rate: (optional) rebin to keep this minimum rate in each channel (if possible). If one number is provided, the same minimum rate is used for each dataset, otherwise a list can be provided with the minimum rate for each dataset :param data_cmap: (str) (optional) the color map used to extract automatically the colors for the data :param model_cmap: (str) (optional) the color map used to extract automatically the colors for the models :param data_colors: (optional) a tuple or list with the color for each dataset :param model_colors: (optional) a tuple or list with the color for each folded model :param data_color: (optional) color for all datasets :param model_color: (optional) color for all folded models :param show_legend: (optional) if True (default), shows a legend :param step: (optional) if True (default), show the folded model as steps, if False, the folded model is plotted :param model_subplot: (optional) axe(s) to plot to for overplotting with linear interpolation between each bin :return: figure instance """ # If the user supplies a subset of the data, we will use that if not data: data_keys = analysis.data_list.keys() else: data_keys = data # Now we want to make sure that we only grab OGIP plugins new_data_keys = [] for key in data_keys: # Make sure it is a valid key if key in analysis.data_list.keys(): if isinstance(analysis.data_list[key], threeML.plugins.SpectrumLike.SpectrumLike): new_data_keys.append(key) else: custom_warnings.warn("Dataset %s is not of the SpectrumLike kind. Cannot be plotted by " "display_spectrum_model_counts" % key) if not new_data_keys: RuntimeError( 'There were no valid SpectrumLike data requested for plotting. Please use the detector names in the data list') data_keys = new_data_keys # default settings # Default is to show the model with steps step = True data_cmap = threeML_config['ogip']['data plot cmap'] # plt.cm.rainbow model_cmap = threeML_config['ogip']['model plot cmap'] # plt.cm.nipy_spectral_r # Legend is on by default show_legend = True show_residuals = True # Default colors data_colors = cmap_intervals(len(data_keys), data_cmap) model_colors = cmap_intervals(len(data_keys), model_cmap) # Now override defaults according to the optional keywords, if present if 'show_data' in kwargs: show_data = bool(kwargs.pop('show_data')) else: show_data = True if 'show_legend' in kwargs: show_legend = bool(kwargs.pop('show_legend')) if 'show_residuals' in kwargs: show_residuals= bool(kwargs.pop('show_residuals')) if 'step' in kwargs: step = bool(kwargs.pop('step')) if 'min_rate' in kwargs: min_rate = kwargs.pop('min_rate') # If min_rate is a floating point, use the same for all datasets, otherwise use the provided ones try: min_rate = float(min_rate) min_rates = [min_rate] * len(data_keys) except TypeError: min_rates = list(min_rate) assert len(min_rates) >= len( data_keys), "If you provide different minimum rates for each data set, you need" \ "to provide an iterable of the same length of the number of datasets" else: # This is the default (no rebinning) min_rates = [NO_REBIN] * len(data_keys) if 'data_cmap' in kwargs: data_cmap = plt.get_cmap(kwargs.pop('data_cmap')) data_colors = cmap_intervals(len(data_keys), data_cmap) if 'model_cmap' in kwargs: model_cmap = kwargs.pop('model_cmap') model_colors = cmap_intervals(len(data_keys), model_cmap) if 'data_colors' in kwargs: data_colors = kwargs.pop('data_colors') assert len(data_colors) >= len(data_keys), "You need to provide at least a number of data colors equal to the " \ "number of datasets" elif 'data_color' in kwargs: data_colors = [kwargs.pop('data_color')] * len(data_keys) if 'model_colors' in kwargs: model_colors = kwargs.pop('model_colors') assert len(model_colors) >= len( data_keys), "You need to provide at least a number of model colors equal to the " \ "number of datasets" ratio_residuals=False if 'ratio_residuals' in kwargs: ratio_residuals = bool(kwargs['ratio_residuals']) elif 'model_color' in kwargs: model_colors = [kwargs.pop('model_color')] * len(data_keys) if 'model_labels' in kwargs: model_labels = kwargs.pop('model_labels') assert len(model_labels) == len(data_keys), 'you must have the same number of model labels as data sets' else: model_labels = ['%s Model' % analysis.data_list[key]._name for key in data_keys] #fig, (ax, ax1) = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]}, **kwargs) residual_plot = ResidualPlot(show_residuals=show_residuals, **kwargs) if show_residuals: axes = [residual_plot.data_axis,residual_plot.residual_axis] else: axes = residual_plot.data_axis # go thru the detectors for key, data_color, model_color, min_rate, model_label in zip(data_keys, data_colors, model_colors, min_rates, model_labels): # NOTE: we use the original (unmasked) vectors because we need to rebin ourselves the data later on data = analysis.data_list[key] # type: threeML.plugins.SpectrumLike.SpectrumLike data.display_model(data_color=data_color, model_color=model_color, min_rate=min_rate, step=step, show_residuals=show_residuals, show_data=show_data, show_legend=show_legend, ratio_residuals=ratio_residuals, model_label=model_label, model_subplot=axes ) return residual_plot.figure
def plot_spectra(*analysis_results, **kwargs) -> plt.Figure: """ plotting routine for fitted point source spectra :param analysis_results: fitted JointLikelihood or BayesianAnalysis objects :param sources_to_use: (optional) list of PointSource string names to plot from the analysis :param energy_unit: (optional) astropy energy unit in string form (can also be frequency) :param flux_unit: (optional) astropy flux unit in string form :param confidence_level: (optional) confidence level to use (default: 0.68) :param ene_min: (optional) minimum energy to plot :param ene_max: (optional) maximum energy to plot :param num_ene: (optional) number of energies to plot :param use_components: (optional) True or False to plot the spectral components :param components_to_use: (optional) list of string names of the components to plot: including 'total' will also plot the total spectrum :param sum_sources: (optional) some all the MLE and Bayesian sources :param show_contours: (optional) True or False to plot the contour region :param plot_style_kwargs: (optional) dictionary of MPL plot styling for the best fit curve :param contour_style_kwargs: (optional) dictionary of MPL plot styling for the contour regions :param fit_cmap: MPL color map to iterate over for plotting multiple analyses :param contour_cmap: MPL color map to iterate over for plotting contours for multiple analyses :param subplot: subplot to use :param xscale: 'log' or 'linear' :param yscale: 'log' or 'linear' :param include_extended: True or False, also plot extended source spectra. :return: """ # allow matplotlib to plot quantities to the access quantity_support() _sub_menu = threeML_config.model_plot.point_source_plot _defaults = { "fit_cmap": _sub_menu.fit_cmap.value, "contour_cmap": _sub_menu.contour_cmap.value, "contour_colors": None, "fit_colors": None, "confidence_level": 0.68, "equal_tailed": True, "best_fit": "median", "energy_unit": _sub_menu.ene_unit, "flux_unit": _sub_menu.flux_unit, "ene_min": _sub_menu.emin, "ene_max": _sub_menu.emax, "num_ene": _sub_menu.num_ene, "use_components": False, "components_to_use": [], "sources_to_use": [], "sum_sources": False, "show_contours": True, "plot_style_kwargs": _sub_menu.plot_style, "contour_style_kwargs": _sub_menu.contour_style, "show_legend": _sub_menu.show_legend, "legend_kwargs": _sub_menu.legend_style, "subplot": None, "xscale": "log", "yscale": "log", "include_extended": False, } for key, value in kwargs.items(): if key in _defaults: _defaults[key] = value if isinstance(_defaults["ene_min"], u.Quantity): if not isinstance(_defaults["ene_max"], u.Quantity): log.error("both energy arguments must be Quantities") raise RuntimeError() if isinstance(_defaults["ene_max"], u.Quantity): if not isinstance(_defaults["ene_min"], u.Quantity): log.error("both energy arguments must be Quantities") raise RuntimeError() if isinstance(_defaults["ene_max"], u.Quantity): energy_range = np.linspace(_defaults["ene_min"], _defaults["ene_max"], _defaults["num_ene"]) # type: u.Quantity #_defaults["energy_unit"] = energy_range.unit if _defaults["xscale"] == "log": energy_range = (np.logspace( np.log10(energy_range.min().value), np.log10(energy_range.max().value), _defaults["num_ene"], ) * energy_range.unit) energy_range = energy_range.to(_defaults["energy_unit"], equivalencies=u.spectral()) else: energy_range = np.logspace( np.log10(_defaults["ene_min"]), np.log10(_defaults["ene_max"]), _defaults["num_ene"], ) * u.Unit(_defaults["energy_unit"]) # scale the units to the defaults _defaults["ene_min"] = _defaults["ene_min"] * \ u.Unit(_defaults["energy_unit"]) _defaults["ene_max"] = _defaults["ene_max"] * \ u.Unit(_defaults["energy_unit"]) ( mle_analyses, bayesian_analyses, num_sources_to_plot, duplicate_keys, ) = _setup_analysis_dictionaries( analysis_results, energy_range, _defaults["energy_unit"], _defaults["flux_unit"], _defaults["use_components"], _defaults["components_to_use"], _defaults["confidence_level"], _defaults["equal_tailed"], differential=True, sources_to_use=_defaults["sources_to_use"], include_extended=_defaults["include_extended"], ) # we are now ready to plot. # all calculations have been made. # if we are not going to sum sources if not _defaults["sum_sources"]: if _defaults["fit_colors"] is None: color_fit = cmap_intervals(num_sources_to_plot + 1, _defaults["fit_cmap"]) else: # duck typing if isinstance(_defaults["fit_colors"], (str, str)): color_fit = [_defaults["fit_colors"]] * num_sources_to_plot elif isinstance(_defaults["fit_colors"], list): assert len(_defaults["fit_colors"]) == num_sources_to_plot, ( "list of colors (%d) must be the same length as sources ot plot (%s)" % (len(_defaults["fit_colors"]), num_sources_to_plot)) color_fit = _defaults["fit_colors"] else: raise ValueError("Can not setup color, wrong type:", type(_defaults["fit_colors"])) if _defaults["contour_colors"] is None: color_contour = cmap_intervals(num_sources_to_plot + 1, _defaults["contour_cmap"]) else: # duck typing if isinstance(_defaults["contour_colors"], (str, str)): color_contour = [_defaults["contour_colors"] ] * num_sources_to_plot elif isinstance(_defaults["contour_colors"], list): assert len( _defaults["contour_colors"] ) == num_sources_to_plot, ( "list of colors (%d) must be the same length as sources ot plot (%s)" % (len(_defaults["contour_colors"]), num_sources_to_plot)) color_contour = _defaults["fit_colors"] else: raise ValueError( "Can not setup contour color, wrong type:", type(_defaults["contour_colors"]), ) color_itr = 0 # go thru the mle analysis and plot spectra plotter = SpectralContourPlot( num_sources_to_plot, xscale=_defaults["xscale"], yscale=_defaults["yscale"], show_legend=_defaults["show_legend"], plot_kwargs=_defaults["plot_style_kwargs"], contour_kwargs=_defaults["contour_style_kwargs"], legend_kwargs=_defaults["legend_kwargs"], emin=_defaults["ene_min"], emax=_defaults["ene_max"], subplot=_defaults["subplot"], ) for key in list(mle_analyses.keys()): # we won't assume to plot the total until the end plot_total = False if _defaults["use_components"]: # if this source has no components or none that we wish to plot # then we will plot the total spectrum after this if (not list(mle_analyses[key]["components"].keys())) or ( "total" in _defaults["components_to_use"]): plot_total = True for component in list(mle_analyses[key]["components"].keys()): positive_error = None negative_error = None # extract the information and plot it if _defaults["best_fit"] == "average": best_fit = mle_analyses[key]["components"][ component].average else: best_fit = mle_analyses[key]["components"][ component].median if _defaults["show_contours"]: positive_error = mle_analyses[key]["components"][ component].upper_error negative_error = mle_analyses[key]["components"][ component].lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 label = "%s: %s" % (key, component) # this is where we keep track of duplicates if key in duplicate_keys: label = "%s: MLE" % label if mle_analyses[key]["components"][ component].is_dimensionless: plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) color_itr += 1 else: plot_total = True if plot_total: # it ends up that we need to plot the total spectrum # which is just a repeat of the process if _defaults["best_fit"] == "average": best_fit = mle_analyses[key]["fitted point source"].average else: best_fit = mle_analyses[key]["fitted point source"].median if _defaults["show_contours"]: positive_error = mle_analyses[key][ "fitted point source"].upper_error negative_error = mle_analyses[key][ "fitted point source"].lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 else: positive_error = None negative_error = None label = "%s" % key if key in duplicate_keys: label = "%s: MLE" % label plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) color_itr += 1 # we will do the exact same thing for the bayesian analyses for key in list(bayesian_analyses.keys()): plot_total = False if _defaults["use_components"]: if (not list(bayesian_analyses[key]["components"].keys())) or ( "total" in _defaults["components_to_use"]): plot_total = True for component in list( bayesian_analyses[key]["components"].keys()): positive_error = None negative_error = None if _defaults["best_fit"] == "average": best_fit = bayesian_analyses[key]["components"][ component].average else: best_fit = bayesian_analyses[key]["components"][ component].median if _defaults["show_contours"]: positive_error = bayesian_analyses[key]["components"][ component].upper_error negative_error = bayesian_analyses[key]["components"][ component].lower_error label = "%s: %s" % (key, component) if key in duplicate_keys: label = "%s: Bayesian" % label if bayesian_analyses[key]["components"][ component].is_dimensionless: plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) color_itr += 1 else: plot_total = True if plot_total: if _defaults["best_fit"] == "average": best_fit = bayesian_analyses[key][ "fitted point source"].average else: best_fit = bayesian_analyses[key][ "fitted point source"].median positive_error = None negative_error = None if _defaults["show_contours"]: positive_error = bayesian_analyses[key][ "fitted point source"].upper_error negative_error = bayesian_analyses[key][ "fitted point source"].lower_error label = "%s" % key if key in duplicate_keys: label = "%s: Bayesian" % label plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label=label, ) color_itr += 1 else: # now we sum sources instead # we keep MLE and Bayes apart because it makes no # sense to sum them together ( total_analysis_mle, component_sum_dict_mle, num_sources_to_plot, ) = _collect_sums_into_dictionaries(mle_analyses, _defaults["use_components"], _defaults["components_to_use"]) ( total_analysis_bayes, component_sum_dict_bayes, num_sources_to_plot_bayes, ) = _collect_sums_into_dictionaries( bayesian_analyses, _defaults["use_components"], _defaults["components_to_use"], ) num_sources_to_plot += num_sources_to_plot_bayes plotter = SpectralContourPlot( num_sources_to_plot, xscale=_defaults["xscale"], yscale=_defaults["yscale"], show_legend=_defaults["show_legend"], plot_kwargs=_defaults["plot_style_kwargs"], contour_kwargs=_defaults["contour_style_kwargs"], legend_kwargs=_defaults["legend_kwargs"], emin=_defaults["ene_min"], emax=_defaults["ene_max"], subplot=_defaults["subplot"], ) color_fit = cmap_intervals(num_sources_to_plot, _defaults["fit_cmap"]) color_contour = cmap_intervals(num_sources_to_plot, _defaults["contour_cmap"]) color_itr = 0 if _defaults["use_components"] and list(component_sum_dict_mle.keys()): # we have components to plot for component, values in component_sum_dict_mle.items(): summed_analysis = sum(values) if _defaults["best_fit"] == "average": best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults["show_contours"]: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 if np.any([ c.is_dimensionless for c in component_sum_dict_mle[component] ]): plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: MLE" % component, ) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: MLE" % component, ) color_itr += 1 if total_analysis_mle: # we will sum and plot the total # analysis summed_analysis = sum(total_analysis_mle) if _defaults["best_fit"] == "average": best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults["show_contours"]: positive_error = best_fit + summed_analysis.upper_error negative_error = best_fit - summed_analysis.lower_error neg_mask = negative_error <= 0 # replace with small number negative_error[neg_mask] = min(best_fit) * 0.9 plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="total: MLE", ) color_itr += 1 if _defaults["use_components"] and list( component_sum_dict_bayes.keys()): # we have components to plot for component, values in component_sum_dict_bayes.items(): summed_analysis = sum(values) if _defaults["best_fit"] == "average": best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults["show_contours"]: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error if np.any([ c.is_dimensionless for c in component_sum_dict_bayes[component] ]): plotter.add_dimensionless_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: Bayesian" % component, ) else: plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="%s: Bayesian" % component, ) color_itr += 1 if total_analysis_bayes: # we will sum and plot the total # analysis summed_analysis = sum(total_analysis_bayes) if _defaults["best_fit"] == "average": best_fit = summed_analysis.average else: best_fit = summed_analysis.median positive_error = None negative_error = None if _defaults["show_contours"]: positive_error = summed_analysis.upper_error negative_error = summed_analysis.lower_error plotter.add_model( energy_range=energy_range, best_fit=best_fit, color=color_fit[color_itr], upper_error=positive_error, lower_error=negative_error, contour_color=color_contour[color_itr], label="total: Bayesian", ) color_itr += 1 return plotter.finalize(_defaults)