diff --git a/src/soundscapy/r_wrapper/RTHORR.R b/src/soundscapy/r_wrapper/RTHORR.R deleted file mode 100644 index 88a2b60..0000000 --- a/src/soundscapy/r_wrapper/RTHORR.R +++ /dev/null @@ -1,295 +0,0 @@ -#' randall function -#' -#' Randomization test of hypothesized order relations -#' -#' @param df_list list of dataframes to be run, dataframes input should have only columns for the variables included in the models (e.g., 6 columns for RIASEC) -#' @param description any information you wish to enter describing each sample -#' @param ord prediction ordering (default "circular6"). -#' For circular models there are two preset inputs for 6 and 8 variables, "circular6" and "circular8". -#' Also accepts a vector of prediction ordering, for example circular6 = c(1,2,3,2,1,1,2,3,2,1,2,3,1,2,1) -#' -#' -#' @return Data frame of RTHOR model results, one row per matrix -#' -#' @examples -#' randall_output <- RTHORR::randall_from_df(df_list = list(data1, data2), -#' ord = "circular6", -#' description = c("sample_one", "sample_two", "sample_three")) -#' -#' @import permute -#' @import gdata -#' -#' @export - - - - -my_randall_from_df<-function(df_list, description, ord = "circular8"){ - - #make this its own function? - #check that the input dataframes all have the same number of columns - column_count <- vector(length = 0) - for (d in df_list){ - column_count <- append(column_count, ncol(d)) - } - if (length(unique(column_count))!=1){ - stop("Number of columns is not equal for all dataframes passed in.") - } - - #check that number of descriptions is the same as the number of data frames passed in - if (length(df_list) != length(description)){ - stop("Number of descriptions not equal to number of dataframes.") - } - - - - #get number of matrixes to analyze - nmat <- length(df_list) - - #get number of columns - n <- ncol(df_list[[1]]) - - - #process ord input - if (ord == "circular6"){ - ord = c(1,2,3,2,1,1,2,3,2,1,2,3,1,2,1) - } else if (ord == "circular8"){ - ord=c(1,2,3,4,3,2,1,1,2,3,4,3,2,1,2,3,4,3,1,2,3,4,1,2,3,1,2,1) - } else { - ord = ord - } - - - # library("permute") - setMaxperm<-(50000) - requireNamespace("utils") - np<- ((n*n)-n)/2 - - - #read input file - #read in diagonal matrix as vector and then fill in - # za<-scan(input) #goodf read in vector - - #reverse order of df_list - df_list <- rev(df_list) - - - #empty vector for correlations - za <- vector(length = 0) - for (d in df_list){ - #run correlations and turn into vector - cor_df <- cor(d) - za <- append(gdata::lowerTriangle(cor_df, diag = TRUE, byrow = TRUE), za) - } - - - - dmatm<-array(dim=c(n,n,nmat)) - for (m in 1:nmat){ - ii<-(m-1)*(np+n) - - for(j in 1:n){ - for(i in 1:n){ - if (i > j) next - ii<-ii+1 - dmatm[i,j,m]<-za[ii]}} #set up matrix - - - ii<-(m-1)*(np+n) - for(i in 1:n){ - for(j in 1:n){ - if (i < j) next - ii<-ii+1 - dmatm[i,j,m]<-za[ii]}} #fill in - } #end nmat loop - - - #mathhyp generation - mathyp<-matrix(nrow=np,ncol=np) - for(i in 1:np){ - for(j in 1:np){ - if(ord[j] 50000)nper<-50000 - permat<-matrix(nrow=nper,ncol=n) - - zz<-matrix(nrow=1,ncol=n) - f<-function (zz){ - zz<-sample.int(n,n,replace=FALSE,prob=NULL) - return(zz)} - - if(nper<50000)permat<-permute::allPerms(n, control = permute::how(maxperm = 50000), check = TRUE) - - if(nper>=50000) for(ll in 1:nper){permat[ll,]<-t(apply(zz,1,f))} - - - - #do big loop over nmat kk - for(kk in 1:nmat){ - - #select matrix from array - - dmat<-dmatm[,,kk] - - #run on original data prior to permutations - #do data 1,0 matc gt=1 eq =2 - - - nagr<-0 - ntie<-0 - - ii=0 - scal<-vector(length=np) - for(i in 1:n){ - for(j in 1:n){ - if (i >= j) next - ii<-ii+1 - scal[ii]<-dmat[i,j]}} - - #match data matc with mathyp 1=conf 2=tie, 3=less - - matc <-matrix(nrow=np,ncol=np) - for(i in 1:np){ - for(j in 1:np){ - if(scal[j] > scal[i]) matc[i,j]<-1 - if(scal[j] == scal[i]) matc[i,j]<-2 - if(scal[j] < scal[i]) matc[i,j]<-0}} - nsup<-0 - nntie<-0 - for(i in 1:np){ - for(j in 1:np){ - if(matc[i,j]==1 & mathyp[i,j]==1) nagr<-nagr+1 - if(matc[i,j]==2 & mathyp[i,j]==1) ntie<-ntie+1}} - - ci<- (nagr-(nhyp-(nagr+ntie)))/nhyp - - - count<- 1 #counter for number exceed values in original permuation - - nperx<-nper-1 ##check on this correction - - #do small loop over nper - for(k in 1:nperx){ #minus 1 - # if(k >50000) break #stop if greater than 50,000 - - #set up new matrix - pp<-permat[k,] - - for(i in 1:n){ - for(j in 1:n){ - dmatp[i,j]<-dmat[pp[i],pp[j]] - }} - - #do data 1,0 matc gt=1 eq =2 - - ii=0 - scal2<-vector(length=np) - for(i in 1:n){ - for(j in 1:n){ - if (i >= j) next - ii<-ii+1 - scal2[ii]<-dmatp[i,j]}} - - ################################ - - #match data matc with mathyp 1=conf 2=tie, 3=less - - # Replace first pair of nested for loops with vectorized operations - matc2 <- matrix(nrow = np, ncol = np) - matc2[] <- as.numeric(scal2[rep(1:np, each = np)] > scal2[rep(1:np, np)]) - matc2[scal2[rep(1:np, each = np)] == scal2[rep(1:np, np)]] <- 2 - - # Replace second pair of nested for loops with vectorized operations - nsup <- sum(matc2 == 1 & mathyp == 1) - nntie <- sum(matc2 == 2 & mathyp == 1) - - ################## - - if(nsup >= nagr) count <- count+1 #count number of cases where fit is equal or greater - - - } #end first loop kz - prob<-count/nper - - out[kk,1]<-kk - out[kk,2]<-nhyp - out[kk,3]<-nagr - out[kk,4]<-ntie - out[kk,5]<-ci - out[kk,6]<-prob - # out[kk,7]<-samp[kk] - out[kk,7]<-description[kk] - - - }#end loop kk different matrices - - colnames(out)<-c("mat","pred","met","tie","CI","p","description") - rownames(out)<-c(1:nmat) - # print(out,quote=FALSE) - - - out <- data.frame(out) - out[,1:6] <- sapply(out[,1:6],as.numeric) - - # out %>% mutate_at('matrixnumber', as.numeric) - - return(out) -} #end randall - - -reorder_mat <- function(mat, order){ - # Author: https://rdrr.io/cran/graph4lg/src/R/reorder_mat.R - # Number of elements in the vector 'order' - n <- length(order) - - # Check whether 'mat' is a 'matrix' - if(!inherits(mat, "matrix")){ - stop("'mat' must be a matrix") - # Check whether 'order' is of class 'character' - } else if (!inherits(order, "character")){ - stop("'order' must be a character vector") - # Check whether 'mat' is a symmetric matrix - } else if(!(isSymmetric(mat))){ - stop("The matrix 'mat' must be symmetric") - # Check whether 'order' has as many elements as there are rows - # and columns in 'mat' - } else if (n != length(colnames(mat))){ - stop("'order' must have as many elements as there are rows and - columns in 'mat'") - # Check whether the column names are in the 'order' vector - } else if(length(which(colnames(mat) %in% order)) != n){ - stop("The column names of the matrix you want to reorder must - be present in the vector 'order'") - # Check whether the row names are in the 'order' vector - } else if (length(which(row.names(mat) %in% order)) != n){ - print("The row names of the matrix you want to reorder must - be present in the vector 'order'") - } else { - - # Reorder 'mat' according to 'order' - mat2 <- mat[order, order] - - return(mat2) - } -} diff --git a/src/soundscapy/r_wrapper/_circe_wrapper.py b/src/soundscapy/r_wrapper/_circe_wrapper.py index a0aa53e..f9a07a7 100644 --- a/src/soundscapy/r_wrapper/_circe_wrapper.py +++ b/src/soundscapy/r_wrapper/_circe_wrapper.py @@ -9,7 +9,7 @@ logger = get_logger() -_, _, _stats_package, _base_package, circe, _ = get_r_session() +_, _, _stats_package, _base_package, circe = get_r_session() logger.debug("R session and packages retrieved successfully.") diff --git a/src/soundscapy/r_wrapper/_r_wrapper.py b/src/soundscapy/r_wrapper/_r_wrapper.py index 73db939..896d281 100644 --- a/src/soundscapy/r_wrapper/_r_wrapper.py +++ b/src/soundscapy/r_wrapper/_r_wrapper.py @@ -28,13 +28,11 @@ _r_checked = False _sn_checked = False _circe_checked = False -_rthorr_checked = False # Session state _r_session = None _sn_package = None _circe_package = None -_rthorr_package = None _stats_package = None _base_package = None _session_active = False @@ -44,7 +42,6 @@ class PKG_SRC(str, Enum): CIRCE = "'MitchellAcoustics/CircE-R'" - RTHORR = "'michaellynnmorris/RTHORR'" def check_r_availability() -> None: @@ -252,64 +249,6 @@ def _raise_sn_check_error(e: Exception) -> NoReturn: _raise_sn_check_error(e) -def check_rthorr_package() -> None: - """ - Check if the R 'RTHORR' package is installed. - - Raises - ------ - ImportError - If the 'RTHORR' package is not installed. - - """ - global _rthorr_checked - - def _raise_rthorr_not_installed_error() -> NoReturn: - msg = ( - "R package 'RTHORR' is not installed. " - f"Please install it by running in R: devtools::install_github({PKG_SRC.RTHORR.value})" # noqa: E501 - ) - raise ImportError(msg) - - def _raise_rthorr_check_error(e: Exception) -> NoReturn: - msg = ( - f"Error checking for R 'RTHORR' package: {e!s}. " - f"Please ensure the package is installed by running in R: devtools::install_github({PKG_SRC.RTHORR.value})" # noqa: E501 - ) - raise ImportError(msg) - - if _rthorr_checked: - return - - # First ensure R is available - check_r_availability() - - try: - import rpy2.robjects.packages as rpackages - - # Check if 'CircE' package is installed - try: - # Just importing to verify it exists - _ = rpackages.importr("RTHORR") - - # Get package version using R to verify compatibility - from rpy2 import robjects - - # Use R code to get the package version - version = robjects.r('as.character(packageVersion("RTHORR"))')[0] # type: ignore[index] - logger.debug("R 'RTHORR' package version: %s", version) - - _rthorr_checked = True - - except rpackages.PackageNotInstalledError: - _raise_rthorr_not_installed_error() - - except Exception as e: - if "RTHORR" in str(e): - raise # Re-raising is okay here - _raise_rthorr_check_error(e) - - def check_dependencies() -> dict[str, Any]: """ Check all required R dependencies for the SPI module. @@ -340,9 +279,6 @@ def check_dependencies() -> dict[str, Any]: # Then check for the CircE package check_circe_package() - # Then check for the RTHORR package - check_rthorr_package() - # If we get here, all dependencies are available # Return information about the dependencies @@ -351,7 +287,6 @@ def check_dependencies() -> dict[str, Any]: "r_version": robjects.r("R.version.string")[0], # type: ignore[index] "sn_version": robjects.r('as.character(packageVersion("sn"))')[0], # type: ignore[index] "circe_version": robjects.r('as.character(packageVersion("CircE"))')[0], # type: ignore[index] - "rthorr_version": robjects.r('as.character(packageVersion("RTHORR"))')[0], # type: ignore[index] } @@ -381,7 +316,7 @@ def initialize_r_session() -> dict[str, Any]: If session initialization fails. """ - global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package, _rthorr_package # noqa: E501, PLW0603 + global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package # noqa: E501, PLW0603 # If session is already active, just return the state if _session_active: @@ -392,7 +327,6 @@ def initialize_r_session() -> dict[str, Any]: "stats_package": "loaded", "base_package": "loaded", "circe_package": "loaded", - "rthorr_package": "loaded", } # First check all dependencies @@ -406,10 +340,9 @@ def initialize_r_session() -> dict[str, Any]: # Import required packages _sn_package = rpackages.importr("sn") _circe_package = rpackages.importr("CircE") - _rthorr_package = rpackages.importr("RTHORR") _stats_package = rpackages.importr("stats") _base_package = rpackages.importr("base") - logger.debug("Imported R packages: sn, CircE, RTHORR, stats, base") + logger.debug("Imported R packages: sn, CircE, stats, base") # Set R random seed for reproducibility robjects.r("set.seed(42)") @@ -439,7 +372,6 @@ def initialize_r_session() -> dict[str, Any]: "stats_package": str(_stats_package), "base_package": str(_base_package), "circe_package": str(_circe_package), - "rthorr_package": str(_rthorr_package), **dep_info, } @@ -451,7 +383,6 @@ def initialize_r_session() -> dict[str, Any]: _stats_package = None _base_package = None _circe_package = None - _rthorr_package = None msg = f"Failed to initialize R session: {e!s}" raise RuntimeError(msg) from e @@ -471,7 +402,7 @@ def shutdown_r_session() -> bool: True if successful, False otherwise. """ - global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package, _rthorr_package # noqa: E501, PLW0603 + global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package # noqa: E501, PLW0603 if not _session_active: logger.debug("No active R session to shutdown") @@ -486,7 +417,6 @@ def shutdown_r_session() -> bool: _stats_package = None _base_package = None _circe_package = None - _rthorr_package = None # Update session state _session_active = False @@ -502,7 +432,7 @@ def shutdown_r_session() -> bool: return True -def get_r_session() -> tuple[Any, Any, Any, Any, Any, Any]: +def get_r_session() -> tuple[Any, Any, Any, Any, Any]: """ Get the current R session and package objects. @@ -512,8 +442,8 @@ def get_r_session() -> tuple[Any, Any, Any, Any, Any, Any]: Returns ------- - tuple[Any, Any, Any, Any] - (r_session, sn_package, stats_package, base_package) + tuple[Any, Any, Any, Any, Any] + (r_session, sn_package, stats_package, base_package, circe_package) Raises ------ @@ -521,7 +451,7 @@ def get_r_session() -> tuple[Any, Any, Any, Any, Any, Any]: If session initialization fails. """ - global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package, _rthorr_package # noqa: E501, PLW0602 + global _r_session, _sn_package, _stats_package, _base_package, _session_active, _circe_package # noqa: E501, PLW0602 if not _session_active: logger.debug("R session not active, initializing") @@ -534,7 +464,6 @@ def get_r_session() -> tuple[Any, Any, Any, Any, Any, Any]: or not _stats_package or not _base_package or not _circe_package - or not _rthorr_package ): msg = "Failed to initialize R session" raise RuntimeError(msg) @@ -545,7 +474,6 @@ def get_r_session() -> tuple[Any, Any, Any, Any, Any, Any]: _stats_package, _base_package, _circe_package, - _rthorr_package, ) @@ -565,7 +493,7 @@ def install_r_packages(packages: list[str] | None = None) -> None: """ if packages is None: - packages = ["sn", "tvtnorm", "CircE", "RTHORR"] + packages = ["sn", "tvtnorm", "CircE"] check_r_availability() @@ -588,11 +516,6 @@ def install_r_packages(packages: list[str] | None = None) -> None: devtools.install_github(PKG_SRC.CIRCE) packnames_to_install.remove("CircE") logger.info("Installed R package 'CircE' from GitHub") - if "RTHORR" in packnames_to_install: - devtools = rpackages.importr("devtools") - devtools.install_github(PKG_SRC.RTHORR) - packnames_to_install.remove("RTHORR") - logger.info("Installed R package 'RTHORR' from GitHub") utils.install_packages(StrVector(packnames_to_install)) logger.info("Installed missing R packages: %s", packnames_to_install) diff --git a/src/soundscapy/r_wrapper/_rsn_wrapper.py b/src/soundscapy/r_wrapper/_rsn_wrapper.py index a975ff7..c42493c 100644 --- a/src/soundscapy/r_wrapper/_rsn_wrapper.py +++ b/src/soundscapy/r_wrapper/_rsn_wrapper.py @@ -11,7 +11,7 @@ logger = get_logger() -_, sn, _, _, _, _ = get_r_session() +_, sn, _, _, _ = get_r_session() logger.debug("R session and packages retrieved successfully.") diff --git a/src/soundscapy/r_wrapper/_rthorr_wrapper.py b/src/soundscapy/r_wrapper/_rthorr_wrapper.py deleted file mode 100644 index ef89f2f..0000000 --- a/src/soundscapy/r_wrapper/_rthorr_wrapper.py +++ /dev/null @@ -1,10 +0,0 @@ -from soundscapy.sspylogging import get_logger - -from ._r_wrapper import get_r_session, install_r_packages - -logger = get_logger() - -install_r_packages() - -_, _, _, _, _, rthorr = get_r_session() -logger.debug("R session and packages retrieved successfully.") diff --git a/test/spi/test_r_wrapper.py b/test/spi/test_r_wrapper.py index 64808ba..ae046aa 100644 --- a/test/spi/test_r_wrapper.py +++ b/test/spi/test_r_wrapper.py @@ -105,20 +105,3 @@ def test_check_circe_package(self): assert "R package 'CircE'" in str(excinfo.value) assert sspyr.PKG_SRC.CIRCE in str(excinfo.value) - - def test_check_rthorr_package(self): - """Test that the R 'RTHORR' package availability is checked.""" - # Skip if dependencies are actually installed - if os.environ.get("SPI_DEPS") == "1": - import soundscapy.r_wrapper as sspyr - - sspyr._r_wrapper.check_rthorr_package() - - else: - with pytest.raises(ImportError) as excinfo: # noqa: PT012 - import soundscapy.r_wrapper as sspyr - - sspyr._r_wrapper.check_rthorr_package() - - assert "R package 'RTHORR'" in str(excinfo.value) - assert sspyr.PKG_SRC.RTHORR in str(excinfo.value) diff --git a/tox.ini b/tox.ini index c789618..da25cbf 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ commands_pre = [testenv:docs] # Documentation build environment dependency_groups = docs +basepython = python3.12 allowlist_externals = mkdocs commands = @@ -42,7 +43,6 @@ commands_pre = # Ensure R 'sn' package is available Rscript -e "if(!require('sn')) { pak::local_install_deps() }" Rscript -e "if(!require('CircE')) { pak::pkg_install('MitchellAcoustics/CircE-R') }" - Rscript -e "if(!require('RTHORR')) { pak::pkg_install('michaellynnmorris/RTHORR')}" commands = # Build the tutorials pytest --nbmake -n=auto docs --ignore=docs/tutorials/BinauralAnalysis.ipynb --ignore=docs/tutorials/4_Understanding_Soundscape_Perception_Index.ipynb --ignore=docs/tutorials/5_Working_with_Soundscape_Databases.ipynb --ignore=docs/tutorials/6_Soundscape_Assessment_Tutorial.ipynb --ignore=docs/tutorials/IoA_Soundscape_Assessment_Tutorial.ipynb --no-cov # BinauralAnalysis is too slow @@ -76,7 +76,6 @@ commands_pre = # Ensure R 'sn' package is available Rscript -e "if(!require('sn')) { pak::local_install_deps() }" Rscript -e "if(!require('CircE')) { pak::pkg_install('MitchellAcoustics/CircE-R') }" - Rscript -e "if(!require('RTHORR')) { pak::pkg_install('michaellynnmorris/RTHORR')}" commands = # Run core tests and R-specific tests pytest --cov --cov-report=xml -k "not optional_deps or optional_deps and spi or skip_if_deps and spi" @@ -93,7 +92,6 @@ commands_pre = # Ensure R 'sn' package is available Rscript -e "if(!require('sn')) { pak::local_install_deps() }" Rscript -e "if(!require('CircE')) { pak::pkg_install('MitchellAcoustics/CircE-R') }" - Rscript -e "if(!require('RTHORR')) { pak::pkg_install('michaellynnmorris/RTHORR')}" commands = # Run all tests, including SPI tests which are skipped with pytestmark pytest --cov --cov-report=xml