plot_histogram now a valid histogram, plot_distribution for non-discrete data (#8762)

* updates and black

* fix for single-bar value and label

* add plot_distribution

* fix for checking for distribution classes since they dict based

* change y label to quasi-probability

* Add release note

* Update releasenotes/notes/plot-hist-797bfaeea2156c53.yaml

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

* Update releasenotes/notes/plot-hist-797bfaeea2156c53.yaml

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

* Allow all but negative distributions to plot_histogram

* just allow all

* Add PendingDeprecationWarning on plot_histogram() with a distribution input

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Paul Nation 2022-10-01 00:40:17 -04:00 committed by GitHub
parent 90eec42675
commit c701b1020d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 212 additions and 14 deletions

View File

@ -102,6 +102,16 @@ Here is an example of using :func:`plot_histogram` to visualize measurement outc
The data can be a dictionary with bit string as key and counts as value, or more commonly a
:class:`~qiskit.result.Counts` object obtained from :meth:`~qiskit.result.Result.get_counts`.
Distribution Visualizations
===========================
This section contains functions that visualize sampled distributions.
.. autosummary::
:toctree: ../stubs/
plot_distribution
State Visualizations
====================
@ -243,7 +253,7 @@ import warnings
from .array import array_to_latex
from .circuit import circuit_drawer
from .counts_visualization import plot_histogram
from .counts_visualization import plot_histogram, plot_distribution
from .state_visualization import (
plot_state_hinton,
plot_bloch_vector,

View File

@ -16,9 +16,12 @@ Visualization functions for measurement counts.
from collections import OrderedDict
import functools
import warnings
import numpy as np
from qiskit.utils import optionals as _optionals
from qiskit.result import QuasiDistribution, ProbDistribution
from .exceptions import VisualizationError
from .utils import matplotlib_close_if_inline
@ -43,7 +46,6 @@ VALID_SORTS = ["asc", "desc", "hamming", "value", "value_desc"]
DIST_MEAS = {"hamming": hamming_distance}
@_optionals.HAS_MATPLOTLIB.require_in_call
def plot_histogram(
data,
figsize=(7, 5),
@ -57,7 +59,7 @@ def plot_histogram(
ax=None,
filename=None,
):
"""Plot a histogram of data.
"""Plot a histogram of input counts data.
Args:
data (list or dict): This is either a list of dictionaries or a single
@ -71,13 +73,13 @@ def plot_histogram(
the x-axis sort.
sort (string): Could be `'asc'`, `'desc'`, `'hamming'`, `'value'`, or
`'value_desc'`. If set to `'value'` or `'value_desc'` the x axis
will be sorted by the maximum probability for each bitstring.
will be sorted by the number of counts for each bitstring.
Defaults to `'asc'`.
target_string (str): Target string if 'sort' is a distance measure.
legend(list): A list of strings to use for labels of the data.
The number of entries must match the length of data (if data is a
list or 1 if it's a dict)
bar_labels (bool): Label each bar in histogram with probability value.
bar_labels (bool): Label each bar in histogram with counts value.
title (str): A string to use for the plot title
ax (matplotlib.axes.Axes): An optional Axes object to be used for
the visualization output. If none is specified a new matplotlib
@ -94,6 +96,7 @@ def plot_histogram(
MissingOptionalLibraryError: Matplotlib not available.
VisualizationError: When legend is provided and the length doesn't
match the input data.
VisualizationError: Input must be Counts or a dict
Examples:
.. jupyter-execute::
@ -117,7 +120,7 @@ def plot_histogram(
counts = {'001': 596, '011': 211, '010': 50, '000': 117, '101': 33, '111': 8,
'100': 6, '110': 3}
# Sort by the probability in descending order
# Sort by the counts in descending order
hist1 = plot_histogram(counts, sort='value_desc')
# Sort by the hamming distance (the number of bit flips to change from
@ -127,6 +130,152 @@ def plot_histogram(
display(hist1, hist2)
"""
if not isinstance(data, list):
data = [data]
kind = "counts"
for dat in data:
if isinstance(dat, (QuasiDistribution, ProbDistribution)) or isinstance(
next(iter(dat.values())), float
):
warnings.warn(
"Using plot histogram with QuasiDistribution, ProbDistribution, or a "
"distribution dictionary will be deprecated in 0.23.0 and subsequently "
"removed in a future release. You should use plot_distribution() instead.",
PendingDeprecationWarning,
stacklevel=2,
)
kind = "distribution"
return _plotting_core(
data,
figsize,
color,
number_to_keep,
sort,
target_string,
legend,
bar_labels,
title,
ax,
filename,
kind=kind,
)
def plot_distribution(
data,
figsize=(7, 5),
color=None,
number_to_keep=None,
sort="asc",
target_string=None,
legend=None,
bar_labels=True,
title=None,
ax=None,
filename=None,
):
"""Plot a distribution from input sampled data.
Args:
data (list or dict): This is either a list of dictionaries or a single
dict containing the values to represent (ex {'001': 130})
figsize (tuple): Figure size in inches.
color (list or str): String or list of strings for distribution bar colors.
number_to_keep (int): The number of terms to plot per dataset. The rest is made into a
single bar called 'rest'. If multiple datasets are given, the ``number_to_keep``
applies to each dataset individually, which may result in more bars than
``number_to_keep + 1``. The ``number_to_keep`` applies to the total values, rather than
the x-axis sort.
sort (string): Could be `'asc'`, `'desc'`, `'hamming'`, `'value'`, or
`'value_desc'`. If set to `'value'` or `'value_desc'` the x axis
will be sorted by the maximum probability for each bitstring.
Defaults to `'asc'`.
target_string (str): Target string if 'sort' is a distance measure.
legend(list): A list of strings to use for labels of the data.
The number of entries must match the length of data (if data is a
list or 1 if it's a dict)
bar_labels (bool): Label each bar in histogram with probability value.
title (str): A string to use for the plot title
ax (matplotlib.axes.Axes): An optional Axes object to be used for
the visualization output. If none is specified a new matplotlib
Figure will be created and used. Additionally, if specified there
will be no returned Figure since it is redundant.
filename (str): file path to save image to.
Returns:
matplotlib.Figure:
A figure for the rendered distribution, if the ``ax``
kwarg is not set.
Raises:
MissingOptionalLibraryError: Matplotlib not available.
VisualizationError: When legend is provided and the length doesn't
match the input data.
Examples:
.. jupyter-execute::
# Plot two counts in the same figure with legends and colors specified.
from qiskit.visualization import plot_distribution
counts1 = {'00': 525, '11': 499}
counts2 = {'00': 511, '11': 514}
legend = ['First execution', 'Second execution']
plot_distribution([counts1, counts2], legend=legend, color=['crimson','midnightblue'],
title="New Distribution")
.. jupyter-execute::
# You can sort the bitstrings using different methods.
counts = {'001': 596, '011': 211, '010': 50, '000': 117, '101': 33, '111': 8,
'100': 6, '110': 3}
# Sort by the counts in descending order
dist1 = plot_distribution(counts, sort='value_desc')
# Sort by the hamming distance (the number of bit flips to change from
# one bitstring to the other) from a target string.
dist2 = plot_distribution(counts, sort='hamming', target_string='001')
display(dist1, dist2)
"""
return _plotting_core(
data,
figsize,
color,
number_to_keep,
sort,
target_string,
legend,
bar_labels,
title,
ax,
filename,
kind="distribution",
)
@_optionals.HAS_MATPLOTLIB.require_in_call
def _plotting_core(
data,
figsize=(7, 5),
color=None,
number_to_keep=None,
sort="asc",
target_string=None,
legend=None,
bar_labels=True,
title=None,
ax=None,
filename=None,
kind="counts",
):
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
@ -184,14 +333,14 @@ def plot_histogram(
length = len(data)
width = 1 / (len(data) + 1) # the width of the bars
labels_dict, all_pvalues, all_inds = _plot_histogram_data(data, labels, number_to_keep)
labels_dict, all_pvalues, all_inds = _plot_data(data, labels, number_to_keep, kind=kind)
rects = []
for item, _ in enumerate(data):
for idx, val in enumerate(all_pvalues[item]):
label = None
if not idx and legend:
label = legend[item]
if val >= 0:
if val > 0:
rects.append(
ax.bar(
idx + item * width,
@ -210,11 +359,13 @@ def plot_histogram(
for rect in rects:
for rec in rect:
height = rec.get_height()
if kind == "distribution":
height = round(height, 3)
if height >= 1e-3:
ax.text(
rec.get_x() + rec.get_width() / 2.0,
1.05 * height,
"%.3f" % float(height),
str(height),
ha="center",
va="bottom",
zorder=3,
@ -230,9 +381,15 @@ def plot_histogram(
)
# add some text for labels, title, and axes ticks
ax.set_ylabel("Probabilities", fontsize=14)
if kind == "counts":
ax.set_ylabel("Count", fontsize=14)
else:
ax.set_ylabel("Quasi-probability", fontsize=14)
all_vals = np.concatenate(all_pvalues).ravel()
ax.set_ylim([0.0, min([1.2, max(1.2 * val for val in all_vals)])])
min_ylim = 0.0
if kind == "distribution":
min_ylim = min(0.0, min(1.1 * val for val in all_vals))
ax.set_ylim([min_ylim, min([1.1 * sum(all_vals), max(1.1 * val for val in all_vals)])])
if "desc" in sort:
ax.invert_xaxis()
@ -280,7 +437,7 @@ def _unify_labels(data):
return out
def _plot_histogram_data(data, labels, number_to_keep):
def _plot_data(data, labels, number_to_keep, kind="counts"):
"""Generate the data needed for plotting counts.
Parameters:
@ -289,6 +446,7 @@ def _plot_histogram_data(data, labels, number_to_keep):
labels (list): The list of bitstring labels for the plot.
number_to_keep (int): The number of terms to plot and rest
is made into a single bar called 'rest'.
kind (str): One of 'counts' or 'distribution`
Returns:
tuple: tuple containing:
@ -316,8 +474,11 @@ def _plot_histogram_data(data, labels, number_to_keep):
else:
labels_dict[key] = 1
values.append(execution[key])
values = np.array(values, dtype=float)
pvalues = values / sum(values)
if kind == "counts":
pvalues = np.array(values, dtype=int)
else:
pvalues = np.array(values, dtype=float)
pvalues /= np.sum(pvalues)
all_pvalues.append(pvalues)
numelem = len(values)
ind = np.arange(numelem) # the x locations for the groups

View File

@ -0,0 +1,27 @@
---
features:
- |
Added a new function :func:`~.plot_distribution` for plotting distributions over quasi-probabilities.
This is suitable for ``Counts``, ``QuasiDistribution`` and ``ProbDistribution``.
Raw `dict` can be passed as well.
upgrade:
- |
The :func:`~.plot_histogram` function has been modified to return an actual
histogram of discrete binned values. The previous behavior for the function
was despite the name to actually generate a visualization of the distribution
of the input. Due to this disparity between the name of the function and the behavior
the function behavior was changed so it's actually generating a proper histogram
of discrete data now. If you wish to preserve the previous behavior of plotting a
probability distribution of the counts data you can leverage the :func:`~.plot_distribution` to generate an
equivalent graph. For example, the previous behavior of
``plot_histogram({'00': 512, '11': 500})`` can be re-created with:
.. jupyter-execute::
from qiskit.visualization import plot_distribution
import matplotlib.pyplot as plt
ax = plt.subplot()
plot_distribution({'00': 512, '11': 500}, ax=ax)
ax.set_ylabel('Probabilities')
ax.get_figure()