@@ -293,6 +293,113 @@ def run_density_evaluation(
293293 return log_density
294294
295295
296+ def compute_kernel (
297+ data : Union [pd .DataFrame , sc .AnnData ],
298+ knn : int = 30 ,
299+ alpha : float = 0 ,
300+ pca_key : str = "X_pca" ,
301+ kernel_key : str = "DM_Kernel" ,
302+ ) -> csr_matrix :
303+ """
304+ Compute the adaptive anisotropic diffusion kernel.
305+
306+ Parameters
307+ ----------
308+ data : Union[pd.DataFrame, sc.AnnData]
309+ Data points (rows) in a feature space (columns) for pd.DataFrame.
310+ For sc.AnnData, it uses the .X attribute.
311+ knn : int
312+ Number of nearest neighbors for adaptive kernel calculation. Default is 30.
313+ alpha : float
314+ Normalization parameter for the diffusion operator. Default is 0.
315+ pca_key : str, optional
316+ Key to retrieve PCA projections from data if it is a sc.AnnData object. Default is 'X_pca'.
317+ kernel_key : str, optional
318+ Key to store the kernel in obsp of data if it is a sc.AnnData object. Default is 'DM_Kernel'.
319+
320+ Returns
321+ -------
322+ csr_matrix
323+ Computed kernel matrix.
324+ """
325+
326+ # If the input is sc.AnnData, convert it to a DataFrame
327+ if isinstance (data , sc .AnnData ):
328+ data_df = pd .DataFrame (data .obsm [pca_key ], index = data .obs_names )
329+ else :
330+ data_df = data
331+
332+ N = data_df .shape [0 ]
333+ temp = sc .AnnData (data_df .values )
334+ sc .pp .neighbors (temp , n_pcs = 0 , n_neighbors = knn )
335+ kNN = temp .obsp ["distances" ]
336+
337+ adaptive_k = int (np .floor (knn / 3 ))
338+ adaptive_std = np .zeros (N )
339+ for i in np .arange (N ):
340+ adaptive_std [i ] = np .sort (kNN .data [kNN .indptr [i ] : kNN .indptr [i + 1 ]])[
341+ adaptive_k - 1
342+ ]
343+
344+ x , y , dists = find (kNN )
345+ dists /= adaptive_std [x ]
346+ W = csr_matrix ((np .exp (- dists ), (x , y )), shape = [N , N ])
347+
348+ kernel = W + W .T
349+
350+ if alpha > 0 :
351+ D = np .ravel (kernel .sum (axis = 1 ))
352+ D [D != 0 ] = D [D != 0 ] ** (- alpha )
353+ mat = csr_matrix ((D , (range (N ), range (N ))), shape = [N , N ])
354+ kernel = mat .dot (kernel ).dot (mat )
355+
356+ if isinstance (data , sc .AnnData ):
357+ data .obsp [kernel_key ] = kernel
358+
359+ return kernel
360+
361+
362+ def diffusion_maps_from_kernel (
363+ kernel : csr_matrix , n_components : int = 10 , seed : Union [int , None ] = 0
364+ ):
365+ """
366+ Compute the diffusion map given a kernel matrix.
367+
368+ Parameters
369+ ----------
370+ kernel : csr_matrix
371+ Precomputed kernel matrix.
372+ n_components : int
373+ Number of diffusion components to compute. Default is 10.
374+ seed : Union[int, None]
375+ Seed for random initialization. Default is 0.
376+
377+ Returns
378+ -------
379+ dict
380+ T-matrix (T), Diffusion components (EigenVectors) and corresponding eigenvalues (EigenValues).
381+ """
382+ N = kernel .shape [0 ]
383+ D = np .ravel (kernel .sum (axis = 1 ))
384+ D [D != 0 ] = 1 / D [D != 0 ]
385+ T = csr_matrix ((D , (range (N ), range (N ))), shape = [N , N ]).dot (kernel )
386+
387+ np .random .seed (seed )
388+ v0 = np .random .rand (min (T .shape ))
389+ D , V = eigs (T , n_components , tol = 1e-4 , maxiter = 1000 , v0 = v0 )
390+
391+ D = np .real (D )
392+ V = np .real (V )
393+ inds = np .argsort (D )[::- 1 ]
394+ D = D [inds ]
395+ V = V [:, inds ]
396+
397+ for i in range (V .shape [1 ]):
398+ V [:, i ] = V [:, i ] / np .linalg .norm (V [:, i ])
399+
400+ return {"T" : T , "EigenVectors" : pd .DataFrame (V ), "EigenValues" : pd .Series (D )}
401+
402+
296403def run_diffusion_maps (
297404 data : Union [pd .DataFrame , sc .AnnData ],
298405 n_components : int = 10 ,
@@ -347,70 +454,21 @@ def run_diffusion_maps(
347454 else :
348455 data_df = data
349456
350- if not isinstance (data_df , pd .DataFrame ):
351- raise ValueError ("'data_df' should be a pd.DataFrame or a sc.AnnData instance " )
457+ if not isinstance (data_df , pd .DataFrame ) and not issparse ( data_df ) :
458+ raise ValueError ("'data_df' should be a pd.DataFrame or sc.AnnData" )
352459
353- # Determine the kernel
354- N = data_df .shape [0 ]
355460 if not issparse (data_df ):
356- print ("Determing nearest neighbor graph..." )
357- temp = sc .AnnData (data_df .values )
358- sc .pp .neighbors (temp , n_pcs = 0 , n_neighbors = knn )
359- kNN = temp .obsp ["distances" ]
360-
361- # Adaptive k
362- adaptive_k = int (np .floor (knn / 3 ))
363- adaptive_std = np .zeros (N )
364-
365- for i in np .arange (len (adaptive_std )):
366- adaptive_std [i ] = np .sort (kNN .data [kNN .indptr [i ] : kNN .indptr [i + 1 ]])[
367- adaptive_k - 1
368- ]
369-
370- # Kernel
371- x , y , dists = find (kNN )
372-
373- # X, y specific stds
374- dists = dists / adaptive_std [x ]
375- W = csr_matrix ((np .exp (- dists ), (x , y )), shape = [N , N ])
376-
377- # Diffusion components
378- kernel = W + W .T
461+ kernel = compute_kernel (data_df , knn , alpha )
379462 else :
463+ warn (
464+ "'data' is a sparse matrix and will be interpreted as kernel. "
465+ "To avoid this warning compute diffusion maps from a precompued kernel using "
466+ "palantir.utils.diffusion_maps_from_kernel()."
467+ )
380468 kernel = data_df
381469
382- # Markov
383- D = np .ravel (kernel .sum (axis = 1 ))
384-
385- if alpha > 0 :
386- # L_alpha
387- D [D != 0 ] = D [D != 0 ] ** (- alpha )
388- mat = csr_matrix ((D , (range (N ), range (N ))), shape = [N , N ])
389- kernel = mat .dot (kernel ).dot (mat )
390- D = np .ravel (kernel .sum (axis = 1 ))
391-
392- D [D != 0 ] = 1 / D [D != 0 ]
393- T = csr_matrix ((D , (range (N ), range (N ))), shape = [N , N ]).dot (kernel )
394- # Eigen value dcomposition
395- np .random .seed (seed )
396- v0 = np .random .rand (min (T .shape ))
397- D , V = eigs (T , n_components , tol = 1e-4 , maxiter = 1000 , v0 = v0 )
398- D = np .real (D )
399- V = np .real (V )
400- inds = np .argsort (D )[::- 1 ]
401- D = D [inds ]
402- V = V [:, inds ]
403-
404- # Normalize
405- for i in range (V .shape [1 ]):
406- V [:, i ] = V [:, i ] / np .linalg .norm (V [:, i ])
470+ res = diffusion_maps_from_kernel (kernel , n_components , seed )
407471
408- # Create are results dictionary
409- res = {"T" : T , "EigenVectors" : V , "EigenValues" : D }
410- res ["EigenVectors" ] = pd .DataFrame (res ["EigenVectors" ])
411- if not issparse (data_df ):
412- res ["EigenVectors" ].index = data_df .index
413- res ["EigenValues" ] = pd .Series (res ["EigenValues" ])
414472 res ["kernel" ] = kernel
415473
416474 if isinstance (data , sc .AnnData ):
0 commit comments