Post

Cell type proportion을 비교하는 방법: Odd Ratio

이전에도 몇번 cell type identification이후에 proportion을 가시화하고 비교하는 방법들을 몇가지 정리했었는데..

👉 cell type proportion barplot으로 표현하기 포스트
👉 Cell type proportion 변화를 표현하는 방법: lined boxplot 포스트
👉 cell type proportion pie chart로 표현하기 포스트

오늘은 만약에 샘플들 사이에 condition이 있다면, Odd Ratio를 사용해서 두가지 condition사이의 (Ctrl 대비 Treated 사이등) cell type proportion이 있는지, 확인하고 그림으로 표현해서 보여줄 수 있는 방법을 정리해보려함.

Odd Ratio

Odd Ratio란 어떤 특정 사건이 일어날 확률과 그 사건이 일어나지 않을 확률의 비율. 간단히 말해서, 어떤 사건이 발생할 ‘기회’를 수치로 나타낸 것이다.

\[Odd Ratio (OR)= \frac{사건이 발생하지 않을 확률}{사건이 발생할 확률}\]

우리는 두가지 Condition에 대해서 OR을 구하고 싶으니까 두 그룹 간의 발생확률의 비율을 구하면 된다. 먼저 그룹별로 사건이 발생할 확률과 그렇지 않을 확률의 비율인 Odds를 구하고, 두 Odds의 비율을 계산하면 Odd Ratio가 됨

\[Odds(A) = \frac{P(A)}{(1 - P(A))}\] \[Odds(B) = \frac{P(B)}{(1 - P(B))}\] \[Odds Ratio = \frac{Odds(A)}{Odds(B)}\]

이 비율이 1보다 크면 조건 A에서 사건이 발생할 확률이 조건 B보다 높음을 의미한다. 반대로, 이 비율이 1보다 작으면 조건 B에서 사건이 발생할 확률이 조건 A보다 높다는 것. 이게 통계적 유의성을 갖는지는 다른 test를 통해서 P-val 또는 FDR을 구해서 증명하도록 하자.

Single-Cell data에서의 Odd Ratio 계산

예를들어 내 data가 어떤 바이러스에 감염된 집단에서 얻은 샘플(Infec)과 감염에서 벗어나 회복된 이후에 얻은 샘플(Recovery)이 있다고 가정하자. 데이터 QC이후 UMAP까지 다 그린뒤, cell type annotation까지 마친 상태.

나는 이제 Infec 과 Recovery 사이에서 양적으로 차이를 보이는 cell type이 있는지 확인하고 싶다. 예를 들면, recovery에서 Monocyte의 수가 준다던가..

확률을 계산하기 위해선 먼저 cell type별로 그 비율을 계산해야겠지. 특정 Cell type이 Infec상태에서는 40%, Recovery에서는 20%의 비율로 나타났다고 치자. 그러면 각각의 Odds는

  • Odd(Infec) = 0.4 / (1-0.4) = 0.67
  • Odd(Recovery) = 0.2 / (1-0.2) = 0.25
  • Odd ratio = 0.67 / 0.25 = 2.68

따라서 이 cell type은 Infec 상태에서 Recovery에 비해 약 2.86배 더 많이 나타난다고 할 수 있다.

이제 R 코드로 짜보자

Sample info.

clustering하고 cell type anootation까지 마쳤고, 난 celltype이 아니라 cluster단위로 condition에 따라 차이가 보이는지 궁금해서 그렇게 짜봄.

R code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# GEX.seurOBJ : 내 seurat object 이름

# 일단 Patient_cluster_condition 별로 cell 몇개인지 count
ncluster = length(levels(GEX.seurOBJ$wsnn_res.0.8))
Patient <- rep(c('P01','P02','P03','P04'),each=2*ncluster)
Condition <- rep(c("Infec","Recovery"), each=ncluster)
cluster <- levels(GEX.seurOBJ$wsnn_res.0.8)
data <- data.frame(Patient,Condition,cluster)
data$value <- 0

for(k in c('P01','P02','P03','P04')){
  Psub <- subset(GEX.seurOBJ, subset=PatientID_re==k)
  for(i in c("Infec","Recovery")){
    Csub <- subset(Psub, subset=Condition==i)
    total <- ncol(Csub)
    for(j in levels(GEX.seurOBJ$wsnn_res.0.8)){
      data[data$Patient==k & data$Condition==i & data$cluster==j,"value"] <- table(Csub$wsnn_res.0.8)[j]
    }}}

# 각 환자별 전체 감염된 세포와 회복된 세포의 수를 계산
totals <- data %>%
  group_by(Patient, Condition) %>%
  summarise(total_count = sum(value)) %>%
  pivot_wider(names_from = Condition, values_from = total_count, values_fill = list(total_count = 0))
# 각 환자와 클러스터별로 감염 및 회복 상태에서의 세포 수를 합산
data_ratio <- data %>%
  group_by(Patient, cluster) %>%
  summarise(infected_count = sum(value[Condition == "Infec"]),
            recovery_count = sum(value[Condition == "Recovery"]),
            .groups = 'drop') %>%
  left_join(totals, by = "Patient") %>%
  mutate(infected_ratio = infected_count / Infec,
         recovery_ratio = recovery_count / Recovery,
         infec_odd = if_else(infected_ratio == 0, 0, infected_ratio / (1 - infected_ratio)),
         recovery_odd = if_else(recovery_ratio == 0, 0, recovery_ratio / (1 - recovery_ratio)), 
         odd_ratio = if_else(infec_odd == 0 | recovery_odd == 0, 0, infec_odd / recovery_odd)) %>% as.data.frame()

p_data<- data_ratio[,c(1,2,9,10)] %>% gather(key = "Condition", value = "Odd", infec_odd, recovery_odd) %>%
  mutate(Condition = ifelse(Condition == "infec_odd", "Infec", "Recovery"))
p_data$cluster <- factor(p_data$cluster, levels=0:13)

# box plot
p <- ggplot(p_data, aes(x = factor(cluster), y = Odd)) +
  geom_boxplot(aes(fill = Condition),width=0.5) + 
  geom_jitter(aes(color = Patient), width = 0.2) + # 환자별 점을 추가
  scale_fill_manual(values = c("Recovery" = "blue", "Infec" = "red")) + # 색상 조정
  labs(x = "Cluster", y = "Odds Ratio") +
  theme_minimal()

# 통계적 유의성 검정 (예: Wilcoxon test) 및 p-value 추가
# 각 클러스터에 대해 별도로 유의성 검정을 수행하고 p-value를 얻습니다.
# 그리고 그 값을 그래프에 추가합니다.
for (cluster_level in unique(p_data$cluster)) {
  # 클러스터별로 데이터 필터링
  data_cluster <- p_data %>% filter(cluster == cluster_level)
  # Wilcoxon test 수행
  test_result <- wilcox.test(Odd ~ Condition, data = data_cluster)
  print(paste0(cluster_level,":",test_result$p.value))

  if (test_result$p.value < 0.05) {
    max_y <- max(data_cluster$Odd, na.rm = TRUE)    
    p <- p + annotate("text", x = cluster_level, y = max_y, label = paste("p =", round(test_result$p.value, 3)),
                      size = 3, vjust = -0.5)
  }
}

ggsave("OddRatio.png",width=12,height=4)

write.table(data_ratio, "CellTypeAnnot_OddRatio.txt", sep='\t')

Result

환자가 4명이라 점이 4개밖에 안찍혀서 박스를 그리는 의미가 있나 싶지고 더 많았으면 좋겠지만ㅋㅋㅋㅋ

오늘도 하나 더 배웠다는데에 의의를 둔다. 데이터는 내잘못이 아니니까!

This post is licensed under CC BY 4.0 by the author.

© Subin Cho. Some rights reserved.

Using the Chirpy theme for Jekyll.