From 30a9b27e1b220e4123bd196dd1fccfa943210129 Mon Sep 17 00:00:00 2001 From: Nils Winter Date: Tue, 10 Dec 2024 16:02:35 +0100 Subject: [PATCH] Remove docs from .gitignore and add to Git --- .gitignore | 1 - documentation/docs/api/cpm_regression.md | 3 + documentation/docs/api/edge_selection.md | 3 + documentation/docs/api/fold.md | 3 + documentation/docs/api/models.md | 3 + documentation/docs/assets/img/CCCPM.pdf | Bin 0 -> 5714 bytes documentation/docs/assets/img/CCCPM.png | Bin 0 -> 4253 bytes documentation/docs/assets/img/CCCPM_large.png | Bin 0 -> 34953 bytes .../docs/assets/img/CCCPM_medium.png | Bin 0 -> 11751 bytes .../docs/examples/human_connectome_project.md | 3 + documentation/docs/examples/simulated_data.md | 3 + documentation/docs/stylesheets/extra.css | 12 ++ examples/example_simulated_data3.py | 32 ++++ examples/hannah.py | 38 ---- examples/hcp_analysis.py | 62 ------- examples/human_connectome_project.py | 49 ------ examples/macs.py | 62 ------- examples/plot_simulation_results2.py | 42 +++++ examples/save_config.py | 2 +- examples/simulate_data_3.py | 162 ++++++++++++------ examples/trap_kongress.py | 31 ---- 21 files changed, 215 insertions(+), 296 deletions(-) create mode 100644 documentation/docs/api/cpm_regression.md create mode 100644 documentation/docs/api/edge_selection.md create mode 100644 documentation/docs/api/fold.md create mode 100644 documentation/docs/api/models.md create mode 100644 documentation/docs/assets/img/CCCPM.pdf create mode 100644 documentation/docs/assets/img/CCCPM.png create mode 100644 documentation/docs/assets/img/CCCPM_large.png create mode 100644 documentation/docs/assets/img/CCCPM_medium.png create mode 100644 documentation/docs/examples/human_connectome_project.md create mode 100644 documentation/docs/examples/simulated_data.md create mode 100644 documentation/docs/stylesheets/extra.css create mode 100644 examples/example_simulated_data3.py delete mode 100644 examples/hannah.py delete mode 100644 examples/hcp_analysis.py delete mode 100644 examples/human_connectome_project.py delete mode 100644 examples/macs.py create mode 100644 examples/plot_simulation_results2.py delete mode 100644 examples/trap_kongress.py diff --git a/.gitignore b/.gitignore index e8b9312..248259c 100644 --- a/.gitignore +++ b/.gitignore @@ -159,7 +159,6 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -docs examples/tmp examples/simulated_data diff --git a/documentation/docs/api/cpm_regression.md b/documentation/docs/api/cpm_regression.md new file mode 100644 index 0000000..8c9c312 --- /dev/null +++ b/documentation/docs/api/cpm_regression.md @@ -0,0 +1,3 @@ +# CPM Regression + +::: cpm.cpm_analysis \ No newline at end of file diff --git a/documentation/docs/api/edge_selection.md b/documentation/docs/api/edge_selection.md new file mode 100644 index 0000000..56ec5b5 --- /dev/null +++ b/documentation/docs/api/edge_selection.md @@ -0,0 +1,3 @@ +# Edge Selection + +::: cpm.edge_selection \ No newline at end of file diff --git a/documentation/docs/api/fold.md b/documentation/docs/api/fold.md new file mode 100644 index 0000000..958cb04 --- /dev/null +++ b/documentation/docs/api/fold.md @@ -0,0 +1,3 @@ +# Fold + +::: cpm.fold \ No newline at end of file diff --git a/documentation/docs/api/models.md b/documentation/docs/api/models.md new file mode 100644 index 0000000..c9a779c --- /dev/null +++ b/documentation/docs/api/models.md @@ -0,0 +1,3 @@ +# Predictive Models + +::: cpm.models \ No newline at end of file diff --git a/documentation/docs/assets/img/CCCPM.pdf b/documentation/docs/assets/img/CCCPM.pdf new file mode 100644 index 0000000000000000000000000000000000000000..879d541f71f7e958c2c25fe3ae36a420f4eac985 GIT binary patch literal 5714 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

!n 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 JPDHLkV1kyo1C0Ox literal 0 HcmV?d00001 diff --git a/documentation/docs/examples/human_connectome_project.md b/documentation/docs/examples/human_connectome_project.md new file mode 100644 index 0000000..974d1b2 --- /dev/null +++ b/documentation/docs/examples/human_connectome_project.md @@ -0,0 +1,3 @@ +```python +--8<-- "examples/hcp_analysis.py" +``` \ No newline at end of file diff --git a/documentation/docs/examples/simulated_data.md b/documentation/docs/examples/simulated_data.md new file mode 100644 index 0000000..542c4b7 --- /dev/null +++ b/documentation/docs/examples/simulated_data.md @@ -0,0 +1,3 @@ +```python +--8<-- "examples/example_simulated_data.py" +``` \ No newline at end of file diff --git a/documentation/docs/stylesheets/extra.css b/documentation/docs/stylesheets/extra.css new file mode 100644 index 0000000..79b16f8 --- /dev/null +++ b/documentation/docs/stylesheets/extra.css @@ -0,0 +1,12 @@ +:root { + --md-primary-fg-color: #257180; + --md-primary-fg-color--light: #257180; + --md-primary-fg-color--dark: #257180; + --md-accent-fg-color: #FD8B51; + --md-accent-fg-color--transparent: #FD8B51; + --md-accent-bg-color: #FD8B51; + --md-accent-bg-color--light: #FD8B51; +} +:root > * { + --md-code-bg-color: rgb(242, 229, 191, 0.3); +} \ No newline at end of file diff --git a/examples/example_simulated_data3.py b/examples/example_simulated_data3.py new file mode 100644 index 0000000..2b63b0c --- /dev/null +++ b/examples/example_simulated_data3.py @@ -0,0 +1,32 @@ +from sklearn.model_selection import KFold, ShuffleSplit, RepeatedKFold + +from cpm import CPMRegression +from cpm.simulate_data import simulate_regression_data_2 +from cpm.edge_selection import PThreshold, UnivariateEdgeSelection + + +link_types = ['no_link', + 'no_no_link', + 'direct_link', + 'weak_link' + ] +edge_statistics = ['pearson', 'pearson_partial'] +results_folder = '/spm-data/vault-data3/mmll/projects/confound_corrected_cpm/results' + +for link in link_types: + for edge_statistic in edge_statistics: + X, y, covariates = simulate_regression_data_2(n_features=1225, n_informative_features=50, link_type=link) + + univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=[edge_statistic], + edge_selection=[PThreshold(threshold=[0.05], + correction=['fdr_by'])]) + cpm = CPMRegression(results_directory=f'{results_folder}/simulated_data_{link}_{edge_statistic}', + cv=RepeatedKFold(n_splits=10, n_repeats=5, random_state=42), + edge_selection=univariate_edge_selection, + inner_cv=ShuffleSplit(n_splits=3, test_size=0.2, random_state=42), + add_edge_filter=True, + n_permutations=2) + cpm.estimate(X=X, y=y, covariates=covariates) + + #cpm._calculate_permutation_results('./tmp/example_simulated_data2') + diff --git a/examples/hannah.py b/examples/hannah.py deleted file mode 100644 index 476ff68..0000000 --- a/examples/hannah.py +++ /dev/null @@ -1,38 +0,0 @@ -import numpy as np -import pandas as pd - -from sklearn.model_selection import KFold - -from cpm import CPMRegression -import scipy.io - -from cpm.edge_selection import PThreshold, UnivariateEdgeSelection -from cpm.reporting.plots.utils import matrix_to_vector_3d - - -# Load the .mat file -covariates_file = '/spm-data/vault-data3/TIP-Studie/09_Connectome/0_Projekte/FürNils/design_matrix_SAD_age_sex_med_01000.mat' -covariates = scipy.io.loadmat(covariates_file)['design_matrix_SAD_age_sex_med_01000'] -y = covariates[:, 1] -covs = covariates[:, 2:] - -brain_file = '/spm-data/vault-data3/TIP-Studie/09_Connectome/0_Projekte/FürNils/Connectivity_dti_nos_Aparc_stacked.mat' -connectome = scipy.io.loadmat(brain_file)['connectivityAparcstacked'] -connectome = np.moveaxis(connectome, -1, 0) -X = matrix_to_vector_3d(connectome) - -atlas_file = "/spm-data/vault-data3/TIP-Studie/09_Connectome/0_Projekte/FürNils/brainRegions_aparc.csv" - -p_threshold = PThreshold(threshold=[0.01], correction=[None]) -univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=['pearson'], - edge_selection=[p_threshold]) - -cpm_regression = CPMRegression(results_directory='./tmp/hannah_sad_pearson', - cv=KFold(n_splits=10, shuffle=True, random_state=42), - edge_selection=univariate_edge_selection, - #cv_edge_selection=KFold(n_splits=2, shuffle=True, random_state=42), - add_edge_filter=True, - n_permutations=1000, - atlas_labels=atlas_file) -results = cpm_regression.estimate(X=X, y=y, covariates=covs) -#cpm_regression._calculate_permutation_results() diff --git a/examples/hcp_analysis.py b/examples/hcp_analysis.py deleted file mode 100644 index 2707368..0000000 --- a/examples/hcp_analysis.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import pandas as pd -import numpy as np -from cpm.reporting.plots.utils import matrix_to_vector_3d -from sklearn.model_selection import RepeatedKFold, KFold - -from cpm import CPMRegression -from cpm.edge_selection import PThreshold, UnivariateEdgeSelection - - -data_folder = "/spm-data/vault-data3/mmll/data/HCP/100_nodes_times_series/" -X_file = "functional_connectivity.npy" -ids_file = "subject_ids.csv" -meta_file = "RESTRICTED_NilsWinter_11_26_2024_8_3_14.csv" -unres_meta_file = "unrestricted_NilsWinter_12_4_2024_10_11_45.csv" - -X = np.load(os.path.join(data_folder, X_file)) -ids = pd.read_csv(os.path.join(data_folder, ids_file)) -ids['Subject'] = ids['Subject_ID'] -df = pd.read_csv(os.path.join(data_folder, meta_file)) -df_unres = pd.read_csv(os.path.join(data_folder, unres_meta_file)) - -df = pd.merge(ids, df, how="left", on="Subject") -df = pd.merge(df, df_unres, how="left", on="Subject") - -df['Sex'] = pd.get_dummies(df['Gender'], drop_first=True, dtype='int').to_numpy() - -X = matrix_to_vector_3d(X) -#target = "BMI" -target = "SSAGA_Income" -#target = "Age_in_Yrs" - -X = X[~df[target].isna()] -df = df[~df[target].isna()] - -#X = X[~df['BMI'].isna()] -#df = df[~df['BMI'].isna()] - -#edge_statistics = ['pearson', 'pearson_partial'] -edge_statistics = ['spearman'] - -#covariates = df['BMI'].to_numpy().reshape(-1, 1) -covariates = df[['Age_in_Yrs', 'Sex']].to_numpy() - - -for edge_statistic in edge_statistics: - univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=[edge_statistic], - edge_selection=[ - PThreshold(threshold=[0.1, 0.05, 0.01, 0.005], - correction=[None]), - # PThreshold(threshold=[0.05], - # correction=['fdr_by']) - ]) - cpm = CPMRegression(results_directory=f'{data_folder}/results/hcp_{target}_{edge_statistic}', - cv=KFold(n_splits=10, shuffle=True, random_state=42), - edge_selection=univariate_edge_selection, - inner_cv=KFold(n_splits=10, shuffle=True, random_state=42), - select_stable_edges=True, - stability_threshold=0, - n_permutations=20) - cpm.estimate(X=X, y=df[target].to_numpy(), covariates=covariates) -print() \ No newline at end of file diff --git a/examples/human_connectome_project.py b/examples/human_connectome_project.py deleted file mode 100644 index d7b3da8..0000000 --- a/examples/human_connectome_project.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -import pandas as pd -import numpy as np - -# Define the folder containing the .txt files -folder_path = ('/spm-data/vault-data3/mmll/data/HCP/100_nodes_times_series/3T_HCP1200_MSMAll_d100_ts2') - -# Initialize a list to store correlation matrices -correlation_matrices = [] - -# Initialize a list to store subject IDs -subject_ids = [] - -file_list = [f for f in os.listdir(folder_path) if f.endswith('.txt')] - -# Sort the filenames by subject ID (assuming subject IDs are in the filename before .txt) -file_list.sort() # This will sort alphabetically, which works if subject IDs are formatted consistently - -# Loop through all files in the specified folder -for filename in file_list: - print(filename) - # Construct the full file path - file_path = os.path.join(folder_path, filename) - - # Read the text file into a DataFrame - df = pd.read_csv(file_path, sep='\s+', header=None) - - # Calculate the correlation matrix - correlation_matrix = df.corr() - - # Append the correlation matrix to the list - correlation_matrices.append(correlation_matrix.values) - - # Extract subject ID from the filename (remove the .txt extension) - subject_id = filename[:-4] # Exclude the last 4 characters ('.txt') - subject_ids.append(subject_id) - -# Convert the list of correlation matrices into a 3D numpy array -correlation_array = np.array(correlation_matrices) - -# Create a DataFrame for subject IDs -subject_ids_df = pd.DataFrame(subject_ids, columns=['Subject_ID']) - -# Save the numpy array and DataFrame to disk -np.save('/spm-data/vault-data3/mmll/data/HCP/100_nodes_times_series/functional_connectivity.npy', correlation_array) # Save as .npy file -subject_ids_df.to_csv('/spm-data/vault-data3/mmll/data/HCP/100_nodes_times_series/subject_ids.csv', index=False) # Save as .csv file - - -print() \ No newline at end of file diff --git a/examples/macs.py b/examples/macs.py deleted file mode 100644 index 748e189..0000000 --- a/examples/macs.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np -import pandas as pd - -from sklearn.model_selection import KFold - -from cpm import CPMRegression - - -#X = np.load('/spm-data/vault-data3/mmll/projects/macs_datahub_example/AnalysisReady/all/FunctionalConnectome/X.npy') -#df = pd.read_csv('/spm-data/vault-data3/mmll/projects/macs_datahub_example/AnalysisReady/all/' -# 'FunctionalConnectome/sample.csv', -# na_values=-99) -X = np.load('/spm-data/vault-data3/mmll/projects/cpm_macs_example/datahub/AnalysisReady/hc/DTI_fractional_anisotropy/X.npy') -df = pd.read_csv('/spm-data/vault-data3/mmll/projects/cpm_macs_example/datahub/AnalysisReady/hc/DTI_fractional_anisotropy/subjects.csv', - na_values=-99) - -X = X[df['Group'] <= 1] -df = df[df['Group'] <= 1] - - -#X = X[~df['CTQ_Sum'].isna()] -#df = df[~df['CTQ_Sum'].isna()] -X = X[~df['IQ'].isna()] -df = df[~df['IQ'].isna()] - -#X = X[~df['NEOFFI_Neurotizismus'].isna()] -#df = df[~df['NEOFFI_Neurotizismus'].isna()] - -#X = X[~df['FSozU_Sum'].isna()] -#df = df[~df['FSozU_Sum'].isna()] - -#X = X[~df['BDI_Sum'].isna()] -#df = df[~df['BDI_Sum'].isna()] -covs = df[['Alter', 'Geschlecht', 'Site']].to_numpy() -#covs = df[['Geschlecht', 'Site']].to_numpy() -#covs = df[['Geschlecht']].to_numpy() -#y = df['BDI_Sum'].to_numpy() -#y = df['FSozU_Sum'].to_numpy() -#y = df['Alter'].to_numpy() -y = df['IQ'].to_numpy() - -#y = df['NEOFFI_Neurotizismus'].to_numpy() -#y = df['CTQ_Sum'].to_numpy() -#covs = df[['Geschlecht']].to_numpy() -#y = df['Alter'].to_numpy() - - -from cpm.edge_selection import PThreshold, UnivariateEdgeSelection -p_threshold = PThreshold(threshold=[0.1, 0.05, 0.01, 0.005, 0.001], correction=[None]) -univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=['pearson_partial'], - edge_selection=[p_threshold]) - -cpm = CPMRegression(results_directory='./tmp/macs_IQ_partial', - cv=KFold(n_splits=10, shuffle=True, random_state=42), - edge_selection=univariate_edge_selection, - cv_edge_selection=KFold(n_splits=10, shuffle=True, random_state=42), - add_edge_filter=True, - n_permutations=20) -results = cpm.estimate(X=X, y=y, covariates=covs) -#print(results) -#cpm._calculate_permutation_results() - diff --git a/examples/plot_simulation_results2.py b/examples/plot_simulation_results2.py new file mode 100644 index 0000000..16bd60c --- /dev/null +++ b/examples/plot_simulation_results2.py @@ -0,0 +1,42 @@ +import os + +import seaborn as sns +import matplotlib.pyplot as plt +import pandas as pd + + +results_folder = "simulated_data" +edge_statistics = ['pearson', 'pearson_partial'] + + +for scenario in ['A', 'B']: + for edge_statistic in edge_statistics: + results = {f"scenario_{scenario}1": os.path.join(results_folder, f"scenario_{scenario}1", "results", edge_statistic), + f"scenario_{scenario}2": os.path.join(results_folder, f"scenario_{scenario}2", "results", edge_statistic), + f"scenario_{scenario}3": os.path.join(results_folder, f"scenario_{scenario}3", "results", edge_statistic), + f"scenario_{scenario}4": os.path.join(results_folder, f"scenario_{scenario}4", "results", edge_statistic), + } + + dfs = [] + for link_type, folder in results.items(): + df = pd.read_csv(os.path.join(folder, 'cv_results.csv')) + df['link_type'] = link_type + dfs.append(df) + + concatenated_df = pd.concat(dfs, ignore_index=True) + + concatenated_df = concatenated_df[concatenated_df['network'] == 'both'] + + + g = sns.FacetGrid(concatenated_df, col="link_type", margin_titles=True, despine=True, + height=2.5, hue="model") + g.map(plt.axvline, x=0, color='grey', linewidth=0.5, zorder=-1) + g.map(sns.violinplot, "pearson_score", "model", inner=None, split=True,# hue="model",# hue_order=[1, 2], + density_norm='count', dodge=True, palette="Blues_r", fill=True) + #g.map(sns.boxplot, "pearson_score", "model", dodge=True, #hue=1, hue_order=[2, 1] + # ) + g.set_titles(col_template="{col_name}", size=7) + g.set_xlabels("Pearson correlation", size=8) + plt.suptitle(edge_statistic) + plt.show() + print() diff --git a/examples/save_config.py b/examples/save_config.py index 6bf1aca..f888955 100644 --- a/examples/save_config.py +++ b/examples/save_config.py @@ -17,7 +17,7 @@ cpm = CPMRegression(results_directory='./tmp/example_simulated_data2', cv=KFold(n_splits=5, shuffle=True, random_state=42), edge_selection=univariate_edge_selection, - cv_edge_selection=ShuffleSplit(n_splits=1, test_size=0.2, random_state=42), + inner_cv=ShuffleSplit(n_splits=1, test_size=0.2, random_state=42), add_edge_filter=True, n_permutations=100) cpm.save_configuration(config_filename='./config.pkl') diff --git a/examples/simulate_data_3.py b/examples/simulate_data_3.py index 6a6a40e..bb4a3b9 100644 --- a/examples/simulate_data_3.py +++ b/examples/simulate_data_3.py @@ -1,7 +1,15 @@ +import os from sklearn.linear_model import LinearRegression +from sklearn.model_selection import KFold, ShuffleSplit, RepeatedKFold from sklearn.metrics import r2_score import numpy as np import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns +from sklearn.model_selection import cross_val_score +from cpm import CPMRegression +from cpm.simulate_data import simulate_regression_data_2 +from cpm.edge_selection import PThreshold, UnivariateEdgeSelection def generate_scenario_data(scenario, n_samples=1000, n_features=10, n_informative=3, noise_level=0.1): @@ -9,39 +17,58 @@ def generate_scenario_data(scenario, n_samples=1000, n_features=10, n_informativ # Generate feature matrix X X = np.random.normal(0, 1, (n_samples, n_features)) + z = np.random.normal(0, 1, n_samples) + y = np.random.normal(0, 1, n_samples) # Generate ground truth coefficients for X's influence on z and y - z_coefficients = np.zeros(n_features) - y_coefficients = np.zeros(n_features) - - if scenario == 1: - # Scenario 1: y and z are influenced by X independently - z_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) # Decreasing influence - y_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) # Decreasing influence - z = np.dot(X, z_coefficients) + np.random.normal(0, noise_level, n_samples) - y = np.dot(X, y_coefficients) + np.random.normal(0, noise_level, n_samples) - - elif scenario == 2: - # Scenario 2: z influences both X and y, inducing spurious association - z = np.random.normal(0, 1, n_samples) - z_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) - y = 2 * z + np.random.normal(0, noise_level, n_samples) + z_coefficients = np.empty(n_features) + y_coefficients = np.empty(n_features) + + z_coefficients[:n_informative] = np.linspace(0.3, 0.1, n_informative) + y_coefficients[:n_informative] = np.linspace(0.3, 0.1, n_informative) + + if scenario == "A1": + pass + + elif scenario == "A2": + for i in range(n_features): + X[:, i] = X[:, i] + 0.8 * z * z_coefficients[i] + + elif scenario == "A3": + y = y + 0.8 * z + + elif scenario == "A4": + for i in range(n_features): + X[:, i] = X[:, i] + 0.8 * z * z_coefficients[i] + y = y + 0.8 * z + + elif scenario == "B1": + for i in range(n_features): + X[:, i] = X[:, i] + y * y_coefficients[i] + + elif scenario == "B2": for i in range(n_features): - X[:, i] = z * z_coefficients[i] + np.random.normal(0, noise_level, n_samples) - - elif scenario == 3: - # Scenario 3: y is influenced by both X and z, with z partially mediating - z_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) - y_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) - z = np.dot(X, z_coefficients) + np.random.normal(0, noise_level, n_samples) - y = np.dot(X, y_coefficients) + 0.5 * z + np.random.normal(0, noise_level, n_samples) - elif scenario == 4: - y_coefficients[:n_informative] = np.linspace(1, 0.1, n_informative) - z = np.random.normal(0, noise_level, n_samples) - y = np.dot(X, y_coefficients) + np.random.normal(0, noise_level, n_samples) + X[:, i] = X[:, i] + y * y_coefficients[i] + model = LinearRegression() + model.fit(y.reshape(-1, 1), X[:, 0]) + resid = X[:, 0] - model.predict(y.reshape(-1, 1)) + z = z + 2 * resid + + elif scenario == "B3": + for i in range(n_features): + X[:, i] = X[:, i] + y * y_coefficients[i] + model = LinearRegression() + model.fit(X[:, 0].reshape(-1, 1), y) + resid = y - model.predict(X[:, 0].reshape(-1, 1)) + z = z + 2 * resid + + elif scenario == "B4": + z = 1.5 * z + y + for i in range(n_features): + X[:, i] = X[:, i] + 2 * y * y_coefficients[i] + 0.1 * z * z_coefficients[i] else: - raise ValueError("Invalid scenario. Choose 1, 2, or 3.") + raise NotImplementedError("Invalid scenario.") return X, y, z @@ -69,28 +96,59 @@ def calculate_explained_variance_y_given_z(y, z): return r2 +def residualize_X(X, z): + """Regress out the effect of z from each column of X.""" + z = z.reshape(-1, 1) # Reshape z for regression + residualized_X = np.zeros_like(X) + + for i in range(X.shape[1]): # Iterate over columns (features) + model = LinearRegression() + model.fit(z, X[:, i]) + # Compute residuals + residualized_X[:, i] = X[:, i] - model.predict(z) + + return residualized_X + + +def calculate_explained_variance_y_with_residualized_X(X, y, z): + """Calculate explained variance of y by X after removing z's influence on X.""" + X_residualized = residualize_X(X, z) + return calculate_explained_variance(X_residualized, y) + + +#edge_statistics = ['pearson', 'pearson_partial'] +edge_statistics = ['pearson'] + # Generate data for each scenario and calculate explained variances -for scenario in range(1, 5): - X, y, z = generate_scenario_data(scenario, noise_level=1) - - # Calculate explained variances - explained_variance_X_y = calculate_explained_variance(X, y) - explained_variance_X_z = calculate_explained_variance(X, z) - explained_variance_y_given_z = calculate_explained_variance_y_given_z(y, z) - - print(f"Scenario {scenario}:") - print(f" Explained Variance (R^2) of X for y: {explained_variance_X_y:.2f}") - print(f" Explained Variance (R^2) of X for z: {explained_variance_X_z:.2f}") - print(f" Explained Variance (R^2) of y for z: {explained_variance_y_given_z:.2f}") - print(f" Corr(X[:, 0], y): {np.corrcoef(X[:, 0], y)[0, 1]:.2f}") - print(f" Corr(X[:, 0], z): {np.corrcoef(X[:, 0], z)[0, 1]:.2f}") - print(f" Corr(y, z): {np.corrcoef(y, z)[0, 1]:.2f}") - - # Plot y vs z for visualization - plt.figure() - plt.scatter(y, z, alpha=0.5, label="y vs z") - plt.title(f"Scenario {scenario}: y vs z") - plt.xlabel("y") - plt.ylabel("z") - plt.legend() - plt.show() +for scenario in ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]: + for edge_statistic in edge_statistics: + print(f"Scenario {scenario}:") + X, y, z = generate_scenario_data(scenario, noise_level=1, n_samples=1000, n_features=105, n_informative=10) + folder = f"simulated_data/scenario_{scenario}" + os.makedirs(folder, exist_ok=True) + np.save(f"{folder}/X.npy", X) + np.save(f"{folder}/y.npy", y) + np.save(f"{folder}/covariates.npy", z) + + + univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=[edge_statistic], + edge_selection=[PThreshold(threshold=[0.05], + correction=['fdr_by'])]) + cpm = CPMRegression(results_directory=os.path.join(folder, 'results', f'{edge_statistic}'), + cv=RepeatedKFold(n_splits=10, n_repeats=5, random_state=42), + edge_selection=univariate_edge_selection, + # cv_edge_selection=ShuffleSplit(n_splits=1, test_size=0.2, random_state=42), + add_edge_filter=True, + n_permutations=2) + cpm.estimate(X=X, y=y, covariates=z.reshape(-1, 1)) + + # Calculate explained variances + explained_variance_X_y = calculate_explained_variance(X[:,0].reshape(-1, 1), y) + explained_variance_X_z = calculate_explained_variance(X[:,0].reshape(-1, 1), z) + explained_variance_y_given_z = calculate_explained_variance_y_given_z(y, z) + explained_variance_X_y_residualized = calculate_explained_variance_y_with_residualized_X(X[:,0].reshape(-1, 1), y, z) + + print(f" Explained Variance (R^2) in y given X: {explained_variance_X_y:.2f}") + print(f" Explained Variance (R^2) in y given X controlling for z: {explained_variance_X_y_residualized:.2f}") + print(f" Explained Variance (R^2) in z given X: {explained_variance_X_z:.2f}") + print(f" Explained Variance (R^2) in y given z: {explained_variance_y_given_z:.2f}") diff --git a/examples/trap_kongress.py b/examples/trap_kongress.py deleted file mode 100644 index f0b2e19..0000000 --- a/examples/trap_kongress.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -import pandas as pd - -from sklearn.model_selection import KFold - -from cpm import CPMRegression -from cpm.edge_selection import PThreshold, UnivariateEdgeSelection - - -X = np.load('/spm-data/vault-data3/mmll/projects/cpm_macs_example/datahub/AnalysisReady/hc/DTI_fractional_anisotropy/X.npy') -df = pd.read_csv('/spm-data/vault-data3/mmll/projects/cpm_macs_example/datahub/AnalysisReady/hc/DTI_fractional_anisotropy/subjects.csv', - na_values=-99) - -X = X[~df['Haushaltsnetto'].isna()] -df = df[~df['Haushaltsnetto'].isna()] -covs = df[['Alter', 'Geschlecht', 'Site']].to_numpy() -y = df['Haushaltsnetto'].to_numpy() - -# define edge selection -p_threshold = PThreshold(threshold=[0.001], correction=[None]) -p_fdr = PThreshold(threshold=[0.05], correction=['fdr_by']) -univariate_edge_selection = UnivariateEdgeSelection(edge_statistic=['pearson_partial'], edge_selection=[p_threshold]) - -# run cpm regression -cpm = CPMRegression(results_directory='/spm-data/vault-data3/mmll/projects/cpm_macs_example/haushaltsnetto', - cv=KFold(n_splits=10, shuffle=True, random_state=42), - edge_selection=univariate_edge_selection, - cv_edge_selection=KFold(n_splits=10, shuffle=True, random_state=42), - add_edge_filter=True, - n_permutations=1000) -results = cpm.estimate(X=X, y=y, covariates=covs)