pheval 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of pheval might be problematic. Click here for more details.

Files changed (42) hide show
  1. pheval/__init__.py +0 -5
  2. pheval/analyse/__init__.py +0 -0
  3. pheval/analyse/analysis.py +703 -0
  4. pheval/analyse/generate_plots.py +312 -0
  5. pheval/analyse/generate_summary_outputs.py +186 -0
  6. pheval/analyse/rank_stats.py +61 -0
  7. pheval/cli.py +22 -7
  8. pheval/cli_pheval.py +37 -12
  9. pheval/cli_pheval_utils.py +225 -8
  10. pheval/config_parser.py +36 -0
  11. pheval/constants.py +1 -0
  12. pheval/implementations/__init__.py +1 -3
  13. pheval/post_processing/__init__.py +0 -0
  14. pheval/post_processing/post_processing.py +210 -0
  15. pheval/prepare/__init__.py +0 -0
  16. pheval/prepare/create_noisy_phenopackets.py +173 -0
  17. pheval/prepare/create_spiked_vcf.py +366 -0
  18. pheval/prepare/custom_exceptions.py +47 -0
  19. pheval/prepare/update_phenopacket.py +53 -0
  20. pheval/resources/alternate_ouputs/CADA_results.txt +11 -0
  21. pheval/resources/alternate_ouputs/DeepPVP_results.txt +22 -0
  22. pheval/resources/alternate_ouputs/OVA_results.txt +11 -0
  23. pheval/resources/alternate_ouputs/Phen2Gene_results.json +814 -0
  24. pheval/resources/alternate_ouputs/Phenolyzer_results.txt +12 -0
  25. pheval/resources/alternate_ouputs/lirical_results.tsv +152 -0
  26. pheval/resources/alternate_ouputs/svanna_results.tsv +9 -0
  27. pheval/resources/hgnc_complete_set_2022-10-01.txt +43222 -0
  28. pheval/run_metadata.py +27 -0
  29. pheval/runners/runner.py +92 -11
  30. pheval/utils/__init__.py +0 -0
  31. pheval/utils/docs_gen.py +105 -0
  32. pheval/utils/docs_gen.sh +18 -0
  33. pheval/utils/file_utils.py +88 -0
  34. pheval/utils/phenopacket_utils.py +356 -0
  35. pheval/utils/semsim_utils.py +156 -0
  36. {pheval-0.1.0.dist-info → pheval-0.2.0.dist-info}/METADATA +12 -4
  37. pheval-0.2.0.dist-info/RECORD +41 -0
  38. {pheval-0.1.0.dist-info → pheval-0.2.0.dist-info}/WHEEL +1 -1
  39. pheval/utils.py +0 -7
  40. pheval-0.1.0.dist-info/RECORD +0 -13
  41. {pheval-0.1.0.dist-info → pheval-0.2.0.dist-info}/LICENSE +0 -0
  42. {pheval-0.1.0.dist-info → pheval-0.2.0.dist-info}/entry_points.txt +0 -0
pheval/cli_pheval.py CHANGED
@@ -1,26 +1,30 @@
1
1
  """
2
2
  Monarch Initiative
3
3
  """
4
+ from pathlib import Path
4
5
 
5
6
  import click
6
7
 
7
8
  from pheval.implementations import get_implementation_resolver
9
+ from pheval.utils.file_utils import write_metadata
8
10
 
9
11
 
10
12
  @click.command()
11
13
  @click.option(
12
- "--inputdir",
14
+ "--input-dir",
13
15
  "-i",
14
16
  metavar="INPUTDIR",
15
17
  required=True,
16
18
  help="The input directory (relative path: e.g exomiser-13.11)",
19
+ type=Path,
17
20
  )
18
21
  @click.option(
19
- "--testdatadir",
22
+ "--testdata-dir",
20
23
  "-t",
21
24
  metavar="TESTDATA",
22
25
  required=True,
23
26
  help="The input directory (relative path: e.g ./data)",
27
+ type=Path,
24
28
  )
25
29
  @click.option(
26
30
  "--runner",
@@ -30,18 +34,20 @@ from pheval.implementations import get_implementation_resolver
30
34
  help="Runner implementation (e.g exomiser-13.11)",
31
35
  )
32
36
  @click.option(
33
- "--tmpdir",
37
+ "--tmp-dir",
34
38
  "-m",
35
39
  metavar="TMPDIR",
36
40
  required=False,
37
41
  help="The path of the temporary directory (optional)",
42
+ type=Path,
38
43
  )
39
44
  @click.option(
40
- "--outputdir",
45
+ "--output-dir",
41
46
  "-o",
42
47
  metavar="OUTPUTDIR",
43
48
  required=True,
44
49
  help="The path of the output directory",
50
+ type=Path,
45
51
  )
46
52
  @click.option(
47
53
  "--config",
@@ -49,20 +55,39 @@ from pheval.implementations import get_implementation_resolver
49
55
  metavar="CONFIG",
50
56
  required=False,
51
57
  help="The path of the configuration file (optional e.g config.yaml)",
58
+ type=Path,
52
59
  )
53
- def run(inputdir, testdatadir, runner, tmpdir, outputdir, config) -> None:
60
+ @click.option(
61
+ "--version",
62
+ "-v",
63
+ required=False,
64
+ help="Version of the tool implementation.",
65
+ type=str,
66
+ )
67
+ def run(
68
+ input_dir: Path,
69
+ testdata_dir: Path,
70
+ runner: str,
71
+ tmp_dir: Path,
72
+ output_dir: Path,
73
+ config: Path,
74
+ version: str,
75
+ ) -> None:
54
76
  """PhEval Runner Command Line Interface
55
-
56
77
  Args:
57
- inputdir (Click.Path): The input directory (relative path: e.g exomiser-13.11)
58
- testdatadir (Click.Path): The input directory (relative path: e.g ./data
78
+ input_dir (Path): The input directory (relative path: e.g exomiser-13.11)
79
+ testdata_dir (Path): The input directory (relative path: e.g ./data
59
80
  runner (str): Runner implementation (e.g exomiser-13.11)
60
- tmpdir (Click.Path): The path of the temporary directory (optional)
61
- outputdir (Click.Path): The path of the output directory
62
- config (Click.Path): The path of the configuration file (optional e.g config.yaml)
81
+ tmp_dir (Path): The path of the temporary directory (optional)
82
+ output_dir (Path): The path of the output directory
83
+ config (Path): The path of the configuration file (optional e.g., config.yaml)
84
+ version (str): The version of the tool implementation
63
85
  """
64
86
  runner_class = get_implementation_resolver().lookup(runner)
65
- runner_instance = runner_class(inputdir, testdatadir, tmpdir, outputdir, config)
87
+ runner_instance = runner_class(input_dir, testdata_dir, tmp_dir, output_dir, config, version)
88
+ runner_instance.build_output_directory_structure()
66
89
  runner_instance.prepare()
67
90
  runner_instance.run()
68
91
  runner_instance.post_process()
92
+ run_metadata = runner_instance.construct_meta_data()
93
+ write_metadata(output_dir, run_metadata)
@@ -1,7 +1,15 @@
1
1
  """PhEval utils Command Line Interface"""
2
2
 
3
+ from pathlib import Path
4
+
3
5
  import click
4
6
 
7
+ from pheval.prepare.create_noisy_phenopackets import scramble_phenopackets
8
+ from pheval.prepare.create_spiked_vcf import spike_vcfs
9
+ from pheval.prepare.custom_exceptions import InputError, MutuallyExclusiveOptionError
10
+ from pheval.prepare.update_phenopacket import update_phenopackets
11
+ from pheval.utils.semsim_utils import percentage_diff, semsim_heatmap_plot
12
+
5
13
 
6
14
  @click.command()
7
15
  @click.option(
@@ -11,19 +19,228 @@ import click
11
19
  metavar="FILE",
12
20
  help="Path to the semantic similarity profile to be scrambled.",
13
21
  )
14
- def scramble_semsim():
22
+ def scramble_semsim(input: Path):
15
23
  """scramble_semsim"""
16
24
  print("running pheval_utils::scramble_semsim command")
17
25
 
18
26
 
19
- @click.command()
27
+ @click.command("scramble-phenopackets")
20
28
  @click.option(
21
- "--input",
22
- "-i",
29
+ "--phenopacket-path",
30
+ "-p",
31
+ metavar="PATH",
32
+ help="Path to phenopacket.",
33
+ type=Path,
34
+ cls=MutuallyExclusiveOptionError,
35
+ mutually_exclusive=["phenopacket_dir"],
36
+ )
37
+ @click.option(
38
+ "--phenopacket-dir",
39
+ "-P",
40
+ metavar="PATH",
41
+ help="Path to phenopackets directory.",
42
+ type=Path,
43
+ cls=MutuallyExclusiveOptionError,
44
+ mutually_exclusive=["phenopacket_path"],
45
+ )
46
+ @click.option(
47
+ "--scramble-factor",
48
+ "-s",
49
+ metavar=float,
50
+ required=True,
51
+ default=0.5,
52
+ show_default=True,
53
+ help="Scramble factor for randomising phenopacket phenotypic profiles.",
54
+ type=float,
55
+ )
56
+ @click.option(
57
+ "--output-dir",
58
+ "-O",
59
+ metavar="PATH",
60
+ required=True,
61
+ help="Path for creation of output directory",
62
+ default="noisy_phenopackets",
63
+ type=Path,
64
+ )
65
+ def scramble_phenopackets_command(
66
+ phenopacket_path: Path,
67
+ phenopacket_dir: Path,
68
+ scramble_factor: float,
69
+ output_dir: Path,
70
+ ):
71
+ """Generate noisy phenopackets from existing ones."""
72
+ if phenopacket_path is None and phenopacket_dir is None:
73
+ raise InputError("Either a phenopacket or phenopacket directory must be specified")
74
+ else:
75
+ scramble_phenopackets(output_dir, phenopacket_path, phenopacket_dir, scramble_factor)
76
+
77
+
78
+ @click.command("semsim-comparison")
79
+ @click.option(
80
+ "--semsim-left",
81
+ "-L",
82
+ required=True,
83
+ metavar="FILE",
84
+ help="Path to the first semantic similarity profile.",
85
+ )
86
+ @click.option(
87
+ "--semsim-right",
88
+ "-R",
23
89
  required=True,
24
90
  metavar="FILE",
25
- help="Path to the phenopacket to be spiked.",
91
+ help="Path to the second semantic similarity profile.",
92
+ )
93
+ @click.option(
94
+ "--score-column",
95
+ "-s",
96
+ required=True,
97
+ type=click.Choice(
98
+ ["jaccard_similarity", "dice_similarity", "phenodigm_score"], case_sensitive=False
99
+ ),
100
+ help="Score column that will be used in comparison",
101
+ )
102
+ @click.option(
103
+ "--analysis",
104
+ "-a",
105
+ required=True,
106
+ type=click.Choice(["heatmap", "percentage_diff"], case_sensitive=False),
107
+ help="""There are two types of analysis:
108
+ heatmap - Generates a heatmap plot that shows the differences between the semantic similarity profiles using the
109
+ score column for this purpose. Defaults to "heatmap".
110
+ percentage_diff - Calculates the score column percentage difference between the semantic similarity profiles""",
111
+ )
112
+ @click.option(
113
+ "--output",
114
+ "-o",
115
+ metavar="FILE",
116
+ default="percentage_diff.semsim.tsv",
117
+ help="Output path for the difference tsv. Defaults to percentage_diff.semsim.tsv",
118
+ )
119
+ def semsim_comparison(
120
+ semsim_left: Path,
121
+ semsim_right: Path,
122
+ score_column: str,
123
+ analysis: str,
124
+ output: Path = "percentage_diff.semsim.tsv",
125
+ ):
126
+ """Compares two semantic similarity profiles
127
+
128
+ Args:
129
+ semsim-left (Path): File path of the first semantic similarity profile
130
+ semsim-right (Path): File path of the second semantic similarity profile
131
+ output (Path): Output path for the difference tsv. Defaults to "percentage_diff.semsim.tsv".
132
+ score_column (str): Score column that will be computed (e.g. jaccard_similarity)
133
+ analysis (str): There are two types of analysis:
134
+ heatmap - Generates a heatmap plot that shows the differences between the semantic similarity profiles using the
135
+ score column for this purpose. Defaults to "heatmap".
136
+ percentage_diff - Calculates the score column percentage difference between the semantic similarity profiles.
137
+ """
138
+ if analysis == "heatmap":
139
+ return semsim_heatmap_plot(semsim_left, semsim_right, score_column)
140
+ if analysis == "percentage_diff":
141
+ percentage_diff(semsim_left, semsim_right, score_column, output)
142
+
143
+
144
+ @click.command("update-phenopackets")
145
+ @click.option(
146
+ "--phenopacket-path",
147
+ "-p",
148
+ metavar="PATH",
149
+ help="Path to phenopacket.",
150
+ type=Path,
151
+ cls=MutuallyExclusiveOptionError,
152
+ mutually_exclusive=["phenopacket_dir"],
153
+ )
154
+ @click.option(
155
+ "--phenopacket-dir",
156
+ "-P",
157
+ metavar="PATH",
158
+ help="Path to phenopacket directory for updating.",
159
+ type=Path,
160
+ cls=MutuallyExclusiveOptionError,
161
+ mutually_exclusive=["phenopacket_path"],
162
+ )
163
+ @click.option(
164
+ "--output-dir",
165
+ "-o",
166
+ metavar="PATH",
167
+ required=True,
168
+ help="Path to write phenopacket.",
169
+ type=Path,
170
+ )
171
+ @click.option(
172
+ "--gene-identifier",
173
+ "-g",
174
+ required=False,
175
+ default="ensembl_id",
176
+ show_default=True,
177
+ help="Gene identifier to add to phenopacket",
178
+ type=click.Choice(["ensembl_id", "entrez_id", "hgnc_id"]),
179
+ )
180
+ def update_phenopackets_command(
181
+ phenopacket_path: Path, phenopacket_dir: Path, output_dir: Path, gene_identifier: str
182
+ ):
183
+ """Update gene symbols and identifiers for phenopackets."""
184
+ if phenopacket_path is None and phenopacket_dir is None:
185
+ raise InputError("Either a phenopacket or phenopacket directory must be specified")
186
+ update_phenopackets(gene_identifier, phenopacket_path, phenopacket_dir, output_dir)
187
+
188
+
189
+ @click.command("create-spiked-vcfs")
190
+ @click.option(
191
+ "--phenopacket-path",
192
+ "-p",
193
+ metavar="PATH",
194
+ help="Path to phenopacket.",
195
+ type=Path,
196
+ cls=MutuallyExclusiveOptionError,
197
+ mutually_exclusive=["phenopacket_dir"],
198
+ )
199
+ @click.option(
200
+ "--phenopacket-dir",
201
+ "-P",
202
+ metavar="PATH",
203
+ help="Path to phenopacket directory for updating.",
204
+ type=Path,
205
+ cls=MutuallyExclusiveOptionError,
206
+ mutually_exclusive=["phenopacket_path"],
207
+ )
208
+ @click.option(
209
+ "--template-vcf-path",
210
+ "-t",
211
+ cls=MutuallyExclusiveOptionError,
212
+ metavar="PATH",
213
+ required=False,
214
+ help="Template VCF file",
215
+ mutually_exclusive=["vcf_dir"],
216
+ type=Path,
217
+ )
218
+ @click.option(
219
+ "--vcf-dir",
220
+ "-v",
221
+ cls=MutuallyExclusiveOptionError,
222
+ metavar="PATH",
223
+ help="Directory containing template VCF files",
224
+ mutually_exclusive=["template_vcf"],
225
+ type=Path,
226
+ )
227
+ @click.option(
228
+ "--output-dir",
229
+ "-O",
230
+ metavar="PATH",
231
+ required=True,
232
+ help="Path for creation of output directory",
233
+ default="vcf",
234
+ type=Path,
26
235
  )
27
- def scramble_phenopacket():
28
- """scramble_phenopacket"""
29
- print("running pheval_utils::scramble_phenopacket command")
236
+ def create_spiked_vcfs_command(
237
+ phenopacket_path: Path,
238
+ phenopacket_dir: Path,
239
+ output_dir: Path,
240
+ template_vcf_path: Path = None,
241
+ vcf_dir: Path = None,
242
+ ):
243
+ """Spikes variants into a template VCF file for a directory of phenopackets."""
244
+ if phenopacket_path is None and phenopacket_dir is None:
245
+ raise InputError("Either a phenopacket or phenopacket directory must be specified")
246
+ spike_vcfs(output_dir, phenopacket_path, phenopacket_dir, template_vcf_path, vcf_dir)
@@ -0,0 +1,36 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import yaml
6
+ from serde import serde
7
+ from serde.yaml import from_yaml
8
+
9
+
10
+ @serde
11
+ @dataclass
12
+ class InputDirConfig:
13
+ """
14
+ Class for defining the fields within the input directory config.
15
+
16
+ Args:
17
+ tool (str): Name of the tool implementation (e.g. exomiser/phen2gene)
18
+ tool_version (str): Version of the tool implementation
19
+ phenotype_only (bool): Whether the tool is run with HPO terms only (True) or with variant data (False)
20
+ tool_specific_configuration_options (Any): Tool specific configurations
21
+
22
+
23
+ """
24
+
25
+ tool: str
26
+ tool_version: str
27
+ phenotype_only: bool
28
+ tool_specific_configuration_options: Any
29
+
30
+
31
+ def parse_input_dir_config(input_dir: Path) -> InputDirConfig:
32
+ """Reads the config file."""
33
+ with open(Path(input_dir).joinpath("config.yaml"), "r") as config_file:
34
+ config = yaml.safe_load(config_file)
35
+ config_file.close()
36
+ return from_yaml(InputDirConfig, yaml.dump(config))
pheval/constants.py ADDED
@@ -0,0 +1 @@
1
+ PHEVAL_RESULTS_DIRECTORY_SUFFIX = "_results"
@@ -13,9 +13,7 @@ def get_implementation_resolver() -> ClassResolver[PhEvalRunner]:
13
13
  Returns:
14
14
  ClassResolver[PhEvalRunner]: _description_
15
15
  """
16
- implementation_resolver: ClassResolver[
17
- PhEvalRunner
18
- ] = ClassResolver.from_subclasses(
16
+ implementation_resolver: ClassResolver[PhEvalRunner] = ClassResolver.from_subclasses(
19
17
  PhEvalRunner,
20
18
  suffix="Implementation",
21
19
  )
File without changes
@@ -0,0 +1,210 @@
1
+ import operator
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from pathlib import Path
5
+
6
+ import pandas as pd
7
+
8
+
9
+ def calculate_end_pos(variant_start: int, variant_ref: str) -> int:
10
+ """Calculate the end position for a variant."""
11
+ return variant_start + len(variant_ref) - 1
12
+
13
+
14
+ @dataclass
15
+ class PhEvalGeneResult:
16
+ """Minimal data required from tool-specific output for gene prioritisation."""
17
+
18
+ gene_symbol: str
19
+ gene_identifier: str
20
+ score: float
21
+
22
+
23
+ @dataclass
24
+ class RankedPhEvalGeneResult:
25
+ """PhEval gene result with corresponding rank."""
26
+
27
+ pheval_gene_result: PhEvalGeneResult
28
+ rank: int
29
+
30
+ def as_dict(self):
31
+ """Return PhEval gene result as dictionary."""
32
+ return {
33
+ "gene_symbol": self.pheval_gene_result.gene_symbol,
34
+ "gene_identifier": self.pheval_gene_result.gene_identifier,
35
+ "score": self.pheval_gene_result.score,
36
+ "rank": self.rank,
37
+ }
38
+
39
+
40
+ @dataclass
41
+ class PhEvalVariantResult:
42
+ """Minimal data required from tool-specific output for variant prioritisation."""
43
+
44
+ chromosome: str
45
+ start: int
46
+ end: int
47
+ ref: str
48
+ alt: str
49
+ score: float
50
+
51
+
52
+ @dataclass
53
+ class RankedPhEvalVariantResult:
54
+ """PhEval variant result with corresponding rank."""
55
+
56
+ pheval_variant_result: PhEvalVariantResult
57
+ rank: int
58
+
59
+ def as_dict(self):
60
+ """Return PhEval variant result as dictionary."""
61
+ return {
62
+ "chromosome": self.pheval_variant_result.chromosome,
63
+ "start": self.pheval_variant_result.start,
64
+ "end": self.pheval_variant_result.end,
65
+ "ref": self.pheval_variant_result.ref,
66
+ "alt": self.pheval_variant_result.alt,
67
+ "score": self.pheval_variant_result.score,
68
+ "rank": self.rank,
69
+ }
70
+
71
+
72
+ class SortOrder(Enum):
73
+ ASCENDING = 1
74
+ DESCENDING = 2
75
+
76
+
77
+ class ResultSorter:
78
+ def __init__(
79
+ self, pheval_results: [PhEvalGeneResult] or [PhEvalVariantResult], sort_order: SortOrder
80
+ ):
81
+ self.pheval_results = pheval_results
82
+ self.sort_order = sort_order
83
+
84
+ def _sort_by_decreasing_score(self) -> [PhEvalGeneResult] or [PhEvalVariantResult]:
85
+ """Sort results in descending order."""
86
+ return sorted(self.pheval_results, key=operator.attrgetter("score"), reverse=True)
87
+
88
+ def _sort_by_increasing_score(self) -> [PhEvalGeneResult] or [PhEvalVariantResult]:
89
+ """Sort results in ascending order."""
90
+ return sorted(self.pheval_results, key=operator.attrgetter("score"), reverse=False)
91
+
92
+ def sort_pheval_results(self) -> [PhEvalGeneResult] or [PhEvalVariantResult]:
93
+ """Sort results with best score first."""
94
+ return (
95
+ self._sort_by_increasing_score()
96
+ if self.sort_order == SortOrder.ASCENDING
97
+ else self._sort_by_decreasing_score()
98
+ )
99
+
100
+
101
+ class ScoreRanker:
102
+ rank: int = 0
103
+ current_score: float = float("inf")
104
+ count: int = 0
105
+
106
+ def __init__(self, sort_order: SortOrder):
107
+ self.sort_order = sort_order
108
+
109
+ def _check_rank_order(self, round_score: float) -> None:
110
+ """Check the results are correctly ordered."""
111
+ if self.sort_order == SortOrder.ASCENDING and round_score < self.current_score != float(
112
+ "inf"
113
+ ):
114
+ raise ValueError("Results are not correctly sorted!")
115
+ elif self.sort_order == SortOrder.DESCENDING and round_score > self.current_score != float(
116
+ "inf"
117
+ ):
118
+ raise ValueError("Results are not correctly sorted!")
119
+
120
+ def rank_scores(self, round_score: float) -> int:
121
+ """Add ranks to a result, equal scores are given the same rank e.g., 1,1,3."""
122
+ self._check_rank_order(round_score)
123
+ self.count += 1
124
+ if self.current_score == round_score:
125
+ return self.rank
126
+ self.current_score = round_score
127
+ self.rank = self.count
128
+ return self.rank
129
+
130
+
131
+ def _rank_pheval_result(
132
+ pheval_result: [PhEvalGeneResult] or [PhEvalVariantResult], sort_order: SortOrder
133
+ ) -> [RankedPhEvalGeneResult] or [RankedPhEvalVariantResult]:
134
+ """Ranks either a PhEval gene or variant result post-processed from a tool specific output.
135
+ Deals with ex aequo scores"""
136
+ score_ranker = ScoreRanker(sort_order)
137
+ ranked_result = []
138
+ for result in pheval_result:
139
+ ranked_result.append(
140
+ RankedPhEvalGeneResult(
141
+ pheval_gene_result=result, rank=score_ranker.rank_scores(result.score)
142
+ )
143
+ ) if type(result) == PhEvalGeneResult else ranked_result.append(
144
+ RankedPhEvalVariantResult(
145
+ pheval_variant_result=result, rank=score_ranker.rank_scores(result.score)
146
+ )
147
+ )
148
+ return ranked_result
149
+
150
+
151
+ def _return_sort_order(sort_order_str: str) -> SortOrder:
152
+ """Return the SortOrder Enum from string derived from config."""
153
+ try:
154
+ return SortOrder[sort_order_str.upper()]
155
+ except KeyError:
156
+ raise ValueError("Incompatible ordering method specified.")
157
+
158
+
159
+ def _create_pheval_result(
160
+ pheval_result: [PhEvalGeneResult] or [PhEvalVariantResult], sort_order_str: str
161
+ ) -> [RankedPhEvalGeneResult] or [RankedPhEvalVariantResult]:
162
+ """Create PhEval gene/variant result with corresponding ranks."""
163
+ sort_order = _return_sort_order(sort_order_str)
164
+ sorted_pheval_result = ResultSorter(pheval_result, sort_order).sort_pheval_results()
165
+ return _rank_pheval_result(sorted_pheval_result, sort_order)
166
+
167
+
168
+ def _write_pheval_gene_result(
169
+ ranked_pheval_result: [RankedPhEvalGeneResult], output_dir: Path, tool_result_path: Path
170
+ ) -> None:
171
+ """Write ranked PhEval gene result to tsv."""
172
+ ranked_result = pd.DataFrame([x.as_dict() for x in ranked_pheval_result])
173
+ pheval_gene_output = ranked_result.loc[:, ["rank", "score", "gene_symbol", "gene_identifier"]]
174
+ pheval_gene_output.to_csv(
175
+ output_dir.joinpath(
176
+ "pheval_gene_results/" + tool_result_path.stem + "-pheval_gene_result.tsv"
177
+ ),
178
+ sep="\t",
179
+ index=False,
180
+ )
181
+
182
+
183
+ def _write_pheval_variant_result(
184
+ ranked_pheval_result: [RankedPhEvalVariantResult], output_dir: Path, tool_result_path: Path
185
+ ) -> None:
186
+ """Write ranked PhEval variant result to tsv."""
187
+ ranked_result = pd.DataFrame([x.as_dict() for x in ranked_pheval_result])
188
+ pheval_variant_output = ranked_result.loc[
189
+ :, ["rank", "score", "chromosome", "start", "end", "ref", "alt"]
190
+ ]
191
+ pheval_variant_output.to_csv(
192
+ output_dir.joinpath(
193
+ "pheval_variant_results/" + tool_result_path.stem + "-pheval_variant_result.tsv"
194
+ ),
195
+ sep="\t",
196
+ index=False,
197
+ )
198
+
199
+
200
+ def generate_pheval_result(
201
+ pheval_result: [PhEvalGeneResult] or [PhEvalVariantResult],
202
+ sort_order_str: str,
203
+ output_dir: Path,
204
+ tool_result_path: Path,
205
+ ):
206
+ """Generate either a PhEval variant or PhEval gene tsv result."""
207
+ ranked_pheval_result = _create_pheval_result(pheval_result, sort_order_str)
208
+ _write_pheval_variant_result(ranked_pheval_result, output_dir, tool_result_path) if all(
209
+ isinstance(result, RankedPhEvalVariantResult) for result in ranked_pheval_result
210
+ ) else _write_pheval_gene_result(ranked_pheval_result, output_dir, tool_result_path)
File without changes