Skip to content

Plotting

Utility functions for creating various types of circumplex plots.

create_circumplex_subplots

create_circumplex_subplots(data_list, plot_type=PlotType.DENSITY, incl_scatter=True, subtitles=None, title='Circumplex Subplots', nrows=None, ncols=None, figsize=(10, 10), **kwargs)

Create a figure with subplots containing circumplex plots.

RETURNS DESCRIPTION
matplotlib.figure.Figure: A figure containing the subplots.
Example
>>> import pandas as pd
>>> import numpy as np
>>> 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=PlotType.SCATTER, nrows=1, ncols=2)
>>> isinstance(fig, plt.Figure)
True
Source code in soundscapy/plotting/plot_functions.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def create_circumplex_subplots(
    data_list: List[pd.DataFrame],
    plot_type: PlotType | str = PlotType.DENSITY,
    incl_scatter: bool = True,
    subtitles: Optional[List[str]] = None,
    title: str = "Circumplex Subplots",
    nrows: int = None,
    ncols: int = None,
    figsize: Tuple[int, int] = (10, 10),
    **kwargs: Any,
) -> plt.Figure:
    """
    Create a figure with subplots containing circumplex plots.

    Parameters
    ----------
        data_list (List[pd.DataFrame]): List of DataFrames to plot.
        plot_type (PlotType): Type of plot to create.
        incl_scatter (bool): Whether to include scatter points on density plots.
        nrows (int): Number of rows in the subplot grid.
        ncols (int): Number of columns in the subplot grid.
        figsize (tuple): Figure size (width, height) in inches.
        **kwargs: Additional keyword arguments to pass to scatter_plot or density_plot.

    Returns
    -------
        matplotlib.figure.Figure: A figure containing the subplots.

    Example
    -------
        >>> import pandas as pd
        >>> import numpy as np
        >>> 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=PlotType.SCATTER, nrows=1, ncols=2)
        >>> isinstance(fig, plt.Figure)
        True
    """
    if isinstance(plot_type, str):
        plot_type = PlotType[plot_type.upper()]

    if nrows is None and ncols is None:
        nrows = 2
        ncols = len(data_list) // nrows
    elif nrows is None:
        nrows = len(data_list) // ncols
    elif ncols is None:
        ncols = len(data_list) // nrows

    if subtitles is None:
        subtitles = [f"({i + 1})" for i in range(len(data_list))]
    elif len(subtitles) != len(data_list):
        raise ValueError("Number of subtitles must match number of dataframes")

    fig, axes = plt.subplots(nrows, ncols, figsize=figsize)
    axes = axes.flatten() if isinstance(axes, np.ndarray) else [axes]

    color = kwargs.get("color", sns.color_palette("colorblind", 1)[0])

    for data, ax, subtitle in zip(data_list, axes, subtitles):
        if plot_type == PlotType.SCATTER or incl_scatter:
            scatter_plot(data, title=subtitle, ax=ax, color=color, **kwargs)
        if plot_type == PlotType.DENSITY:
            density_plot(data, title=subtitle, ax=ax, color=color, **kwargs)
        elif plot_type == PlotType.SIMPLE_DENSITY:
            density_plot(
                data, title=subtitle, simple_density=True, ax=ax, color=color, **kwargs
            )

    plt.suptitle(title)

    plt.tight_layout()
    return fig

density_plot

density_plot(data, x='ISOPleasant', y='ISOEventful', hue=None, title='Soundscape Density Plot', xlim=DEFAULT_XLIM, ylim=DEFAULT_YLIM, palette='colorblind', fill=True, incl_outline=False, incl_scatter=True, diagonal_lines=False, show_labels=True, legend=True, legend_location='best', backend=Backend.SEABORN, apply_styling=True, figsize=DEFAULT_FIGSIZE, simple_density=False, simple_density_thresh=0.5, simple_density_levels=2, simple_density_alpha=0.5, ax=None, extra_params={}, **kwargs)

Create a density plot using the CircumplexPlot class.

RETURNS DESCRIPTION
plt.Axes | go.Figure: The resulting plot object.
Source code in soundscapy/plotting/plot_functions.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def density_plot(
    data: pd.DataFrame,
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    hue: Optional[str] = None,
    title: str = "Soundscape Density Plot",
    xlim: Tuple[float, float] = DEFAULT_XLIM,
    ylim: Tuple[float, float] = DEFAULT_YLIM,
    palette: str = "colorblind",
    fill: bool = True,
    incl_outline: bool = False,
    incl_scatter: bool = True,
    diagonal_lines: bool = False,
    show_labels: bool = True,
    legend=True,
    legend_location: str = "best",
    backend: Backend = Backend.SEABORN,
    apply_styling: bool = True,
    figsize: Tuple[int, int] = DEFAULT_FIGSIZE,
    simple_density: bool = False,
    simple_density_thresh: float = 0.5,
    simple_density_levels: int = 2,
    simple_density_alpha: float = 0.5,
    ax: Optional[plt.Axes] = None,
    extra_params: ExtraParams = {},
    **kwargs: Any,
) -> plt.Axes | go.Figure:
    """
    Create a density plot using the CircumplexPlot class.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        x (str): Column name for x-axis data.
        y (str): Column name for y-axis data.
        hue (Optional[str]): Column name for color-coding data points.
        title (str): Title of the plot.
        xlim (Tuple[float, float]): x-axis limits.
        ylim (Tuple[float, float]): y-axis limits.
        palette (str): Color palette to use.
        fill (bool): Whether to fill the density contours.
        incl_outline (bool): Whether to include an outline for the density contours.
        diagonal_lines (bool): Whether to draw diagonal lines.
        show_labels (bool): Whether to show axis labels.
        legend (bool): Whether to show the legend.
        legend_location (str): Location of the legend.
        backend (Backend): The plotting backend to use.
        apply_styling (bool): Whether to apply circumplex-specific styling.
        figsize (Tuple[int, int]): Size of the figure.
        simple_density (bool): Whether to use simple density plot (Seaborn only).
        simple_density_thresh (float): Threshold for simple density plot.
        simple_density_levels (int): Number of levels for simple density plot.
        simple_density_alpha (float): Alpha value for simple density plot.
        ax (Optional[plt.Axes]): A matplotlib Axes object to plot on.
        extra_params (ExtraParams): Additional parameters for backend-specific functions.
        **kwargs: Additional keyword arguments to pass to the backend.

    Returns
    -------
        plt.Axes | go.Figure: The resulting plot object.

    """
    params = CircumplexPlotParams(
        x=x,
        y=y,
        hue=hue,
        title=title,
        xlim=xlim,
        ylim=ylim,
        palette=palette if hue else None,
        fill=fill,
        incl_outline=incl_outline,
        diagonal_lines=diagonal_lines,
        show_labels=show_labels,
        legend=legend,
        legend_location=legend_location,
        extra_params={**extra_params, **kwargs},
    )

    style_options = StyleOptions(
        figsize=figsize,
        simple_density=dict(
            thresh=simple_density_thresh,
            levels=simple_density_levels,
            alpha=simple_density_alpha,
        )
        if simple_density
        else None,
    )

    plot = CircumplexPlot(data, params, backend, style_options)

    if incl_scatter and backend == Backend.SEABORN:
        plot.scatter(apply_styling=True, ax=ax)
        ax = plot.get_axes()
    elif incl_scatter and backend == Backend.PLOTLY:
        # TODO: Implement overlaying scatter on density plot for Plotly backend
        raise NotImplementedError(
            "Overlaying a scatter on a density plot is not yet supported for Plotly backend. "
            "Please change to Seaborn backend or use `incl_scatter=False`."
        )

    if simple_density:
        plot.simple_density(apply_styling=apply_styling, ax=ax)
    else:
        plot.density(apply_styling=apply_styling, ax=ax)

    if isinstance(plot._backend, SeabornBackend):
        return plot.get_axes()
    else:
        return plot.get_figure()

scatter_plot

scatter_plot(data, x='ISOPleasant', y='ISOEventful', hue=None, title='Soundscape Scatter Plot', xlim=DEFAULT_XLIM, ylim=DEFAULT_YLIM, palette='colorblind', diagonal_lines=False, show_labels=True, legend=True, legend_location='best', backend=Backend.SEABORN, apply_styling=True, figsize=DEFAULT_FIGSIZE, ax=None, extra_params={}, **kwargs)

Create a scatter plot using the CircumplexPlot class.

RETURNS DESCRIPTION
plt.Axes | go.Figure: The resulting plot object.
Source code in soundscapy/plotting/plot_functions.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def scatter_plot(
    data: pd.DataFrame,
    x: str = "ISOPleasant",
    y: str = "ISOEventful",
    hue: Optional[str] = None,
    title: str = "Soundscape Scatter Plot",
    xlim: Tuple[float, float] = DEFAULT_XLIM,
    ylim: Tuple[float, float] = DEFAULT_YLIM,
    palette: str = "colorblind",
    diagonal_lines: bool = False,
    show_labels: bool = True,
    legend=True,
    legend_location: str = "best",
    backend: Backend = Backend.SEABORN,
    apply_styling: bool = True,
    figsize: Tuple[int, int] = DEFAULT_FIGSIZE,
    ax: Optional[plt.Axes] = None,
    extra_params: ExtraParams = {},
    **kwargs: Any,
) -> plt.Axes | go.Figure:
    """
    Create a scatter plot using the CircumplexPlot class.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        x (str): Column name for x-axis data.
        y (str): Column name for y-axis data.
        hue (Optional[str]): Column name for color-coding data points.
        title (str): Title of the plot.
        xlim (Tuple[float, float]): x-axis limits.
        ylim (Tuple[float, float]): y-axis limits.
        palette (str): Color palette to use.
        diagonal_lines (bool): Whether to draw diagonal lines.
        show_labels (bool): Whether to show axis labels.
        legend (bool): Whether to show the legend.
        legend_location (str): Location of the legend.
        backend (Backend): The plotting backend to use.
        apply_styling (bool): Whether to apply circumplex-specific styling.
        figsize (Tuple[int, int]): Size of the figure.
        ax (Optional[plt.Axes]): A matplotlib Axes object to plot on.
        extra_params (ExtraParams): Additional parameters for backend-specific functions.
        **kwargs: Additional keyword arguments to pass to the backend.

    Returns
    -------
        plt.Axes | go.Figure: The resulting plot object.

    """
    params = CircumplexPlotParams(
        x=x,
        y=y,
        hue=hue,
        title=title,
        xlim=xlim,
        ylim=ylim,
        palette=palette if hue else None,
        diagonal_lines=diagonal_lines,
        show_labels=show_labels,
        legend=legend,
        legend_location=legend_location,
        extra_params={**extra_params, **kwargs},
    )

    style_options = StyleOptions(figsize=figsize)

    plot = CircumplexPlot(data, params, backend, style_options)
    plot.scatter(apply_styling=apply_styling, ax=ax)

    if isinstance(plot._backend, SeabornBackend):
        return plot.get_axes()
    else:
        return plot.get_figure()

Circumplex Plotting

Main module for creating circumplex plots using different backends.

CircumplexPlot

CircumplexPlot(data, params=CircumplexPlotParams(), backend=Backend.SEABORN, style_options=StyleOptions())

A class for creating circumplex plots using different backends.

This class provides methods for creating scatter plots and density plots based on the circumplex model of soundscape perception. It supports multiple backends (currently Seaborn and Plotly) and offers various customization options.

Source code in soundscapy/plotting/circumplex_plot.py
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(
    self,
    data: pd.DataFrame,
    params: CircumplexPlotParams = CircumplexPlotParams(),
    backend: Backend = Backend.SEABORN,
    style_options: StyleOptions = StyleOptions(),
):
    self.data = data
    self.params = params
    self.style_options = style_options
    self._backend = self._create_backend(backend)
    self._plot = None

density

density(apply_styling=True, ax=None)

Create a density plot.

Source code in soundscapy/plotting/circumplex_plot.py
125
126
127
128
129
def density(
    self, apply_styling: bool = True, ax: Optional[plt.Axes] = None
) -> "CircumplexPlot":
    """Create a density plot."""
    return self._create_plot(PlotType.DENSITY, apply_styling, ax)

get_axes

get_axes()

Get the axes object of the plot (only for Seaborn backend).

Source code in soundscapy/plotting/circumplex_plot.py
153
154
155
156
157
158
159
160
161
162
def get_axes(self):
    """Get the axes object of the plot (only for Seaborn backend)."""
    if self._plot is None:
        raise ValueError(
            "No plot has been created yet. Call scatter(), density(), or simple_density() first."
        )
    if isinstance(self._backend, SeabornBackend):
        return self._plot[1]  # Return the axes object
    else:
        raise AttributeError("Axes object is not available for Plotly backend")

get_figure

get_figure()

Get the figure object of the plot.

Source code in soundscapy/plotting/circumplex_plot.py
145
146
147
148
149
150
151
def get_figure(self):
    """Get the figure object of the plot."""
    if self._plot is None:
        raise ValueError(
            "No plot has been created yet. Call scatter(), density(), or simple_density() first."
        )
    return self._plot

get_style_options

get_style_options()

Get the current StyleOptions.

Source code in soundscapy/plotting/circumplex_plot.py
164
165
166
def get_style_options(self) -> StyleOptions:
    """Get the current StyleOptions."""
    return copy.deepcopy(self.style_options)

iso_annotation

iso_annotation(location, x_adj=0, y_adj=0, **kwargs)

Add an annotation to the plot (only for Seaborn backend).

Source code in soundscapy/plotting/circumplex_plot.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def iso_annotation(self, location, x_adj: float = 0, y_adj: float = 0, **kwargs):
    """Add an annotation to the plot (only for Seaborn backend)."""
    if isinstance(self._backend, SeabornBackend):
        ax = self.get_axes()
        x = self.data[self.params.x].iloc[location]
        y = self.data[self.params.y].iloc[location]
        ax.annotate(
            text=self.data.index[location],
            xy=(x, y),
            xytext=(x + x_adj, y + y_adj),
            ha="center",
            va="center",
            arrowprops=dict(arrowstyle="-", ec="black"),
            **kwargs,
        )
    else:
        raise AttributeError("iso_annotation is not available for Plotly backend")
    return self

scatter

scatter(apply_styling=True, ax=None)

Create a scatter plot.

Source code in soundscapy/plotting/circumplex_plot.py
119
120
121
122
123
def scatter(
    self, apply_styling: bool = True, ax: Optional[plt.Axes] = None
) -> "CircumplexPlot":
    """Create a scatter plot."""
    return self._create_plot(PlotType.SCATTER, apply_styling, ax)

show

show()

Display the plot.

Source code in soundscapy/plotting/circumplex_plot.py
137
138
139
140
141
142
143
def show(self):
    """Display the plot."""
    if self._plot is None:
        raise ValueError(
            "No plot has been created yet. Call scatter(), density(), or simple_density() first."
        )
    self._backend.show(self._plot)

simple_density

simple_density(apply_styling=True, ax=None)

Create a simple density plot.

Source code in soundscapy/plotting/circumplex_plot.py
131
132
133
134
135
def simple_density(
    self, apply_styling: bool = True, ax: Optional[plt.Axes] = None
) -> "CircumplexPlot":
    """Create a simple density plot."""
    return self._create_plot(PlotType.SIMPLE_DENSITY, apply_styling, ax)

update_style_options

update_style_options(**kwargs)

Update the StyleOptions with new values.

Source code in soundscapy/plotting/circumplex_plot.py
168
169
170
171
172
173
174
175
176
177
178
179
def update_style_options(self, **kwargs) -> "CircumplexPlot":
    """Update the StyleOptions with new values."""
    new_style_options = copy.deepcopy(self.style_options)
    for key, value in kwargs.items():
        if hasattr(new_style_options, key):
            setattr(new_style_options, key, value)
        else:
            raise ValueError(f"Invalid StyleOptions attribute: {key}")

    self.style_options = new_style_options
    self._backend.style_options = new_style_options
    return self

CircumplexPlotParams dataclass

CircumplexPlotParams(x='ISOPleasant', y='ISOEventful', hue=None, title='Soundscape Plot', xlim=DEFAULT_XLIM, ylim=DEFAULT_YLIM, alpha=0.8, fill=True, palette=None, incl_outline=False, diagonal_lines=False, show_labels=True, legend='auto', legend_location='best', extra_params=dict())

Parameters for customizing CircumplexPlot.

Backends

PlotBackend

Bases: ABC

Abstract base class for plot backends.

This class defines the interface for creating scatter and density plots, as well as applying styling to the plots.

apply_styling abstractmethod

apply_styling(plot_obj, params)

Apply styling to the plot.

RETURNS DESCRIPTION
The styled plot object.
Source code in soundscapy/plotting/backends.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@abstractmethod
def apply_styling(self, plot_obj, params):
    """
    Apply styling to the plot.

    Parameters
    ----------
        plot_obj: The plot object to style.
        params (CircumplexPlotParams): The parameters for styling.

    Returns
    -------
        The styled plot object.
    """
    pass

create_density abstractmethod

create_density(data, params)

Create a density plot.

RETURNS DESCRIPTION
The created plot object.
Source code in soundscapy/plotting/backends.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@abstractmethod
def create_density(self, data, params):
    """
    Create a density plot.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        The created plot object.
    """
    pass

create_scatter abstractmethod

create_scatter(data, params)

Create a scatter plot.

RETURNS DESCRIPTION
The created plot object.
Source code in soundscapy/plotting/backends.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@abstractmethod
def create_scatter(self, data, params):
    """
    Create a scatter plot.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        The created plot object.
    """
    pass

PlotlyBackend

PlotlyBackend()

Bases: PlotBackend

Backend for creating plots using Plotly.

Source code in soundscapy/plotting/backends.py
245
246
247
248
249
def __init__(self):
    warnings.warn(
        "PlotlyBackend is very experimental and not fully implemented.", UserWarning
    )
    pass

apply_styling

apply_styling(plot_obj, params)

Apply styling to the Plotly plot.

RETURNS DESCRIPTION
go.Figure: The styled Plotly figure object.
Source code in soundscapy/plotting/backends.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def apply_styling(self, plot_obj, params):
    """
    Apply styling to the Plotly plot.

    Parameters
    ----------
        plot_obj (go.Figure): A Plotly figure object.
        params (CircumplexPlotParams): The parameters for styling.

    Returns
    -------
        go.Figure: The styled Plotly figure object.
    """
    fig = plot_obj
    if params.diagonal_lines:
        fig.add_trace(
            go.Scatter(
                x=[params.xlim[0], params.xlim[1]],
                y=[params.ylim[0], params.ylim[1]],
                mode="lines",
                line=dict(color="gray", dash="dash"),
                showlegend=False,
            )
        )
        fig.add_trace(
            go.Scatter(
                x=[params.xlim[0], params.xlim[1]],
                y=[params.ylim[1], params.ylim[0]],
                mode="lines",
                line=dict(color="gray", dash="dash"),
                showlegend=False,
            )
        )
    return fig

create_density

create_density(data, params)

Create a density plot using Plotly.

RETURNS DESCRIPTION
go.Figure: A Plotly figure object.
Source code in soundscapy/plotting/backends.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def create_density(self, data, params):
    """
    Create a density plot using Plotly.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        go.Figure: A Plotly figure object.
    """
    if len(data) < 30:
        warnings.warn(
            "Density plots are not recommended for small datasets (<30 samples). Consider using a scatter plot instead.",
            UserWarning,
        )

    fig = px.density_heatmap(
        data,
        x=params.x,
        y=params.y,
        title=params.title,
        range_x=params.xlim,
        range_y=params.ylim,
        **params.extra_params,
    )
    fig.update_layout(
        width=600,
        height=600,
        xaxis=dict(scaleanchor="y", scaleratio=1),
        yaxis=dict(scaleanchor="x", scaleratio=1),
    )
    scatter_trace = px.scatter(
        data, x=params.x, y=params.y, color=params.hue, opacity=0.5
    ).data[0]
    fig.add_trace(scatter_trace)
    return fig

create_scatter

create_scatter(data, params)

Create a scatter plot using Plotly.

RETURNS DESCRIPTION
go.Figure: A Plotly figure object.
Source code in soundscapy/plotting/backends.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
def create_scatter(self, data, params):
    """
    Create a scatter plot using Plotly.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        go.Figure: A Plotly figure object.
    """
    fig = px.scatter(
        data,
        x=params.x,
        y=params.y,
        color=params.hue,
        title=params.title,
        range_x=params.xlim,
        range_y=params.ylim,
        **params.extra_params,
    )
    fig.update_layout(
        width=600,
        height=600,
        xaxis=dict(scaleanchor="y", scaleratio=1),
        yaxis=dict(scaleanchor="x", scaleratio=1),
    )
    return fig

show

show(fig)

Display the Plotly figure.

Source code in soundscapy/plotting/backends.py
357
358
359
360
361
362
363
364
365
def show(self, fig):
    """
    Display the Plotly figure.

    Parameters
    ----------
        fig (go.Figure): The Plotly figure to display.
    """
    fig.show()

SeabornBackend

SeabornBackend(style_options=StyleOptions())

Bases: PlotBackend

Backend for creating plots using Seaborn and Matplotlib.

Source code in soundscapy/plotting/backends.py
74
75
def __init__(self, style_options: StyleOptions = StyleOptions()):
    self.style_options = style_options

apply_styling

apply_styling(plot_obj, params)

Apply styling to the Seaborn plot.

RETURNS DESCRIPTION
tuple: The styled figure and axes objects.
Source code in soundscapy/plotting/backends.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def apply_styling(self, plot_obj, params):
    """
    Apply styling to the Seaborn plot.

    Parameters
    ----------
        plot_obj (tuple): A tuple containing the figure and axes objects.
        params (CircumplexPlotParams): The parameters for styling.

    Returns
    -------
        tuple: The styled figure and axes objects.
    """
    fig, ax = plot_obj
    styler = SeabornStyler(params, self.style_options)
    return styler.apply_styling(fig, ax)

create_density

create_density(data, params, ax=None)

Create a density plot using Seaborn.

RETURNS DESCRIPTION
tuple: A tuple containing the figure and axes objects.
Source code in soundscapy/plotting/backends.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def create_density(self, data, params, ax=None):
    """
    Create a density plot using Seaborn.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        tuple: A tuple containing the figure and axes objects.
    """
    if len(data) < 30:
        warnings.warn(
            "Density plots are not recommended for small datasets (<30 samples).",
            UserWarning,
        )

    if ax is None:
        fig, ax = plt.subplots(figsize=self.style_options.figsize)
    else:
        fig = ax.get_figure()
    sns.kdeplot(
        data=data,
        x=params.x,
        y=params.y,
        hue=params.hue,
        palette=params.palette,
        fill=params.fill,
        alpha=params.alpha,
        ax=ax,
        bw_adjust=self.style_options.bw_adjust,
        zorder=self.style_options.data_zorder,
        common_norm=False,
        legend=params.legend,
        **params.extra_params,
    )
    if params.incl_outline:
        sns.kdeplot(
            data=data,
            x=params.x,
            y=params.y,
            hue=params.hue,
            alpha=1,
            palette=params.palette,
            fill=False,
            ax=ax,
            bw_adjust=self.style_options.bw_adjust,
            zorder=self.style_options.data_zorder,
            common_norm=False,
            legend=False,
            **params.extra_params,
        )
    return fig, ax

create_scatter

create_scatter(data, params, ax=None)

Create a scatter plot using Seaborn.

RETURNS DESCRIPTION
tuple: A tuple containing the figure and axes objects.
Source code in soundscapy/plotting/backends.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def create_scatter(self, data, params, ax=None):
    """
    Create a scatter plot using Seaborn.

    Parameters
    ----------
        data (pd.DataFrame): The data to plot.
        params (CircumplexPlotParams): The parameters for the plot.

    Returns
    -------
        tuple: A tuple containing the figure and axes objects.
    """
    if ax is None:
        fig, ax = plt.subplots(figsize=self.style_options.figsize)
    else:
        fig = ax.get_figure()
    if "incl_scatter" in params.extra_params:
        params.extra_params.pop("incl_scatter")
    if "density_type" in params.extra_params:
        params.extra_params.pop("density_type")
    if "fill" in params.extra_params:
        params.extra_params.pop("fill")
    sns.scatterplot(
        data=data,
        x=params.x,
        y=params.y,
        hue=params.hue,
        palette=params.palette if params.hue else None,
        alpha=params.alpha,
        ax=ax,
        zorder=self.style_options.data_zorder,
        legend=params.legend,
        **params.extra_params,
    )
    return fig, ax

show

show(plot_obj)

Display the Matplotlib figure.

Source code in soundscapy/plotting/backends.py
228
229
230
231
232
233
234
235
236
237
def show(self, plot_obj):
    """
    Display the Matplotlib figure.

    Parameters
    ----------
        fig: The figure to display.
    """
    fig, _ = plot_obj
    plt.show()

Stylers

Styling utilities for circumplex plots using Seaborn and Matplotlib.

SeabornStyler

SeabornStyler(params, style_options=StyleOptions())

Class for applying Seaborn styles to circumplex plots.

Source code in soundscapy/plotting/stylers.py
50
51
52
def __init__(self, params: Any, style_options: StyleOptions = StyleOptions()):
    self.params = params
    self.style_options = style_options

apply_styling

apply_styling(fig, ax)

Apply styling to the plot.

RETURNS DESCRIPTION
Tuple[mpl.figure.Figure, mpl.axes.Axes]: The styled figure and axes.
Source code in soundscapy/plotting/stylers.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def apply_styling(
    self, fig: mpl.figure.Figure, ax: mpl.axes.Axes
) -> Tuple[mpl.figure.Figure, mpl.axes.Axes]:
    """
    Apply styling to the plot.

    Parameters
    ----------
        fig (mpl.figure.Figure): The figure object.
        ax (mpl.axes.Axes): The axes object.

    Returns
    -------
        Tuple[mpl.figure.Figure, mpl.axes.Axes]: The styled figure and axes.
    """
    self.set_style()
    self.circumplex_grid(ax)
    self.set_circum_title(ax)
    self.deal_w_default_labels(ax)
    if self.params.hue and self.params.legend is not False:
        self.move_legend(ax)
    return fig, ax

circumplex_grid

circumplex_grid(ax)

Add the circumplex grid to the plot.

Source code in soundscapy/plotting/stylers.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def circumplex_grid(self, ax: mpl.axes.Axes) -> None:
    """Add the circumplex grid to the plot."""
    ax.set_xlim(self.params.xlim)
    ax.set_ylim(self.params.ylim)

    ax.get_xaxis().set_minor_locator(mpl.ticker.AutoMinorLocator())
    ax.get_yaxis().set_minor_locator(mpl.ticker.AutoMinorLocator())

    ax.grid(visible=True, which="major", color="grey", alpha=0.5)
    ax.grid(
        visible=True,
        which="minor",
        color="grey",
        linestyle="dashed",
        linewidth=0.5,
        alpha=0.4,
        zorder=self.style_options.prim_lines_zorder,
    )

    self.primary_lines_and_labels(ax)
    if self.params.diagonal_lines:
        self.diagonal_lines_and_labels(ax)

deal_w_default_labels

deal_w_default_labels(ax)

Handle the default labels for the axes.

Source code in soundscapy/plotting/stylers.py
109
110
111
112
113
114
115
116
def deal_w_default_labels(self, ax: mpl.axes.Axes) -> None:
    """Handle the default labels for the axes."""
    if not self.params.show_labels:
        ax.set_xlabel("")
        ax.set_ylabel("")
    else:
        ax.set_xlabel(self.params.x)
        ax.set_ylabel(self.params.y)

diagonal_lines_and_labels

diagonal_lines_and_labels(ax)

Add diagonal lines and labels to the plot.

Source code in soundscapy/plotting/stylers.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def diagonal_lines_and_labels(self, ax: mpl.axes.Axes) -> None:
    """Add diagonal lines and labels to the plot."""
    x_lim, y_lim = ax.get_xlim(), ax.get_ylim()
    line_weights = 1.5

    ax.plot(
        [x_lim[0], x_lim[1]],
        [y_lim[0], y_lim[1]],
        linestyle="dashed",
        color="grey",
        alpha=0.5,
        lw=line_weights,
        zorder=self.style_options.diag_lines_zorder,
    )
    ax.plot(
        [x_lim[0], x_lim[1]],
        [y_lim[1], y_lim[0]],
        linestyle="dashed",
        color="grey",
        alpha=0.5,
        lw=line_weights,
        zorder=self.style_options.diag_lines_zorder,
    )

    diag_ax_font = {
        "fontstyle": "italic",
        "fontsize": "small",
        "fontweight": "bold",
        "color": "black",
        "alpha": 0.5,
    }
    ax.text(
        x_lim[1] / 2,
        y_lim[1] / 2,
        "(vibrant)",
        ha="center",
        va="center",
        fontdict=diag_ax_font,
        zorder=self.style_options.diag_labels_zorder,
    )
    ax.text(
        x_lim[0] / 2,
        y_lim[1] / 2,
        "(chaotic)",
        ha="center",
        va="center",
        fontdict=diag_ax_font,
        zorder=self.style_options.diag_labels_zorder,
    )
    ax.text(
        x_lim[0] / 2,
        y_lim[0] / 2,
        "(monotonous)",
        ha="center",
        va="center",
        fontdict=diag_ax_font,
        zorder=self.style_options.diag_labels_zorder,
    )
    ax.text(
        x_lim[1] / 2,
        y_lim[0] / 2,
        "(calm)",
        ha="center",
        va="center",
        fontdict=diag_ax_font,
        zorder=self.style_options.diag_labels_zorder,
    )

move_legend

move_legend(ax)

Move the legend to the specified location.

Source code in soundscapy/plotting/stylers.py
118
119
120
121
122
123
124
def move_legend(self, ax: mpl.axes.Axes) -> None:
    """Move the legend to the specified location."""
    old_legend = ax.get_legend()
    handles = old_legend.legend_handles
    labels = [t.get_text() for t in old_legend.get_texts()]
    title = old_legend.get_title().get_text()
    ax.legend(handles, labels, loc=self.params.legend_location, title=title)

primary_lines_and_labels

primary_lines_and_labels(ax)

Add primary lines to the plot.

Source code in soundscapy/plotting/stylers.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def primary_lines_and_labels(self, ax: mpl.axes.Axes) -> None:
    """Add primary lines to the plot."""
    line_weights = 1.5

    ax.axhline(
        y=0,
        color="grey",
        linestyle="dashed",
        alpha=1,
        lw=line_weights,
        zorder=self.style_options.prim_lines_zorder,
    )
    ax.axvline(
        x=0,
        color="grey",
        linestyle="dashed",
        alpha=1,
        lw=line_weights,
        zorder=self.style_options.prim_lines_zorder,
    )

set_circum_title

set_circum_title(ax)

Set the title for the circumplex plot.

Source code in soundscapy/plotting/stylers.py
104
105
106
107
def set_circum_title(self, ax: mpl.axes.Axes) -> None:
    """Set the title for the circumplex plot."""
    title_pad = 6.0
    ax.set_title(self.params.title, pad=title_pad)

set_style

set_style()

Set the overall style for the plot.

Source code in soundscapy/plotting/stylers.py
77
78
79
def set_style(self) -> None:
    """Set the overall style for the plot."""
    sns.set_style({"xtick.direction": "in", "ytick.direction": "in"})

StyleOptions dataclass

StyleOptions(diag_lines_zorder=1, diag_labels_zorder=4, prim_lines_zorder=2, data_zorder=3, bw_adjust=1.2, figsize=DEFAULT_FIGSIZE, simple_density=lambda: {'thresh': 0.5, 'levels': 2, 'incl_outline': True, 'alpha': 0.5}())

Configuration options for styling circumplex plots.

Attributes: diag_lines_zorder (int): Z-order for diagonal lines. diag_labels_zorder (int): Z-order for diagonal labels. prim_lines_zorder (int): Z-order for primary lines. data_zorder (int): Z-order for plotted data. bw_adjust (float): Bandwidth adjustment for kernel density estimation. figsize (Tuple[int, int]): Figure size (width, height) in inches. simple_density (Dict[str, Any]): Configuration for simple density plots.

Plotting Utilities

Utility functions and constants for the soundscapy plotting module.

Backend

Bases: Enum

Enum for supported plotting backends.

ExtraParams

Bases: TypedDict

TypedDict for extra parameters passed to plotting functions.

PlotType

Bases: Enum

Enum for supported plot types.

Likert Scale Plotting

Plotting functions for visualising Likert scale data.

paq_radar_plot

paq_radar_plot(data, ax=None, index=None)

Generate a radar/spider plot of PAQ values

PARAMETER DESCRIPTION
data

dataframe of PAQ values recommended max number of values: 3

TYPE: Dataframe

ax

existing subplot axes to plot to, by default None

TYPE: Axes DEFAULT: None

RETURNS DESCRIPTION
Axes

matplotlib Axes with radar plot

Source code in soundscapy/plotting/likert.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def paq_radar_plot(data, ax=None, index=None):
    """Generate a radar/spider plot of PAQ values

    Parameters
    ----------
    data : pd.Dataframe
        dataframe of PAQ values
        recommended max number of values: 3
    ax : matplotlib.pyplot.Axes, optional
        existing subplot axes to plot to, by default None

    Returns
    -------
    plt.Axes
        matplotlib Axes with radar plot
    """
    # TODO: Resize the plot
    # TODO WARNING: Likely broken now
    if index:
        data = data.set_index(index)
    data = data[PAQ_LABELS]
    if ax is None:
        ax = plt.axes(polar=True)
    # ---------- Part 1: create background
    # Number of variables
    categories = [
        "          pleasant",
        "    vibrant",
        "eventful",
        "chaotic    ",
        "annoying          ",
        "monotonous            ",
        "uneventful",
        "calm",
    ]
    N = len(categories)

    # What will be the angle of each axis in the plot (we divide the plot / number of variable)
    angles = [n / float(N) * 2 * pi for n in range(N)]
    angles += angles[:1]

    # Draw one axe per variable + add labels
    plt.xticks(angles[:-1], categories)

    # Draw ylabels
    ax.set_rlabel_position(0)
    plt.yticks([1, 2, 3, 4, 5], ["1", "2", "3", "4", "5"], color="grey", size=8)
    plt.ylim(1, 5)

    # -------- Part 2: Add plots

    # Plot each individual = each line of the data
    fill_col = ["b", "r", "g"]
    for i in range(len(data.index)):
        # Ind1
        values = data.iloc[i].values.flatten().tolist()
        values += values[:1]
        ax.plot(angles, values, linewidth=1, linestyle="solid", label=data.index[i])
        ax.fill(angles, values, fill_col[i], alpha=0.25)

    # Add legend
    ax.legend(loc="upper right", bbox_to_anchor=(0.1, 0.1))
    return ax