From 46c9a33d73f325f3b58157d5213ee434891e31a1 Mon Sep 17 00:00:00 2001 From: Oliver Lindemann Date: Thu, 21 Nov 2024 19:21:42 +0100 Subject: [PATCH] plots --- pynsn/__init__.py | 2 +- pynsn/analysis/__init__.py | 1 - pynsn/analysis/_collection_stats.py | 51 ---------------- pynsn/plots/__init__.py | 3 + pynsn/plots/_collection_plots.py | 93 +++++++++++++++++++++++++++++ pyproject.toml | 4 +- 6 files changed, 100 insertions(+), 54 deletions(-) delete mode 100644 pynsn/analysis/__init__.py delete mode 100644 pynsn/analysis/_collection_stats.py create mode 100644 pynsn/plots/__init__.py create mode 100644 pynsn/plots/_collection_plots.py diff --git a/pynsn/__init__.py b/pynsn/__init__.py index 09723c5..41c06d2 100644 --- a/pynsn/__init__.py +++ b/pynsn/__init__.py @@ -2,7 +2,7 @@ # pylint: disable=C0413 __author__ = "Oliver Lindemann " -__version__ = '1.0.8' +__version__ = '1.0.9-dev1' from sys import version_info as _python_version_info from ._misc import is_interactive_mode as _is_interactive_mode diff --git a/pynsn/analysis/__init__.py b/pynsn/analysis/__init__.py deleted file mode 100644 index bd207d6..0000000 --- a/pynsn/analysis/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._collection_stats import property_regression_sheet diff --git a/pynsn/analysis/_collection_stats.py b/pynsn/analysis/_collection_stats.py deleted file mode 100644 index 59ddd37..0000000 --- a/pynsn/analysis/_collection_stats.py +++ /dev/null @@ -1,51 +0,0 @@ -from math import ceil -from typing import List, Tuple, Union - -import matplotlib.pyplot as plt -import pandas as pd -from scipy.stats import linregress - -from .._stimulus.properties import VP, ensure_vp, VPOrList -from ..collections._coll_stim_pairs import CollectionStimulusPairs - - -def _regression_plot(ax: plt.Axes, df: pd.DataFrame, x: str, y: str): # type: ignore - slope, intercept, r_value, p_value, std_err = linregress(df[x], df[y]) - - ax.scatter(df[x], df[y]) - # Add regression line - reg_line = slope * df[x] + intercept # type: ignore - ax.plot(df[x], reg_line, color='green') - ax.set_title(f"{x}, {y} (r={r_value:.2f})") - return - - -def property_regression_sheet(stim_set: CollectionStimulusPairs, - dv: Union[str, VP], - iv: VPOrList, - ratios: bool = False, - figsize: Tuple[float, float] = (10, 8)): - if isinstance(iv, List): - iv_prop = [ensure_vp(p) for p in iv] - else: - iv_prop = [ensure_vp(iv)] - dv = ensure_vp(dv) - - if ratios: - r = stim_set.property_ratios(iv_prop + [dv]) - else: - r = stim_set.property_differences(iv_prop + [dv]) - - if len(iv_prop) == 1: - n_col = 1 - n_row = 1 - else: - n_col = 2 - n_row = ceil(len(iv_prop)/2) - - fig, axs = plt.subplots(n_row, n_col, figsize=figsize) - for i, p in enumerate(iv_prop): - _regression_plot(axs.flat[i], r, dv.name, p.name) - - plt.tight_layout() - return fig diff --git a/pynsn/plots/__init__.py b/pynsn/plots/__init__.py new file mode 100644 index 0000000..b2b80b4 --- /dev/null +++ b/pynsn/plots/__init__.py @@ -0,0 +1,3 @@ +from ._collection_stats import (property_regression, + property_difference_regression, + property_ratio_regression) diff --git a/pynsn/plots/_collection_plots.py b/pynsn/plots/_collection_plots.py new file mode 100644 index 0000000..d07ab17 --- /dev/null +++ b/pynsn/plots/_collection_plots.py @@ -0,0 +1,93 @@ +from math import ceil +from typing import List, Tuple, Union + +import matplotlib.pyplot as plt +import pandas as pd +from scipy.stats import linregress + +from .._stimulus.properties import VP, ensure_vp, VPOrList +from ..collections._coll_stim_pairs import CollectionStimulusPairs, CollectionStimuli + + +def property_regression(stimuli: CollectionStimuli, + dv: Union[str, VP], + iv: VPOrList, + figsize: Tuple[float, float] = (10, 8)): + + if isinstance(iv, List): + iv_prop = [ensure_vp(p) for p in iv] + else: + iv_prop = [ensure_vp(iv)] + dv = ensure_vp(dv) + + data = stimuli.property_dataframe() + cols = [p.name for p in iv_prop + [dv]] + + return _regression_plots(data[cols], dv=dv, + ivs=iv_prop, figsize=figsize) + + +def property_difference_regression(stim_pairs: CollectionStimulusPairs, + dv: Union[str, VP], + iv: VPOrList, + figsize: Tuple[float, float] = (10, 8)): + + if isinstance(iv, List): + iv_prop = [ensure_vp(p) for p in iv] + else: + iv_prop = [ensure_vp(iv)] + dv = ensure_vp(dv) + + data = stim_pairs.property_differences(iv_prop + [dv]) + return _regression_plots(data, dv=dv, + ivs=iv_prop, figsize=figsize) + + +def property_ratio_regression(stim_pairs: CollectionStimulusPairs, + dv: Union[str, VP], + iv: VPOrList, + figsize: Tuple[float, float] = (10, 8)): + + if isinstance(iv, List): + iv_prop = [ensure_vp(p) for p in iv] + else: + iv_prop = [ensure_vp(iv)] + dv = ensure_vp(dv) + + data = stim_pairs.property_ratios(iv_prop + [dv]) + return _regression_plots(data, dv=dv, + ivs=iv_prop, figsize=figsize) + + +def _regression_plots(data: pd.DataFrame, + dv: VP, + ivs: List[VP], + figsize: Tuple[float, float] = (10, 8)): + + if dv in ivs: + raise ValueError(f"Dependent variable '{dv.name}' is also" + " in list of independent variables.") + if len(ivs) == 1: + n_col = 1 + n_row = 1 + else: + n_col = 2 + n_row = ceil(len(ivs)/2) + + fig, axs = plt.subplots(n_row, n_col, figsize=figsize) + for i, p in enumerate(ivs): + _reg_plot(axs.flat[i], data, dv.name, p.name) + + plt.tight_layout() + return fig + + +def _reg_plot(ax: plt.Axes, df: pd.DataFrame, x: str, y: str): # type: ignore + slope, intercept, r_value, _, _ = linregress(df[x], df[y]) + + ax.scatter(df[x], df[y]) + # Add regression line + reg_line = slope * df[x] + intercept # type: ignore + ax.plot(df[x], reg_line, color='green') + ax.set_title(f"{x}, {y} (r={r_value:.2f})") + return diff --git a/pyproject.toml b/pyproject.toml index e54cc1d..bfc8fe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,13 +12,15 @@ dynamic = ["version", "description"] requires-python = ">=3.10" dependencies = ["numpy>=2.1.3", "pandas>=2.2", + "scipy>=1.14", + "matplotlib>=3.9", "shapely>=2.0", "orjson>=3.10", "svgwrite>=1.4", "Pillow>=11.0"] [project.optional-dependencies] -analysis = ["scipy>=1.14", "matplotlib>=3.9"] +analysis = [] pygame = ["pygame>=2.5"] #expyriment = ["expyriment>=0.10"] test = [