@@ -26,6 +26,29 @@ class CellNotFoundException(Exception):
2626 pass
2727
2828
29+ def _slice_pca (ad : AnnData , n_comps : int ) -> None :
30+ """Slice PCA results to fewer components without recomputing.
31+
32+ Parameters
33+ ----------
34+ ad : AnnData
35+ AnnData object with PCA results in obsm["X_pca"], uns["pca"], and varm["PCs"].
36+ n_comps : int
37+ Number of components to keep.
38+ """
39+ if "X_pca" in ad .obsm :
40+ ad .obsm ["X_pca" ] = ad .obsm ["X_pca" ][:, :n_comps ]
41+ if "pca" in ad .uns :
42+ for key in ("variance" , "variance_ratio" ):
43+ if key in ad .uns ["pca" ]:
44+ ad .uns ["pca" ][key ] = np .asarray (ad .uns ["pca" ][key ])[:n_comps ]
45+ params = ad .uns ["pca" ].get ("params" )
46+ if isinstance (params , dict ) and "n_comps" in params :
47+ params ["n_comps" ] = n_comps
48+ if "PCs" in ad .varm :
49+ ad .varm ["PCs" ] = ad .varm ["PCs" ][:, :n_comps ]
50+
51+
2952def run_pca (
3053 data : Union [pd .DataFrame , AnnData ],
3154 n_components : int = 300 ,
@@ -53,25 +76,14 @@ def run_pca(
5376 Tuple of PCA projections of the data and the explained variance.
5477 If AnnData is passed as data, the results are also written to the input object and None is returned.
5578 """
56- def _slice_pca (ad : AnnData , n_comps : int ) -> None :
57- if "X_pca" in ad .obsm :
58- ad .obsm ["X_pca" ] = ad .obsm ["X_pca" ][:, :n_comps ]
59- if "pca" in ad .uns :
60- for key in ("variance" , "variance_ratio" ):
61- if key in ad .uns ["pca" ]:
62- ad .uns ["pca" ][key ] = np .asarray (ad .uns ["pca" ][key ])[:n_comps ]
63- params = ad .uns ["pca" ].get ("params" )
64- if isinstance (params , dict ) and "n_comps" in params :
65- params ["n_comps" ] = n_comps
66- if "PCs" in ad .varm :
67- ad .varm ["PCs" ] = ad .varm ["PCs" ][:, :n_comps ]
68-
6979 if isinstance (data , pd .DataFrame ):
7080 ad = AnnData (data .values )
81+ old_pca = None
7182 else :
7283 ad = data
73- if pca_key != "X_pca" :
74- old_pca = ad .obsm .get ("X_pca" , None )
84+ # Preserve existing X_pca if user wants results in a different key
85+ if pca_key != "X_pca" and "X_pca" in ad .obsm :
86+ old_pca = ad .obsm ["X_pca" ].copy ()
7587 else :
7688 old_pca = None
7789
@@ -91,15 +103,19 @@ def _slice_pca(ad: AnnData, n_comps: int) -> None:
91103 elif n_comps > l_n_comps :
92104 sc .pp .pca (ad , n_comps = n_comps , mask_var = "highly_variable" , zero_center = False )
93105
94- if isinstance (data , AnnData ):
106+ # Capture results before potentially modifying obsm
107+ pca_projections = pd .DataFrame (ad .obsm ["X_pca" ], index = ad .obs_names )
108+ variance_ratio = ad .uns ["pca" ]["variance_ratio" ]
109+
110+ if isinstance (data , AnnData ) and pca_key != "X_pca" :
95111 data .obsm [pca_key ] = ad .obsm ["X_pca" ]
96- if pca_key != "X_pca" :
97- del data .obsm ["X_pca" ]
98- elif old_pca is not None :
112+ # Restore original X_pca if it existed, otherwise remove the temporary one
113+ if old_pca is not None :
99114 data .obsm ["X_pca" ] = old_pca
115+ else :
116+ del data .obsm ["X_pca" ]
100117
101- pca_projections = pd .DataFrame (ad .obsm [pca_key ], index = ad .obs_names )
102- return pca_projections , ad .uns ["pca" ]["variance_ratio" ]
118+ return pca_projections , variance_ratio
103119
104120
105121def run_low_density_variability (
0 commit comments