-
Notifications
You must be signed in to change notification settings - Fork 2
Fix 'flan global prepare' and 'global fit' pipeline execution errors #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9e38946
242990b
e9e9d8a
a339c66
e0d0bd1
ffa8170
2de0dc6
d03333e
de891d0
f403eb0
3b6bb9e
3b4ce34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from flan import * |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,13 +28,17 @@ def astype(self, new_type): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new_y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load_phenotype(phenotype_path: str, out_type = numpy.float32, encode = False) -> numpy.ndarray: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load_phenotype(phenotype_path: str, out_type = numpy.float32, encode = False, keep_iids = None) -> numpy.ndarray: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param phenotype_path: Phenotypes location | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param out_type: convert to type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :param encode: whether phenotypes are strings and we want to code them as ints) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = pandas.read_table(phenotype_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Highlighted Fix: If a list of aligned IIDs is provided, filter and order by them | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if keep_iids is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = data.set_index('IID').reindex(keep_iids).reset_index() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = data.iloc[:, -1].values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if encode: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, data = numpy.unique(data, return_inverse=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -48,8 +52,9 @@ def load_plink_pcs(path, order_as_in_file=None): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if order_as_in_file is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y = pandas.read_csv(order_as_in_file, sep='\t').set_index('IID') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert len(df) == len(y) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| df = df.reindex(y.index) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Highlighted Fix: Drop the strict assert check and intersect valid indices instead | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common_ids = y.index.intersection(df.index) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| df = df.reindex(common_ids) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return df | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
53
to
59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Silently dropping non-common IDs in Using
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -58,38 +63,55 @@ class LocalDataLoader: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger = logging.getLogger() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _load_phenotype(self, path: str) -> numpy.ndarray: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phenotype = load_phenotype(path, out_type=numpy.int64, encode=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _load_phenotype(self, path: str, keep_iids = None) -> numpy.ndarray: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| phenotype = load_phenotype(path, out_type=numpy.int64, encode=True, keep_iids=keep_iids) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f'Phenotype dtype is {phenotype.dtype}') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if numpy.isnan(phenotype).sum() > 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(f'There are {numpy.isnan(phenotype).sum()} nan values in phenotype from {path}') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return phenotype | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load(self, cache: FileCache, fold: int) -> Tuple[X, Y]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_train = self._load_phenotype(cache.phenotype_path(fold, 'train')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_val = self._load_phenotype(cache.phenotype_path(fold, 'val')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_test = self._load_phenotype(cache.phenotype_path(fold, 'test')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Highlighted Fix: Dynamically read available sample IIDs from the generated sscore files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iids_train = pandas.read_csv(cache.pca_path(fold, 'train', 'sscore'), sep='\t').rename(columns={'#IID': 'IID'})['IID'].values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iids_val = pandas.read_csv(cache.pca_path(fold, 'val', 'sscore'), sep='\t').rename(columns={'#IID': 'IID'})['IID'].values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iids_test = pandas.read_csv(cache.pca_path(fold, 'test', 'sscore'), sep='\t').rename(columns={'#IID': 'IID'})['IID'].values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Load features matching the safe intersections | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x = self._load_pcs(cache, fold, iids_train, iids_val, iids_test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Load phenotypes safely aligned with those exact feature IDs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_train = self._load_phenotype(cache.phenotype_path(fold, 'train'), keep_iids=iids_train) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_val = self._load_phenotype(cache.phenotype_path(fold, 'val'), keep_iids=iids_val) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y_test = self._load_phenotype(cache.phenotype_path(fold, 'test'), keep_iids=iids_test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| y = Y(y_train, y_val, y_test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x = self._load_pcs(cache, fold) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return x, y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _load_pcs(self, cache: FileCache, fold: int) -> X: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _load_pcs(self, cache: FileCache, fold: int, iids_train=None, iids_val=None, iids_test=None) -> X: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_train = load_plink_pcs(path=cache.pca_path(fold, 'train', 'sscore'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'train')).values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'train')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if iids_train is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_train = X_train.reindex(iids_train) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_train = X_train.values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_val = load_plink_pcs(path=cache.pca_path(fold, 'val', 'sscore'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'val')).values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'val')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if iids_val is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+91
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Reindexing PCs with IIDs can create all-NaN rows when PCA IIDs and phenotype IIDs differ. In |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_val = X_val.reindex(iids_val) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_val = X_val.values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_test = load_plink_pcs(path=cache.pca_path(fold, 'test', 'sscore'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'test')).values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order_as_in_file=cache.phenotype_path(fold, 'test')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if iids_test is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_test = X_test.reindex(iids_test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_test = X_test.values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return X(X_train, X_val, X_test) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load_for_prediction(self, cache: FileCache) -> Tuple[numpy.ndarray, numpy.ndarray]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| X_pred = load_plink_pcs(path=cache.pca_path(None, 'pred', 'sscore')).values.astype(numpy.float32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # TODO: if fold 0 train dataset does not contain all possible target values, then we are in trouble | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = pandas.read_table(cache.phenotype_path(0, 'train')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data = data.iloc[:, -1].values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unique, _ = numpy.unique(data, return_inverse=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return X_pred, unique | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return X_pred, unique | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,11 +16,23 @@ def __init__(self, qc_config: Dict) -> None: | |
| self.qc_config = qc_config | ||
|
|
||
| def fit_transform(self, cache: FileCache) -> None: | ||
| run_plink(args_list=['--pfile', str(cache.pfile_path()), 'vzs', '--make-pgen'], | ||
| args_dict={**{'--out': str(cache.pfile_path()), # Merging dicts here | ||
| '--set-missing-var-ids': '@:#'}, | ||
| **self.qc_config}) | ||
|
|
||
| # Create a new output path for QC-processed data | ||
| qc_path = str(cache.pfile_path()) + "_qc" | ||
|
|
||
| run_plink( | ||
| args_list=[ | ||
| '--pfile', str(cache.pfile_path()), | ||
| '--make-pgen' | ||
| ], | ||
| args_dict={ | ||
| '--out': qc_path, | ||
| '--set-missing-var-ids': '@:#:$r:$a', | ||
| **self.qc_config | ||
| } | ||
| ) | ||
|
|
||
| # ✅ VERY IMPORTANT: update cache to point to QC output | ||
| cache._pfile_path = qc_path | ||
|
Comment on lines
+34
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Directly mutating Assigning Suggested implementation: args_dict={
'--out': str(qc_path),
'--set-missing-var-ids': '@:#:$r:$a',
**self.qc_config
}
)
# ✅ VERY IMPORTANT: update cache to point to QC output
cache.set_pfile_path(qc_path)
|
||
|
|
||
| def transform(self, source_path: str, dest_path: str) -> None: | ||
| run_plink(args_list=['--make-pgen', '--pfile', str(source_path)], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,10 @@ def _split_ids(self, | |
| y: y can be passed to trigger StratifiedKFold instead of KFold | ||
| random_state (int): Fixed random_state for train_test_split sklearn function | ||
| """ | ||
| # adding min 5 folds | ||
| num_folds = getattr(self.args, "num_folds", 5) | ||
| self.args.num_folds = num_folds | ||
|
Comment on lines
+32
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Forcing a minimum of 5 folds on Here |
||
|
|
||
| ids = pandas.read_table(cache.ids_path()).rename(columns={'#IID': 'IID'}).filter(['FID', 'IID']) | ||
| indices = numpy.arange(ids.shape[0]) | ||
| if self.args.num_folds == 1: | ||
|
|
@@ -67,12 +71,17 @@ def _split_ids(self, | |
| ids.iloc[indices, :].to_csv(out_path, sep='\t', index=False) | ||
|
|
||
| def _split_genotypes(self, cache: FileCache) -> None: | ||
| # 🔥 Force use of QC-processed genotype | ||
| base_path = str(cache.pfile_path()) | ||
| if not base_path.endswith("_qc"): | ||
| base_path = base_path + "_qc" | ||
|
|
||
| for fold_index, part in product(range(cache.num_folds), ['train', 'val', 'test']): | ||
| run_plink( | ||
| args_dict={ | ||
| '--pfile': str(cache.pfile_path()), | ||
| '--pfile': base_path, # ✅ FIXED: use QC data | ||
| '--keep': str(cache.ids_path(fold_index, part)), | ||
| '--out': str(cache.pfile_path(fold_index, part)) | ||
| '--out': str(cache.pfile_path(fold_index, part)) | ||
| }, | ||
| args_list=['--make-pgen'] | ||
| ) | ||
|
|
@@ -89,7 +98,9 @@ def _split_phenotypes(self, cache: FileCache) -> None: | |
| ) | ||
|
|
||
| def fit_transform(self, cache: FileCache) -> None: | ||
|
|
||
| # Force splitter to use QC output | ||
| if not str(cache.pfile_path()).endswith("_qc"): | ||
| cache._pfile_path = str(cache.pfile_path()) + "_qc" | ||
| self._split_ids(cache) | ||
| self._split_genotypes(cache) | ||
| self._split_phenotypes(cache) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| path: /data/flan/.cache/deep_ancestry/node1 | ||
| path: ./data/flan/.cache/deep_ancestry/node1 | ||
| num_folds: 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| link: /data/flan/node1_50 | ||
| link: ./data/flan/node1_50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): NaNs introduced by
keep_iidsare silently encoded into integers, hiding missing phenotypes.When
keep_iidsis used,set_index(...).reindex(keep_iids).reset_index()will produce NaNs for IIDs present inkeep_iidsbut missing from the phenotype file. Becausenumpy.unique(..., return_inverse=True)runs before any NaN check, those NaNs are treated as a category and converted to integers, so a laternumpy.isnan(phenotype)check can never detect them. Please either (a) validate for NaNs immediately after thereindexand fail if any are found, or (b) perform the NaN check on the unencodeddatabefore callingnumpy.unique.