scran_pca
Principal component analysis for single-cell data
Loading...
Searching...
No Matches
simple_pca.hpp
Go to the documentation of this file.
1#ifndef SCRAN_PCA_SIMPLE_PCA_HPP
2#define SCRAN_PCA_SIMPLE_PCA_HPP
3
4#include <vector>
5#include <type_traits>
6#include <algorithm>
7#include <memory>
8#include <optional>
9
10#include "tatami/tatami.hpp"
11#include "tatami_stats/tatami_stats.hpp"
12#include "quickstats/quickstats.hpp"
13#include "irlba/irlba.hpp"
14#include "irlba/parallel.hpp"
15#include "irlba_tatami/irlba_tatami.hpp"
16#include "Eigen/Dense"
17#include "sanisizer/sanisizer.hpp"
18
19#include "utils.hpp"
20
26namespace scran_pca {
27
33template<typename EigenVector_ = Eigen::VectorXd>
39 // Avoid throwing an error if too many PCs are requested.
40 irlba_options.cap_number = true;
41 }
51 int number = 25;
52
58 bool scale = false;
59
64 bool transpose = true;
65
70 bool realize_matrix = true;
71
78 int num_threads = 1;
79
84};
85
89template<typename Value_, typename Index_, class EigenVector_>
90void compute_row_means_and_variances(const tatami::Matrix<Value_, Index_>& mat, const int num_threads, EigenVector_& center_v, EigenVector_& scale_v) {
91 tatami_stats::RssOptions<typename EigenVector_::Scalar> opts;
92 opts.num_threads = num_threads;
93 opts.mean_placeholder = 0; // rss() emits NaNs if there are no cells, we replace them with zeros to avoid downstream problems with propagation.
94
95 tatami_stats::RssBuffers<typename EigenVector_::Scalar> buffers;
96 buffers.mean = center_v.data();
97 buffers.rss = scale_v.data();
98 tatami_stats::rss(true, mat, buffers, opts);
99
100 const auto ncells = mat.ncol();
101 if (ncells > 1) {
102 scale_v /= ncells - 1;
103 }
104}
105
106template<class EigenVector_, class EigenMatrix_>
107std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > prepare_deferred_matrix_for_irlba(
108 std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > ptr,
109 const SimplePcaOptions<EigenVector_>& options,
110 const EigenVector_& center_v,
111 const EigenVector_& scale_v
112) {
113 std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > alt;
114 alt.reset(new irlba::CenteredMatrix<EigenVector_, EigenMatrix_, I<decltype(ptr)>, I<decltype(&center_v)> >(std::move(ptr), &center_v));
115 ptr.swap(alt);
116
117 if (options.scale) {
118 alt.reset(new irlba::ScaledMatrix<EigenVector_, EigenMatrix_, I<decltype(ptr)>, I<decltype(&scale_v)> >(std::move(ptr), &scale_v, true, true));
119 ptr.swap(alt);
120 }
121
122 return ptr;
123}
124
125template<class EigenMatrix_, typename Value_, typename Index_, class EigenVector_>
126std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > prepare_sparse_matrix_for_irlba(
128 const SimplePcaOptions<EigenVector_>& options,
129 EigenVector_& center_v,
130 EigenVector_& scale_v,
131 typename EigenVector_::Scalar& total_var
132) {
133 const auto ngenes = mat.nrow();
134 sanisizer::resize(center_v, ngenes);
135 sanisizer::resize(scale_v, ngenes);
136 std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > output;
137
138 if (options.realize_matrix) {
139 // 'extracted' contains row-major contents...
141 mat,
142 /* row = */ true,
143 [&]{
145 opt.two_pass = false;
146 opt.num_threads = options.num_threads;
147 return opt;
148 }()
149 );
150
151 // But we effectively transpose it to CSC with genes in columns.
152 const Index_ ncells = mat.ncol();
153 const auto sparse_ptr = new irlba::ParallelSparseMatrix<
154 EigenVector_,
155 EigenMatrix_,
156 I<decltype(extracted.value)>,
157 I<decltype(extracted.index)>,
158 I<decltype(extracted.pointers)>
159 >(
160 ncells,
161 ngenes,
162 std::move(extracted.value),
163 std::move(extracted.index),
164 std::move(extracted.pointers),
165 true,
166 options.num_threads
167 );
168 output.reset(sparse_ptr);
169
170 tatami::parallelize([&](const int, const Index_ start, const Index_ length) -> void {
171 const auto& pointers = sparse_ptr->get_pointers();
172 const auto& values = sparse_ptr->get_values();
173 quickstats::RssWorkspace<typename EigenVector_::Scalar> work;
174
175 for (Index_ g = start, end = start + length; g < end; ++g) {
176 const auto offset = pointers[g];
177 const auto next_offset = pointers[g + 1]; // increment won't overflow as 'g + 1 <= end'.
178 const Index_ num_nonzero = next_offset - offset;
179 const auto results = quickstats::rss(ncells, num_nonzero, values.data() + offset, work);
180 center_v.coeffRef(g) = results.mean;
181 scale_v.coeffRef(g) = results.rss;
182 }
183 }, ngenes, options.num_threads);
184
185 if (ncells > 1) {
186 // if there are fewer than 2 cells, scale_v will naturally be set to zero.
187 scale_v /= ncells - 1;
188 } else if (!ncells) {
189 // override quickstats::rss()'s setting of the mean to NaN if there are no cells.
190 std::fill(center_v.begin(), center_v.end(), 0);
191 }
192
193 total_var = process_scale_vector(options.scale, scale_v);
194
195 } else {
196 compute_row_means_and_variances(mat, options.num_threads, center_v, scale_v);
197 total_var = process_scale_vector(options.scale, scale_v);
198 output.reset(
199 new irlba_tatami::Transposed<EigenVector_, EigenMatrix_, Value_, Index_, decltype(&mat)>(&mat, options.num_threads)
200 );
201 }
202
203 return prepare_deferred_matrix_for_irlba(std::move(output), options, center_v, scale_v);
204}
205
206template<class EigenMatrix_, typename Value_, typename Index_, class EigenVector_>
207std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > prepare_dense_matrix_for_irlba(
209 const SimplePcaOptions<EigenVector_>& options,
210 EigenVector_& center_v,
211 EigenVector_& scale_v,
212 typename EigenVector_::Scalar& total_var
213) {
214 const Index_ ngenes = mat.nrow();
215 sanisizer::resize(center_v, ngenes);
216 sanisizer::resize(scale_v, ngenes);
217
218 if (options.realize_matrix) {
219 // Create a matrix with genes in columns.
220 const Index_ ncells = mat.ncol();
221 auto emat = std::make_unique<EigenMatrix_>(
222 sanisizer::cast<I<decltype(std::declval<EigenMatrix_>().rows())> >(ncells),
223 sanisizer::cast<I<decltype(std::declval<EigenMatrix_>().cols())> >(ngenes)
224 );
225
226 // By default, Eigen's matrices are column major. In such cases, because we want to do
227 // a transposition, we pretend it's row major during the conversion.
228 static_assert(!EigenMatrix_::IsRowMajor);
230 mat,
231 /* row_major = */ true,
232 emat->data(),
233 [&]{
234 tatami::ConvertToDenseOptions opt;
235 opt.num_threads = options.num_threads;
236 return opt;
237 }()
238 );
239
240 center_v.array() = emat->array().colwise().sum();
241 if (ncells) { // if there are no cells, center_v will naturally be set to zero.
242 center_v /= ncells;
243 }
244 emat->array().rowwise() -= center_v.adjoint().array(); // applying it to avoid wasting time with deferred operations inside IRLBA.
245
246 scale_v.array() = emat->array().colwise().squaredNorm();
247 if (ncells > 1) { // if there are fewer than 2 cells, scale_v will naturally be set to zero.
248 scale_v /= ncells - 1;
249 }
250
251 total_var = process_scale_vector(options.scale, scale_v);
252 if (options.scale) {
253 emat->array().rowwise() /= scale_v.adjoint().array();
254 }
255
256 return std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> >(
258 );
259
260 } else {
261 compute_row_means_and_variances(mat, options.num_threads, center_v, scale_v);
262 total_var = process_scale_vector(options.scale, scale_v);
263 std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > output(
264 new irlba_tatami::Transposed<EigenVector_, EigenMatrix_, Value_, Index_, decltype(&mat)>(&mat, options.num_threads)
265 );
266 return prepare_deferred_matrix_for_irlba(std::move(output), options, center_v, scale_v);
267 }
268}
278template<typename EigenMatrix_, typename EigenVector_>
288 EigenMatrix_ components;
289
295 EigenVector_ variance_explained;
296
301 typename EigenVector_::Scalar total_variance = 0;
302
308 EigenMatrix_ rotation;
309
315 EigenVector_ center;
316
324 std::optional<EigenVector_> scale;
325
330};
331
335template<typename Value_, typename Index_, typename EigenMatrix_, class EigenVector_, class SubsetFunction_>
336void simple_pca_internal(
338 const SimplePcaOptions<EigenVector_>& options,
340 SubsetFunction_ subset_fun
341) {
343
345 std::unique_ptr<irlba::Matrix<EigenVector_, EigenMatrix_> > ptr;
346 if (mat.sparse()) {
347 ptr = prepare_sparse_matrix_for_irlba<EigenMatrix_>(mat, options, output.center, scale, output.total_variance);
348 } else {
349 ptr = prepare_dense_matrix_for_irlba<EigenMatrix_>(mat, options, output.center, scale, output.total_variance);
350 }
351
352 output.metrics = irlba::compute(*ptr, options.number, output.components, output.rotation, output.variance_explained, options.irlba_options);
353
354 subset_fun(output.components, output.variance_explained);
355
356 clean_up(mat.ncol(), output.components, output.variance_explained);
357 if (options.transpose) {
358 output.components.adjointInPlace();
359 }
360
361 if (options.scale) {
362 output.scale = std::move(scale);
363 }
364}
392template<typename Value_, typename Index_, typename EigenMatrix_, class EigenVector_>
394 simple_pca_internal(
395 mat,
396 options,
397 output,
398 [](const EigenMatrix_&, const EigenVector_&) -> void {}
399 );
400}
401
417template<typename EigenMatrix_ = Eigen::MatrixXd, class EigenVector_ = Eigen::VectorXd, typename Value_, typename Index_>
423
424}
425
426#endif
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual std::unique_ptr< MyopicSparseExtractor< Value_, Index_ > > sparse(bool row, const Options &opt) const=0
Metrics compute(const Matrix_ &matrix, const Eigen::Index number, EigenMatrix_ &outU, EigenMatrix_ &outV, EigenVector_ &outD, const Options< EigenVector_ > &options)
Principal component analysis on single-cell data.
void simple_pca(const tatami::Matrix< Value_, Index_ > &mat, const SimplePcaOptions< EigenVector_ > &options, SimplePcaResults< EigenMatrix_, EigenVector_ > &output)
Definition simple_pca.hpp:393
CompressedSparseContents< StoredValue_, StoredIndex_, StoredPointer_ > retrieve_compressed_sparse_contents(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row, const RetrieveCompressedSparseContentsOptions &options)
int parallelize(Function_ fun, const Index_ tasks, const int workers)
void convert_to_dense(const Matrix< InputValue_, InputIndex_ > &matrix, const bool row_major, StoredValue_ *const store, const ConvertToDenseOptions &options)
Container_ create_container_of_Index_size(const Index_ x, Args_ &&... args)
Options for simple_pca().
Definition simple_pca.hpp:34
bool scale
Definition simple_pca.hpp:58
int number
Definition simple_pca.hpp:51
bool realize_matrix
Definition simple_pca.hpp:70
int num_threads
Definition simple_pca.hpp:78
bool transpose
Definition simple_pca.hpp:64
irlba::Options< EigenVector_ > irlba_options
Definition simple_pca.hpp:83
Results of simple_pca().
Definition simple_pca.hpp:279
EigenMatrix_ components
Definition simple_pca.hpp:288
std::optional< EigenVector_ > scale
Definition simple_pca.hpp:324
EigenMatrix_ rotation
Definition simple_pca.hpp:308
EigenVector_ center
Definition simple_pca.hpp:315
EigenVector_ variance_explained
Definition simple_pca.hpp:295
EigenVector_::Scalar total_variance
Definition simple_pca.hpp:301
irlba::Metrics metrics
Definition simple_pca.hpp:329