11# NOTE: These helper functions are ports of the original Python functions in 'helper_functions.py'
22
3- # ' Adds annotations (.var and .obs) from the unintegrated dataset to the
4- # ' integrated dataset. In the case of the control method "perfect_integration",
5- # ' the function will fetch annotations from the validation dataset instead.
3+ library(dplyr )
4+ requireNamespace(" anndataR" , quietly = TRUE )
5+
6+ # ' Adds annotations (.var and .obs) from the unintegrated data to the
7+ # ' integrated dataset.
8+ # ' In the case of the control method "perfect_integration",
9+ # ' the function will fetch the batch label from the unintegrated data
10+ # ' based on the split.
11+ # ' i.e., if in split 1, donor 3-5 is from batch 2, then the batch label for that split
12+ # ' will be changed from batch 1 to batch 2.
613# '
7- # ' @param i_adata AnnData object, batch- integrated dataset
8- # ' @param v_adata AnnData object, validation dataset
14+ # ' @param s1_adata AnnData object, integrated data from split 1
15+ # ' @param s2_adata AnnData object, integrated data from split 2
916# ' @param u_adata AnnData object, unintegrated dataset
1017# ' @return AnnData object with .var and .obs added
11- get_obs_var_for_integrated <- function (i_adata , v_adata , u_adata ) {
12- if (i_adata $ uns $ method_id == " perfect_integration_horizontal" ) {
13- if (i_adata $ n_obs != v_adata $ n_obs ) {
14- stop(
15- " The number of cells in the integrated (perfect_integration_horizontal) " ,
16- " and validation datasets do not match"
17- )
18- }
19- i_adata $ obs <- v_adata $ obs [rownames(i_adata ), , drop = FALSE ]
20- i_adata $ var <- v_adata $ var [colnames(i_adata ), , drop = FALSE ]
21- } else if (i_adata $ uns $ method_id == " perfect_integration_vertical" ) {
22- comb_adata <- anndata :: concat(list (v_adata , u_adata ))
23- # subset to just batch 1
24- # Check if 'batch' column exists
25- if (! " batch" %in% colnames(comb_adata $ obs )) {
26- stop(
27- " Column 'batch' not found in comb_adata$obs for " ,
28- " perfect_integration_vertical."
29- )
30- }
31- comb_adata <- comb_adata [comb_adata $ obs $ batch == 1 , ]
18+ # '
19+ get_obs_var_for_integrated <- function (s1_adata , s2_adata , u_adata ) {
20+
21+ s1_adata $ obs <- u_adata $ obs [s1_adata $ obs_names , ]
22+ s2_adata $ obs <- u_adata $ obs [s2_adata $ obs_names , ]
23+ s1_adata $ var <- u_adata $ var [s1_adata $ var_names , ]
24+ s2_adata $ var <- u_adata $ var [s2_adata $ var_names , ]
3225
33- if (i_adata $ n_obs != comb_adata $ n_obs ) {
34- stop(
35- " The number of cells in the integrated (perfect_integration_vertical) " ,
36- " and validation + unintegrated datasets do not match."
37- )
38- }
39- i_adata $ obs <- comb_adata $ obs [rownames(i_adata ), , drop = FALSE ]
40- i_adata $ var <- v_adata $ var [colnames(i_adata ), , drop = FALSE ]
41- } else {
42- if (i_adata $ n_obs != u_adata $ n_obs ) {
43- stop(
44- " The number of cells in the integrated and unintegrated datasets do not match"
45- )
46- }
47- # Compare obs_names for ordering
48- if (! all(rownames(i_adata ) == rownames(u_adata ))) {
49- warning(
50- " The cell ordering in the integrated and unintegrated datasets do not match"
51- )
26+ # if integrated data came from perfect integration, change the batch labels of the samples
27+ # everything is from batch 1, but some samples need to be labelled to come from batch 2
28+ if (s1_adata $ uns [" method_id" ] == " perfect_integration" ) {
29+ cat(
30+ " Control method 'perfect_integration' detected. Changing batch labels for split 2.\n "
31+ )
32+
33+ cat(" Computing new batch labels\n " )
34+ # mutate is needed as donors that are used for controls, we won't have the mapping
35+ s1_adata_new_batch_labels <- get_batch_label_perfect_integration(
36+ u_adata = u_adata ,
37+ i_adata = s1_adata ,
38+ split_id = 1
39+ )
40+
41+ s2_adata_new_batch_labels <- get_batch_label_perfect_integration(
42+ u_adata = u_adata ,
43+ i_adata = s2_adata ,
44+ split_id = 2
45+ )
46+
47+ cat(" Attaching new batch labels\n " )
48+ s1_adata $ obs $ batch <- s1_adata_new_batch_labels $ new_batch_label
49+ s2_adata $ obs $ batch <- s2_adata_new_batch_labels $ new_batch_label
5250 }
5351
54- i_adata $ obs <- u_adata $ obs [rownames(i_adata ), , drop = FALSE ]
55- i_adata $ var <- u_adata $ var [colnames(i_adata ), , drop = FALSE ]
56- }
52+ return (list (
53+ " s1_adata" = s1_adata ,
54+ " s2_adata" = s2_adata
55+ ))
56+ }
57+
58+ # ' Helper function to get the batch label for perfect integration.
59+ # ' First, get donor batch map for a given split.
60+ # ' Then apply the map to the integrated data.
61+ # '
62+ # ' @param u_adata AnnData object, unintegrated dataset.
63+ # ' @param i_adata AnnData object, integrated data.
64+ # ' @param split_id numeric, split id of the integrated data.
65+ # '
66+ # ' @return a dataframe with donor and new batch label
67+ # '
68+ get_batch_label_perfect_integration <- function (u_adata , i_adata , split_id ) {
69+ actual_donor_batch_map <- unique(
70+ u_adata $ obs [(u_adata $ obs $ split == split_id ),
71+ c(" donor" , " batch" )]
72+ )
73+ # mutate is needed as donors that are used for controls, we won't have the mapping
74+ i_adata_new_batch_labels <- i_adata $ obs [, c(" donor" , " batch" )] %> %
75+ left_join(actual_donor_batch_map , by = " donor" , suffix = c(" _old" , " _new" )) %> %
76+ mutate(new_batch_label = ifelse(is.na(batch_new ), batch_old , batch_new )) %> %
77+ select(donor , new_batch_label )
5778
58- i_adata
79+ return ( i_adata_new_batch_labels )
5980}
6081
6182# ' Subsets the anndata object to remove the control cells.
@@ -64,12 +85,12 @@ get_obs_var_for_integrated <- function(i_adata, v_adata, u_adata) {
6485# ' @param adata AnnData object
6586# ' @return AnnData object with cells from control samples removed
6687subset_nocontrols <- function (adata ) {
67- if (! " is_control" %in% colnames(adata $ obs )) {
68- stop(" The column 'is_control' is not present in the adata object." )
69- }
88+ if (! " is_control" %in% colnames(adata $ obs )) {
89+ stop(" The column 'is_control' is not present in the adata object." )
90+ }
7091
71- # Subset the adata to remove cells where is_control != 0
72- adata [adata $ obs $ is_control == 0 , ]
92+ # Subset the adata to remove cells where is_control != 0
93+ adata [adata $ obs $ is_control == 0 , ]
7394}
7495
7596# ' Subsets the anndata object to only include markers that need to be
@@ -79,7 +100,7 @@ subset_nocontrols <- function(adata) {
79100# ' @param adata AnnData object
80101# ' @return AnnData object with only the markers to correct
81102subset_markers_tocorrect <- function (adata ) {
82- adata [, adata $ var $ to_correct ]
103+ adata [, adata $ var $ to_correct ]
83104}
84105
85106# ' Subsets the anndata object to remove all cells where the marker is not
@@ -89,12 +110,12 @@ subset_markers_tocorrect <- function(adata) {
89110# ' @param adata AnnData object
90111# ' @return AnnData object with only the labeled cells
91112remove_unlabelled <- function (adata ) {
92- if (! " cell_type" %in% colnames(adata $ obs )) {
93- stop(" The column 'cell_type' is not present in the adata object." )
94- }
113+ if (! " cell_type" %in% colnames(adata $ obs )) {
114+ stop(" The column 'cell_type' is not present in the adata object." )
115+ }
95116
96- # Convert to lowercase and filter out "unlabelled" and "unlabeled"
97- is_unlabelled <- tolower(adata $ obs $ cell_type ) %in%
98- c(" unlabelled" , " unlabeled" )
99- adata [! is_unlabelled , ]
117+ # Convert to lowercase and filter out "unlabelled" and "unlabeled"
118+ is_unlabelled <- tolower(adata $ obs $ cell_type ) %in%
119+ c(" unlabelled" , " unlabeled" )
120+ adata [! is_unlabelled , ]
100121}
0 commit comments