GenomicDataStream
A scalable interface between data and analysis
Loading...
Searching...
No Matches
pgenstream.h
Go to the documentation of this file.
1/***********************************************************************
2 * @file pgenstream.h
3 * @author Gabriel Hoffman
4 * @email gabriel.hoffman@mssm.edu
5 * @brief reads a plink2/PGEN into matrix in chunks, storing variants in columns
6 * Copyright (C) 2024 Gabriel Hoffman
7 ***********************************************************************/
8
9
10#ifndef PGEN_STREAM_H_
11#define PGEN_STREAM_H_
12
13#ifndef DISABLE_EIGEN
14// #include <Eigen/Sparse>
15#include <RcppEigen.h>
16#endif
17
18#include <string>
19#include <regex>
20#include <unordered_map>
21#include <algorithm>
22
23#include "VariantInfo.h"
25#include "GenomicRanges.h"
26#include "DataTable.h"
27#include "pgen/RPgenReader.h"
28#include "VariantSet.h"
29#include "utils.h"
30
31using namespace std;
32using namespace arma;
33
34namespace gds {
35
39class pgenstream :
40 public GenomicDataStream {
41 public:
42
44
48
49 genoFileType = getFileType(param.file);
50
51 // Parse PVAR/BIM file of variant positions and IDs
52 // evaluate subsetting of variants
53 process_variants();
54
55 if( genoFileType == PGEN ){
56 // Read index file (pvar)
57 pvar = new RPvar();
58 pvar->Load(fileIdx, true, true);
59 }
60
61 // if PBED, set missingToMean to TRUE
62 missingToMean = (genoFileType == PBED) ?
63 true : param.missingToMean;
64
65 // Parse PSAM/FAM file of sample identifiers
66 // evaluate subsetting of samples
67 process_samples();
68
69 // Read data file (pgen/bed)
70 pg = new RPgenReader();
71 pg->Load(param.file, pvar, n_samples_psam, sampleIdx1);
72
73 // Initialize vector with capacity to store nVariants
74 // Note, this allocates memory but does not change .size()
75 // After j variants have been inserted, only entries up to j*nsamples are populated
76 // the rest of the vector is allocated doesn't have valid data
77 matDosage.reserve( n_samples() * param.chunkSize );
78 }
79
83 if( vInfo != nullptr) delete vInfo;
84 if( pg != nullptr){
85 pg->Close();
86 delete pg;
87 }
88 if( pvar != nullptr){
89 pvar->Close();
90 delete pvar;
91 }
92 }
93
96 void setRegions(const vector<string> &regions ) override {
97
98 // Initialize genomic regions
99 // from delimited string
100 GenomicRanges gr( regions );
101 // if not empty
102 if( gr.size() != 0){
103
104 // Search is log time for each interval
105 varIdx = vs.getIndeces( gr );
106
107 }else{
108 varIdx.clear();
109 for(int i=0; i<dt.nrows(); i++){
110 varIdx.push_back(i);
111 }
112 }
113
114 // total number of requested variants
115 n_requested_variants = varIdx.size();
116
117 // set current position in varIdx
118 currentIdx = 0;
119 }
120
123 int n_samples() override {
124 return number_of_samples;
125 }
126
129 vector<string> getSampleNames() override {
130 return vInfo->sampleNames;
131 }
132
135 string getStreamType() override {
136 return toString( param.fileType);
137 }
138
140 return vs.getChromRanges();
141 }
142
143 bool getNextChunk( DataChunk<arma::mat> & chunk, const bool &useFilter = true) override {
144
145 // Update matDosage and vInfo for the chunk
146 bool ret = getNextChunk_helper();
147
148 if( useFilter ){
149 // modifies matDosage and vInfo directly
150 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
151 }
152
153 arma::mat M(matDosage.data(), number_of_samples, vInfo->size(), false, true);
154
155 chunk = DataChunk<arma::mat>( M, vInfo );
156
157 return ret;
158 }
159
160 bool getNextChunk( DataChunk<arma::sp_mat> & chunk, const bool &useFilter = true) override {
161
162 // Update matDosage and vInfo for the chunk
163 bool ret = getNextChunk_helper();
164
165 if( useFilter ){
166 // modifies matDosage and vInfo directly
167 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
168 }
169
170 arma::mat M(matDosage.data(), number_of_samples, vInfo->size(), false, true);
171
172 chunk = DataChunk<arma::sp_mat>( arma::sp_mat(M), vInfo );
173
174 return ret;
175 }
176
177 #ifndef DISABLE_EIGEN
178 bool getNextChunk( DataChunk<Eigen::MatrixXd> & chunk, const bool &useFilter = true) override {
179
180 // Update matDosage and vInfo for the chunk
181 bool ret = getNextChunk_helper();
182
183 if( useFilter ){
184 // modifies matDosage and vInfo directly
185 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
186 }
187
188 Eigen::MatrixXd M = Eigen::Map<Eigen::MatrixXd>(matDosage.data(), number_of_samples, vInfo->size());
189
190 chunk = DataChunk<Eigen::MatrixXd>( M, vInfo );
191
192 return ret;
193 }
194
195 bool getNextChunk( DataChunk<Eigen::SparseMatrix<double> > & chunk, const bool &useFilter = true) override {
196
197 // Update matDosage and vInfo for the chunk
198 bool ret = getNextChunk_helper();
199
200 if( useFilter ){
201 // modifies matDosage and vInfo directly
202 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
203 }
204
205 Eigen::MatrixXd M = Eigen::Map<Eigen::MatrixXd>(matDosage.data(), number_of_samples, vInfo->size());
206
207 chunk = DataChunk<Eigen::SparseMatrix<double>>( M.sparseView(), vInfo );
208
209 return ret;
210 }
211 #endif
212
213 #ifndef DISABLE_RCPP
214 bool getNextChunk( DataChunk<Rcpp::NumericMatrix> & chunk, const bool &useFilter = true) override {
215
216 // Update matDosage and vInfo for the chunk
217 bool ret = getNextChunk_helper();
218
219 if( useFilter ){
220 // modifies matDosage and vInfo directly
221 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
222 }
223
224 Rcpp::NumericMatrix M(number_of_samples, vInfo->size(), matDosage.data());
225 colnames(M) = Rcpp::wrap( vInfo->getFeatureNames() );
226 rownames(M) = Rcpp::wrap( vInfo->sampleNames );
227
228 chunk = DataChunk<Rcpp::NumericMatrix>( M, vInfo );
229
230 return ret;
231 }
232 #endif
233
234 bool getNextChunk( DataChunk<vector<double> > & chunk, const bool &useFilter = true) override {
235
236 // Update matDosage and vInfo for the chunk
237 bool ret = getNextChunk_helper();
238
239 if( useFilter ){
240 // modifies matDosage and vInfo directly
241 applyVariantFilter(matDosage, vInfo, number_of_samples, getMAF(), getMinVariance() );
242 }
243
244 chunk = DataChunk<vector<double> >( matDosage, vInfo );
245
246 return ret;
247 }
248
249 private:
250 size_t number_of_samples = 0;
251 int n_requested_variants = 0;
252 int currentIdx;
253 vector<double> matDosage;
254 vector<int> varIdx;
255 VariantInfo *vInfo = nullptr;
256 RPgenReader *pg = nullptr;
257 RPvar *pvar = nullptr;
258 DataTable dt;
259 VariantSet vs;
260 string fileIdx;
261 int n_samples_psam;
262 vector<int> sampleIdx1;
263 FileType genoFileType;
264 bool missingToMean;
265
266 bool getNextChunk_helper (){
267
268 // clear data, but keep allocated capacity
269 matDosage.clear();
270 vInfo->clear();
271
272 // number of variants in this chunk
273 int chunkSize = min(param.chunkSize, n_requested_variants - currentIdx);
274 chunkSize = max(chunkSize, 0);
275
276 // if no variants remain, return false
277 if( chunkSize == 0) return false;
278
279 // indeces of variants in chunk
280 auto end = min( varIdx.begin() + currentIdx + chunkSize, varIdx.end());
281 vector<int> varIdx_sub = {varIdx.begin() + currentIdx, end};
282
283 // read dosage into matDosage using
284 // 1-based indeces
285 // vector<int> varIdx_sub1(varIdx_sub);
286 vector<int> varIdx_sub1(varIdx_sub);
287 for(int &i : varIdx_sub1) i++;
288
289 pg->ReadList( matDosage, varIdx_sub1, missingToMean);
290
291 // Populate vInfo from DataTable
292 // Looking up column in DataTable is slow
293 // so do it once and process vector
294 vInfo->addVariants( subset_vector(dt["CHROM"], varIdx_sub),
295 subset_vector(dt["POS"], varIdx_sub),
296 subset_vector(dt["ID"], varIdx_sub),
297 subset_vector(dt["REF"], varIdx_sub),
298 subset_vector(dt["ALT"], varIdx_sub));
299
300 // increment current index
301 currentIdx += varIdx_sub.size();
302
303 return true;
304 }
305
306 // bool getNextChunk_helper(){return getNextChunk_helper2();}
307
308 /* Parse PVAR file of variant positions and IDs
309 */
310 void process_variants(){
311
312 // if file is PGEN
313 if( genoFileType == PGEN ){
314 // Name of .pvar file based on replacing .pgen$
315 fileIdx = regex_replace(param.file, regex("pgen$"), "pvar");
316
317 // Read .pvar file into DataTable
318 // column names are defined by line starting with "#CHROM"
319 // lines before this are ignored
320 dt = DataTable( fileIdx, "#CHROM" );
321
322 // if file is BED
323 }else if( genoFileType == PBED ){
324
325 // Name of .bim file based on replacing .pgen$
326 fileIdx = regex_replace(param.file, regex("bed$"), "bim");
327
328 // Read BIM file with no headerKey
329
330 dt = DataTable( fileIdx );
331 dt.setColNames({"CHROM", "ID", "CM", "POS", "ALT", "REF"});
332 }else{
333 throw logic_error("Not valid genotype file extension: " + param.file);
334 }
335
336 // initialize variant set
337 vs = VariantSet(dt["CHROM"], stoi_vec(dt["POS"]));
338
339 // Set genomic regions regions
341 }
342
343 void process_samples(){
344
345 DataTable dt2;
346
347 string fileSamples;
348
349 // Get path to samples file
350 // if custom samples file is given
351 if( param.fileSamples.compare("") != 0){
352 fileSamples = param.fileSamples;
353 }else{
354 if( genoFileType == PGEN ){
355 // Name of .psam file based on replacing .pgen$
356 fileSamples = regex_replace(param.file, regex("pgen$"), "psam");
357 }else if( genoFileType == PBED ){
358 // Name of .fam file based on replacing .pgen$
359 fileSamples = regex_replace(param.file, regex("bed$"), "fam");
360 }
361 }
362
363 // Read sample file depending on extension
364 if( regex_search(fileSamples, regex("psam$")) ){
365 // Read .psam file into DataTable
366 // column names are define by line starting with "#IID"
367 // lines before this are ignored
368 dt2 = DataTable(fileSamples, "#IID");
369
370 }else if( regex_search(fileSamples, regex("fam$")) ){
371
372 // Read BIM file with no headerKey
373 // space delimited entries
374 dt2 = DataTable( fileSamples, "");
375
376 // set column names
377 vector<string> names = {"FID", "IID", "PID", "MID", "SEX", "PHENO"};
378 vector<string> names_sub(names.begin(), names.begin() + dt.ncols());
379 dt2.setColNames(names_sub);
380 }else{
381 throw logic_error("Not valid sample file extension: " + fileSamples);
382 }
383
384 // Sample names from PSAM file
385 vector<string> SamplesNames = dt2["IID"];
386 n_samples_psam = SamplesNames.size();
387
388 // indeces of samples to include
389 vector<int> sampleIdx;
390
391 // Filter samples
392 // If param.samples contains entries
393 if( param.samples.compare("-") != 0 ){
394
395 // get sample ids from param.samples
396 vector<string> requestedSamples;
397
398 // split delmited string into vector
399 // boost::split(requestedSamples, param.samples, boost::is_any_of("\t,\n"));
400 requestedSamples = split_any_of(param.samples, "\t,\n");
401
402 // Use unordered_map linking sample id to index
403 // for fast searching
404 // sn = sampleNames
405 unordered_map<string,int> map_sn;
406 for(int i=0; i<SamplesNames.size(); i++){
407 map_sn.emplace(SamplesNames[i], i);
408 }
409
410 // For each requested sample ID,
411 // get its index in the PSAM file
412 for( string & name : requestedSamples){
413 if( map_sn.count(name) == 0){
414 throw logic_error("Sample id not found: " + name);
415 }
416 sampleIdx.push_back( map_sn[name] );
417 }
418 sort(sampleIdx.begin(), sampleIdx.end());
419
420 // Get the requested sample ID's
421 // sorted according to the PSAM file
422 vector<string> requestedSamples_ordered;
423 for(int i : sampleIdx){
424 requestedSamples_ordered.push_back( SamplesNames[i] );
425 }
426
427 // populate VariantInfo with requested sample IDs
428 // in the order from the PSAM file
429 vInfo = new VariantInfo( requestedSamples_ordered );
430 number_of_samples = requestedSamples_ordered.size();
431
432 // convert to 1-based indeces
433 sampleIdx1.assign(sampleIdx.begin(), sampleIdx.end());
434 for(int &i : sampleIdx1) i++;
435 }else{
436 vInfo = new VariantInfo( SamplesNames );
437 number_of_samples = SamplesNames.size();
438
439 // set sampleIdx1 to be seq(1, number_of_samples)
440 sampleIdx1.resize(number_of_samples);
441 iota(begin(sampleIdx1), end(sampleIdx1), 1);
442 }
443 }
444
445};
446
447}
448
449#endif
Definition GenomicDataStream_virtual.h:40
Definition DataTable.h:28
DataTable()
Definition DataTable.h:32
double getMinVariance()
Definition GenomicDataStream_virtual.h:219
double getMAF() const
Definition GenomicDataStream_virtual.h:225
Param param
Definition GenomicDataStream_virtual.h:273
GenomicDataStream()
Definition GenomicDataStream_virtual.h:187
Definition GenomicRanges.h:25
const int size() const
Definition GenomicRanges.h:61
Definition VariantInfo.h:24
void clear()
Definition VariantInfo.h:142
VariantInfo()
Definition VariantInfo.h:28
void addVariants(const vector< string > &chr, const vector< string > &pos, const vector< string > &id, const vector< string > &allele1, const vector< string > &allele2)
Definition VariantInfo.h:54
Definition VariantSet.h:45
VariantSet()
Definition VariantSet.h:48
vector< string > getSampleNames() override
Definition pgenstream.h:129
bool getNextChunk(DataChunk< Rcpp::NumericMatrix > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:214
bool getNextChunk(DataChunk< vector< double > > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:234
~pgenstream()
Definition pgenstream.h:82
bool getNextChunk(DataChunk< Eigen::SparseMatrix< double > > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:195
string getStreamType() override
Definition pgenstream.h:135
bool getNextChunk(DataChunk< arma::mat > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:143
pgenstream(const Param &param)
Definition pgenstream.h:47
void setRegions(const vector< string > &regions) override
Definition pgenstream.h:96
bool getNextChunk(DataChunk< Eigen::MatrixXd > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:178
GenomicRanges getChromRanges() override
Definition pgenstream.h:139
int n_samples() override
Definition pgenstream.h:123
bool getNextChunk(DataChunk< arma::sp_mat > &chunk, const bool &useFilter=true) override
Definition pgenstream.h:160
pgenstream()
Definition pgenstream.h:43
Definition bgenstream.h:33
FileType
Definition utils.h:298
@ PBED
Definition utils.h:304
@ PGEN
Definition utils.h:303
Definition GenomicDataStream_virtual.h:84
vector< string > regions
Definition GenomicDataStream_virtual.h:173
string samples
Definition GenomicDataStream_virtual.h:174
string fileSamples
Definition GenomicDataStream_virtual.h:171
FileType fileType
Definition GenomicDataStream_virtual.h:178
string file
Definition GenomicDataStream_virtual.h:171
int chunkSize
Definition GenomicDataStream_virtual.h:176
bool missingToMean
Definition GenomicDataStream_virtual.h:177