Skip to content

Likert Scale Plotting

This module provides functions for visualizing Likert scale data, including radar plots and stacked Likert plots.

Plotting functions for visualizing Likert scale data.

FUNCTION DESCRIPTION
paq_likert

Create a Likert scale plot for PAQ (Perceived Affective Quality) data.

paq_radar_plot

Generate a radar/spider plot of PAQ values.

paq_likert

paq_likert(data, title='Stacked Likert Plot', paq_cols=PAQ_IDS, *, legend=True, ax=None, plot_percentage=False, bar_labels=True, **kwargs)

Create a Likert scale plot for PAQ (Perceived Affective Quality) data.

PARAMETER DESCRIPTION
data

DataFrame containing PAQ values.

TYPE: DataFrame

paq_cols

List of column names containing PAQ data, by default PAQ_IDS.

TYPE: list[str] DEFAULT: PAQ_IDS

title

Plot title, by default "Stacked Likert Plot".

TYPE: str DEFAULT: 'Stacked Likert Plot'

legend

Whether to show the legend, by default True.

TYPE: bool DEFAULT: True

ax

Matplotlib axes to plot on, by default None.

TYPE: Axes DEFAULT: None

plot_percentage

Whether to show percentages instead of absolute values, by default False.

TYPE: bool DEFAULT: False

bar_labels

Whether to show bar labels, by default True.

TYPE: bool DEFAULT: True

**kwargs

Additional keyword arguments passed to plot_likert.plot_likert.

DEFAULT: {}

RETURNS DESCRIPTION
None

This function does not return anything, it plots directly to the given axes.

Examples:

>>> import soundscapy as sspy
>>> data = sspy.isd.load(['CamdenTown'])
>>> paq_likert(data, "Camden Town Likert data")
>>> plt.show() # xdoctest: +SKIP
Source code in soundscapy/plotting/likert.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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
def paq_likert(
    data: pd.DataFrame,
    title: str = "Stacked Likert Plot",
    paq_cols: list[str] = PAQ_IDS,
    *,
    legend: bool = True,
    ax: Axes | None = None,
    plot_percentage: bool = False,
    bar_labels: bool = True,
    **kwargs,
) -> None:
    """
    Create a Likert scale plot for PAQ (Perceived Affective Quality) data.

    Parameters
    ----------
    data : pd.DataFrame
        DataFrame containing PAQ values.
    paq_cols : list[str], optional
        List of column names containing PAQ data, by default PAQ_IDS.
    title : str, optional
        Plot title, by default "Stacked Likert Plot".
    legend : bool, optional
        Whether to show the legend, by default True.
    ax : Axes, optional
        Matplotlib axes to plot on, by default None.
    plot_percentage : bool, optional
        Whether to show percentages instead of absolute values, by default False.
    bar_labels : bool, optional
        Whether to show bar labels, by default True.
    **kwargs
        Additional keyword arguments passed to plot_likert.plot_likert.

    Returns
    -------
    None
        This function does not return anything, it plots directly to the given axes.

    Examples
    --------
    >>> import soundscapy as sspy
    >>> data = sspy.isd.load(['CamdenTown'])
    >>> paq_likert(data, "Camden Town Likert data")
    >>> plt.show() # xdoctest: +SKIP

    """
    warnings.warn(
        "This is an experimental function. It may change in the future. ",
        ExperimentalWarning,
        stacklevel=2,
    )

    new_data = data[paq_cols].copy()
    new_data = new_data.apply(likert_categorical_from_data, axis=0)  # type: ignore

    if ax is None:
        _, ax = plt.subplots(figsize=(8, 6))

    plot_likert.plot_likert(
        new_data,
        LIKERT_SCALES.paq,
        plot_percentage=plot_percentage,
        ax=ax,
        legend=legend,
        bar_labels=bar_labels,  # show the bar labels
        title=title,
        **kwargs,
    )

paq_radar_plot

paq_radar_plot(data, ax=None, index=None, angles=EQUAL_ANGLES, *, figsize=(8, 8), palette='colorblind', alpha=0.25, linewidth=1.5, linestyle='solid', ylim=(1, 5), title=None, label_pad=15, legend_loc='upper right', legend_bbox_to_anchor=(0.1, 0.1))

Generate a radar/spider plot of PAQ values.

This function creates a radar plot showing PAQ (Perceived Affective Quality) values from a dataframe. The radar plot displays values for all 8 PAQ dimensions arranged in a circular layout.

PARAMETER DESCRIPTION
data

DataFrame containing PAQ values. Must contain columns matching PAQ_LABELS or they will be filtered out.

TYPE: DataFrame

ax

Existing polar subplot axes to plot to. If None, new axes will be created.

TYPE: Axes DEFAULT: None

index

Column(s) to set as index for the data. Useful for labeling in the legend.

TYPE: str DEFAULT: None

figsize

Figure size (width, height) in inches, by default (8, 8). Only used when creating new axes.

TYPE: Tuple[float, float] DEFAULT: (8, 8)

colors

Colors for the plot lines and fills. Can be: - List of color names/values for each data row - Dictionary mapping index values to colors - Single color name/value to use for all data rows - A matplotlib colormap to generate colors from If None, a default colormap will be used.

TYPE: Optional[Union[List[str], Dict[str, str], str, Colormap]]

alpha

Transparency for the filled areas, by default 0.25

TYPE: float DEFAULT: 0.25

linewidth

Width of the plot lines, by default 1.5

TYPE: float DEFAULT: 1.5

linestyle

Style of the plot lines, by default "solid"

TYPE: str DEFAULT: 'solid'

ylim

Y-axis limits (min, max), by default (1, 5) for standard Likert scale

TYPE: Tuple[int, int] DEFAULT: (1, 5)

title

Plot title, by default None

TYPE: str DEFAULT: None

text_padding

Padding for category labels, by default auto-generated

TYPE: Dict[str, int]

legend_loc

Legend location, by default "upper right"

TYPE: str DEFAULT: 'upper right'

legend_bbox_to_anchor

Legend bbox_to_anchor parameter, by default (0.1, 0.1)

TYPE: Tuple[float, float] DEFAULT: (0.1, 0.1)

RETURNS DESCRIPTION
Axes

Matplotlib Axes with radar plot

Examples:

>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> from soundscapy.plotting.likert import paq_radar_plot
>>>
>>> # Sample data with PAQ values for two locations
>>> data = pd.DataFrame({
...     "Location": ["Park", "Street"],
...     "pleasant": [4.2, 2.1],
...     "vibrant": [3.5, 4.2],
...     "eventful": [2.8, 4.5],
...     "chaotic": [1.5, 3.9],
...     "annoying": [1.2, 3.7],
...     "monotonous": [2.5, 1.8],
...     "uneventful": [3.1, 1.9],
...     "calm": [4.3, 1.4]
... })
>>>
>>> # Create radar plot with the "Location" column as index
>>> ax = paq_radar_plot(data, index="Location", title="PAQ Comparison")
>>> plt.show() # xdoctest: +SKIP
Source code in soundscapy/plotting/likert.py
 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
 99
100
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
def paq_radar_plot(
    data: pd.DataFrame,
    ax: Axes | None = None,
    index: str | None = None,
    angles: list[float] | tuple[float, ...] = EQUAL_ANGLES,
    *,
    figsize: tuple[float, float] = (8, 8),
    palette: str | Sequence[str] | None = "colorblind",
    alpha: float = 0.25,
    linewidth: float = 1.5,
    linestyle: str = "solid",
    ylim: tuple[int, int] = (1, 5),
    title: str | None = None,
    label_pad: float | None = 15,
    legend_loc: str = "upper right",
    legend_bbox_to_anchor: tuple[float, float] | None = (0.1, 0.1),
) -> Axes:
    """
    Generate a radar/spider plot of PAQ values.

    This function creates a radar plot showing PAQ (Perceived Affective Quality)
    values from a dataframe. The radar plot displays values for all 8 PAQ dimensions
    arranged in a circular layout.

    Parameters
    ----------
    data : pd.DataFrame
        DataFrame containing PAQ values. Must contain columns matching PAQ_LABELS
        or they will be filtered out.
    ax : matplotlib.pyplot.Axes, optional
        Existing polar subplot axes to plot to. If None, new axes will be created.
    index : str, optional
        Column(s) to set as index for the data. Useful for labeling in the legend.
    figsize : Tuple[float, float], optional
        Figure size (width, height) in inches, by default (8, 8).
        Only used when creating new axes.
    colors : Optional[Union[List[str], Dict[str, str], str, Colormap]], optional
        Colors for the plot lines and fills. Can be:
        - List of color names/values for each data row
        - Dictionary mapping index values to colors
        - Single color name/value to use for all data rows
        - A matplotlib colormap to generate colors from
        If None, a default colormap will be used.
    alpha : float, optional
        Transparency for the filled areas, by default 0.25
    linewidth : float, optional
        Width of the plot lines, by default 1.5
    linestyle : str, optional
        Style of the plot lines, by default "solid"
    ylim : Tuple[int, int], optional
        Y-axis limits (min, max), by default (1, 5) for standard Likert scale
    title : str, optional
        Plot title, by default None
    text_padding : Dict[str, int], optional
        Padding for category labels, by default auto-generated
    legend_loc : str, optional
        Legend location, by default "upper right"
    legend_bbox_to_anchor : Tuple[float, float], optional
        Legend bbox_to_anchor parameter, by default (0.1, 0.1)

    Returns
    -------
    plt.Axes
        Matplotlib Axes with radar plot

    Examples
    --------
    >>> import pandas as pd
    >>> import matplotlib.pyplot as plt
    >>> from soundscapy.plotting.likert import paq_radar_plot
    >>>
    >>> # Sample data with PAQ values for two locations
    >>> data = pd.DataFrame({
    ...     "Location": ["Park", "Street"],
    ...     "pleasant": [4.2, 2.1],
    ...     "vibrant": [3.5, 4.2],
    ...     "eventful": [2.8, 4.5],
    ...     "chaotic": [1.5, 3.9],
    ...     "annoying": [1.2, 3.7],
    ...     "monotonous": [2.5, 1.8],
    ...     "uneventful": [3.1, 1.9],
    ...     "calm": [4.3, 1.4]
    ... })
    >>>
    >>> # Create radar plot with the "Location" column as index
    >>> ax = paq_radar_plot(data, index="Location", title="PAQ Comparison")
    >>> plt.show() # xdoctest: +SKIP

    """
    # Input validation
    if not isinstance(data, pd.DataFrame):
        msg = "The 'data' parameter must be a pandas DataFrame"
        raise TypeError(msg)

    # Set index if provided
    if index is not None:
        data = data.set_index(index)

    # Filter to only include columns that match PAQ_LABELS
    # This handles cases where the data might have extra columns
    data = rename_paqs(data, paq_aliases=PAQ_LABELS)
    data = return_paqs(data, incl_ids=False)

    # Create axes if needed
    if ax is None:
        fig = plt.figure(figsize=figsize)
        ax = fig.add_subplot(111, polar=True)

    # ---------- Part 1: Create background
    # Calculate angles for each axis
    rad_angles = np.deg2rad(angles)

    # Draw one axis per variable + add labels
    plt.xticks(rad_angles, PAQ_LABELS)
    ax.tick_params(axis="x", pad=label_pad)

    # Draw y-labels
    ax.set_rlabel_position(0)  # type: ignore[reportAttributeAccessIssues]
    y_ticks = list(range(ylim[0], ylim[1] + 1))
    plt.yticks(y_ticks, [str(y) for y in y_ticks], color="grey", size=8)
    plt.ylim(*ylim)

    # Add title if provided
    if title:
        ax.set_title(title, pad=2.5 * label_pad if label_pad else 20, fontsize=16)

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

    # Need to add the first value to the end of the data to close the loop
    ext_angles = [*list(rad_angles), rad_angles[0]]
    # Plot each row of data
    with sns.color_palette(palette) as plot_colors:
        for i, (idx, row) in enumerate(data.iterrows()):
            if i == 4:  # noqa: PLR2004
                warnings.warn(
                    "More than 4 sets of data may not be visually clear.", stacklevel=2
                )

            # Extract values and duplicate the first value at the end to close the loop
            values = row.to_numpy().flatten().tolist()
            values += values[:1]

            # Get current color
            color = plot_colors[i]

            # Plot values
            ax.plot(
                ext_angles,
                values,
                linewidth=linewidth,
                linestyle=linestyle,
                color=color,
                label=idx,
            )
            ax.fill(ext_angles, values, color=color, alpha=alpha)

    # Add legend
    if legend_bbox_to_anchor:
        ax.legend(loc=legend_loc, bbox_to_anchor=legend_bbox_to_anchor)
    else:
        ax.legend(loc=legend_loc)

    plt.tight_layout()

    return ax

show_submodules: true