Package: poola
poolaPython package for pooled screen analysisInstallInstall from github for the latest development release:pip install git+git://github.com/gpp-rnd/poola.git#egg=poolaOr install the most recent distribution from PyPi:pip install poolaHow to useAdditional packages required for this tutorial can be install usingpip install -r requirements.txtfrom poola import core as pool
import pandas as pd
import seaborn as sns
import gpplot
import matplotlib.pyplot as pltTo demonstrate the functionality of this module we'll use read counts fromSanson et al. 2018.supp_reads = 'https://static-content.springer.com/esm/art%3A10.1038%2Fs41467-018-07901-8/MediaObjects/41467_2018_7901_MOESM4_ESM.xlsx'
read_counts = pd.read_excel(supp_reads,
                            sheet_name = 'A375_orig_tracr raw reads', 
                            header = None,
                            skiprows = 3, 
                            names = ['sgRNA Sequence', 'pDNA', 'A375_RepA', 'A375_RepB'], 
                            engine='openpyxl')
guide_annotations = pd.read_excel(supp_reads,
                                  sheet_name='sgRNA annotations', 
                                  engine='openpyxl')The input data has three columns with read counts and one column with sgRNA annotationsread_counts.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA SequencepDNAA375_RepAA375_RepB0AAAAAAAATCCGGACAATGG5227297741AAAAAAAGGATGGTGATCAA511148413932AAAAAAATGACATTACTGCA4673756033AAAAAAATGTCAGTCGAGTG2007375064AAAAAACACAAGCAAGACCG286672352lognorms = pool.lognorm_columns(reads_df=read_counts, columns=['pDNA', 'A375_RepA', 'A375_RepB'])
filtered_lognorms = pool.filter_pdna(lognorm_df=lognorms, pdna_cols=['pDNA'], z_low=-3)
print('Filtered ' + str(lognorms.shape[0] - filtered_lognorms.shape[0]) + ' columns due to low pDNA abundance')Filtered 576 columns due to low pDNA abundanceNote that the column names for the lognorms remain the samelognorms.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA SequencepDNAA375_RepAA375_RepB0AAAAAAAATCCGGACAATGG4.1927563.3739243.5217551AAAAAAAGGATGGTGATCAA4.1637264.3268284.3126202AAAAAAATGACATTACTGCA4.0413902.5406243.1967673AAAAAAATGTCAGTCGAGTG2.9304373.3881592.9735994AAAAAACACAAGCAAGACCG3.3883943.2682222.528233lfc_df = pool.calculate_lfcs(lognorm_df=filtered_lognorms, ref_col='pDNA', target_cols=['A375_RepA', 'A375_RepB'])We drop the pDNA column after calculating log-fold changeslfc_df.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA SequenceA375_RepAA375_RepB0AAAAAAAATCCGGACAATGG-0.818831-0.6710001AAAAAAAGGATGGTGATCAA0.1631020.1488942AAAAAAATGACATTACTGCA-1.500766-0.8446223AAAAAAATGTCAGTCGAGTG0.4577210.0431614AAAAAACACAAGCAAGACCG-0.120172-0.860161Since we only have two conditions it's easy to visualize replicates as a point densityplot usinggpplotplt.subplots(figsize=(4,4))
gpplot.point_densityplot(data=lfc_df, x='A375_RepA', y='A375_RepB')
gpplot.add_correlation(data=lfc_df, x='A375_RepA', y='A375_RepB')
sns.despine()Since we see a strong correlation, we'll average the log-fold change of each sgRNA across replicatesavg_replicate_lfc_df = pool.average_replicate_lfcs(lfcs=lfc_df, guide_col='sgRNA Sequence', condition_indices=[0],
                                                   sep='_')After averaging log-fold changes our dataframe is melted, so the condition column specifies the experimental condition (A375 here) and the n_obs specifies the number of replicatesavg_replicate_lfc_df.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA Sequenceconditionavg_lfcn_obs0AAAAAAAATCCGGACAATGGA375-0.74491621AAAAAAAGGATGGTGATCAAA3750.15599822AAAAAAATGACATTACTGCAA375-1.17269423AAAAAAATGTCAGTCGAGTGA3750.25044124AAAAAACACAAGCAAGACCGA375-0.4901662It's sometimes helpful to group controls into pseudo-genes so they're easier to compare with target genes. Our annotation file maps from sgRNA sequences to gene symbolsremapped_annotations = pool.group_pseudogenes(annotations=guide_annotations, pseudogene_size=4, 
                                              gene_col='Annotated Gene Symbol', 
                                              control_regex=['NO_CURRENT'])
remapped_annotations.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA SequenceAnnotated Gene SymbolAnnotated Gene ID0AAAAAAAATCCGGACAATGGSLC25A24299571AAAAAAAGGATGGTGATCAAFASTKD3790722AAAAAAATGACATTACTGCABCAS2102863AAAAAAATGTCAGTCGAGTGGPR1828414AAAAAACACAAGCAAGACCGZNF470388566We provide two methods for scaling log-fold change values to controls:Z-score from a set of negative controlsScale scores between a set of negative and positive controlsFor both scoring methods, you can input either a regex or a list of genes to define control setsFor our set of negative controls, we'll usenonessentialgenesnonessential_genes = (pd.read_table('https://raw.githubusercontent.com/gpp-rnd/genesets/master/human/non-essential-genes-Hart2014.txt',
                                    names=['gene'])
                      .gene)
annot_guide_lfcs = pool.annotate_guide_lfcs(avg_replicate_lfc_df, remapped_annotations, 'Annotated Gene Symbol',
                                            merge_on='sgRNA Sequence', z_score_neg_ctls=True,
                                            z_score_neg_ctl_genes=nonessential_genes)
annot_guide_lfcs.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>sgRNA Sequenceconditionavg_lfcn_obsAnnotated Gene SymbolAnnotated Gene IDz_scored_avg_lfc0AAAAAAAATCCGGACAATGGA375-0.7449162SLC25A2429957-1.6519211AAAAAAAGGATGGTGATCAAA3750.1559982FASTKD3790720.1665852AAAAAAATGACATTACTGCAA375-1.1726942BCAS210286-2.5153963AAAAAAATGTCAGTCGAGTGA3750.2504412GPR1828410.3572194AAAAAACACAAGCAAGACCGA375-0.4901662ZNF470388566-1.137705To aggregate scores at the gene level, we specify columns to average, and columns to z-score.
From our z-scores we calculate a p-value and FDR using theBenjamini-Hochberg proceduregene_lfcs = pool.aggregate_gene_lfcs(annot_guide_lfcs, 'Annotated Gene Symbol',
                                     average_cols=['avg_lfc'],
                                     zscore_cols=['z_scored_avg_lfc'])
gene_lfcs.head()<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>conditionAnnotated Gene Symboln_guidesavg_lfcz_scored_avg_lfcz_scored_avg_lfc_p_valuez_scored_avg_lfc_fdr0A375A1BG3-0.084621-0.5527110.5804610.8728331A375A1CF40.5003141.7231810.0848560.3380552A375A2M40.2886290.8686040.3850640.7598293A375A2ML14-0.481786-2.2415800.0249890.1321644A375A3GALT240.059362-0.0569520.9545830.990693Finally, to evaluate the quality this screen, we'll calculate the ROC-AUC betweenessentialandnonessentialgenes for each conditionessential_genes = (pd.read_table('https://raw.githubusercontent.com/gpp-rnd/genesets/master/human/essential-genes-Hart2015.txt', 
                                 names=['gene'])
                   .gene)
roc_aucs, roc_df = pool.get_roc_aucs(lfcs=gene_lfcs, tp_genes=essential_genes, fp_genes=nonessential_genes, gene_col='Annotated Gene Symbol',
                                     condition_col='condition', score_col='avg_lfc')
print('ROC-AUC: ' + str(round(roc_aucs['ROC-AUC'].values[0], 3)))ROC-AUC: 0.976Note that we can also use this function to calculate roc-aucs at the guide levelannotated_guide_lfcs = lfc_df.merge(guide_annotations, how='inner', on='sgRNA Sequence')
roc_aucs, roc_df = pool.get_roc_aucs(lfcs=annotated_guide_lfcs, tp_genes=essential_genes, fp_genes=nonessential_genes, gene_col='Annotated Gene Symbol',
                                     condition_list=['A375_RepA', 'A375_RepB'])
roc_aucs<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}</style>conditionROC-AUC0A375_RepA0.9185051A375_RepB0.917600And plot ROC curves from theroc_dfplt.subplots(figsize=(4,4))
sns.lineplot(data=roc_df, x='fpr', y='tpr', hue='condition', ci=None)
gpplot.add_xy_line()
sns.despine()