Source code for pygmt.src.histogram

"""
Histogram - Calculate and plot histograms.
"""

from collections.abc import Sequence
from typing import Literal

from pygmt._typing import PathLike, TableLike
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.exceptions import GMTParameterError
from pygmt.helpers import (
    build_arg_list,
    deprecate_parameter,
    fmt_docstring,
    kwargs_to_strings,
    use_alias,
)
from pygmt.params import Axis, Frame

__doctest_skip__ = ["histogram"]


@fmt_docstring
# TODO(PyGMT>=0.22.0): Remove the deprecated "extreme" parameter.
@deprecate_parameter("extreme", "out_range", "0.20.0", remove_version="0.22.0")
@use_alias(
    D="annotate",
    N="distribution",
    T="series",
    Z="histtype",
    b="binary",
    d="nodata",
    e="find",
    h="header",
    l="label",
    w="wrap",
)
@kwargs_to_strings(T="sequence")
def histogram(
    self,
    data: PathLike | TableLike,
    bar_width: float | str | None = None,
    bar_offset: float | str | None = None,
    cmap: str | bool = False,
    pen: str | None = None,
    fill: str | None = None,
    horizontal: bool = False,
    center: bool = False,
    out_range: Literal["first", "last", "both"] | None = None,
    stairs: bool = False,
    cumulative: bool | Literal["reverse"] = False,
    projection: str | None = None,
    region: Sequence[float | str] | str | None = None,
    frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False,
    verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
    | bool = False,
    panel: int | Sequence[int] | bool = False,
    incols: int | str | Sequence[int | str] | None = None,
    perspective: float | Sequence[float] | str | bool = False,
    transparency: float | None = None,
    **kwargs,
):
    r"""
    Calculate and plot histograms.

    Full GMT docs at :gmt-docs:`histogram.html`.

    $aliases
       - A = horizontal
       - B = frame
       - C = cmap
       - E = bar_width, **+o**: bar_offset
       - G = fill
       - J = projection
       - L = out_range
       - Q = cumulative
       - R = region
       - S = stairs
       - V = verbose
       - W = pen
       - c = panel
       - i = incols
       - p = perspective
       - t = transparency

    Parameters
    ----------
    data
        Pass in either a file name to an ASCII data table, a Python list, a 2-D
        $table_classes.
    $cmap
    pen
        Draw bar outline (or stair-case curve) using the specified pen thickness
        [Default is no outline].
    fill
         Set color or pattern for filling bars [Default is no fill].
    annotate : bool or str
        [**+b**][**+f**\ *font*][**+o**\ *off*][**+r**].
        Annotate each bar with the count it represents. Append any of the
        following modifiers: Use **+b** to place the labels beneath the bars
        instead of above; use **+f** to change to another font than the default
        annotation font; use **+o** to change the offset between bar and
        label [Default is ``"6p"``]; use **+r** to rotate the labels from
        horizontal to vertical.
    bar_width
        Use an alternative histogram bar width than the default set via ``series``. Give
        either an alternative width in data units, or the user may append a
        :ref:`dimension unit <dimension-units>` for a fixed dimension instead.
    bar_offset
        Shift all bars along the axis by a constant value. It may be given in data units
        of plot dimension units by appending the relevant unit. Requires ``bar_width``.
    center
        Center bin on each value specified via ``series`` [Default uses the values to
        define the left edge of each bin].
    distribution : bool, float, or str
        [*mode*][**+p**\ *pen*].
        Draw the equivalent normal distribution; append desired
        *pen* [Default is ``"0.25p,black,solid"``].
        The *mode* selects which central location and scale to use:

        * 0 = mean and standard deviation [Default];
        * 1 = median and L1 scale (1.4826 \* median absolute deviation; MAD);
        * 2 = LMS (least median of squares) mode and scale.
    out_range
        Handle values that fall outside the range set by ``series``. By default, these
        values are ignored. Valid values are:

        - ``"first"``: only include values below first bin into the first bin
        - ``"last"``: only include values above the last bin into that last bin
        - ``"both"``: include values into the first or last bins
    cumulative
        Pass ``True`` to draw a cumulative histogram, or set it to ``"reverse"`` to draw
        a reverse cumulative histogram instead.
    stairs
        Draw a stairs-step diagram which does not include the internal bars of the
        default histogram.
    horizontal
        Plot the histogram horizontally from x = 0 [Default is vertically from y = 0].
        The plot dimensions remain the same, but the two axes are flipped, i.e., the
        x-axis is plotted vertically and the y-axis is plotted horizontally.
    series : int, str, or list
        [*min*\ /*max*\ /]\ *inc*\ [**+n**\ ].
        Set the interval for the width of each bar in the histogram.
    histtype : int or str
        [*type*][**+w**].
        Choose between 6 types of histograms:

        * 0 = counts [Default]
        * 1 = frequency_percent
        * 2 = log (1.0 + count)
        * 3 = log (1.0 + frequency_percent)
        * 4 = log10 (1.0 + count)
        * 5 = log10 (1.0 + frequency_percent).

        To use weights provided as a second data column instead of pure counts,
        append **+w**.
    $projection
    $region
    $frame
    $verbose
    $binary
    $panel
    $nodata
    $find
    $header
    $incols
    $label
    $perspective
    $transparency
    $wrap

    Examples
    --------

    >>> import numpy as np
    >>> import pygmt
    >>> # Generate random data from a normal distribution
    >>> rng = np.random.default_rng(seed=100)
    >>> data = rng.normal(loc=100, scale=25, size=1024)
    >>> fig = pygmt.Figure()
    >>> fig.histogram(data=data, frame=True, series=5, fill="red3", pen="1p")
    >>> fig.show()
    """
    if bar_offset is not None and bar_width is None:
        raise GMTParameterError(
            required="bar_width", reason="Required when 'bar_offset' is set."
        )

    aliasdict = AliasSystem(
        A=Alias(horizontal, name="horizontal"),
        C=Alias(cmap, name="cmap"),
        E=[
            Alias(bar_width, name="bar_width"),
            Alias(bar_offset, name="bar_offset", prefix="+o"),
        ],
        F=Alias(center, name="center"),
        G=Alias(fill, name="fill"),
        L=Alias(
            out_range,
            name="out_range",
            mapping={"first": "l", "last": "h", "both": "b"},
        ),
        Q=Alias(cumulative, name="cumulative", mapping={"reverse": "r"}),
        S=Alias(stairs, name="stairs"),
        W=Alias(pen, name="pen"),
    ).add_common(
        B=frame,
        J=projection,
        R=region,
        V=verbose,
        c=panel,
        i=incols,
        p=perspective,
        t=transparency,
    )
    aliasdict.merge(kwargs)

    self._activate_figure()
    with Session() as lib:
        with lib.virtualfile_in(check_kind="vector", data=data) as vintbl:
            lib.call_module(
                module="histogram", args=build_arg_list(aliasdict, infile=vintbl)
            )