mirror of https://github.com/Qiskit/qiskit.git
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:
parent
90eec42675
commit
c701b1020d
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue