This is a test version of Biostars. For the public version, visit https://www.biostars.org.
How to plot proportion of cells in each cluster with scanpy?

Hello

I am analyzing single cell data with scanpy. I have using leiden to cluster my samples. I would like to figure out how many cells are in each cluster and plot the proportion of cells for each cluster. I have crossposted the question on stackoverflow but I have not gotten an answer so I am trying here too (https://stackoverflow.com/questions/77160135/how-to-plot-proportion-of-cells-in-each-cluster-with-scanpy)

I have found the code shown below from this link https://nbisweden.github.io/workshop-scRNAseq/labs/compiled/scanpy/scanpy_04_clustering.html

tmp = pd.crosstab(adata.obs['leiden_0.6'],adata.obs['type'], normalize='index')
tmp.plot.bar(stacked=True).legend(loc='upper right')

However, I am not sure how to adjust it for my data because I don't have 2 groups. I just want a graph that shows that cluster 1 is 10% of the total cells, cluster 2 is 20% etc.

Thank you

scrna-seq scanpy single-cell

2 answers

Hi. If I get this right, you simply need to compute the percentage of cells in each cluster at the dataset level?

import pandas as pd
import seaborn as sns
import matplotlib.plt as plt

data={}
for v in adata.obs['leiden_1_0'].unique():
    data[v]=adata[adata.obs['leiden_1_0']==v].shape[0]/adata.shape[0]*100
df = pd.DataFrame.from_dict(data,orient='index',columns=['percentage'])    
df['cluster']=df.index
df=df.reset_index(drop=True)
sns.barplot(data=df, x='cluster', y='percentage')
plt.show()

You can then save your df as a CSV file if you want with df.to_csv('path')

Thank you so much. That worked perfectly and it was much faster than what I was trying.

Please try this-

tmp = pd.crosstab(adata.obs['leiden_0.6'],adata.obs['type'], normalize='columns').T.plot(kind='bar', stacked=True)
tmp.legend(title='leiden_0.6', bbox_to_anchor=(1.26, 1.02),loc='upper right')

enter image description here

Thank you for the suggestion. Unfortunately, that does not work for me because I do not have a "type" argument in the adata.obs. I think that at the end I would need to have one column on the chart with the different percentages for each cluster for leiden_0.6. It would be nice if I could also get the amount of cells per cluster printed on a separate file.

Why you had type in your code above then?

Because that was the code from the link. It is how they specified they had 2 groups of samples but I don't have 2 groups. Sorry for the confusion.

The following code will write percentage in your stacked barplot.

cross_tab = pd.crosstab(adata.obs['leiden_0.6'],adata.obs['type'], normalize='columns')*100
    ax = cross_tab.plot(kind='bar', stacked=True, figsize=(8, 6))
    ax.legend(title="leiden_0.6", bbox_to_anchor=(1.18, 1.02), loc="upper right")
    # Add labels to the bars
    for p in ax.patches:
        width, height = p.get_width(), p.get_height()
        x, y = p.get_xy() 
        ax.annotate(f'{height:.1f}%', (x + width/2, y + height/2), ha='center', va='center')
        # Set labels and title
    plt.xlabel('Category')
    plt.ylabel('Percentage')
    plt.title('Stacked Bar Plot with Percentage Labels')

Thank you for replying again. The "type" still causes issues but the reply by Radu Tanasa worked.

Log in to answer this question.