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.
Files changed (27) hide show
  1. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/__init__.py +1 -1
  2. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/font_utils.py +37 -9
  3. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/gemma_gwas.py +18 -18
  4. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/modeltraining.py +521 -598
  5. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/plink_ld.py +21 -19
  6. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/preprocess.py +384 -137
  7. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/visualization.py +213 -196
  8. g2pinsight-1.0.7/G2PInsight/main.py +351 -0
  9. {g2pinsight-1.0.4 → g2pinsight-1.0.7/G2PInsight.egg-info}/PKG-INFO +124 -71
  10. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/SOURCES.txt +1 -0
  11. g2pinsight-1.0.7/G2PInsight.egg-info/requires.txt +38 -0
  12. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/LICENSE +1 -1
  13. g2pinsight-1.0.4/README.md → g2pinsight-1.0.7/PKG-INFO +148 -49
  14. g2pinsight-1.0.4/G2PInsight.egg-info/PKG-INFO → g2pinsight-1.0.7/README.md +95 -95
  15. g2pinsight-1.0.7/pyproject.toml +98 -0
  16. g2pinsight-1.0.4/G2PInsight/main.py +0 -276
  17. g2pinsight-1.0.4/G2PInsight.egg-info/requires.txt +0 -25
  18. g2pinsight-1.0.4/pyproject.toml +0 -89
  19. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/__init__.py +0 -0
  20. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/software/gemma-0.98.5-linux-static-AMD64 +0 -0
  21. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight/bin/software/plink +0 -0
  22. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/dependency_links.txt +0 -0
  23. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/entry_points.txt +0 -0
  24. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/G2PInsight.egg-info/top_level.txt +0 -0
  25. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/MANIFEST.in +0 -0
  26. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/setup.cfg +0 -0
  27. {g2pinsight-1.0.4 → g2pinsight-1.0.7}/setup.py +0 -0
@@ -1,3 +1,3 @@
1
- __version__ = '1.0.4'
1
+ __version__ = '1.0.7'
2
2
  __author__ = 'chenrf'
3
3
  __email__ = '12024128035@stu.ynu.edu.cn'
@@ -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', 'Times New Roman', 'Calibri']
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 ('DejaVu Sans', 'DejaVu Sans')
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 or english_font is None:
55
+ if chinese_font is None and english_font is None:
55
56
  chinese_font, english_font = detect_available_fonts()
56
- default_font = chinese_font if chinese_font else english_font
57
- plt.rcParams['font.sans-serif'] = [default_font, english_font, 'DejaVu Sans']
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 or english_font is None:
86
+ if chinese_font is None and english_font is None:
65
87
  chinese_font, english_font = detect_available_fonts()
66
- default_font = chinese_font if chinese_font else english_font
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.info(f'Using system PLINK executable from PATH: {found}')
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.info(f'Using system GEMMA executable from PATH: {found}')
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.info(f'Input file validation passed: {input_prefix}')
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.warning('The first two columns in the phenotype file contain missing values')
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.info(f'[RUN] {step_name}: {cmd_str}')
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.warning(f'stderr (potential issue): {stderr_content}')
83
+ logger.debug(f'stderr (potential issue): {stderr_content}')
84
84
  else:
85
- logger.info(f'stderr (informational): {stderr_content}')
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.info(f'[DONE] {step_name}')
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.info(f'Phenotype matching completed: {matched}/{total} samples matched (-9 used for missing phenotypes)')
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.info(f'Valid samples saved to: {output_sample_file} (n={len(valid_df)})')
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.info(f'Filtered PCA saved to: {output_pca_file} (n={len(filtered_df)} samples)')
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.info(f'PCA covariates saved to: {output_cov_file} (PCs={cov_df.shape[1]})')
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.info(f'GWAS working directory: {current_dir}')
157
- logger.info(f'GWAS output prefix: {gwas_prefix} (results expected under {current_dir}/output/)')
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.info(f'GWAS result file generated: {expected_result_file}')
162
+ logger.debug(f'GWAS result file generated: {expected_result_file}')
163
163
  else:
164
- logger.warning(f'GWAS result file not found at expected path: {expected_result_file}')
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.info('Skipping PLINK QC in GWAS step based on upstream configuration')
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.info('GWAS pipeline completed')
190
+ logger.debug('GWAS pipeline completed')