Skip to content

Plot Functions

soundscapy.plotting.plot_functions

Plotting functions for visualizing circumplex data.

FUNCTION DESCRIPTION
iso_plot

Plot a soundscape visualization based on the specified metrics using different

create_iso_subplots

Create a set of subplots displaying data visualizations for soundscape analysis.

allocate_subplot_axes

Allocate the subplot axes based on the number of data subsets.

scatter

Plot ISOcoordinates as scatter points on a soundscape circumplex grid.

density

Plot a density plot of ISOCoordinates.

create_circumplex_subplots

Create a figure with subplots containing circumplex plots.

jointplot

Create a jointplot with a central distribution and marginal plots.

iso_annotation

Add text annotations to circumplex plot based on coordinate values.

scatter_plot

Wrapper for the scatter function to maintain backwards compatibility.

density_plot

Wrapper for the density function to maintain backwards compatibility.

iso_plot

iso_plot(
    data: DataFrame,
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    title: str | None = "Soundscapy Plot",
    plot_layers: Literal[
        "scatter", "density", "simple_density"
    ]
    | Sequence[
        Literal["scatter", "density", "simple_density"]
    ] = ("scatter", "density"),
    **kwargs,
) -> Axes

Plot a soundscape visualization based on the specified metrics using different combinations of layers such as scatter, density, or simple density plots.

The function generates a 2D plot (via Matplotlib Axes object) based on the x and y metrics provided. Users can choose between individual layers or specify a combination of the supported plot layers. It supports automatic handling for specific layer combinations, such as "scatter + density". The core plotting functionality is delegated to other helper functions (scatter and density).

PARAMETER DESCRIPTION
data

The dataset containing the metrics to be plotted. Must include the columns specified for x and y.

TYPE: DataFrame

x

The column name within data to be used for the x-axis. Defaults to "ISOPleasant".

TYPE: str DEFAULT: 'ISOPleasant'

y

The column name within data to be used for the y-axis. Defaults to "ISOEventful".

TYPE: str DEFAULT: 'ISOEventful'

title

The title of the plot. If not specified, defaults to "Soundscapy Plot".

TYPE: str | None DEFAULT: 'Soundscapy Plot'

plot_layers

Specifies the type or combination of plot layers to generate. Valid options include:

  • "scatter": A scatter plot
  • "density": A density plot (without scatter points, unless combined)
  • "simple_density": A simplified density plot (without scatter points, unless combined).

Can be passed as a string (single layer) or as a sequence of strings (combination of layers). Defaults to ("scatter", "density").

TYPE: Literal['scatter', 'density', 'simple_density'] | Sequence[Literal['scatter', 'density', 'simple_density']] DEFAULT: ('scatter', 'density')

**kwargs

Additional keyword arguments to be passed to the underlying plotting functions (scatter or density). These allow for customization such as marker styles, colors, etc.

DEFAULT: {}

RETURNS DESCRIPTION
Axes

A Matplotlib Axes object corresponding to the generated plot.

Notes

This function supports only specific combinations of layers. If an unsupported combination is specified, an exception will be raised. Layer compatibility rules:

  • Single layers: "scatter", "density", or "simple_density".
  • Dual layers:
    • "scatter" + "density"
    • "scatter" + "simple_density"
RAISES DESCRIPTION
TypeError

If the plot_layers argument is not a string or a sequence of strings.

ValueError

If the plot_layers argument specifies an unsupported plot type or combination of plot layers.

Examples:

Basic density and scatter plot with default settings:

>>> import soundscapy as sspy
>>> import matplotlib.pyplot as plt
>>> data = sspy.isd.load()
>>> data = sspy.add_iso_coords(data)
>>> ax = sspy.iso_plot(data)
>>> plt.show()

Basic scatter plot:

>>> ax = sspy.iso_plot(data, plot_layers="scatter")
>>> plt.show()

Simple density plot with fewer contour levels:

>>> ax = sspy.iso_plot(data, plot_layers="simple_density")
>>> plt.show()

Simple density with scatter points

>>> ax = sspy.iso_plot(data, plot_layers=["scatter", "simple_density"])
>>> plt.show()

Density plot with custom styling:

>>> sub_data = sspy.isd.select_location_ids(
...    data, ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields'])
>>> ax = sspy.iso_plot(
...     sub_data,
...     hue="SessionID",
...     plot_layers = ["scatter", "simple_density"],
...     legend_loc="upper right",
...     fill = False,
... )
>>> plt.show()

Add density to existing plots:

>>> fig, axes = plt.subplots(1, 2, figsize=(12, 6))
>>> axes[0] = sspy.iso_plot(
...     sspy.isd.select_location_ids(data, ['CamdenTown', 'PancrasLock']),
...     ax=axes.flatten()[0], title="CamdenTown and PancrasLock", hue="LocationID",
... )
>>> axes[1] = sspy.iso_plot(
...     sspy.isd.select_location_ids(data, ['RegentsParkJapan']),
...     ax=axes.flatten()[1], title="RegentsParkJapan"
... )
>>> plt.tight_layout()
>>> plt.show()
>>> plt.close('all')
Source code in src/soundscapy/plotting/plot_functions.py
def iso_plot(
    data: pd.DataFrame,
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    title: str | None = "Soundscapy Plot",
    plot_layers: Literal["scatter", "density", "simple_density"]
    | Sequence[Literal["scatter", "density", "simple_density"]] = (
        "scatter",
        "density",
    ),
    **kwargs,
) -> Axes:
    """
    Plot a soundscape visualization based on the specified metrics using different
    combinations of layers such as scatter, density, or simple density plots.

    The function generates a 2D plot (via Matplotlib Axes object) based on the `x` and
    `y` metrics provided. Users can choose between individual layers or specify a
    combination of the supported plot layers. It supports automatic handling for
    specific layer combinations, such as "scatter + density". The core plotting
    functionality is delegated to other helper functions (`scatter` and `density`).

    Parameters
    ----------
    data
        The dataset containing the metrics to be plotted. Must include the columns
        specified for `x` and `y`.
    x
        The column name within `data` to be used for the x-axis. Defaults to
        "ISOPleasant".
    y
        The column name within `data` to be used for the y-axis. Defaults to
        "ISOEventful".
    title
        The title of the plot. If not specified, defaults to "Soundscapy Plot".
    plot_layers
        Specifies the type or combination of plot layers to generate. Valid options
        include:

         - "scatter": A scatter plot
         - "density": A density plot (without scatter points, unless combined)
         - "simple_density": A simplified density plot (without scatter points,
           unless combined).

        Can be passed as a string (single layer) or as a sequence of strings
        (combination of layers). Defaults to ("scatter", "density").
    **kwargs
        Additional keyword arguments to be passed to the underlying plotting
        functions (`scatter` or `density`). These allow for customization such as
        marker styles, colors, etc.

    Returns
    -------
    :
        A Matplotlib Axes object corresponding to the generated plot.

    Notes
    -----
    This function supports only specific combinations of layers. If an unsupported
    combination is specified, an exception will be raised. Layer compatibility
    rules:

      - Single layers: "scatter", "density", or "simple_density".
      - Dual layers:
        - "scatter" + "density"
        - "scatter" + "simple_density"

    Raises
    ------
    TypeError
        If the `plot_layers` argument is not a string or a sequence of strings.
    ValueError
        If the `plot_layers` argument specifies an unsupported plot type or
        combination of plot layers.

    Examples
    --------
    Basic density and scatter plot with default settings:

    >>> import soundscapy as sspy
    >>> import matplotlib.pyplot as plt
    >>> data = sspy.isd.load()
    >>> data = sspy.add_iso_coords(data)
    >>> ax = sspy.iso_plot(data)
    >>> plt.show()  # doctest: +SKIP

    Basic scatter plot:

    >>> ax = sspy.iso_plot(data, plot_layers="scatter")
    >>> plt.show()  # doctest: +SKIP

    Simple density plot with fewer contour levels:

    >>> ax = sspy.iso_plot(data, plot_layers="simple_density")
    >>> plt.show() # doctest: +SKIP

    Simple density with scatter points

    >>> ax = sspy.iso_plot(data, plot_layers=["scatter", "simple_density"])
    >>> plt.show() # doctest: +SKIP

    Density plot with custom styling:

    >>> sub_data = sspy.isd.select_location_ids(
    ...    data, ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields'])
    >>> ax = sspy.iso_plot(
    ...     sub_data,
    ...     hue="SessionID",
    ...     plot_layers = ["scatter", "simple_density"],
    ...     legend_loc="upper right",
    ...     fill = False,
    ... )
    >>> plt.show() # doctest: +SKIP

    Add density to existing plots:

    >>> fig, axes = plt.subplots(1, 2, figsize=(12, 6))
    >>> axes[0] = sspy.iso_plot(
    ...     sspy.isd.select_location_ids(data, ['CamdenTown', 'PancrasLock']),
    ...     ax=axes.flatten()[0], title="CamdenTown and PancrasLock", hue="LocationID",
    ... )
    >>> axes[1] = sspy.iso_plot(
    ...     sspy.isd.select_location_ids(data, ['RegentsParkJapan']),
    ...     ax=axes.flatten()[1], title="RegentsParkJapan"
    ... )
    >>> plt.tight_layout()
    >>> plt.show() # doctest: +SKIP
    >>> plt.close('all')

    """  # noqa: D205
    if isinstance(plot_layers, str):
        plot_layers = [plot_layers]

    if not isinstance(plot_layers, Sequence):
        raise TypeError(PLOT_LAYER_TYPE_ERROR.format(type=type(plot_layers)))

    # Handle single layer case
    if len(plot_layers) == 1:
        layer_type = plot_layers[0]
        if layer_type == "scatter":
            return scatter(data, x=x, y=y, title=title, **kwargs)
        if layer_type == "simple_density":
            return density(
                data,
                x=x,
                y=y,
                title=title,
                density_type="simple",
                incl_scatter=False,
                **kwargs,
            )
        if layer_type == "density":
            return density(data, x=x, y=y, title=title, incl_scatter=False, **kwargs)

        raise ValueError(PLOT_LAYER_VALUE_ERROR.format(layers=plot_layers))

    # Handle two layer case
    if len(plot_layers) == 2:  # noqa: PLR2004
        layers_set = set(plot_layers)

        if "scatter" in layers_set and "density" in layers_set:
            return density(data, x=x, y=y, title=title, incl_scatter=True, **kwargs)

        if "scatter" in layers_set and "simple_density" in layers_set:
            return density(
                data,
                x=x,
                y=y,
                title=title,
                density_type="simple",
                incl_scatter=True,
                **kwargs,
            )

        # Default case for unrecognized but valid length combinations
        return density(data, x=x, y=y, title=title, incl_scatter=True, **kwargs)

    # More than 2 layers is not supported
    raise ValueError(PLOT_LAYER_VALUE_ERROR.format(layers=plot_layers))

create_iso_subplots

create_iso_subplots(
    data: DataFrame | list[DataFrame],
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    subplot_by: str | None = None,
    title: str | None = "Soundscapy Plot",
    plot_layers: Literal[
        "scatter", "density", "simple_density"
    ]
    | Sequence[
        Literal["scatter", "simple_density", "density"]
    ] = ("scatter", "density"),
    *,
    subplot_size: tuple[int, int] = (4, 4),
    subplot_titles: Literal["by_group", "numbered"]
    | list[str]
    | None = "by_group",
    subplot_title_prefix: str = "Plot",
    nrows: int | None = None,
    ncols: int | None = None,
    **kwargs,
) -> tuple[Figure, np.ndarray]

Create a set of subplots displaying data visualizations for soundscape analysis.

This function generates a collection of subplots, where each subplot corresponds to a subset of the input data. The subplots can display scatter plots, density plots, or simplified density plots, and can be organized by specific grouping criteria. Users can specify titles, overall size, row and column layout, and layering of plot types.

PARAMETER DESCRIPTION
data

Input data to be visualized. Can be a single data frame or a list of data frames for use in multiple subplots.

TYPE: DataFrame | list[DataFrame]

x

The name of the column in the data to be used for the x-axis. Default is "ISOPleasant".

TYPE: str DEFAULT: 'ISOPleasant'

y

The name of the column in the data to be used for the y-axis. Default is "ISOEventful".

TYPE: str DEFAULT: 'ISOEventful'

subplot_by

The column name by which to group data into subplots. If None, data is not grouped and plotted in a single set of axes. Default is None.

TYPE: str | None DEFAULT: None

title

The overarching title of the figure. If None, no overall title is added. Default is "Soundscapy Plot".

TYPE: str | None DEFAULT: 'Soundscapy Plot'

plot_layers

Type(s) of plot layers to include in each subplot. Can be a single type or a sequence of types. Default is ("scatter", "density").

TYPE: Literal['scatter', 'density', 'simple_density'] | Sequence[Literal['scatter', 'simple_density', 'density']] DEFAULT: ('scatter', 'density')

subplot_size

Size of each subplot in inches as (width, height). Default is (4, 4).

TYPE: tuple[int, int] DEFAULT: (4, 4)

subplot_titles

Determines how subplot titles are assigned. Options are "by_group" (titles derived from group names), "numbered" (titles as indices), or a list of custom titles. If None, no titles are added. Default is "by_group".

TYPE: Literal['by_group', 'numbered'] | list[str] | None DEFAULT: 'by_group'

subplot_title_prefix

Prefix for subplot titles if "numbered" is selected as subplot_titles. Default is "Plot".

TYPE: str DEFAULT: 'Plot'

nrows

Number of rows for the subplot grid. If None, automatically calculated based on the number of subplots. Default is None.

TYPE: int | None DEFAULT: None

ncols

Number of columns for the subplot grid. If None, automatically calculated based on the number of subplots. Default is None.

TYPE: int | None DEFAULT: None

**kwargs

Additional keyword arguments to pass to matplotlib's plt.subplots or for customizing the figure and subplots.

DEFAULT: {}

RETURNS DESCRIPTION
tuple[Figure, ndarray]

A tuple containing:

  • fig : matplotlib.figure.Figure The created matplotlib figure object containing the subplots.

  • np.ndarray An array of matplotlib.axes.Axes objects corresponding to the subplots.

Examples:

Basic subplots with default settings:

>>> import soundscapy as sspy
>>> import matplotlib.pyplot as plt
>>> import pandas as pd
>>> data = sspy.isd.load()
>>> data = sspy.add_iso_coords(data)
>>> four_locs = sspy.isd.select_location_ids(data,
...     ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields']
... )
>>> fig, axes = sspy.create_iso_subplots(four_locs, subplot_by="LocationID")
>>> plt.show()

Create subplots by specifying a list of data

>>> data1 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
>>> data2 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
>>> fig, axes = create_iso_subplots(
...     [data1, data2], plot_layers="scatter", nrows=1, ncols=2
... )
>>> plt.show()
>>> assert len(axes) == 2
>>> plt.close('all')
Source code in src/soundscapy/plotting/plot_functions.py
def create_iso_subplots(
    data: pd.DataFrame | list[pd.DataFrame],
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    subplot_by: str | None = None,
    title: str | None = "Soundscapy Plot",
    plot_layers: Literal["scatter", "density", "simple_density"]
    | Sequence[Literal["scatter", "simple_density", "density"]] = (
        "scatter",
        "density",
    ),
    *,
    subplot_size: tuple[int, int] = (4, 4),
    subplot_titles: Literal["by_group", "numbered"] | list[str] | None = "by_group",
    subplot_title_prefix: str = "Plot",  # Only used if subplot_titles = 'numbered'
    nrows: int | None = None,
    ncols: int | None = None,
    **kwargs,
) -> tuple[Figure, np.ndarray]:
    """
    Create a set of subplots displaying data visualizations for soundscape analysis.

    This function generates a collection of subplots, where each subplot corresponds
    to a subset of the input data. The subplots can display scatter plots, density
    plots, or simplified density plots, and can be organized by specific grouping
    criteria. Users can specify titles, overall size, row and column layout, and
    layering of plot types.

    Parameters
    ----------
    data
        Input data to be visualized. Can be a single data frame or a list of data
        frames for use in multiple subplots.
    x
        The name of the column in the data to be used for the x-axis. Default is
        "ISOPleasant".
    y
        The name of the column in the data to be used for the y-axis. Default is
        "ISOEventful".
    subplot_by
        The column name by which to group data into subplots. If None, data is not
        grouped and plotted in a single set of axes. Default is None.
    title
        The overarching title of the figure. If None, no overall title is added.
        Default is "Soundscapy Plot".
    plot_layers
        Type(s) of plot layers to include in each subplot. Can be a single type
        or a sequence of types. Default is ("scatter", "density").
    subplot_size
        Size of each subplot in inches as (width, height). Default is (4, 4).
    subplot_titles
        Determines how subplot titles are assigned. Options are "by_group" (titles
        derived from group names), "numbered" (titles as indices), or a list of
        custom titles. If None, no titles are added. Default is "by_group".
    subplot_title_prefix
        Prefix for subplot titles if "numbered" is selected as `subplot_titles`.
        Default is "Plot".
    nrows
        Number of rows for the subplot grid. If None, automatically calculated
        based on the number of subplots. Default is None.
    ncols
        Number of columns for the subplot grid. If None, automatically calculated
        based on the number of subplots. Default is None.
    **kwargs
        Additional keyword arguments to pass to matplotlib's `plt.subplots` or for
        customizing the figure and subplots.

    Returns
    -------
    :
        A tuple containing:

        - fig : matplotlib.figure.Figure
            The created matplotlib figure object containing the subplots.

        - np.ndarray
            An array of matplotlib.axes.Axes objects corresponding to the subplots.

    Examples
    --------
    Basic subplots with default settings:
    >>> import soundscapy as sspy
    >>> import matplotlib.pyplot as plt
    >>> import pandas as pd
    >>> data = sspy.isd.load()
    >>> data = sspy.add_iso_coords(data)
    >>> four_locs = sspy.isd.select_location_ids(data,
    ...     ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields']
    ... )
    >>> fig, axes = sspy.create_iso_subplots(four_locs, subplot_by="LocationID")
    >>> plt.show() # doctest: +SKIP

    Create subplots by specifying a list of data
    >>> data1 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
    ...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
    >>> data2 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
    ...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
    >>> fig, axes = create_iso_subplots(
    ...     [data1, data2], plot_layers="scatter", nrows=1, ncols=2
    ... )
    >>> plt.show() # doctest: +SKIP
    >>> assert len(axes) == 2
    >>> plt.close('all')

    """
    # Process input data and prepare for subplot creation
    data_list, subplot_titles_list, n_subplots = _prepare_subplot_data(
        data=data, x=y, y=y, subplot_by=subplot_by, subplot_titles=subplot_titles
    )

    # Calculate subplot layout
    nrows, ncols, n_subplots = allocate_subplot_axes(nrows, ncols, n_subplots)

    # Set up figure and subplots
    vert_adjust = 1.2 if title else 1.0
    figsize = kwargs.pop(
        "figsize", (ncols * subplot_size[0], nrows * (vert_adjust * subplot_size[1]))
    )

    subplots_params = SubplotsParams()
    subplots_params.update(
        nrows=nrows,
        ncols=ncols,
        figsize=figsize,
        subplot_by=subplot_by,
        extra="ignore",
        **kwargs,
    )

    fig, axes = plt.subplots(**subplots_params.as_plt_subplots_args())

    # Create each subplot
    _create_subplots(
        data_list,
        axes,
        n_subplots,
        subplot_titles_list,
        x,
        y,
        plot_layers,
        subplot_title_prefix,
        **kwargs,
    )

    # Add overall title and adjust layout
    if title:
        fig.suptitle(title, fontsize=DEFAULT_STYLE_PARAMS["title_fontsize"])

    fig.tight_layout()

    return fig, axes

allocate_subplot_axes

allocate_subplot_axes(
    nrows: int | None, ncols: int | None, n_subplots: int
) -> tuple[int, int, int]

Allocate the subplot axes based on the number of data subsets.

PARAMETER DESCRIPTION
nrows

Number of rows for subplots. Can be None to auto-determine.

TYPE: int | None

ncols

Number of columns for subplots. Can be None to auto-determine.

TYPE: int | None

n_subplots

Total number of subplots needed.

TYPE: int

RETURNS DESCRIPTION
tuple[int, int, int]

The number of rows and columns for the subplot grid.

Notes

Logic for determining subplot layout:

  • If both nrows and ncols are specified, use those values
  • If both are None, calculate a grid as close to square as possible
  • If only one is specified, calculate the other to fit all subplots
Source code in src/soundscapy/plotting/plot_functions.py
def allocate_subplot_axes(
    nrows: int | None, ncols: int | None, n_subplots: int
) -> tuple[int, int, int]:
    """
    Allocate the subplot axes based on the number of data subsets.

    Parameters
    ----------
    nrows
        Number of rows for subplots. Can be None to auto-determine.
    ncols
        Number of columns for subplots. Can be None to auto-determine.
    n_subplots
        Total number of subplots needed.

    Returns
    -------
    :
        The number of rows and columns for the subplot grid.

    Notes
    -----
    Logic for determining subplot layout:

    - If both nrows and ncols are specified, use those values
    - If both are None, calculate a grid as close to square as possible
    - If only one is specified, calculate the other to fit all subplots

    """
    # If both dimensions are specified, return them as is
    if nrows is not None and ncols is not None:
        return nrows, ncols, n_subplots

    # If both dimensions are None, create a grid as close to square as possible
    if nrows is None and ncols is None:
        ncols = int(np.ceil(np.sqrt(n_subplots)))
        nrows = int(np.ceil(n_subplots / ncols))
    # If only one dimension is specified, calculate the other
    elif nrows is not None and ncols is None:
        ncols = int(np.ceil(n_subplots / nrows))
    elif nrows is None and ncols is not None:
        nrows = int(np.ceil(n_subplots / ncols))
    else:
        msg = (
            "We should never reach this point - both nrows and ncols are None, "
            "but were missed in earlier check."
        )
        raise ValueError(msg)

    n_subplots = nrows * ncols

    return nrows, ncols, n_subplots

scatter

scatter(
    data: DataFrame,
    title: str | None = "Soundscape Scatter Plot",
    ax: Axes | None = None,
    *,
    x: str | None = "ISOPleasant",
    y: str | None = "ISOEventful",
    hue: str | None = None,
    palette: SeabornPaletteType | None = "colorblind",
    legend: Literal[
        "auto", "brief", "full", False
    ] = "auto",
    prim_labels: bool | None = None,
    **kwargs,
) -> Axes

Plot ISOcoordinates as scatter points on a soundscape circumplex grid.

Creates a scatter plot of data on a standardized circumplex grid with the custom Soundscapy styling for soundscape circumplex visualisations.

PARAMETER DESCRIPTION
data

Input data structure containing coordinate data, typically with ISOPleasant and ISOEventful columns.

TYPE: DataFrame

x

Column name for x variable, by default "ISOPleasant"

TYPE: str | None DEFAULT: 'ISOPleasant'

y

Column name for y variable, by default "ISOEventful"

TYPE: str | None DEFAULT: 'ISOEventful'

title

Title to add to circumplex plot, by default "Soundscape Scatter Plot"

TYPE: str | None DEFAULT: 'Soundscape Scatter Plot'

ax

Pre-existing matplotlib axes for the plot, by default None If None call matplotlib.pyplot.subplots with figsize internally.

TYPE: Axes | None DEFAULT: None

hue

Grouping variable that will produce points with different colors.

Can be either categorical or numeric, although color mapping will behave differently in latter case, by default None

TYPE: str | None DEFAULT: None

palette

Method for choosing the colors to use when mapping the hue semantic. String values are passed to seaborn.color_palette(). List or dict values imply categorical mapping, while a colormap object implies numeric mapping, by default "colorblind"

TYPE: SeabornPaletteType | None DEFAULT: 'colorblind'

legend

How to draw the legend. If "brief", numeric hue and size variables will be represented with a sample of evenly spaced values. If "full", every group will get an entry in the legend. If "auto", choose between brief or full representation based on number of levels.

If False, no legend data is added and no legend is drawn, by default "auto"

TYPE: Literal['auto', 'brief', 'full', False] DEFAULT: 'auto'

prim_labels

Deprecated. Use xlabel and ylabel parameters instead.

TYPE: bool | None DEFAULT: None

**kwargs

Additional style arguments. Common options include color (default "#0173B2" when hue mapping is unused), figsize (default (5, 5) when creating a new figure), s (default 20 for point size), axis label and limit controls such as xlabel, ylabel, xlim, ylim, legend placement via legend_loc, diagonal_lines, and font settings such as prim_ax_fontdict, fontsize, fontweight, fontstyle, family, c, alpha, and parse_math.

DEFAULT: {}

RETURNS DESCRIPTION
Axes

Axes object containing the plot.

Notes

This function applies special styling appropriate for circumplex plots including gridlines, axis labels, and proportional axes.

Examples:

Basic scatter plot with default settings:

>>> import soundscapy as sspy
>>> import matplotlib.pyplot as plt
>>> data = sspy.isd.load()
>>> data = sspy.add_iso_coords(data)
>>> ax = sspy.scatter(data)
>>> plt.show()

Scatter plot with grouping by location:

>>> ax = sspy.scatter(data, hue="LocationID", diagonal_lines=True, legend=False)
>>> plt.show()
>>> plt.close('all')
Source code in src/soundscapy/plotting/plot_functions.py
def scatter(
    data: pd.DataFrame,
    title: str | None = "Soundscape Scatter Plot",
    ax: Axes | None = None,
    *,
    x: str | None = "ISOPleasant",
    y: str | None = "ISOEventful",
    hue: str | None = None,
    palette: SeabornPaletteType | None = "colorblind",
    legend: Literal["auto", "brief", "full", False] = "auto",
    prim_labels: bool | None = None,  # Alias for primary_labels, deprecated
    **kwargs,
) -> Axes:
    """
    Plot ISOcoordinates as scatter points on a soundscape circumplex grid.

    Creates a scatter plot of data on a standardized circumplex grid with the custom
    Soundscapy styling for soundscape circumplex visualisations.

    Parameters
    ----------
    data
        Input data structure containing coordinate data, typically with ISOPleasant
        and ISOEventful columns.
    x
        Column name for x variable, by default "ISOPleasant"
    y
        Column name for y variable, by default "ISOEventful"
    title
        Title to add to circumplex plot, by default "Soundscape Scatter Plot"
    ax
        Pre-existing matplotlib axes for the plot, by default None
        If `None` call `matplotlib.pyplot.subplots` with `figsize` internally.
    hue
        Grouping variable that will produce points with different colors.

        Can be either categorical or numeric,
        although color mapping will behave differently in latter case, by default None
    palette
        Method for choosing the colors to use when mapping the hue semantic.
        String values are passed to seaborn.color_palette().
        List or dict values imply categorical mapping, while a colormap object
        implies numeric mapping, by default "colorblind"
    legend
        How to draw the legend. If "brief", numeric hue and size variables will be
        represented with a sample of evenly spaced values. If "full", every group will
        get an entry in the legend. If "auto", choose between brief or full
        representation based on number of levels.

        If False, no legend data is added and no legend is drawn, by default "auto"
    prim_labels
        Deprecated. Use xlabel and ylabel parameters instead.

    **kwargs
        Additional style arguments. Common options include `color` (default
        `"#0173B2"` when hue mapping is unused), `figsize` (default `(5, 5)` when
        creating a new figure), `s` (default `20` for point size), axis label and
        limit controls such as `xlabel`, `ylabel`, `xlim`, `ylim`, legend placement
        via `legend_loc`, `diagonal_lines`, and font settings such as
        `prim_ax_fontdict`, `fontsize`, `fontweight`, `fontstyle`, `family`, `c`,
        `alpha`, and `parse_math`.

    Returns
    -------
    :
        Axes object containing the plot.

    Notes
    -----
    This function applies special styling appropriate for circumplex plots including
    gridlines, axis labels, and proportional axes.

    Examples
    --------
    Basic scatter plot with default settings:

    >>> import soundscapy as sspy
    >>> import matplotlib.pyplot as plt
    >>> data = sspy.isd.load()
    >>> data = sspy.add_iso_coords(data)
    >>> ax = sspy.scatter(data)
    >>> plt.show() # doctest: +SKIP

    Scatter plot with grouping by location:

    >>> ax = sspy.scatter(data, hue="LocationID", diagonal_lines=True, legend=False)
    >>> plt.show() # doctest: +SKIP
    >>> plt.close('all')

    """
    style_args, subplots_args, kwargs = _setup_style_and_subplots_args_from_kwargs(
        x=x, y=y, prim_labels=prim_labels, kwargs=kwargs
    )

    scatter_args = ScatterParams()
    scatter_args.update(
        data=data,
        x=x,
        y=y,
        palette=palette,
        hue=hue,
        legend=legend,
        extra="allow",
        ignore_null=False,
        **kwargs,
    )  # pass all the rest to scatter

    # Removes the palette if no hue is specified
    scatter_args.crosscheck_palette_hue()

    if ax is None:
        _, ax = plt.subplots(1, 1, figsize=subplots_args.figsize)

    p = sns.scatterplot(ax=ax, **scatter_args.as_dict())

    _set_style()
    _circumplex_grid(
        ax=ax,
        **style_args.get_multiple(
            ["xlim", "ylim", "xlabel", "ylabel", "diagonal_lines", "prim_ax_fontdict"]
        ),
    )
    if title is not None:
        _set_circum_title(
            ax=ax,
            title=title,
            xlabel=style_args.get("xlabel"),
            ylabel=style_args.get("ylabel"),
        )
    if legend is not None and hue is not None and style_args.legend_loc is not False:
        _move_legend(ax=ax, new_loc=style_args.get("legend_loc"))
    return p

density

density(
    data: DataFrame,
    title: str | None = "Soundscape Density Plot",
    ax: Axes | None = None,
    *,
    x: str | None = "ISOPleasant",
    y: str | None = "ISOEventful",
    hue: str | None = None,
    incl_scatter: bool = True,
    density_type: str = "full",
    palette: SeabornPaletteType | None = "colorblind",
    scatter_kws: dict | None = None,
    legend: Literal[
        "auto", "brief", "full", False
    ] = "auto",
    prim_labels: bool | None = None,
    **kwargs,
) -> Axes

Plot a density plot of ISOCoordinates.

Creates a kernel density estimate visualization of data distribution on a circumplex grid with the custom Soundscapy styling for soundscape circumplex visualisations. Can optionally include a scatter plot of the underlying data points.

PARAMETER DESCRIPTION
data

Input data structure containing coordinate data, typically with ISOPleasant and ISOEventful columns.

TYPE: DataFrame

title

Title to add to circumplex plot, by default "Soundscape Density Plot"

TYPE: str | None DEFAULT: 'Soundscape Density Plot'

ax

Pre-existing axes object to use for the plot, by default None If None call matplotlib.pyplot.subplots with figsize internally.

TYPE: Axes | None DEFAULT: None

x

Column name for x variable, by default "ISOPleasant"

TYPE: str | None DEFAULT: 'ISOPleasant'

y

Column name for y variable, by default "ISOEventful"

TYPE: str | None DEFAULT: 'ISOEventful'

hue

Grouping variable that will produce density contours with different colors. Can be either categorical or numeric, although color mapping will behave differently in latter case, by default None

TYPE: str | None DEFAULT: None

incl_scatter

Whether to include a scatter plot of the data points, by default True

TYPE: bool DEFAULT: True

density_type

Type of density plot to draw. "full" uses default parameters, "simple" uses a lower number of levels (2), higher threshold (0.5), and lower alpha (0.5) for a cleaner visualization, by default "full"

TYPE: str DEFAULT: 'full'

palette

Method for choosing the colors to use when mapping the hue semantic. String values are passed to seaborn.color_palette(). List or dict values imply categorical mapping, while a colormap object implies numeric mapping, by default "colorblind"

TYPE: SeabornPaletteType | None DEFAULT: 'colorblind'

scatter_kws

Keyword arguments to pass to seaborn.scatterplot if incl_scatter is True, by default {"s": 25, "linewidth": 0}

TYPE: dict | None DEFAULT: None

legend

How to draw the legend. If "brief", numeric hue variables will be represented with a sample of evenly spaced values. If "full", every group will get an entry in the legend. If "auto", choose between brief or full representation based on number of levels. If False, no legend data is added and no legend is drawn, by default "auto"

TYPE: Literal['auto', 'brief', 'full', False] DEFAULT: 'auto'

prim_labels

Deprecated. Use xlabel and ylabel parameters instead.

TYPE: bool | None DEFAULT: None

**kwargs

Additional styling parameters. Common options include incl_outline, density controls such as alpha, fill, levels, thresh, and bw_adjust, axis label and limit controls such as xlabel, ylabel, xlim, ylim, legend placement via legend_loc, diagonal_lines, figsize for newly created figures, font settings such as prim_ax_fontdict, fontsize, fontweight, fontstyle, family, c, alpha, and parse_math, plus any extra keyword arguments accepted by matplotlib's contour and contourf.

DEFAULT: {}

RETURNS DESCRIPTION
Axes

Axes object containing the plot.

Notes

This function will raise a warning if the dataset has fewer than RECOMMENDED_MIN_SAMPLES (30) data points, as density plots are not reliable with small sample sizes.

Examples:

Basic density plot with default settings:

>>> import soundscapy as sspy
>>> import matplotlib.pyplot as plt
>>> data = sspy.isd.load()
>>> data = sspy.add_iso_coords(data)
>>> ax = sspy.density(data)
>>> plt.show()

Simple density plot with fewer contour levels:

>>> ax = sspy.density(data, density_type="simple")
>>> plt.show()

Density plot with custom styling:

>>> sub_data = sspy.isd.select_location_ids(
...    data, ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields'])
>>> ax = sspy.density(
...     sub_data,
...     hue="SessionID",
...     incl_scatter=True,
...     legend_loc="upper right",
...     fill = False,
...     density_type = "simple",
... )
>>> plt.show()

Add density to existing plots:

>>> fig, axes = plt.subplots(1, 2, figsize=(12, 6))
>>> axes[0] = sspy.density(
...     sspy.isd.select_location_ids(data, ['CamdenTown', 'PancrasLock']),
...     ax=axes[0], title="CamdenTown and PancrasLock", hue="LocationID",
...     density_type="simple"
... )
>>> axes[1] = sspy.density(
...     sspy.isd.select_location_ids(data, ['RegentsParkJapan']),
...     ax=axes[1], title="RegentsParkJapan"
... )
>>> plt.tight_layout()
>>> plt.show()
>>> plt.close('all')
Source code in src/soundscapy/plotting/plot_functions.py
def density(
    data: pd.DataFrame,
    title: str | None = "Soundscape Density Plot",
    ax: Axes | None = None,
    *,
    x: str | None = "ISOPleasant",
    y: str | None = "ISOEventful",
    hue: str | None = None,
    incl_scatter: bool = True,
    density_type: str = "full",
    palette: SeabornPaletteType | None = "colorblind",
    scatter_kws: dict | None = None,
    legend: Literal["auto", "brief", "full", False] = "auto",
    prim_labels: bool | None = None,  # Alias for primary_labels, deprecated
    **kwargs,
) -> Axes:
    """
    Plot a density plot of ISOCoordinates.

    Creates a kernel density estimate visualization of data distribution on a
    circumplex grid with the custom Soundscapy styling for soundscape circumplex
    visualisations. Can optionally include a scatter plot of the underlying data points.

    Parameters
    ----------
    data
        Input data structure containing coordinate data, typically with ISOPleasant
        and ISOEventful columns.
    title
        Title to add to circumplex plot, by default "Soundscape Density Plot"
    ax
        Pre-existing axes object to use for the plot, by default None
        If `None` call `matplotlib.pyplot.subplots` with `figsize` internally.
    x
        Column name for x variable, by default "ISOPleasant"
    y
        Column name for y variable, by default "ISOEventful"
    hue
        Grouping variable that will produce density contours with different colors.
        Can be either categorical or numeric, although color mapping will behave
        differently in latter case, by default None
    incl_scatter
        Whether to include a scatter plot of the data points, by default True
    density_type
        Type of density plot to draw. "full" uses default parameters, "simple"
        uses a lower number of levels (2), higher threshold (0.5), and lower alpha (0.5)
        for a cleaner visualization, by default "full"
    palette
        Method for choosing the colors to use when mapping the hue semantic.
        String values are passed to seaborn.color_palette().
        List or dict values imply categorical mapping, while a colormap object
        implies numeric mapping, by default "colorblind"
    scatter_kws
        Keyword arguments to pass to `seaborn.scatterplot` if incl_scatter is True,
        by default {"s": 25, "linewidth": 0}
    legend
        How to draw the legend. If "brief", numeric hue variables will be
        represented with a sample of evenly spaced values. If "full", every group will
        get an entry in the legend. If "auto", choose between brief or full
        representation based on number of levels.
        If False, no legend data is added and no legend is drawn, by default "auto"
    prim_labels
        Deprecated. Use xlabel and ylabel parameters instead.

    **kwargs
        Additional styling parameters. Common options include `incl_outline`,
        density controls such as `alpha`, `fill`, `levels`, `thresh`, and
        `bw_adjust`, axis label and limit controls such as `xlabel`, `ylabel`,
        `xlim`, `ylim`, legend placement via `legend_loc`, `diagonal_lines`,
        `figsize` for newly created figures, font settings such as
        `prim_ax_fontdict`, `fontsize`, `fontweight`, `fontstyle`, `family`, `c`,
        `alpha`, and `parse_math`, plus any extra keyword arguments accepted by
        matplotlib's `contour` and `contourf`.

    Returns
    -------
    :
        Axes object containing the plot.

    Notes
    -----
    This function will raise a warning if the dataset has fewer than
    RECOMMENDED_MIN_SAMPLES (30) data points, as density plots are not reliable
    with small sample sizes.

    Examples
    --------
    Basic density plot with default settings:

    >>> import soundscapy as sspy
    >>> import matplotlib.pyplot as plt
    >>> data = sspy.isd.load()
    >>> data = sspy.add_iso_coords(data)
    >>> ax = sspy.density(data)
    >>> plt.show() # doctest: +SKIP

    Simple density plot with fewer contour levels:

    >>> ax = sspy.density(data, density_type="simple")
    >>> plt.show() # doctest: +SKIP

    Density plot with custom styling:

    >>> sub_data = sspy.isd.select_location_ids(
    ...    data, ['CamdenTown', 'PancrasLock', 'RegentsParkJapan', 'RegentsParkFields'])
    >>> ax = sspy.density(
    ...     sub_data,
    ...     hue="SessionID",
    ...     incl_scatter=True,
    ...     legend_loc="upper right",
    ...     fill = False,
    ...     density_type = "simple",
    ... )
    >>> plt.show() # doctest: +SKIP

    Add density to existing plots:

    >>> fig, axes = plt.subplots(1, 2, figsize=(12, 6))
    >>> axes[0] = sspy.density(
    ...     sspy.isd.select_location_ids(data, ['CamdenTown', 'PancrasLock']),
    ...     ax=axes[0], title="CamdenTown and PancrasLock", hue="LocationID",
    ...     density_type="simple"
    ... )
    >>> axes[1] = sspy.density(
    ...     sspy.isd.select_location_ids(data, ['RegentsParkJapan']),
    ...     ax=axes[1], title="RegentsParkJapan"
    ... )
    >>> plt.tight_layout()
    >>> plt.show() # doctest: +SKIP
    >>> plt.close('all')

    """
    style_args, subplots_args, kwargs = _setup_style_and_subplots_args_from_kwargs(
        x=x, y=y, prim_labels=prim_labels, kwargs=kwargs
    )

    # Set up density parameters
    density_args = _setup_density_params(
        data=data,
        x=x,
        y=y,
        hue=hue,
        density_type=density_type,
        palette=palette,
        legend=legend,
        **kwargs,
    )

    # Check if dataset is large enough for density plots
    _valid_density(data)

    if ax is None:
        _, ax = plt.subplots(1, 1, figsize=subplots_args.get("figsize"))

    # Removes the palette if no hue is specified
    if density_args.get("hue") is None:
        density_args.update(palette=None)

    # Set up scatter parameters if needed
    scatter_args = ScatterParams()
    scatter_args.update(
        data=data,
        x=x,
        y=y,
        palette=palette,
        hue=density_args.get("hue"),
        color=density_args.get("color"),
        **(scatter_kws or {}),
    )

    scatter_args.crosscheck_palette_hue()
    density_args.crosscheck_palette_hue()

    if incl_scatter:
        d = sns.scatterplot(ax=ax, **scatter_args.as_seaborn_kwargs())

    if density_type == "simple":
        d = sns.kdeplot(ax=ax, **density_args.as_seaborn_kwargs())
        d = sns.kdeplot(ax=ax, **density_args.to_outline().as_seaborn_kwargs())

    elif density_type == "full":
        d = sns.kdeplot(ax=ax, **density_args.as_seaborn_kwargs())
    else:
        raise ValueError

    _set_style()
    _circumplex_grid(
        ax=ax,
        xlim=style_args.get("xlim"),
        ylim=style_args.get("ylim"),
        xlabel=style_args.get("xlabel"),
        ylabel=style_args.get("ylabel"),
        diagonal_lines=style_args.get("diagonal_lines"),
        prim_ax_fontdict=style_args.get("prim_ax_fontdict"),
    )
    if title is not None:
        _set_circum_title(
            ax=ax,
            title=title,
            xlabel=style_args.get("xlabel"),
            ylabel=style_args.get("ylabel"),
        )
    if legend is not None and hue is not None:
        _move_legend(ax=ax, new_loc=style_args.get("legend_loc"))

    return d

create_circumplex_subplots

create_circumplex_subplots(
    data_list: list[DataFrame],
    plot_type: Literal[
        "density", "scatter", "simple_density"
    ] = "density",
    incl_scatter: bool = True,
    subtitles: list[str] | None = None,
    title: str = "Circumplex Subplots",
    nrows: int | None = None,
    ncols: int | None = None,
    figsize: tuple[int, int] = (10, 10),
    **kwargs: Any,
) -> Figure

Create a figure with subplots containing circumplex plots.

Deprecated v0.8.0

Use :func:create_iso_subplots instead.

PARAMETER DESCRIPTION
data_list

A list of pandas DataFrames, each containing the data for one subplot.

TYPE: list[DataFrame]

plot_type

Type of plot to create in each subplot. Options are "scatter", "density",

TYPE: Literal['density', 'scatter', 'simple_density'] DEFAULT: 'density'

incl_scatter

Whether to include scatter points in density plots. Only applies if plot_type is "density" or "simple_density". By default, True.

TYPE: bool DEFAULT: True

subtitles

A list of titles for each subplot. If None, no titles will be added to subplots. If "numbered", subplots will be titled "Plot 1", "Plot 2", etc. Default is None.

TYPE: list[str] | None DEFAULT: None

title

Title for the entire figure, by default "Circumplex Subplots"

TYPE: str DEFAULT: 'Circumplex Subplots'

nrows

Number of rows in the subplot grid. If None, it will be automatically determined based on the number of subplots and ncols. Default is None.

TYPE: int | None DEFAULT: None

ncols

Number of columns in the subplot grid. If None, it will be automatically determined based on the number of subplots and nrows. Default is None.

TYPE: int | None DEFAULT: None

figsize

Size of the entire figure, by default (10, 10)

TYPE: tuple[int, int] DEFAULT: (10, 10)

**kwargs

Additional keyword arguments to pass to the underlying plotting functions.

For scatter plots, accepts any parameters from seaborn.scatterplot. For density plots, accepts any parameters from seaborn.kdeplot.

Also accepts style parameters for the circumplex plots such as xlim, ylim, xlabel, ylabel, diagonal_lines, legend_loc, and prim_ax_fontdict.

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
Figure

A matplotlib Figure object containing the created subplots.

Example

import pandas as pd import numpy as np import matplotlib.pyplot as plt np.random.seed(42) data1 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50), ... 'ISOEventful': np.random.uniform(-1, 1, 50)}) data2 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50), ... 'ISOEventful': np.random.uniform(-1, 1, 50)}) fig = create_circumplex_subplots( ... [data1, data2], plot_type="scatter", nrows=1, ncols=2 ... ) plt.show() # doctest: +SKIP isinstance(fig, plt.Figure) True plt.close('all')

Source code in src/soundscapy/plotting/plot_functions.py
def create_circumplex_subplots(
    data_list: list[pd.DataFrame],
    plot_type: Literal["density", "scatter", "simple_density"] = "density",
    incl_scatter: bool = True,  # noqa: FBT001, FBT002
    subtitles: list[str] | None = None,
    title: str = "Circumplex Subplots",
    nrows: int | None = None,
    ncols: int | None = None,
    figsize: tuple[int, int] = (10, 10),
    **kwargs: Any,
) -> Figure:
    """
    Create a figure with subplots containing circumplex plots.

    !!! warning "Deprecated v0.8.0"
       Use :func:`create_iso_subplots` instead.

    Parameters
    ----------
    data_list
        A list of pandas DataFrames, each containing the data for one subplot.
    plot_type
        Type of plot to create in each subplot. Options are "scatter", "density",
    incl_scatter
        Whether to include scatter points in density plots. Only applies if plot_type
        is "density" or "simple_density". By default, True.
    subtitles
        A list of titles for each subplot. If None, no titles will be added to
        subplots. If "numbered", subplots will be titled "Plot 1", "Plot 2", etc.
         Default is None.
    title
        Title for the entire figure, by default "Circumplex Subplots"
    nrows
        Number of rows in the subplot grid. If None, it will be automatically
        determined based on the number of subplots and ncols. Default is None.
    ncols
        Number of columns in the subplot grid. If None, it will be automatically
        determined based on the number of subplots and nrows. Default is None.
    figsize
        Size of the entire figure, by default (10, 10)
    **kwargs
        Additional keyword arguments to pass to the underlying plotting functions.

         For scatter plots, accepts any parameters from seaborn.scatterplot.
         For density plots, accepts any parameters from seaborn.kdeplot.

         Also accepts style parameters for the circumplex plots such as xlim, ylim,
         xlabel, ylabel, diagonal_lines, legend_loc, and prim_ax_fontdict.

    Returns
    -------
    :
        A matplotlib Figure object containing the created subplots.
    Example
    -------
    >>> import pandas as pd
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> np.random.seed(42)
    >>> data1 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
    ...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
    >>> data2 = pd.DataFrame({'ISOPleasant': np.random.uniform(-1, 1, 50),
    ...                       'ISOEventful': np.random.uniform(-1, 1, 50)})
    >>> fig = create_circumplex_subplots(
    ...     [data1, data2], plot_type="scatter", nrows=1, ncols=2
    ... )
    >>> plt.show() # doctest: +SKIP
    >>> isinstance(fig, plt.Figure)
    True
    >>> plt.close('all')

    """
    warnings.warn(
        "The `create_circumplex_subplots` function is deprecated and will be removed "
        "in a future version. Use `create_iso_subplots` instead.",
        DeprecationWarning,
        stacklevel=2,
    )

    # Map plot_type to plot_layers
    if plot_type == "scatter":
        plot_layers = ["scatter"]
    elif plot_type == "density":
        plot_layers = ["density"]
        if incl_scatter:
            plot_layers.insert(0, "scatter")
    elif plot_type == "simple_density":
        plot_layers = ["simple_density"]
        if incl_scatter:
            plot_layers.insert(0, "scatter")
    else:
        warnings.warn(
            "Can't recognize plot type. "
            "Using default 'density' plot type with scatter.",
            UserWarning,
            stacklevel=2,
        )
        plot_layers = ["scatter", "density"]

    # Map subtitles to subplot_titles
    subplot_titles = subtitles

    # Call create_iso_subplots with translated parameters
    fig, _ = create_iso_subplots(
        data=data_list,
        subplot_titles=subplot_titles,
        title=title,
        nrows=nrows,
        ncols=ncols,
        figsize=figsize,
        plot_layers=plot_layers,  # type: ignore[arg-type]
        **kwargs,
    )

    return fig

jointplot

jointplot(
    data: DataFrame,
    *,
    x: str = DEFAULT_XCOL,
    y: str = DEFAULT_YCOL,
    title: str | None = "Soundscape Joint Plot",
    hue: str | None = None,
    incl_scatter: bool = True,
    density_type: str = "full",
    palette: SeabornPaletteType | None = "colorblind",
    color: ColorType | None = DEFAULT_COLOR,
    scatter_kws: dict[str, Any] | None = None,
    incl_outline: bool = False,
    alpha: float = DEFAULT_SEABORN_PARAMS["alpha"],
    fill: bool = True,
    levels: int | tuple[float, ...] = 10,
    thresh: float = 0.05,
    bw_adjust: float = DEFAULT_BW_ADJUST,
    legend: Literal[
        "auto", "brief", "full", False
    ] = "auto",
    prim_labels: bool | None = None,
    joint_kws: dict[str, Any] | None = None,
    marginal_kws: dict[str, Any] | None = None,
    marginal_kind: str = "kde",
    **kwargs,
) -> sns.JointGrid

Create a jointplot with a central distribution and marginal plots.

Creates a visualization with a main plot (density or scatter) in the center and marginal distribution plots along the x and y axes. The main plot uses the custom Soundscapy styling for soundscape circumplex visualisations, and the marginals show the individual distributions of each variable.

PARAMETER DESCRIPTION
data

Input data structure containing coordinate data, typically with ISOPleasant and ISOEventful columns.

TYPE: DataFrame

x

Column name for x variable, by default "ISOPleasant"

TYPE: str DEFAULT: DEFAULT_XCOL

y

Column name for y variable, by default "ISOEventful"

TYPE: str DEFAULT: DEFAULT_YCOL

title

Title to add to the jointplot, by default "Soundscape Joint Plot"

TYPE: str | None DEFAULT: 'Soundscape Joint Plot'

hue

Grouping variable that will produce plots with different colors. Can be either categorical or numeric, although color mapping will behave differently in latter case, by default None

TYPE: str | None DEFAULT: None

incl_scatter

Whether to include a scatter plot of the data points in the joint plot, by default True

TYPE: bool DEFAULT: True

density_type

Type of density plot to draw. "full" uses default parameters, "simple" uses a lower number of levels (2), higher threshold (0.5), and lower alpha (0.5) for a cleaner visualization, by default "full"

TYPE: str DEFAULT: 'full'

palette

Method for choosing the colors to use when mapping the hue semantic. String values are passed to seaborn.color_palette(). List or dict values imply categorical mapping, while a colormap object implies numeric mapping, by default "colorblind"

TYPE: SeabornPaletteType | None DEFAULT: 'colorblind'

scatter_kws

Additional keyword arguments to pass to scatter plot if incl_scatter is True, by default None

TYPE: dict[str, Any] | None DEFAULT: None

incl_outline

Whether to include an outline for the density contours, by default False

TYPE: bool DEFAULT: False

alpha

Opacity level for the density fill, by default 0.8

TYPE: float DEFAULT: DEFAULT_SEABORN_PARAMS['alpha']

fill

Whether to fill the density contours, by default True

TYPE: bool DEFAULT: True

levels

Number of contour levels or specific levels to draw. A vector argument must have increasing values in [0, 1], by default 10

TYPE: int | tuple[float, ...] DEFAULT: 10

thresh

Lowest iso-proportion level at which to draw contours, by default 0.05

TYPE: float DEFAULT: 0.05

bw_adjust

Factor that multiplicatively scales the bandwidth. Increasing will make the density estimate smoother, by default 1.2

TYPE: float DEFAULT: DEFAULT_BW_ADJUST

legend

How to draw the legend for hue mapping, by default "auto"

TYPE: Literal['auto', 'brief', 'full', False] DEFAULT: 'auto'

prim_labels

Deprecated. Use xlabel and ylabel parameters instead.

TYPE: bool | None DEFAULT: None

joint_kws

Additional keyword arguments to pass to the joint plot, by default None

TYPE: dict[str, Any] | None DEFAULT: None

marginal_kws

Additional keyword arguments to pass to the marginal plots, by default {"fill": True, "common_norm": False}

TYPE: dict[str, Any] | None DEFAULT: None

marginal_kind

Type of plot to draw in the marginal axes, either "kde" for kernel density estimation or "hist" for histogram, by default "kde"

TYPE: str DEFAULT: 'kde'

**kwargs

Additional styling parameters. Common options include color, figsize, axis label and limit controls such as xlabel, ylabel, xlim, ylim, legend placement via legend_loc, diagonal_lines, and font settings such as prim_ax_fontdict, fontsize, fontweight, fontstyle, family, c, alpha, and parse_math.

DEFAULT: {}

RETURNS DESCRIPTION
JointGrid

The seaborn JointGrid object containing the plot

Notes

This function will raise a warning if the dataset has fewer than RECOMMENDED_MIN_SAMPLES (30) data points, as density plots are not reliable with small sample sizes.

Examples:

Basic jointplot with default settings:

>>> import soundscapy as sspy
>>> import matplotlib.pyplot as plt
>>> data = sspy.isd.load()
>>> data = sspy.add_iso_coords(data)
>>> g = sspy.jointplot(data)
>>> plt.show()

Jointplot with histogram marginals:

>>> g = sspy.jointplot(data, marginal_kind="hist")
>>> plt.show()

Jointplot with custom styling and grouping:

>>> g = sspy.jointplot(
...     data,
...     hue="LocationID",
...     incl_scatter=True,
...     density_type="simple",
...     diagonal_lines=True,
...     figsize=(6, 6),
...     title="Grouped Soundscape Analysis"
... )
>>> plt.show()
>>> plt.close('all')
Source code in src/soundscapy/plotting/plot_functions.py
def jointplot(
    data: pd.DataFrame,
    *,
    x: str = DEFAULT_XCOL,
    y: str = DEFAULT_YCOL,
    title: str | None = "Soundscape Joint Plot",
    hue: str | None = None,
    incl_scatter: bool = True,
    density_type: str = "full",
    palette: SeabornPaletteType | None = "colorblind",
    color: ColorType | None = DEFAULT_COLOR,
    scatter_kws: dict[str, Any] | None = None,
    incl_outline: bool = False,
    alpha: float = DEFAULT_SEABORN_PARAMS["alpha"],
    fill: bool = True,
    levels: int | tuple[float, ...] = 10,
    thresh: float = 0.05,
    bw_adjust: float = DEFAULT_BW_ADJUST,
    legend: Literal["auto", "brief", "full", False] = "auto",
    prim_labels: bool | None = None,  # Alias for primary_labels, deprecated
    joint_kws: dict[str, Any] | None = None,
    marginal_kws: dict[str, Any] | None = None,
    marginal_kind: str = "kde",
    **kwargs,
) -> sns.JointGrid:
    """
    Create a jointplot with a central distribution and marginal plots.

    Creates a visualization with a main plot (density or scatter) in the center and
    marginal distribution plots along the x and y axes. The main plot uses the custom
    Soundscapy styling for soundscape circumplex visualisations, and the marginals show
    the individual distributions of each variable.

    Parameters
    ----------
    data
        Input data structure containing coordinate data, typically with ISOPleasant
        and ISOEventful columns.
    x
        Column name for x variable, by default "ISOPleasant"
    y
        Column name for y variable, by default "ISOEventful"
    title
        Title to add to the jointplot, by default "Soundscape Joint Plot"
    hue
        Grouping variable that will produce plots with different colors.
        Can be either categorical or numeric, although color mapping will behave
        differently in latter case, by default None
    incl_scatter
        Whether to include a scatter plot of the data points in the joint plot,
        by default True
    density_type
        Type of density plot to draw. "full" uses default parameters, "simple"
        uses a lower number of levels (2), higher threshold (0.5), and lower alpha (0.5)
        for a cleaner visualization, by default "full"
    palette
        Method for choosing the colors to use when mapping the hue semantic.
        String values are passed to seaborn.color_palette().
        List or dict values imply categorical mapping, while a colormap object
        implies numeric mapping, by default "colorblind"
    scatter_kws
        Additional keyword arguments to pass to scatter plot if incl_scatter is True,
        by default None
    incl_outline
        Whether to include an outline for the density contours, by default False
    alpha
        Opacity level for the density fill, by default 0.8
    fill
        Whether to fill the density contours, by default True
    levels
        Number of contour levels or specific levels to draw. A vector argument
        must have increasing values in [0, 1], by default 10
    thresh
        Lowest iso-proportion level at which to draw contours, by default 0.05
    bw_adjust
        Factor that multiplicatively scales the bandwidth. Increasing will make
        the density estimate smoother, by default 1.2
    legend
        How to draw the legend for hue mapping, by default "auto"
    prim_labels
        Deprecated. Use xlabel and ylabel parameters instead.
    joint_kws
        Additional keyword arguments to pass to the joint plot, by default None
    marginal_kws
        Additional keyword arguments to pass to the marginal plots,
        by default {"fill": True, "common_norm": False}
    marginal_kind
        Type of plot to draw in the marginal axes, either "kde" for kernel
        density estimation or "hist" for histogram, by default "kde"

    **kwargs
        Additional styling parameters. Common options include `color`,
        `figsize`, axis label and limit controls such as `xlabel`, `ylabel`,
        `xlim`, `ylim`, legend placement via `legend_loc`, `diagonal_lines`, and
        font settings such as `prim_ax_fontdict`, `fontsize`, `fontweight`,
        `fontstyle`, `family`, `c`, `alpha`, and `parse_math`.

    Returns
    -------
    :
        The seaborn JointGrid object containing the plot

    Notes
    -----
    This function will raise a warning if the dataset has fewer than
    RECOMMENDED_MIN_SAMPLES (30) data points, as density plots are not reliable
    with small sample sizes.

    Examples
    --------
    Basic jointplot with default settings:

    >>> import soundscapy as sspy
    >>> import matplotlib.pyplot as plt
    >>> data = sspy.isd.load()
    >>> data = sspy.add_iso_coords(data)
    >>> g = sspy.jointplot(data)
    >>> plt.show() # doctest: +SKIP

    Jointplot with histogram marginals:

    >>> g = sspy.jointplot(data, marginal_kind="hist")
    >>> plt.show() # doctest: +SKIP

    Jointplot with custom styling and grouping:

    >>> g = sspy.jointplot(
    ...     data,
    ...     hue="LocationID",
    ...     incl_scatter=True,
    ...     density_type="simple",
    ...     diagonal_lines=True,
    ...     figsize=(6, 6),
    ...     title="Grouped Soundscape Analysis"
    ... )
    >>> plt.show() # doctest: +SKIP
    >>> plt.close('all')

    """
    # Check if dataset is large enough for density plots
    _valid_density(data)

    style_args, subplots_args, kwargs = _setup_style_and_subplots_args_from_kwargs(
        x=x, y=y, prim_labels=prim_labels, kwargs=kwargs
    )

    # Initialize default dicts if None
    scatter_args = ScatterParams()
    scatter_args.update(**scatter_kws) if scatter_kws is not None else None

    joint_kws = {} if joint_kws is None else joint_kws
    marginal_kws = (
        {"fill": True, "common_norm": False} if marginal_kws is None else marginal_kws
    )

    if density_type == "simple":
        thresh = DEFAULT_SIMPLE_DENSITY_PARAMS["thresh"]
        levels = DEFAULT_SIMPLE_DENSITY_PARAMS["levels"]
        alpha = DEFAULT_SIMPLE_DENSITY_PARAMS["alpha"]
        incl_outline = True

    # Handle hue and color
    if hue is None:
        # Removes the palette if no hue is specified
        palette = None
        color = sns.color_palette("colorblind", 1)[0] if color is None else color

    # Create the joint grid
    g = sns.JointGrid(
        data=data,
        x=x,
        y=y,
        hue=hue,
        palette=palette,
        xlim=style_args.xlim,
        ylim=style_args.ylim,
    )

    # Add the density plot to the joint plot area
    density(
        data,
        x=x,
        y=y,
        incl_scatter=incl_scatter,
        density_type=density_type,
        title=None,  # We'll set the title separately
        ax=g.ax_joint,
        hue=hue,
        palette=palette,
        color=color,
        scatter_kws=scatter_kws,
        incl_outline=incl_outline,
        legend_loc=style_args.legend_loc,
        alpha=alpha,
        legend=legend,
        fill=fill,
        levels=levels,
        thresh=thresh,
        bw_adjust=bw_adjust,
        diagonal_lines=style_args.diagonal_lines,
        xlim=style_args.xlim,
        ylim=style_args.ylim,
        **joint_kws,
    )

    # Add the marginal plots
    if marginal_kind == "hist":
        sns.histplot(
            data=data,
            x=x,
            hue=hue,
            palette=palette,
            ax=g.ax_marg_x,
            binrange=style_args.xlim,
            legend=False,
            **marginal_kws,
        )
        sns.histplot(
            data=data,
            y=y,
            hue=hue,
            palette=palette,
            ax=g.ax_marg_y,
            binrange=style_args.ylim,
            legend=False,
            **marginal_kws,
        )
    elif marginal_kind == "kde":
        sns.kdeplot(
            data=data,
            x=x,
            hue=hue,
            palette=palette,
            ax=g.ax_marg_x,
            bw_adjust=bw_adjust,
            legend=False,
            **marginal_kws,
        )
        sns.kdeplot(
            data=data,
            y=y,
            hue=hue,
            palette=palette,
            ax=g.ax_marg_y,
            bw_adjust=bw_adjust,
            legend=False,
            **marginal_kws,
        )

    # Set title
    if title is not None:
        g.ax_marg_x.set_title(title, pad=6.0)

    _set_style()
    _circumplex_grid(
        ax=g.ax_joint,
        xlim=style_args.get("xlim"),
        ylim=style_args.get("ylim"),
        xlabel=style_args.get("xlabel"),
        ylabel=style_args.get("ylabel"),
        diagonal_lines=style_args.get("diagonal_lines"),
        prim_ax_fontdict=style_args.get("prim_ax_fontdict"),
    )

    if legend is not None and hue is not None:
        _move_legend(ax=g.ax_joint, new_loc=style_args.get("legend_loc"))

    return g

iso_annotation

iso_annotation(
    ax: Axes,
    data: DataFrame,
    location: str,
    *,
    x_adj: int = 0,
    y_adj: int = 0,
    x_key: str = DEFAULT_XCOL,
    y_key: str = DEFAULT_YCOL,
    ha: str = "center",
    va: str = "center",
    fontsize: str = "small",
    arrowprops: dict | None = None,
    **text_kwargs,
) -> None

Add text annotations to circumplex plot based on coordinate values.

Directly uses plt.annotate

PARAMETER DESCRIPTION
ax

existing plt axes to add to

TYPE: Axes

data

dataframe of coordinate points

TYPE: DataFrame

location

name of the coordinate to plot

TYPE: str

x_adj

value to adjust x location by, by default 0

TYPE: int DEFAULT: 0

y_adj

value to adjust y location by, by default 0

TYPE: int DEFAULT: 0

x_key

name of x column, by default "ISOPleasant"

TYPE: str DEFAULT: DEFAULT_XCOL

y_key

name of y column, by default "ISOEventful"

TYPE: str DEFAULT: DEFAULT_YCOL

ha

horizontal alignment, by default "center"

TYPE: str DEFAULT: 'center'

va

vertical alignment, by default "center"

TYPE: str DEFAULT: 'center'

fontsize

by default "small"

TYPE: str DEFAULT: 'small'

arrowprops

dict of properties to send to plt.annotate, by default dict(arrowstyle="-", ec="black")

TYPE: dict | None DEFAULT: None

Source code in src/soundscapy/plotting/plot_functions.py
def iso_annotation(
    ax: Axes,
    data: pd.DataFrame,
    location: str,
    *,
    x_adj: int = 0,
    y_adj: int = 0,
    x_key: str = DEFAULT_XCOL,
    y_key: str = DEFAULT_YCOL,
    ha: str = "center",
    va: str = "center",
    fontsize: str = "small",
    arrowprops: dict | None = None,
    **text_kwargs,
) -> None:
    """
    Add text annotations to circumplex plot based on coordinate values.

    Directly uses plt.annotate

    Parameters
    ----------
    ax
        existing plt axes to add to
    data
        dataframe of coordinate points
    location
        name of the coordinate to plot
    x_adj
        value to adjust x location by, by default 0
    y_adj
        value to adjust y location by, by default 0
    x_key
        name of x column, by default "ISOPleasant"
    y_key
        name of y column, by default "ISOEventful"
    ha
        horizontal alignment, by default "center"
    va
        vertical alignment, by default "center"
    fontsize
        by default "small"
    arrowprops
        dict of properties to send to plt.annotate,
        by default dict(arrowstyle="-", ec="black")

    """
    if arrowprops is None:
        arrowprops = {"arrowstyle": "-", "ec": "black"}

    # noinspection PyTypeChecker
    ax.annotate(
        text=data["LocationID"][location],
        xy=(
            data[x_key][location],
            data[y_key][location],
        ),
        xytext=(
            data[x_key][location] + x_adj,
            data[y_key][location] + y_adj,
        ),
        ha=ha,
        va=va,
        arrowprops=arrowprops,
        annotation_clip=True,
        fontsize=fontsize,
        **text_kwargs,
    )

scatter_plot

scatter_plot(*args, **kwargs) -> Axes

Wrapper for the scatter function to maintain backwards compatibility.

PARAMETER DESCRIPTION
*args

Positional arguments to pass to the scatter function.

DEFAULT: ()

**kwargs

Keyword arguments to pass to the scatter function.

DEFAULT: {}

RETURNS DESCRIPTION
Axes

The Axes object containing the plot.

Source code in src/soundscapy/plotting/plot_functions.py
def scatter_plot(*args, **kwargs) -> Axes:  # noqa: ANN002
    """
    Wrapper for the scatter function to maintain backwards compatibility.

    Parameters
    ----------
    *args
        Positional arguments to pass to the scatter function.
    **kwargs
        Keyword arguments to pass to the scatter function.

    Returns
    -------
    :
        The Axes object containing the plot.

    """  # noqa: D401
    warnings.warn(
        "The `scatter_plot` function is deprecated and will be removed in a "
        "future version. Use `scatter` instead."
        "\nAs of v0.8, `scatter_plot` is an alias for `scatter` and does not maintain "
        "full backwards compatibility with v0.7. It may work, or some arguments may "
        "fail.",
        DeprecationWarning,
        stacklevel=2,
    )
    if kwargs.pop("backend", None) is not None:
        warnings.warn(
            "`Backend` is no longer supported in the scatter_plot function (v0.8+).",
            DeprecationWarning,
            stacklevel=2,
        )

    kwargs = {
        k: v
        for k, v in kwargs.items()
        if k not in ("backend", "show_labels", "apply_styling")
    }

    return scatter(*args, **kwargs)

density_plot

density_plot(
    *args, **kwargs
) -> Axes | np.ndarray | ISOPlot

Wrapper for the density function to maintain backwards compatibility.

PARAMETER DESCRIPTION
*args

Positional arguments to pass to the density function.

DEFAULT: ()

**kwargs

Keyword arguments to pass to the density function.

DEFAULT: {}

RETURNS DESCRIPTION
Axes | ndarray | ISOPlot

The Axes object containing the plot.

Source code in src/soundscapy/plotting/plot_functions.py
def density_plot(*args, **kwargs) -> Axes | np.ndarray | ISOPlot:  # noqa: ANN002
    """
    Wrapper for the density function to maintain backwards compatibility.

    Parameters
    ----------
    *args
        Positional arguments to pass to the density function.
    **kwargs
        Keyword arguments to pass to the density function.

    Returns
    -------
    :
        The Axes object containing the plot.

    """  # noqa: D401
    warnings.warn(
        "The `density_plot` function is deprecated and will be removed in a "
        "future version. Use `density` instead."
        "\nAs of v0.8, `density_plot` is an alias for `density` and does not maintain "
        "full backwards compatibility with v0.7. It may work, or some arguments may "
        "fail.",
        DeprecationWarning,
        stacklevel=2,
    )
    if kwargs.pop("backend", None) is not None:
        warnings.warn(
            "`Backend` is no longer supported in the density_plot function (v0.8+).",
            DeprecationWarning,
            stacklevel=2,
        )
    filtered_args = [a for a in args if not isinstance(a, Backend)]

    kwargs = {
        k: v
        for k, v in kwargs.items()
        if k
        not in (
            "backend",
            "show_labels",
            "apply_styling",
            "simple_density",
            "simple_density_thresh",
            "simple_density_levels",
            "simple_density_alpha",
        )
    }

    # Convert simple_density parameters to the new API if they exist
    if "density_type" not in kwargs and kwargs.pop("simple_density", False):
        kwargs["density_type"] = "simple"

    return density(*filtered_args, **kwargs)