scran_pca
Principal component analysis for single-cell data
Loading...
Searching...
No Matches
subset_pca.hpp
Go to the documentation of this file.
1#ifndef SCRAN_PCA_SUBSET_HPP
2#define SCRAN_PCA_SUBSET_HPP
3
4#include <vector>
5#include <optional>
6#include <cstddef>
7
8#include "simple_pca.hpp"
9#include "blocked_pca.hpp"
10#include "utils.hpp"
11
12#include "sanisizer/sanisizer.hpp"
13#include "tatami_mult/tatami_mult.hpp"
14#include "tatami/tatami.hpp"
15#include "Eigen/Dense"
16
22namespace scran_pca {
23
27template<typename Index_, class SubsetVector_>
28std::vector<Index_> invert_subset(const Index_ total, const SubsetVector_& subset) {
29 std::vector<Index_> output;
30 output.reserve(total - subset.size());
31 const auto end = subset.size();
32 I<decltype(end)> pos = 0;
33 for (Index_ i = 0; i < total; ++i) {
34 if (pos != end && sanisizer::is_equal(subset[pos], i)) {
35 ++pos;
36 continue;
37 }
38 output.push_back(i);
39 }
40 return output;
41}
42
43template<typename Value_, typename Index_, typename EigenMatrix_>
44std::vector<typename EigenMatrix_::Scalar> multiply_by_right_singular_vectors(const tatami::Matrix<Value_, Index_>& mat, const EigenMatrix_& rhs_vectors, int num_threads) {
45 const auto num_features = mat.nrow();
46 const auto num_cells = mat.ncol();
47 const auto rank = rhs_vectors.cols();
48
49 typedef typename EigenMatrix_::Scalar Scalar;
50 std::vector<Scalar> output(sanisizer::product<typename std::vector<Scalar>::size_type>(num_features, rank));
51 static_assert(!EigenMatrix_::IsRowMajor);
52 auto get_right = [&](I<decltype(rank)> r) -> auto {
53 return rhs_vectors.data() + sanisizer::product_unsafe<std::size_t>(r, num_cells);
54 };
55
56 if (mat.sparse()) {
57 if (mat.prefer_rows()) {
58 tatami_mult::MultiplySparseRowWithDenseColumnMatrixToColumnOutputOptions options;
59 options.num_threads = num_threads;
60 tatami_mult::multiply_sparse_row_with_dense_column_matrix_to_column_output(mat, rank, get_right, output.data(), options);
61 } else {
62 tatami_mult::MultiplySparseColumnWithDenseColumnMatrixToColumnOutputOptions options;
63 options.num_threads = num_threads;
64 tatami_mult::multiply_sparse_column_with_dense_column_matrix_to_column_output(mat, rank, get_right, output.data(), options);
65 }
66 } else {
67 if (mat.prefer_rows()) {
68 tatami_mult::MultiplyDenseRowWithDenseColumnMatrixToColumnOutputOptions options;
69 options.num_threads = num_threads;
70 tatami_mult::multiply_dense_row_with_dense_column_matrix_to_column_output(mat, rank, get_right, output.data(), options);
71 } else {
72 tatami_mult::MultiplyDenseColumnWithDenseColumnMatrixToColumnOutputOptions options;
73 options.num_threads = num_threads;
74 tatami_mult::multiply_dense_column_with_dense_column_matrix_to_column_output(mat, rank, get_right, output.data(), options);
75 }
76 }
77
78 return output;
79}
80
81template<class SubsetVector_, class EigenVector_>
82void expand_into_vector(const SubsetVector_& subset, const EigenVector_& source, EigenVector_& dest) {
83 const auto nsub = subset.size();
84 for (I<decltype(nsub)> s = 0; s < nsub; ++s) {
85 dest.coeffRef(subset[s]) = source.coeff(s);
86 }
87}
88
89template<class SubsetVector_, class EigenMatrix_>
90void expand_into_matrix_rows(const SubsetVector_& subset, const EigenMatrix_& source, EigenMatrix_& dest) {
91 const auto nsub = subset.size();
92
93 // This access pattern should be a little more cache-friendly for the
94 // default column-major storage of Eigen::MatrixXd's.
95 const auto cols = dest.cols();
96 for (I<decltype(cols)> c = 0; c < cols; ++c) {
97 for (I<decltype(nsub)> s = 0; s < nsub; ++s) {
98 dest.coeffRef(subset[s], c) = source.coeff(s, c);
99 }
100 }
101}
102
103template<class SubsetVector_, class EigenMatrix_>
104void expand_into_matrix_columns(const SubsetVector_& subset, const EigenMatrix_& source, EigenMatrix_& dest) {
105 const auto nsub = subset.size();
106 for (I<decltype(nsub)> s = 0; s < nsub; ++s) {
107 dest.col(subset[s]) = source.col(s);
108 }
109}
120template<typename EigenVector_ = Eigen::VectorXd>
122
133template<typename EigenMatrix_, class EigenVector_>
135
161template<typename Value_, typename Index_, typename SubsetVector_, typename EigenMatrix_, class EigenVector_>
164 const SubsetVector_& subset,
165 const SubsetPcaOptions<EigenVector_>& options,
167) {
168 const auto full_size = mat.nrow();
169 auto final_center = sanisizer::create<EigenVector_>(full_size);
170 auto final_scale = sanisizer::create<EigenVector_>(full_size);
171 EigenMatrix_ final_rotation;
172
173 // Don't move subset into the constructor, we'll need it later.
175
176 simple_pca_internal(
177 sub_mat,
178 options,
179 output,
180 [&](const EigenMatrix_& rhs_vectors, const EigenVector_& sing_vals) -> void {
181 const auto inv_subset = invert_subset(mat.nrow(), subset);
182 // Don't move inv_subset into the constructor, we'll need it later.
183 tatami::DelayedSubsetSortedUnique<Value_, Index_, I<decltype(inv_subset)> > inv_mat(tatami::wrap_shared_ptr(&mat), inv_subset, true);
184
185 const auto num_inv = inv_mat.nrow();
186 auto inv_center = sanisizer::create<EigenVector_>(num_inv);
187 auto inv_scale = sanisizer::create<EigenVector_>(num_inv);
188 compute_row_means_and_variances(inv_mat, options.num_threads, inv_center, inv_scale);
189 process_scale_vector(options.scale, inv_scale);
190
191 const auto product = multiply_by_right_singular_vectors(inv_mat, rhs_vectors, options.num_threads);
192 const auto rank = rhs_vectors.cols();
193 final_rotation.resize(sanisizer::cast<Eigen::Index>(full_size), rank);
194 for (I<decltype(rank)> r = 0; r < rank; ++r) {
195 const auto varexp = sing_vals.coeff(r);
196 if (varexp == 0) {
197 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
198 final_rotation.coeffRef(inv_subset[i], r) = 0;
199 }
200 continue;
201 }
202
203 const auto curshift = rhs_vectors.col(r).sum();
204 const auto optr = product.data() + sanisizer::product_unsafe<std::size_t>(r, num_inv);
205 const auto compute = [&](I<decltype(num_inv)> i) -> typename EigenVector_::Scalar {
206 return (optr[i] - curshift * inv_center.coeff(i)) / varexp;
207 };
208
209 if (!options.scale) {
210 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
211 final_rotation.coeffRef(inv_subset[i], r) = compute(i);
212 }
213 } else {
214 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
215 final_rotation.coeffRef(inv_subset[i], r) = compute(i) / inv_scale.coeff(i);
216 }
217 }
218 }
219
220 expand_into_vector(inv_subset, inv_center, final_center);
221 if (options.scale) {
222 expand_into_vector(inv_subset, inv_scale, final_scale);
223 }
224 }
225 );
226
227 expand_into_vector(subset, output.center, final_center);
228 output.center.swap(final_center);
229
230 if (options.scale) {
231 expand_into_vector(subset, *(output.scale), final_scale);
232 output.scale->swap(final_scale);
233 }
234
235 expand_into_matrix_rows(subset, output.rotation, final_rotation);
236 output.rotation.swap(final_rotation);
237}
238
258template<typename EigenMatrix_ = Eigen::MatrixXd, class EigenVector_ = Eigen::VectorXd, typename Value_, typename Index_, class SubsetVector_>
261 const SubsetVector_& subset,
262 const SubsetPcaOptions<EigenVector_>& options
263) {
265 subset_pca(mat, subset, options, output);
266 return output;
267}
268
275template<typename EigenVector_ = Eigen::VectorXd>
277
288template<typename EigenMatrix_, class EigenVector_>
290
320template<typename Value_, typename Index_, class SubsetVector_, typename Block_, typename EigenMatrix_, class EigenVector_>
323 const SubsetVector_& subset,
324 const Block_* block,
325 const std::size_t num_blocks,
328) {
329 const auto full_size = mat.nrow();
330 EigenMatrix_ final_center(
331 sanisizer::cast<I<decltype(std::declval<EigenMatrix_>().rows())> >(num_blocks),
332 sanisizer::cast<I<decltype(std::declval<EigenMatrix_>().cols())> >(full_size)
333 );
334 auto final_scale = sanisizer::create<EigenVector_>(full_size);
335 EigenMatrix_ final_rotation;
336
337 // Don't move subset into the constructor, we'll need it later.
339
340 blocked_pca_internal<Value_, Index_, Block_, EigenMatrix_, EigenVector_>(
341 sub_mat,
342 block,
343 num_blocks,
344 options,
345 output,
346 [&](
347 const std::size_t num_blocks,
348 const std::vector<Index_>& block_sizes,
349 const std::optional<BlockingDetails<EigenVector_> >& block_details,
350 const EigenMatrix_& rhs_vectors,
351 const EigenVector_& sing_vals
352 ) -> void {
353 auto inv_subset = invert_subset(mat.nrow(), subset);
354 // Don't move inv_subset into the constructor, we'll need it later.
355 tatami::DelayedSubsetSortedUnique<Value_, Index_, I<decltype(inv_subset)> > inv_mat(tatami::wrap_shared_ptr(&mat), inv_subset, true);
356
357 const auto num_cells = inv_mat.ncol();
358 const auto num_inv = inv_mat.nrow();
359 EigenMatrix_ inv_center;
360 auto inv_scale = sanisizer::create<EigenVector_>(num_inv);
361 compute_blockwise_mean_and_variance_tatami(inv_mat, block, num_blocks, block_sizes, block_details, inv_center, inv_scale, options.num_threads);
362 process_scale_vector(options.scale, inv_scale);
363
364 // Need to adjust the RHS singular vector matrix to mimic weighting of the input matrix.
365 const EigenMatrix_* rhs_ptr = NULL;
366 std::optional<EigenMatrix_> weighted_rhs;
367 if (block_details.has_value()) {
368 weighted_rhs = rhs_vectors;
369 weighted_rhs->array().colwise() *= block_details->expanded_weights.array();
370 rhs_ptr = &(*weighted_rhs);
371 } else {
372 rhs_ptr = &rhs_vectors;
373 }
374
375 const auto product = multiply_by_right_singular_vectors(inv_mat, *rhs_ptr, options.num_threads);
376 final_rotation.resize(
377 sanisizer::cast<I<decltype(final_rotation.rows())> >(full_size),
378 rhs_vectors.cols()
379 );
380
381 const auto rank = rhs_vectors.cols();
382 auto shift_buffer = sanisizer::create<EigenVector_>(num_blocks);
383 for (I<decltype(rank)> r = 0; r < rank; ++r) {
384 const auto varexp = sing_vals.coeff(r);
385 if (varexp == 0) {
386 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
387 final_rotation.coeffRef(inv_subset[i], r) = 0;
388 }
389 continue;
390 }
391
392 std::fill(shift_buffer.begin(), shift_buffer.end(), 0);
393 for (I<decltype(num_cells)> i = 0; i < num_cells; ++i) {
394 shift_buffer.coeffRef(block[i]) += rhs_vectors.coeff(i, r);
395 }
396
397 const auto optr = product.data() + sanisizer::product_unsafe<std::size_t>(r, num_inv);
398 const auto compute = [&](I<decltype(num_inv)> i) -> typename EigenVector_::Scalar {
399 typename EigenVector_::Scalar curshift = 0;
400 for (I<decltype(num_blocks)> b = 0; b < num_blocks; ++b) {
401 curshift += shift_buffer.coeff(b) * inv_center.coeff(b, i);
402 }
403 return (optr[i] - curshift) / varexp;
404 };
405
406 if (options.scale) {
407 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
408 final_rotation.coeffRef(inv_subset[i], r) = compute(i) / inv_scale.coeff(i);
409 }
410 } else {
411 for (I<decltype(num_inv)> i = 0; i < num_inv; ++i) {
412 final_rotation.coeffRef(inv_subset[i], r) = compute(i);
413 }
414 }
415 }
416
417 expand_into_matrix_columns(inv_subset, inv_center, final_center);
418 if (options.scale) {
419 expand_into_vector(inv_subset, inv_scale, final_scale);
420 }
421 }
422 );
423
424 expand_into_matrix_columns(subset, output.center, final_center);
425 output.center.swap(final_center);
426
427 if (options.scale) {
428 expand_into_vector(subset, (*output.scale), final_scale);
429 output.scale->swap(final_scale);
430 }
431
432 expand_into_matrix_rows(subset, output.rotation, final_rotation);
433 output.rotation.swap(final_rotation);
434}
435
460template<typename EigenMatrix_ = Eigen::MatrixXd, class EigenVector_ = Eigen::VectorXd, typename Value_, typename Index_, class SubsetVector_, typename Block_>
463 const SubsetVector_& subset,
464 const Block_* block,
465 const std::size_t num_blocks,
467) {
469 subset_pca_blocked(mat, subset, block, num_blocks, options, output);
470 return output;
471}
472
473}
474
475#endif
PCA on residuals after regressing out a blocking factor.
virtual Index_ ncol() const=0
virtual Index_ nrow() const=0
virtual bool prefer_rows() const=0
virtual std::unique_ptr< MyopicSparseExtractor< Value_, Index_ > > sparse(bool row, const Options &opt) const=0
Principal component analysis on single-cell data.
void subset_pca_blocked(const tatami::Matrix< Value_, Index_ > &mat, const SubsetVector_ &subset, const Block_ *block, const std::size_t num_blocks, const SubsetPcaBlockedOptions< EigenVector_ > &options, SubsetPcaBlockedResults< EigenMatrix_, EigenVector_ > &output)
Definition subset_pca.hpp:321
void subset_pca(const tatami::Matrix< Value_, Index_ > &mat, const SubsetVector_ &subset, const SubsetPcaOptions< EigenVector_ > &options, SubsetPcaResults< EigenMatrix_, EigenVector_ > &output)
Definition subset_pca.hpp:162
std::shared_ptr< const Matrix< Value_, Index_ > > wrap_shared_ptr(const Matrix< Value_, Index_ > *const ptr)
PCA on a gene-by-cell matrix.
Options for blocked_pca().
Definition blocked_pca.hpp:36
bool scale
Definition blocked_pca.hpp:61
int num_threads
Definition blocked_pca.hpp:106
Results of blocked_pca().
Definition blocked_pca.hpp:713
std::optional< EigenVector_ > scale
Definition blocked_pca.hpp:759
EigenMatrix_ rotation
Definition blocked_pca.hpp:742
EigenMatrix_ center
Definition blocked_pca.hpp:750
Options for simple_pca().
Definition simple_pca.hpp:34
bool scale
Definition simple_pca.hpp:58
int num_threads
Definition simple_pca.hpp:78
Results of simple_pca().
Definition simple_pca.hpp:279
std::optional< EigenVector_ > scale
Definition simple_pca.hpp:324
EigenMatrix_ rotation
Definition simple_pca.hpp:308
EigenVector_ center
Definition simple_pca.hpp:315