SATP¶
soundscapy.satp
¶
Soundscape Attributes Translation (SATP) calculation module.
This module provides functions and classes for conducting the SATP analysis, based on the R implementation. Requires optional dependencies.
| MODULE | DESCRIPTION |
|---|---|
circe |
Circumplex SEM Analysis for Soundscape Attributes Translation Project (SATP). |
CircE¶
soundscapy.satp.circe
¶
Circumplex SEM Analysis for Soundscape Attributes Translation Project (SATP).
This module provides tools for analyzing soundscape perception data using circumplex Structural Equation Modeling (SEM). It includes data validation schemas, model fitting classes, and analysis workflows for the Soundscape Attributes Translation Project.
The module supports various circumplex model types (unconstrained, equal angles, equal communalities, and full circumplex) and provides automated data preprocessing including within-person centering (column-wise centering per participant).
| FUNCTION | DESCRIPTION |
|---|---|
normalize_polar_angles |
Correct reflected polar-angle solutions to canonical orientation |
person_center |
Column-wise within-participant centering of PAQ ratings |
fit_circe |
Fit circumplex SEM models and return a tidy DataFrame |
| CLASS | DESCRIPTION |
|---|---|
CircModelE : Enum |
Enumeration of available circumplex model types |
SATPSchema : DataFrameModel |
Pandera schema for validating SATP data format |
CircE : dataclass |
Results container for a fitted circumplex model |
CircModelE
¶
Bases: StrEnum
Enumeration of circumplex model types.
| ATTRIBUTE | DESCRIPTION |
|---|---|
equal_ang |
Whether this model constrains all angles to be equally spaced.
TYPE:
|
equal_com |
Whether this model constrains all communalities to be equal.
TYPE:
|
equal_ang
property
¶
Whether this model constrains all angles to be equally spaced.
True for EQUAL_ANG and CIRCUMPLEX; False for UNCONSTRAINED and EQUAL_COM.
equal_com
property
¶
Whether this model constrains all communalities to be equal.
True for EQUAL_COM and CIRCUMPLEX; False for UNCONSTRAINED and EQUAL_ANG.
SATPSchema
¶
Bases: DataFrameModel
Pandera schema for validating SATP (Soundscape Attributes Translation Project) data.
This schema validates DataFrame columns containing PAQ ratings and participant identifiers. PAQ ratings must be between 0 and 100.
| CLASS | DESCRIPTION |
|---|---|
Config |
Configuration for the schema validation behavior. |
| METHOD | DESCRIPTION |
|---|---|
column_alias |
Parse and rename DataFrame columns to match the schema. |
Config
¶
Configuration for the schema validation behavior.
column_alias
¶
Parse and rename DataFrame columns to match the schema.
Uses _COLUMN_ALIASES (module-level constant) for a single-pass
case-insensitive lookup. Handles PAQ label names (e.g. "pleasant"
or "Pleasant" → "PAQ1"), PAQ IDs in any case ("paq1" →
"PAQ1"), and the participant field in any capitalisation
("PARTICIPANT" → "participant").
| PARAMETER | DESCRIPTION |
|---|---|
df
|
Input DataFrame to rename columns for
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
|
Source code in src/soundscapy/satp/circe.py
CircE
dataclass
¶
CircE(
model: CircModelE,
datasource: str,
language: str,
n: int,
m: int | None,
chisq: float | None,
d: int | None,
p: float | None,
cfi: float | None,
gfi: float | None,
agfi: float | None,
srmr: float | None,
mcsc: float | None,
rmsea: float | None,
rmsea_l: float | None,
rmsea_u: float | None,
polar_angles: Series | None = None,
)
Results container for a fitted CircE (circumplex SEM) model.
| ATTRIBUTE | DESCRIPTION |
|---|---|
model |
The circumplex model type that was fitted.
TYPE:
|
datasource |
Source identifier for the dataset.
TYPE:
|
language |
Language code for the dataset.
TYPE:
|
n |
Number of observations (complete cases) used to fit the model.
TYPE:
|
m |
Number of common factors.
TYPE:
|
chisq |
Chi-squared fit statistic.
TYPE:
|
d |
Model degrees of freedom.
TYPE:
|
p |
p-value for the chi-squared statistic.
TYPE:
|
cfi |
Comparative Fit Index.
TYPE:
|
gfi |
Goodness of Fit Index.
TYPE:
|
agfi |
Adjusted Goodness of Fit Index.
TYPE:
|
srmr |
Standardised Root Mean Square Residual.
TYPE:
|
mcsc |
Mean Communality Squared Cosines.
TYPE:
|
rmsea |
Root Mean Square Error of Approximation.
TYPE:
|
rmsea_l |
Lower bound of the 90% confidence interval for RMSEA.
TYPE:
|
rmsea_u |
Upper bound of the 90% confidence interval for RMSEA.
TYPE:
|
polar_angles |
Estimated polar angles (degrees) for each PAQ item, with PAQ_IDS as
the index. Only available for models with free angle parameters
(UNCONSTRAINED, EQUAL_COM).
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
from_bfgs |
Create a CircE instance from BFGS fit output. |
compute_bfgs_fit |
Compute and return a CircE from the given correlation matrix. |
to_dict |
Return all model fit statistics as a flat dictionary. |
gdiff
property
¶
RMSD between fitted polar angles and ideal circumplex spacing.
Measures how closely the unconstrained angle estimates match perfect
45°-spaced circumplex positions. Only defined for models with free
angles (UNCONSTRAINED, EQUAL_COM); returns None for EQUAL_ANG and
CIRCUMPLEX (where polar_angles is None).
A smaller value indicates better agreement with circumplex structure.
| RETURNS | DESCRIPTION |
|---|---|
float | None
|
Rounded RMSD value (2 decimal places), or |
from_bfgs
classmethod
¶
from_bfgs(
fit_stats: Mapping[str, Any] | ListVector,
datasource: str,
language: str,
circ_model: CircModelE,
n: int,
) -> CircE
Create a CircE instance from BFGS fit output.
Deprecated v0.8.4
Passing an rpy2 ListVector as fit_stats is deprecated and
will be removed in a future release.
Call :func:soundscapy.r_wrapper.bfgs_fit to obtain a dict and
pass that instead.
| PARAMETER | DESCRIPTION |
|---|---|
fit_stats
|
Either a pre-extracted |
datasource
|
Source identifier for the dataset.
TYPE:
|
language
|
Language code for the dataset.
TYPE:
|
circ_model
|
Circumplex model type that was fitted.
TYPE:
|
n
|
Number of observations used to compute the correlation matrix.
TYPE:
|
Source code in src/soundscapy/satp/circe.py
270 271 272 273 274 275 276 277 278 279 280 281 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 321 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 | |
compute_bfgs_fit
classmethod
¶
compute_bfgs_fit(
data_cor: DataFrame,
n: int,
datasource: str,
language: str,
circ_model: CircModelE,
) -> CircE
Compute and return a CircE from the given correlation matrix.
| PARAMETER | DESCRIPTION |
|---|---|
data_cor
|
Correlation matrix of the PAQ data (8x8).
TYPE:
|
n
|
Number of observations used to compute
TYPE:
|
datasource
|
Source identifier for the dataset.
TYPE:
|
language
|
Language code for the dataset.
TYPE:
|
circ_model
|
Circumplex model type to fit.
TYPE:
|
Examples:
>>> import soundscapy as sspy
>>> data = sspy.isd.load()
>>> data_paqs = data[PAQ_IDS]
>>> data_paqs = data_paqs.dropna()
>>> data_cor = data_paqs.corr()
>>> n = len(data_paqs)
>>> circ_model = sspy.satp.CircModelE.CIRCUMPLEX
>>> circe_res = sspy.satp.CircE.compute_bfgs_fit(
... data_cor, n, "ISD", "EN", circ_model)
...
Source code in src/soundscapy/satp/circe.py
to_dict
¶
Return all model fit statistics as a flat dictionary.
Polar angle columns (PAQ1-PAQ8) are expanded as individual keys.
For models with fixed angles (EQUAL_ANG, CIRCUMPLEX), PAQ values
are None.
| RETURNS | DESCRIPTION |
|---|---|
dict[str, Any]
|
Flat dictionary suitable for constructing a pandas DataFrame row. |
Source code in src/soundscapy/satp/circe.py
CircEResults
dataclass
¶
CircEResults(
models: list[CircE],
language: str,
datasource: str,
error_rows: list[dict] = list(),
)
Collection of fitted CircE models returned by fit_circe.
Holds both successfully-fitted CircE instances and any error rows
from models that failed to converge. Access the full tidy DataFrame via
table; access individual model results via for_model.
| ATTRIBUTE | DESCRIPTION |
|---|---|
models |
Successfully-fitted |
language |
Language code passed to
TYPE:
|
datasource |
Dataset identifier passed to
TYPE:
|
error_rows |
Dicts for model runs that raised an exception during fitting.
Each dict contains |
| METHOD | DESCRIPTION |
|---|---|
__len__ |
Total number of model runs (successful + failed). |
for_model |
Return the fitted |
table
property
¶
Full tidy DataFrame of all model fit statistics.
One row per model (including error rows). Columns match those
described in fit_circe. Integer columns (n, d, m)
use pandas nullable Int64 dtype so that None in error rows does
not promote the whole column to float64.
__len__
¶
for_model
¶
Return the fitted CircE result for a specific model type.
| PARAMETER | DESCRIPTION |
|---|---|
model
|
The
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If no successful result exists for the requested model (e.g. it failed to converge). |
Source code in src/soundscapy/satp/circe.py
normalize_polar_angles
¶
Return polar angles in canonical (counter-clockwise) orientation.
CircE's BFGS optimisation may converge to a mathematically equivalent reflected solution in which the PAQ attributes are arranged in clockwise (decreasing) order rather than the canonical counter-clockwise (increasing) order. Both solutions fit the correlation data equally well, but the reflected form is inconsistent with the standard circumplex ordering (pleasant → vibrant → eventful → …) and will produce incorrect GDIFF values if compared against the ideal equally-spaced angles.
Detection uses a monotonicity check on the first three angles after PAQ1: if the angle for PAQ2 exceeds PAQ3, or PAQ3 exceeds PAQ4, the solution is reflected. This is more robust than a threshold-on-sum heuristic because it tests the structural property of the orientation directly.
When reflection is detected, 360 - angle is applied to PAQ2-PAQ8.
PAQ1 is anchored at 0° and is left unchanged.
| PARAMETER | DESCRIPTION |
|---|---|
angles
|
Series of polar angle estimates (degrees) with PAQ_IDS as the index.
Typically the
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Series
|
Polar angles in canonical (counter-clockwise) orientation, with the same index as the input. |
Examples:
>>> from soundscapy.surveys.survey_utils import PAQ_IDS
>>> import pandas as pd
>>> reflected = pd.Series(
>>> [0.0, 315.0, 270.0, 225.0, 180.0, 135.0, 90.0, 45.0],
>>> index=PAQ_IDS)
>>> normalize_polar_angles(reflected).tolist()
[0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0]
Source code in src/soundscapy/satp/circe.py
person_center
¶
Center PAQ ratings within each participant (column-wise within-person centering).
Deprecated v0.8.0
Use soundscapy.surveys.ipsatize with method="column_wise"
instead. For the centering that matches the published SATP analysis,
use method="grand_mean" (the default of
~soundscapy.surveys.ipsatize).
This function applies column-wise centering: for every PAQ column independently, each participant's mean across their observations is subtracted (8 centering scalars per participant).
Note
This is not the centering described in the original SATP R
implementation (Aletta et al., 2024), which applies grand-mean
centering (one scalar per participant across all PAQ columns and
observations). Use soundscapy.surveys.ipsatize with
method="grand_mean" to match the R reference implementation.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
DataFrame containing PAQ columns and a participant grouping column.
TYPE:
|
by
|
Column to group by for centering. Default is
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
DataFrame containing only the PAQ columns (not |
Source code in src/soundscapy/satp/circe.py
fit_circe
¶
fit_circe(
data: DataFrame,
language: str,
datasource: str,
*,
models: list[CircModelE] | None = None,
center_by_participant: bool = True,
errors: Literal["raise", "warn"] = "raise",
) -> CircEResults
Fit circumplex SEM models to PAQ data and return a tidy DataFrame.
Validates input data, optionally applies grand-mean within-person centering
(matching the published SATP analysis), computes a complete-case correlation
matrix, and fits the requested circumplex model types using Browne's BFGS
optimisation via the R CircE package.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
DataFrame with PAQ1-PAQ8 and a
TYPE:
|
language
|
Language code for the dataset (e.g.
TYPE:
|
datasource
|
Dataset identifier (e.g.
TYPE:
|
models
|
List of model types to fit. Default: all four
TYPE:
|
center_by_participant
|
Whether to apply grand-mean within-person centering (via
TYPE:
|
errors
|
How to handle rows that fail schema validation (PAQ values outside
Note If you pass already-centered data, set
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
CircEResults
|
Collection of fitted models. Access the tidy DataFrame via
|
Examples:
>>> import soundscapy as sspy
>>> from soundscapy.satp import fit_circe
>>> data = sspy.isd.load()
>>> data = data.rename(columns={'SessionID': 'participant'})
>>> results = fit_circe(data, language='eng', datasource='ISD', errors='warn')
>>> len(results)
4
Source code in src/soundscapy/satp/circe.py
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 | |