def generate_correlation_scatter_plots(data_sets,
                                       abscissa_label,
                                       assume_scaled_solar,
                                       compare_against_reference_labels,
                                       output_figure_stem,
                                       run_title,
                                       abundances_over_h=True):
    """
    Create a set of plots of a number of Cannon runs.

    :param data_sets:
        A list of runs of the Cannon which are to be plotted. This should be a list of dictionaries. Each dictionary
        should have the entries:

        cannon_output: The filename of the JSON output file from the Cannon, without the ".summary.json.gz" suffix.
        title: Legend caption to use for each Cannon run.
        filters: A string containing semicolon-separated set of constraints on stars we are to include.
        colour: The colour to plot the dataset in.
        line_type: The Pyxplot line type to use for this Cannon run.
        point_type: The Pyxplot point type to use for this Cannon run.


    :param abscissa_label:
        The name of the label we are to plot on the horizontal axis. This should be 'SNR/A', 'SNR/pixel', 'ebv'. See
        <lib/abscissa_information.py>, where these are defined.

    :param compare_against_reference_labels:
        If true, we measure the difference between each label value estimated by the Cannon and the target value taken
        from that star's metadata in the original spectrum library. I.e. the value used to synthesise the spectrum.

        If false, we measure the deviation from the value for each label estimated at the highest abscissa value -- e.g.
        highest SNR.

    :param output_figure_stem:
        Directory to save plots in

    :param run_title:
        A suffix to put at the end of the label in the top-left corner of each plot

    :param abundances_over_h:
        Boolean flag to select whether we plot abundances over H or Fe.

    :return:
        None
    """

    # Metadata about all the labels which we can plot the Cannon's precision in estimating
    label_metadata = LabelInformation().label_metadata

    # Metadata data about all of the horizontal axes that we can plot precision against
    abscissa_info = AbscissaInformation().abscissa_labels[abscissa_label]

    # Look up a list of all the (unique) labels the Cannon tried to fit in all the data sets we're plotting
    unique_json_files = set([item['cannon_output'] for item in data_sets])
    labels_in_each_data_set = [
        json.loads(gzip.open(json_file + ".summary.json.gz",
                             "rt").read())['labels']
        for json_file in unique_json_files
    ]
    unique_labels = sorted(
        set([
            label for label_list in labels_in_each_data_set
            for label in label_list
        ]))

    # Filter out any labels where we don't have metadata about how to plot them
    label_names = [item for item in unique_labels if item in label_metadata]

    # LaTeX strings to use to label each stellar label on graph axes
    labels_info = [label_metadata[ln] for ln in label_names]

    # Create directory to store output files in
    os.system("mkdir -p {}".format(output_figure_stem))

    data_set_titles = []
    output_figure_stem = os_path.abspath(output_figure_stem) + "/"
    data_set_counter = -1
    plot_cross_correlations = [{} for j in data_sets]

    # If requested, plot all abundances (apart from Fe) over Fe
    if not abundances_over_h:
        for j, label_name in enumerate(label_names):
            test = re.match("\[(.*)/H\]", label_name)
            if test is not None:
                if test.group(1) != "Fe":
                    label_names[j] = "[{}/Fe]".format(test.group(1))

    # Loop over the various Cannon runs we have, e.g. LRS and HRS
    data_file_names = []
    for counter, data_set in enumerate(data_sets):

        cannon_output = json.loads(
            gzip.open(data_set['cannon_output'] + ".full.json.gz",
                      "rt").read())

        # If no label has been specified for this Cannon run, use the description field from the JSON output
        if data_set['title'] is None:
            data_set['title'] = re.sub("_", r"\_",
                                       cannon_output['description'])

        # Calculate the accuracy of the Cannon's abundance determinations
        accuracy_calculator = CannonAccuracyCalculator(
            cannon_json_output=cannon_output,
            label_names=label_names,
            compare_against_reference_labels=compare_against_reference_labels,
            assume_scaled_solar=assume_scaled_solar,
            abscissa_field=abscissa_info['field'])

        stars_which_meet_filter = accuracy_calculator.filter_test_stars(
            constraints=data_set['filters'].split(";"))

        accuracy_calculator.calculate_cannon_offsets(
            filter_on_indices=stars_which_meet_filter)

        # Add data set to plot
        legend_label = data_set[
            'title']  # Read the title which was supplied on the command line for this dataset
        if run_title:
            legend_label += " ({})".format(
                run_title
            )  # Possibly append a run title to the end, if supplied

        # add data set

        # Work out multiplication factor to convert SNR/pixel to SNR/A
        snr_converter = SNRConverter(
            raster=np.array(cannon_output['wavelength_raster']),
            snr_at_wavelength=snr_defined_at_wavelength)

        data_set_titles.append(legend_label)

        # Create a sorted list of all the abscissa values we've got
        abscissa_values = list(accuracy_calculator.label_offsets.keys())
        abscissa_values = sorted(set(abscissa_values))

        data_set_counter += 1

        # Construct a datafile listing all the offsets for each label, for each abscissa value
        # This full list of data points is used to make histograms
        for abscissa_index, abscissa_value in enumerate(abscissa_values):
            displayed_abscissa_value = abscissa_value
            if abscissa_label == "SNR/A":
                displayed_abscissa_value = snr_converter.per_pixel(
                    abscissa_value).per_a()

            y = []
            for i, (label_name,
                    label_info) in enumerate(zip(label_names, labels_info)):
                # List of offsets
                diffs = accuracy_calculator.label_offsets[abscissa_value][
                    label_name]
                y.append(diffs)

            # Filename for data file containing all offsets
            data_file = "{}/data_offsets_all_{:d}_{:06.1f}.dat".format(
                output_figure_stem, data_set_counter, displayed_abscissa_value)

            # Output data file of label mismatches at this abscissa value
            np.savetxt(fname=data_file,
                       X=np.transpose(y),
                       header="""
# Each row represents a star
# {column_headings}

""".format(column_headings="     ".join(
                           ["offset_{}".format(x) for x in label_names])))

            # Output scatter plots of label cross-correlations at this abscissa value
            plot_cross_correlations[data_set_counter][
                displayed_abscissa_value] = (data_file, snr_converter)
            data_file_names.append(data_file)

        del cannon_output

    # Now plot the data

    # Create pyxplot script to produce this plot
    plotter = PyxplotDriver()

    # Create a new pyxplot script for correlation plots
    item_width = 4  # centimetres
    for data_set_counter, data_set_items in enumerate(plot_cross_correlations):
        for abscissa_index, (displayed_abscissa_value, plot_item) in enumerate(
                sorted(data_set_items.items())):
            data_filename, snr_converter = plot_item

            if abscissa_label == "SNR/A":
                snr = snr_converter.per_a(displayed_abscissa_value)
                caption = "SNR/A {0:.1f}; SNR/pixel {1:.1f}". \
                    format(snr.per_a(), snr.per_pixel())
            elif abscissa_label == "SNR/pixel":
                snr = snr_converter.per_pixel(displayed_abscissa_value)
                caption = "SNR/A {0:.1f}; SNR/pixel {1:.1f}". \
                    format(snr.per_a(), snr.per_pixel())
            else:
                caption = "{0} {1}".format(abscissa_info["latex"],
                                           displayed_abscissa_value)

            ppl = """
set numerics errors quiet
clear
set width {width}
set size square
set multiplot
set nokey
set fontsize 1.6
                  """.format(width=item_width)

            for i in range(len(label_names) - 1):
                for j in range(i + 1, len(label_names)):
                    label_info = label_metadata[label_names[j]]
                    if i == 0:
                        ppl += "unset yformat\n"
                        ppl += "set ylabel \"$\Delta$ {}\"\n".format(
                            label_info["latex"])
                    else:
                        ppl += "set yformat '' ; set ylabel ''\n"
                    ppl += "set yrange [{}:{}]\n".format(
                        -label_info["offset_max"] * 1.2,
                        label_info["offset_max"] * 1.2)

                    label_info = label_metadata[label_names[i]]
                    if j == len(label_names) - 1:
                        ppl += "unset xformat\n"
                        ppl += "set xlabel \"$\Delta$ {}\"\n".format(
                            label_info["latex"])
                    else:
                        ppl += "set xformat '' ; set xlabel ''\n"

                    ppl += "set xrange [{}:{}]\n".format(
                        -label_info["offset_max"] * 1.2,
                        label_info["offset_max"] * 1.2)

                    ppl += "set origin {},{}\n".format(
                        i * item_width,
                        (len(label_names) - 1 - j) * item_width)

                    ppl += "plot  \"{}\" using {}:{} w dots ps 2\n".format(
                        data_filename, i + 1, j + 1)

            output_filename = "{}/correlation_{:d}_{:d}".format(
                output_figure_stem, abscissa_index, data_set_counter)

            plotter.make_plot(
                output_filename=output_filename,
                data_files=data_file_names,
                #                               caption=r"""
                # {data_set_title} \newline {caption}
                #                               """.format(data_set_title=data_set_titles[data_set_counter],
                #                                          caption=caption
                #                                          ).strip(),
                pyxplot_script=ppl)
Esempio n. 2
0
def generate_rms_precision_plots(data_sets, abscissa_label, assume_scaled_solar,
                                 compare_against_reference_labels, output_figure_stem, run_title,
                                 abundances_over_h=True):
    """
    Create a set of plots of a number of Cannon runs.

    :param data_sets:
        A list of runs of the Cannon which are to be plotted. This should be a list of dictionaries. Each dictionary
        should have the entries:

        cannon_output: The filename of the JSON output file from the Cannon, without the ".summary.json.gz" suffix.
        title: Legend caption to use for each Cannon run.
        filters: A string containing semicolon-separated set of constraints on stars we are to include.
        colour: The colour to plot the dataset in.
        line_type: The Pyxplot line type to use for this Cannon run.
        point_type: The Pyxplot point type to use for this Cannon run.


    :param abscissa_label:
        The name of the label we are to plot on the horizontal axis. This should be 'SNR/A', 'SNR/pixel', 'ebv'. See
        <lib/abscissa_information.py>, where these are defined.

    :param compare_against_reference_labels:
        If true, we measure the difference between each label value estimated by the Cannon and the target value taken
        from that star's metadata in the original spectrum library. I.e. the value used to synthesise the spectrum.

        If false, we measure the deviation from the value for each label estimated at the highest abscissa value -- e.g.
        highest SNR.

    :param output_figure_stem:
        Directory to save plots in

    :param run_title:
        A suffix to put at the end of the label in the top-left corner of each plot

    :param abundances_over_h:
        Boolean flag to select whether we plot abundances over H or Fe.

    :return:
        None
    """

    # Metadata about all the labels which we can plot the Cannon's precision in estimating
    label_metadata = LabelInformation().label_metadata

    # Metadata data about all of the horizontal axes that we can plot precision against
    abscissa_info = AbscissaInformation().abscissa_labels[abscissa_label]

    # Look up a list of all the (unique) labels the Cannon tried to fit in all the data sets we're plotting
    unique_json_files = set([item['cannon_output'] for item in data_sets])
    labels_in_each_data_set = [json.loads(gzip.open(json_file + ".summary.json.gz", "rt").read())['labels']
                               for json_file in unique_json_files]
    unique_labels = sorted(set([label for label_list in labels_in_each_data_set for label in label_list]))

    # Filter out any labels where we don't have metadata about how to plot them
    label_names = [item for item in unique_labels if item in label_metadata]

    # LaTeX strings to use to label each stellar label on graph axes
    labels_info = [label_metadata[ln] for ln in label_names]

    # Create directory to store output files in
    os.system("mkdir -p {}".format(output_figure_stem))

    data_set_titles = []
    output_figure_stem = os_path.abspath(output_figure_stem) + "/"
    data_set_counter = -1
    plot_precision = [[] for i in label_names]

    # If requested, plot all abundances (apart from Fe) over Fe
    if not abundances_over_h:
        for j, label_name in enumerate(label_names):
            test = re.match("\[(.*)/H\]", label_name)
            if test is not None:
                if test.group(1) != "Fe":
                    label_names[j] = "[{}/Fe]".format(test.group(1))

    common_x_limits = list(abscissa_info["axis_range"])

    # Loop over the various Cannon runs we have, e.g. LRS and HRS
    data_file_names = []
    for counter, data_set in enumerate(data_sets):

        try:
            cannon_output = json.loads(gzip.open(data_set['cannon_output'] + ".full.json.gz", "rt").read())
        except json.decoder.JSONDecodeError:
            print("Corrupt JSON file <{}>. Skipping.".format(data_set['cannon_output'] + ".full.json.gz"))
            continue

        # If no label has been specified for this Cannon run, use the description field from the JSON output
        if data_set['title'] is None:
            data_set['title'] = re.sub("_", r"\_", cannon_output['description'])

        # Calculate the accuracy of the Cannon's abundance determinations
        accuracy_calculator = CannonAccuracyCalculator(
            cannon_json_output=cannon_output,
            label_names=label_names,
            compare_against_reference_labels=compare_against_reference_labels,
            assume_scaled_solar=assume_scaled_solar,
            abscissa_field=abscissa_info['field']
        )

        stars_which_meet_filter = accuracy_calculator.filter_test_stars(constraints=data_set['filters'].split(";"))

        accuracy_calculator.calculate_cannon_offsets(filter_on_indices=stars_which_meet_filter)

        # Add data set to plot
        legend_label = data_set['title']  # Read the title which was supplied on the command line for this dataset
        if run_title:
            legend_label += " ({})".format(run_title)  # Possibly append a run title to the end, if supplied

        # add data set

        # Work out multiplication factor to convert SNR/pixel to SNR/A
        snr_converter = SNRConverter(raster=np.array(cannon_output['wavelength_raster']),
                                     snr_at_wavelength=snr_defined_at_wavelength)

        data_set_titles.append(legend_label)

        # Create a sorted list of all the abscissa values we've got
        abscissa_values = list(accuracy_calculator.label_offsets.keys())
        abscissa_values = sorted(set(abscissa_values))

        # If all abscissa values are off the range of the x axis, rescale axis
        if common_x_limits is not None:
            if abscissa_values[0] > common_x_limits[1]:
                common_x_limits[1] = abscissa_values[0]
                print("Rescaling x-axis to include {:.1f}".format(abscissa_values[0]))
            if abscissa_values[-1] < common_x_limits[0]:
                common_x_limits[0] = abscissa_values[-1]
                print("Rescaling x-axis to include {:.1f}".format(abscissa_values[-1]))

        data_set_counter += 1

        # Construct a data file listing the RMS and percentiles of the offset distribution for each label
        for i, (label_name, label_info) in enumerate(zip(label_names, labels_info)):
            y = []
            for abscissa_index, abscissa_value in enumerate(abscissa_values):
                displayed_abscissa_value = abscissa_value
                if abscissa_label == "SNR/A":
                    displayed_abscissa_value = snr_converter.per_pixel(abscissa_value).per_a()

                # List of offsets
                diffs = accuracy_calculator.label_offsets[abscissa_value][label_name]

                # Remove non-finite offsets
                diffs = np.asarray(diffs)
                diffs = diffs[np.logical_not(np.isnan(diffs))]

                # Create a blank row in output data table to receive this entry
                y.append([])

                # Compute root mean square offset
                y[-1].extend([displayed_abscissa_value])
                y[-1].extend([np.sqrt(np.mean(np.square(diffs)))])

            # Filename for data containing statistics on the RMS, and percentiles of the offset distributions
            file_name = "{}/data_offsets_rms_{:d}_{:d}.dat".format(output_figure_stem, i, data_set_counter)

            # Output table of statistical measures of label-mismatch-distribution as a function of abscissa
            # 1st column is RMS.
            np.savetxt(fname=file_name,
                       X=y,
                       header="""
# Abscissa_(probably_SNR)     RMS_offset

            """)

            plot_precision[i].append({
                "plot_item": "\"{}\" using 1:2".format(file_name),
                "title": legend_label,
                "style": "with lp pt {:d} col {} lt {:d}".format(int(data_set["point_type"]),
                                                                 data_set["colour"],
                                                                 int(data_set["line_type"])),
                "star_count": len(stars_which_meet_filter),
            })

            data_file_names.append(file_name)

        del cannon_output

    # Now plot the data

    # Create pyxplot script to produce this plot
    plotter_all = PyxplotDriver(multiplot_filename="{}/precision_all_multiplot".format(output_figure_stem),
                                multiplot_aspect=6. / 8)

    plotter = PyxplotDriver(multiplot_filename="{}/precision_multiplot".format(output_figure_stem),
                            multiplot_aspect=6. / 8)

    # Create a new pyxplot script for precision in all elements in one plot
    for j in range(len(plot_precision[0])):
        plotter_all.make_plot(output_filename="{}/precisionall_{:d}".format(output_figure_stem, j),
                              data_files=data_file_names,
                              caption=r"""
{title} ({star_count} stars)
                              """.format(
                                  title=plot_precision[2][j]["title"],
                                  star_count=plot_precision[2][j]["star_count"]
                              ).strip(),
                              pyxplot_script="""

set numerics errors quiet
set fontsize 1.3
set key top {keypos}
set keycols 2
set ylabel "RMS offset in abundance [dex]"
set xlabel "{x_label}"
set yrange [0:0.5]
{set_log}
{set_x_range}

plot {plot_items}
                              """.format(
                                  keypos="right" if abscissa_info["field"] == "SNR" else "left",
                                  x_label=abscissa_info["latex"],
                                  set_log=("set log x" if abscissa_info["log_axis"] else ""),
                                  set_x_range=("set xrange [{}:{}]".format(common_x_limits[0], common_x_limits[1])
                                               if common_x_limits is not None else ""),
                                  plot_items=", ".join(["""
{filename} title "{title}" w lp pt {point_type}
                                      """.format(
                                      filename=plot_precision[i][j]["plot_item"],
                                      title=label_info["latex"][:-13],  # Remove string "[dex]" from end of label
                                      point_type=16 + (i - 2)
                                  ).strip()
                                                        for i, (label_name, label_info) in
                                                        enumerate(zip(label_names, labels_info))
                                                        if label_name.startswith("[")
                                                        ] + ["""
{target} with lines col grey(0.75) notitle
                                      """.format(
                                      target=target_value
                                  ).strip()
                                                             for target_value in (0.1, 0.2)
                                                             ])
                              )
                              )

    # Create a new pyxplot script for precision plots
    for i, (label_name, label_info) in enumerate(zip(label_names, labels_info)):
        plotter.make_plot(output_filename="{}/precision_{:d}".format(output_figure_stem, i),
                          data_files=data_file_names,
                          caption=label_info["latex"],
                          pyxplot_script="""
                                  
{set_key}
set fontsize 1.3
set ylabel "RMS offset in {y_label}"
set xlabel "{x_label}"
set yrange [{y_min}:{y_max}]
{set_log}
{set_x_range}

plot {plot_items}

                                  """.format(
                              set_key=("set key top {}".format("right"
                                                               if abscissa_info["field"] == "SNR"
                                                               else "left")
                                       if (len(plot_precision[i]) > 1)
                                       else "set nokey"),
                              x_label=abscissa_info["latex"],
                              y_label=label_info["latex"],
                              y_min=0.5 * label_info["offset_min"],
                              y_max=0.5 * label_info["offset_max"],
                              set_log=("set log x" if abscissa_info["log_axis"] else ""),
                              set_x_range=("set xrange [{}:{}]".format(common_x_limits[0], common_x_limits[1])
                                           if common_x_limits is not None else ""),
                              plot_items=", ".join(["""
{plot_item} title "{title} ({star_count} stars)" {style}
                                      """.format(
                                  **item
                              ).strip()
                                                    for item in plot_precision[i]
                                                    ] + ["""
{target} with lines col grey(0.75) notitle
                                      """.format(
                                  target=target_value
                              ).strip()
                                                         for target_value in label_info["targets"]
                                                         ])

                          )
                          )
Esempio n. 3
0
def generate_box_and_whisker_plots(data_sets,
                                   abscissa_label,
                                   assume_scaled_solar,
                                   compare_against_reference_labels,
                                   output_figure_stem,
                                   run_title,
                                   abundances_over_h=True):
    """
    Create a set of plots of a number of Cannon runs.

    :param data_sets:
        A list of runs of the Cannon which are to be plotted. This should be a list of dictionaries. Each dictionary
        should have the entries:

        cannon_output: The filename of the JSON output file from the Cannon, without the ".summary.json.gz" suffix.
        title: Legend caption to use for each Cannon run.
        filters: A string containing semicolon-separated set of constraints on stars we are to include.
        colour: The colour to plot the dataset in.
        line_type: The Pyxplot line type to use for this Cannon run.
        point_type: The Pyxplot point type to use for this Cannon run.


    :param abscissa_label:
        The name of the label we are to plot on the horizontal axis. This should be 'SNR/A', 'SNR/pixel', 'ebv'. See
        <lib/abscissa_information.py>, where these are defined.

    :param compare_against_reference_labels:
        If true, we measure the difference between each label value estimated by the Cannon and the target value taken
        from that star's metadata in the original spectrum library. I.e. the value used to synthesise the spectrum.

        If false, we measure the deviation from the value for each label estimated at the highest abscissa value -- e.g.
        highest SNR.

    :param output_figure_stem:
        Directory to save plots in

    :param run_title:
        A suffix to put at the end of the label in the top-left corner of each plot

    :param abundances_over_h:
        Boolean flag to select whether we plot abundances over H or Fe.

    :return:
        None
    """

    # Metadata about all the labels which we can plot the Cannon's precision in estimating
    label_metadata = LabelInformation().label_metadata

    # Metadata data about all of the horizontal axes that we can plot precision against
    abscissa_info = AbscissaInformation().abscissa_labels[abscissa_label]

    # Look up a list of all the (unique) labels the Cannon tried to fit in all the data sets we're plotting
    unique_json_files = set([item['cannon_output'] for item in data_sets])
    labels_in_each_data_set = [
        json.loads(gzip.open(json_file + ".summary.json.gz",
                             "rt").read())['labels']
        for json_file in unique_json_files
    ]
    unique_labels = sorted(
        set([
            label for label_list in labels_in_each_data_set
            for label in label_list
        ]))

    # Filter out any labels where we don't have metadata about how to plot them
    label_names = [item for item in unique_labels if item in label_metadata]

    # LaTeX strings to use to label each stellar label on graph axes
    labels_info = [label_metadata[ln] for ln in label_names]

    # Create directory to store output files in
    os.system("mkdir -p {}".format(output_figure_stem))

    data_set_titles = []
    output_figure_stem = os_path.abspath(output_figure_stem) + "/"
    data_set_counter = -1
    plot_box_whiskers = [[[] for j in data_sets] for i in label_names]

    # If requested, plot all abundances (apart from Fe) over Fe
    if not abundances_over_h:
        for j, label_name in enumerate(label_names):
            test = re.match("\[(.*)/H\]", label_name)
            if test is not None:
                if test.group(1) != "Fe":
                    label_names[j] = "[{}/Fe]".format(test.group(1))

    common_x_limits = list(abscissa_info["axis_range"])

    # Loop over the various Cannon runs we have, e.g. LRS and HRS
    data_file_names = []
    for counter, data_set in enumerate(data_sets):

        cannon_output = json.loads(
            gzip.open(data_set['cannon_output'] + ".full.json.gz",
                      "rt").read())

        # If no label has been specified for this Cannon run, use the description field from the JSON output
        if data_set['title'] is None:
            data_set['title'] = re.sub("_", r"\_",
                                       cannon_output['description'])

        # Calculate the accuracy of the Cannon's abundance determinations
        accuracy_calculator = CannonAccuracyCalculator(
            cannon_json_output=cannon_output,
            label_names=label_names,
            compare_against_reference_labels=compare_against_reference_labels,
            assume_scaled_solar=assume_scaled_solar,
            abscissa_field=abscissa_info['field'])

        stars_which_meet_filter = accuracy_calculator.filter_test_stars(
            constraints=data_set['filters'].split(";"))

        accuracy_calculator.calculate_cannon_offsets(
            filter_on_indices=stars_which_meet_filter)

        # Add data set to plot
        legend_label = data_set[
            'title']  # Read the title which was supplied on the command line for this dataset
        if run_title:
            legend_label += " ({})".format(
                run_title
            )  # Possibly append a run title to the end, if supplied

        # add data set

        # Work out multiplication factor to convert SNR/pixel to SNR/A
        snr_converter = SNRConverter(
            raster=np.array(cannon_output['wavelength_raster']),
            snr_at_wavelength=snr_defined_at_wavelength)

        data_set_titles.append(legend_label)

        # Create a sorted list of all the abscissa values we've got
        abscissa_values = list(accuracy_calculator.label_offsets.keys())
        abscissa_values = sorted(set(abscissa_values))

        # If all abscissa values are off the range of the x axis, rescale axis
        if common_x_limits is not None:
            if abscissa_values[0] > common_x_limits[1]:
                common_x_limits[1] = abscissa_values[0]
                print("Rescaling x-axis to include {:.1f}".format(
                    abscissa_values[0]))
            if abscissa_values[-1] < common_x_limits[0]:
                common_x_limits[0] = abscissa_values[-1]
                print("Rescaling x-axis to include {:.1f}".format(
                    abscissa_values[-1]))

        data_set_counter += 1

        # Construct a data file listing the RMS and percentiles of the offset distribution for each label
        for i, (label_name,
                label_info) in enumerate(zip(label_names, labels_info)):
            y = []
            for abscissa_index, abscissa_value in enumerate(abscissa_values):
                displayed_abscissa_value = abscissa_value
                if abscissa_label == "SNR/A":
                    displayed_abscissa_value = snr_converter.per_pixel(
                        abscissa_value).per_a()

                # List of offsets
                diffs = accuracy_calculator.label_offsets[abscissa_value][
                    label_name]

                # Remove non-finite offsets
                diffs = np.asarray(diffs)
                diffs = diffs[np.logical_not(np.isnan(diffs))]

                # Sort list
                diffs.sort()

                def percentile(fraction):
                    return diffs[int(fraction / 100. * len(diffs))]

                y.append([])
                y[-1].extend([displayed_abscissa_value])
                y[-1].extend([
                    percentile(5),
                    percentile(25),
                    percentile(50),
                    percentile(75),
                    percentile(95)
                ])

            # Filename for data containing statistics on percentiles of the offset distributions
            file_name = "{}data_offsets_dist_{:d}_{:d}.dat".format(
                output_figure_stem, i, data_set_counter)

            # Output table of statistical measures of label-mismatch-distribution as a function of abscissa
            # Subsequent columns are various percentiles (see above)
            np.savetxt(fname=file_name,
                       X=y,
                       header="""
# Abscissa_(probably_SNR)     5th_percentile     25th_percentile    Median    75th_percentile     95th_percentile

""")

            plot_box_whiskers[i][data_set_counter] = [
                "\"{}\" using 1:4:2:6 with yerrorrange col black".format(
                    file_name)
            ]

            # Filename for data used to make box-and-whisker diagrams, with boxes explicitly defined
            file_name = "{}/data_whiskers_{:d}_{:d}.dat".format(
                output_figure_stem, i, data_set_counter)

            with open(file_name, "w") as f:
                f.write("""
# Each block within this file represents a rectangular region to shade on a box-and-whisker plot
# x y

""")
                for j, datum in enumerate(y):
                    if abscissa_info["log_axis"]:
                        x_min = datum[0] / abscissa_info["box_whisker_width"]
                        x_max = datum[0] * abscissa_info["box_whisker_width"]
                    else:
                        x_min = datum[0] - abscissa_info["box_whisker_width"]
                        x_max = datum[0] + abscissa_info["box_whisker_width"]
                    f.write("{} {}\n".format(x_min, datum[2]))
                    f.write("{} {}\n".format(x_min, datum[4]))
                    f.write("{} {}\n".format(x_max, datum[4]))
                    f.write("{} {}\n\n\n".format(x_max, datum[2]))

                    plot_box_whiskers[i][data_set_counter].insert(
                        0, """
\"{0}\" using 1:2 with filledregion fc red col black lw 0.5 index {1}
""".format(file_name, j).strip())

            data_file_names.append(file_name)

        del cannon_output

    # Now plot the data

    # Create pyxplot script to produce this plot
    plotter = PyxplotDriver(
        multiplot_filename="{}/whiskers_multiplot".format(output_figure_stem),
        multiplot_aspect=6. / 8)

    # Create a new pyxplot script for precision plots
    for i, (label_name, label_info) in enumerate(zip(label_names,
                                                     labels_info)):

        # Create a new pyxplot script for box and whisker plots
        for data_set_counter, plot_items in enumerate(plot_box_whiskers[i]):
            plotter.make_plot(
                output_filename="{}/whiskers_{:d}_{:d}".format(
                    output_figure_stem, i, data_set_counter),
                data_files=data_file_names,
                caption="""
{label_name}; {data_set_title}
                              """.format(
                    label_name=label_info["latex"],
                    data_set_title=data_set_titles[data_set_counter]).strip(),
                pyxplot_script="""
set numerics errors quiet
set nokey
set fontsize 1.3
set ylabel "$\Delta$ {label_name}"
set xlabel "{x_label}"
set yrange [{y_min}:{y_max}]
{set_log}
{set_x_range}

plot {plot_items}
      
                              """.format(
                    label_name=label_info["latex"],
                    x_label=abscissa_info["latex"],
                    y_min=-1.2 * label_info["offset_max"],
                    y_max=1.2 * label_info["offset_max"],
                    set_log=("set log x" if abscissa_info["log_axis"] else ""),
                    set_x_range=("set xrange [{}:{}]".format(
                        common_x_limits[0], common_x_limits[1])
                                 if common_x_limits is not None else ""),
                    plot_items=",".join(plot_items)))