G2PInsight 1.0.4__tar.gz → 1.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/__init__.py +1 -1
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/font_utils.py +37 -9
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/gemma_gwas.py +18 -18
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/modeltraining.py +521 -598
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/plink_ld.py +21 -19
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/preprocess.py +384 -137
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/visualization.py +213 -196
- g2pinsight-1.0.7/G2PInsight/main.py +351 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7/G2PInsight.egg-info}/PKG-INFO +124 -71
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/SOURCES.txt +1 -0
- g2pinsight-1.0.7/G2PInsight.egg-info/requires.txt +38 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/LICENSE +1 -1
- g2pinsight-1.0.4/README.md → g2pinsight-1.0.7/PKG-INFO +148 -49
- g2pinsight-1.0.4/G2PInsight.egg-info/PKG-INFO → g2pinsight-1.0.7/README.md +95 -95
- g2pinsight-1.0.7/pyproject.toml +98 -0
- g2pinsight-1.0.4/G2PInsight/main.py +0 -276
- g2pinsight-1.0.4/G2PInsight.egg-info/requires.txt +0 -25
- g2pinsight-1.0.4/pyproject.toml +0 -89
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/__init__.py +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/software/gemma-0.98.5-linux-static-AMD64 +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/software/plink +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/dependency_links.txt +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/entry_points.txt +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/top_level.txt +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/MANIFEST.in +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/setup.cfg +0 -0
- {g2pinsight-1.0.4 → g2pinsight-1.0.7}/setup.py +0 -0
|
@@ -27,7 +27,7 @@ def detect_available_fonts() -> Tuple[Optional[str], Optional[str]]:
|
|
|
27
27
|
try:
|
|
28
28
|
mpl_fonts = _collect_matplotlib_font_names()
|
|
29
29
|
chinese_font_candidates = ['SimHei', 'SimSun', 'Microsoft YaHei', 'KaiTi', 'FangSong', 'STSong', 'STKaiti']
|
|
30
|
-
english_fonts = ['Arial', 'DejaVu Sans', 'Liberation Sans', 'Helvetica', '
|
|
30
|
+
english_fonts = ['Times New Roman', 'Arial', 'DejaVu Sans', 'Liberation Sans', 'Helvetica', 'Calibri']
|
|
31
31
|
chinese_font = None
|
|
32
32
|
for font_name in chinese_font_candidates:
|
|
33
33
|
if font_name in mpl_fonts:
|
|
@@ -38,32 +38,60 @@ def detect_available_fonts() -> Tuple[Optional[str], Optional[str]]:
|
|
|
38
38
|
if font_name in mpl_fonts:
|
|
39
39
|
english_font = font_name
|
|
40
40
|
break
|
|
41
|
-
if not chinese_font:
|
|
42
|
-
chinese_font = 'DejaVu Sans'
|
|
43
41
|
if not english_font:
|
|
44
42
|
english_font = 'DejaVu Sans'
|
|
43
|
+
# Only keep a Chinese default when a real CJK face exists; otherwise prefer English.
|
|
44
|
+
if not chinese_font:
|
|
45
|
+
chinese_font = None
|
|
45
46
|
return (chinese_font, english_font)
|
|
46
47
|
except Exception:
|
|
47
|
-
return (
|
|
48
|
+
return (None, 'DejaVu Sans')
|
|
48
49
|
|
|
49
50
|
def setup_matplotlib_font(chinese_font: Optional[str]=None, english_font: Optional[str]=None) -> None:
|
|
50
51
|
try:
|
|
51
52
|
mpl = _load_module('matplotlib')
|
|
52
53
|
mpl.use('Agg')
|
|
53
54
|
plt = _load_module('matplotlib.pyplot')
|
|
54
|
-
if chinese_font is None
|
|
55
|
+
if chinese_font is None and english_font is None:
|
|
55
56
|
chinese_font, english_font = detect_available_fonts()
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
elif chinese_font is None or english_font is None:
|
|
58
|
+
detected_cn, detected_en = detect_available_fonts()
|
|
59
|
+
if chinese_font is None:
|
|
60
|
+
chinese_font = detected_cn
|
|
61
|
+
if english_font is None:
|
|
62
|
+
english_font = detected_en
|
|
63
|
+
# Prefer real Chinese face when present; otherwise use English (e.g. Times New Roman).
|
|
64
|
+
default_font = chinese_font or english_font or 'DejaVu Sans'
|
|
65
|
+
fallback_en = english_font or 'DejaVu Sans'
|
|
66
|
+
serif_prefer = {'Times New Roman', 'Times', 'Liberation Serif', 'DejaVu Serif', 'Georgia'}
|
|
67
|
+
if default_font in serif_prefer or (not chinese_font and fallback_en in serif_prefer):
|
|
68
|
+
face = default_font if default_font in serif_prefer else fallback_en
|
|
69
|
+
plt.rcParams['font.family'] = 'serif'
|
|
70
|
+
plt.rcParams['font.serif'] = [face, 'Times New Roman', 'DejaVu Serif', 'Liberation Serif']
|
|
71
|
+
plt.rcParams['font.sans-serif'] = [fallback_en, 'DejaVu Sans']
|
|
72
|
+
# STIX math glyphs (subscripts etc.) — Times New Roman lacks Unicode ₁₀
|
|
73
|
+
plt.rcParams['mathtext.fontset'] = 'stix'
|
|
74
|
+
else:
|
|
75
|
+
plt.rcParams['font.family'] = 'sans-serif'
|
|
76
|
+
plt.rcParams['font.sans-serif'] = [default_font, fallback_en, 'DejaVu Sans']
|
|
77
|
+
if english_font:
|
|
78
|
+
plt.rcParams['font.serif'] = [english_font, 'DejaVu Serif', 'Liberation Serif']
|
|
79
|
+
plt.rcParams['mathtext.fontset'] = 'dejavusans'
|
|
58
80
|
plt.rcParams['axes.unicode_minus'] = False
|
|
59
81
|
except Exception:
|
|
60
82
|
pass
|
|
61
83
|
|
|
62
84
|
def setup_plotly_font(chinese_font: Optional[str]=None, english_font: Optional[str]=None) -> dict:
|
|
63
85
|
try:
|
|
64
|
-
if chinese_font is None
|
|
86
|
+
if chinese_font is None and english_font is None:
|
|
65
87
|
chinese_font, english_font = detect_available_fonts()
|
|
66
|
-
|
|
88
|
+
elif chinese_font is None or english_font is None:
|
|
89
|
+
detected_cn, detected_en = detect_available_fonts()
|
|
90
|
+
if chinese_font is None:
|
|
91
|
+
chinese_font = detected_cn
|
|
92
|
+
if english_font is None:
|
|
93
|
+
english_font = detected_en
|
|
94
|
+
default_font = chinese_font or english_font or 'Arial'
|
|
67
95
|
return {'family': default_font, 'size': 12}
|
|
68
96
|
except Exception:
|
|
69
97
|
return {'family': 'Arial', 'size': 12}
|
|
@@ -19,7 +19,7 @@ def _get_plink_executable() -> str:
|
|
|
19
19
|
return str(_bundled_plink)
|
|
20
20
|
found = shutil.which('plink')
|
|
21
21
|
if found:
|
|
22
|
-
logger.
|
|
22
|
+
logger.debug(f'Using system PLINK executable from PATH: {found}')
|
|
23
23
|
return found
|
|
24
24
|
raise FileNotFoundError(f'PLINK executable not found. Checked bundled path: {_bundled_plink} and system PATH.')
|
|
25
25
|
|
|
@@ -28,7 +28,7 @@ def _get_gemma_executable() -> str:
|
|
|
28
28
|
return str(_bundled_gemma)
|
|
29
29
|
found = shutil.which('gemma')
|
|
30
30
|
if found:
|
|
31
|
-
logger.
|
|
31
|
+
logger.debug(f'Using system GEMMA executable from PATH: {found}')
|
|
32
32
|
return found
|
|
33
33
|
raise FileNotFoundError(f'GEMMA executable not found. Checked bundled path: {_bundled_gemma} and system PATH.')
|
|
34
34
|
plink_executable = _get_plink_executable()
|
|
@@ -45,7 +45,7 @@ def validate_input_files(input_prefix: str) -> None:
|
|
|
45
45
|
missing_files.append(file_path)
|
|
46
46
|
if missing_files:
|
|
47
47
|
raise FileNotFoundError(f"Missing PLINK binary files: {', '.join(missing_files)}")
|
|
48
|
-
logger.
|
|
48
|
+
logger.debug(f'Input file validation passed: {input_prefix}')
|
|
49
49
|
|
|
50
50
|
def validate_phenotype_file(phenotype_file: str) -> pd.DataFrame:
|
|
51
51
|
if not isinstance(phenotype_file, str) or not phenotype_file.strip():
|
|
@@ -56,14 +56,14 @@ def validate_phenotype_file(phenotype_file: str) -> pd.DataFrame:
|
|
|
56
56
|
pheno_df = pd.read_csv(phenotype_file, sep='\\s+', header=None, usecols=[0, 1], skipinitialspace=True)
|
|
57
57
|
pheno_df.columns = ['IID', 'PHENO']
|
|
58
58
|
if pheno_df['IID'].isnull().any() or pheno_df['PHENO'].isnull().any():
|
|
59
|
-
logger.
|
|
59
|
+
logger.debug('The first two columns in the phenotype file contain missing values')
|
|
60
60
|
return pheno_df
|
|
61
61
|
except Exception as e:
|
|
62
62
|
raise RuntimeError(f'Failed to read phenotype file: {str(e)}')
|
|
63
63
|
|
|
64
64
|
def run_shell_command(cmd: list, step_name: str) -> None:
|
|
65
65
|
cmd_str = ' '.join(cmd)
|
|
66
|
-
logger.
|
|
66
|
+
logger.debug(f'[RUN] {step_name}: {cmd_str}')
|
|
67
67
|
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8')
|
|
68
68
|
stderr_content = ''
|
|
69
69
|
if result.stderr:
|
|
@@ -80,14 +80,14 @@ def run_shell_command(cmd: list, step_name: str) -> None:
|
|
|
80
80
|
if result.returncode == 0:
|
|
81
81
|
error_keywords = ['ERROR', 'FATAL', 'FAIL', 'Exception', 'Traceback']
|
|
82
82
|
if any((keyword in stderr_content.upper() for keyword in error_keywords)):
|
|
83
|
-
logger.
|
|
83
|
+
logger.debug(f'stderr (potential issue): {stderr_content}')
|
|
84
84
|
else:
|
|
85
|
-
logger.
|
|
85
|
+
logger.debug(f'stderr (informational): {stderr_content}')
|
|
86
86
|
else:
|
|
87
87
|
logger.error(f'stderr: {stderr_content}')
|
|
88
88
|
if result.returncode != 0:
|
|
89
89
|
raise RuntimeError(f'{step_name} failed with return code: {result.returncode}')
|
|
90
|
-
logger.
|
|
90
|
+
logger.debug(f'[DONE] {step_name}')
|
|
91
91
|
|
|
92
92
|
def plink_quality_control(input_prefix: str, output_prefix: str) -> None:
|
|
93
93
|
cmd = [plink_executable, '--bfile', input_prefix, '--maf', '0.05', '--geno', '0.2', '--allow-extra-chr', '--allow-no-sex', '--make-bed', '--out', output_prefix]
|
|
@@ -106,7 +106,7 @@ def merge_phenotype_to_fam(pheno_df: pd.DataFrame, fam_file: str) -> None:
|
|
|
106
106
|
new_fam_df.to_csv(fam_file, sep=' ', header=False, index=False)
|
|
107
107
|
matched = (fam_df['PHENO'] != '-9').sum()
|
|
108
108
|
total = len(fam_df)
|
|
109
|
-
logger.
|
|
109
|
+
logger.debug(f'Phenotype matching completed: {matched}/{total} samples matched (-9 used for missing phenotypes)')
|
|
110
110
|
|
|
111
111
|
def calculate_pca(input_prefix: str, output_prefix: str) -> None:
|
|
112
112
|
cmd = [plink_executable, '--bfile', input_prefix, '--pca', '5', '--allow-extra-chr', '--allow-no-sex', '--out', output_prefix]
|
|
@@ -118,7 +118,7 @@ def filter_valid_samples(pheno_df: pd.DataFrame, fam_file: str, output_sample_fi
|
|
|
118
118
|
fam_df['IID'] = fam_df['IID'].astype(str).str.strip()
|
|
119
119
|
valid_df = fam_df[fam_df['IID'].isin(valid_iids)][['FID', 'IID']]
|
|
120
120
|
valid_df.to_csv(output_sample_file, sep=' ', header=False, index=False)
|
|
121
|
-
logger.
|
|
121
|
+
logger.debug(f'Valid samples saved to: {output_sample_file} (n={len(valid_df)})')
|
|
122
122
|
|
|
123
123
|
def filter_plink_samples(input_prefix: str, sample_file: str, output_prefix: str) -> None:
|
|
124
124
|
cmd = [plink_executable, '--bfile', input_prefix, '--keep', sample_file, '--allow-extra-chr', '--allow-no-sex', '--make-bed', '--out', output_prefix]
|
|
@@ -131,13 +131,13 @@ def filter_pca_by_samples(pca_file: str, sample_file: str, output_pca_file: str)
|
|
|
131
131
|
pca_df['key'] = list(zip(pca_df[0].astype(str).str.strip(), pca_df[1].astype(str).str.strip()))
|
|
132
132
|
filtered_df = pca_df[pca_df['key'].isin(valid_set)].drop('key', axis=1)
|
|
133
133
|
filtered_df.to_csv(output_pca_file, sep=' ', header=False, index=False)
|
|
134
|
-
logger.
|
|
134
|
+
logger.debug(f'Filtered PCA saved to: {output_pca_file} (n={len(filtered_df)} samples)')
|
|
135
135
|
|
|
136
136
|
def extract_pca_covariates(pca_file: str, output_cov_file: str) -> None:
|
|
137
137
|
pca_df = pd.read_csv(pca_file, sep='\\s+', header=None)
|
|
138
138
|
cov_df = pca_df.iloc[:, 2:]
|
|
139
139
|
cov_df.to_csv(output_cov_file, sep=' ', header=False, index=False)
|
|
140
|
-
logger.
|
|
140
|
+
logger.debug(f'PCA covariates saved to: {output_cov_file} (PCs={cov_df.shape[1]})')
|
|
141
141
|
|
|
142
142
|
def calculate_kinship_matrix(genotype_prefix: str, output_prefix: str) -> str:
|
|
143
143
|
base_prefix = Path(output_prefix).name
|
|
@@ -153,15 +153,15 @@ def run_gemma_gwas(genotype_prefix: str, kinship_file: str, cov_file: str, outpu
|
|
|
153
153
|
base_prefix = Path(output_prefix).name
|
|
154
154
|
gwas_prefix = f'{base_prefix}_gwas'
|
|
155
155
|
current_dir = os.getcwd()
|
|
156
|
-
logger.
|
|
157
|
-
logger.
|
|
156
|
+
logger.debug(f'GWAS working directory: {current_dir}')
|
|
157
|
+
logger.debug(f'GWAS output prefix: {gwas_prefix} (results expected under {current_dir}/output/)')
|
|
158
158
|
cmd = [gemma_executable, '-bfile', genotype_prefix, '-k', kinship_file, '-c', cov_file, '-lmm', '1', '-o', gwas_prefix]
|
|
159
159
|
run_shell_command(cmd, 'GWAS association analysis')
|
|
160
160
|
expected_result_file = Path(current_dir) / 'output' / f'{gwas_prefix}.assoc.txt'
|
|
161
161
|
if expected_result_file.exists():
|
|
162
|
-
logger.
|
|
162
|
+
logger.debug(f'GWAS result file generated: {expected_result_file}')
|
|
163
163
|
else:
|
|
164
|
-
logger.
|
|
164
|
+
logger.debug(f'GWAS result file not found at expected path: {expected_result_file}')
|
|
165
165
|
|
|
166
166
|
def run_complete_gwas_pipeline(input_plink_prefix: str, phenotype_file: str, output_prefix: str, perform_plink_qc: bool=True) -> None:
|
|
167
167
|
validate_input_files(input_plink_prefix)
|
|
@@ -170,7 +170,7 @@ def run_complete_gwas_pipeline(input_plink_prefix: str, phenotype_file: str, out
|
|
|
170
170
|
clean_geno_prefix = f'{output_prefix}_clean_geno'
|
|
171
171
|
plink_quality_control(input_plink_prefix, clean_geno_prefix)
|
|
172
172
|
else:
|
|
173
|
-
logger.
|
|
173
|
+
logger.debug('Skipping PLINK QC in GWAS step based on upstream configuration')
|
|
174
174
|
clean_geno_prefix = input_plink_prefix
|
|
175
175
|
pca_prefix = f'{output_prefix}_geno_pca'
|
|
176
176
|
calculate_pca(clean_geno_prefix, pca_prefix)
|
|
@@ -187,4 +187,4 @@ def run_complete_gwas_pipeline(input_plink_prefix: str, phenotype_file: str, out
|
|
|
187
187
|
extract_pca_covariates(filtered_pca_file, cov_file)
|
|
188
188
|
kinship_file = calculate_kinship_matrix(filtered_geno_prefix, output_prefix)
|
|
189
189
|
run_gemma_gwas(filtered_geno_prefix, kinship_file, cov_file, output_prefix)
|
|
190
|
-
logger.
|
|
190
|
+
logger.debug('GWAS pipeline completed')
|