├── Neighborhoods ├── Cell Type Differential Enrichment.ipynb ├── Neighborhood Identification.ipynb ├── Neighborhood Mixing.ipynb ├── Tensor Decomposition.ipynb ├── Voronoi Generation.ipynb ├── app_CRC_contacts.R ├── cca_cleaned_up.ipynb ├── tensor_decomposition_cleaned_up.ipynb └── voronoi.py ├── README.md └── paper_submission.zip /Neighborhoods/Cell Type Differential Enrichment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# import statsmodels.api as sm\n", 10 | "# from statsmodels.stats.multitest import multipletests as mpt\n", 11 | "# import numpy as np\n", 12 | "import pandas as pd\n", 13 | "# from matplotlib import pyplot as plt\n", 14 | "# import itertools\n", 15 | "# import seaborn as sns\n", 16 | "# %pylab inline" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "import numpy" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "collapsed": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "'''\n", 37 | "preparing dataframes (best to do this on your data):\n", 38 | "ct_freq should be a df of cell type frequencies per patient\n", 39 | "\n", 40 | "all_freqs should have cols patient, neighborhood, cell types, giving\n", 41 | "the frequency of cells of that type in that neighborhood in that patient\n", 42 | "'''\n", 43 | "ct_freq = pd.read_csv('cell_type_freqs_per_patient')\n", 44 | "all_freqs = pd.read_csv('all_freqs')\n", 45 | "\n", 46 | "#you will need to set the following with your own data\n", 47 | "neighborhood_col = 'neighborhood10'\n", 48 | "nbs = [0,2,3,4,5,6,7,8,9]\n", 49 | "patients = range(1,36) \n", 50 | "group = pd.Series([0]*15+ [1]*20) \n", 51 | "cells = ['B cells','T cells']" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 3, 57 | "metadata": { 58 | "collapsed": true 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "def normalize(X):\n", 63 | " arr = np.array(X.fillna(0).values)\n", 64 | " return pd.DataFrame(np.log2(1e-3 + arr/arr.sum(axis =1, keepdims = True)), index = X.index.values, columns = X.columns).fillna(0)\n" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": { 71 | "collapsed": true 72 | }, 73 | "outputs": [], 74 | "source": [ 75 | "# data prep\n", 76 | "# normalized overall cell type frequencies\n", 77 | "X_cts = normalize(ct_frq.reset_index().set_index('patients').loc[patients,cells])\n", 78 | "\n", 79 | "# normalized neighborhood specific cell type frequencies\n", 80 | "df_list = []\n", 81 | "\n", 82 | "for nb in nbs:\n", 83 | " cond_nb = all_freqs.loc[all_freqs[neighborhood_col]==nb,cells].rename({col: col+'_'+str(nb) for col in cells},axis = 1).set_index('patients')\n", 84 | " df_list.append(normalize(cond_nb))\n", 85 | "\n", 86 | "X_cond_nb = pd.concat(df_list,axis = 1).loc[patients]" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": { 93 | "collapsed": true 94 | }, 95 | "outputs": [], 96 | "source": [ 97 | "#differential enrichment for all cell subsets\n", 98 | "changes = {}\n", 99 | "nbs =[0, 2, 3, 4, 6, 7, 8, 9]\n", 100 | "for col in cells:\n", 101 | " for nb in nbs:\n", 102 | " #build a design matrix with a constant, group 0 or 1 and the overall frequencies\n", 103 | " X = pd.concat([X_cts[col], group.astype('int'),pd.Series(np.ones(len(group)), index = group.index.values)],axis = 1).values\n", 104 | " if col+'_%d'%nb in X_condnb_all.columns:\n", 105 | " #set the neighborhood specific ct freqs as the outcome\n", 106 | " Y = X_cond_nb[col+'_%d'%nb].values\n", 107 | " X = X[~pd.isna(Y)]\n", 108 | " Y = Y[~pd.isna(Y)]\n", 109 | " #fit a linear regression model\n", 110 | " results = sm.OLS(Y,X).fit()\n", 111 | " #find the params and pvalues for the group coefficient\n", 112 | " changes[(col,nb)] = (results.pvalues[1], results.params[1])\n", 113 | " \n", 114 | "\n", 115 | "#make a dataframe with coeffs and pvalues\n", 116 | "dat = (pd.DataFrame(changes).loc[1].unstack())\n", 117 | "dat = pd.DataFrame(np.nan_to_num(dat.values),index = dat.index, columns = dat.columns).T.sort_index(ascending=True).loc[:,X_cts.columns]\n", 118 | "pvals = (pd.DataFrame(changes).loc[0].unstack()).T.sort_index(ascending=True).loc[:,X_cts.columns]\n", 119 | "\n", 120 | "#this is where you should correct pvalues for multiple testing \n", 121 | "\n", 122 | "\n", 123 | "#plot as heatmap\n", 124 | "f, ax = plt.subplots(figsize = (20,10))\n", 125 | "g = sns.heatmap(dat,cmap = 'bwr', vmin = -1, vmax = 1,cbar=False,ax = ax)\n", 126 | "for a,b in zip(*np.where (pvals<0.05)):\n", 127 | " plt.text(b+.5,a+.55,'*',fontsize = 20,ha = 'center',va = 'center')\n", 128 | "plt.tight_layout()\n" 129 | ] 130 | } 131 | ], 132 | "metadata": { 133 | "kernelspec": { 134 | "display_name": "Python 3", 135 | "language": "python", 136 | "name": "python3" 137 | }, 138 | "language_info": { 139 | "codemirror_mode": { 140 | "name": "ipython", 141 | "version": 3 142 | }, 143 | "file_extension": ".py", 144 | "mimetype": "text/x-python", 145 | "name": "python", 146 | "nbconvert_exporter": "python", 147 | "pygments_lexer": "ipython3", 148 | "version": "3.6.10" 149 | }, 150 | "latex_envs": { 151 | "LaTeX_envs_menu_present": true, 152 | "autoclose": false, 153 | "autocomplete": true, 154 | "bibliofile": "biblio.bib", 155 | "cite_by": "apalike", 156 | "current_citInitial": 1, 157 | "eqLabelWithNumbers": true, 158 | "eqNumInitial": 1, 159 | "hotkeys": { 160 | "equation": "Ctrl-E", 161 | "itemize": "Ctrl-I" 162 | }, 163 | "labels_anchors": false, 164 | "latex_user_defs": false, 165 | "report_style_numbering": false, 166 | "user_envs_cfg": false 167 | } 168 | }, 169 | "nbformat": 4, 170 | "nbformat_minor": 2 171 | } 172 | -------------------------------------------------------------------------------- /Neighborhoods/Neighborhood Mixing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "\"\"\"\n", 12 | "Code for computing the amount of shared surface area between two neighborhoods\n", 13 | "'cells2': Main dataframe output by neighborhoodtemplate with x,y,clusterID, and neighborhood allocation\n", 14 | "\"\"\"" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "import pandas as pd\n", 26 | "import numpy as np\n", 27 | "from sklearn.neighbors import NearestNeighbors\n", 28 | "import seaborn as sns\n", 29 | "from matplotlib import pyplot as plt\n", 30 | "%matplotlib inline\n", 31 | "cells2 = pd.read_pickle('main_fcs')" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": { 38 | "collapsed": true 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "tissue_col = 'spots'\n", 43 | "neigh_col = 'neighborhood10'\n", 44 | "patient_col = 'patients'\n", 45 | "group_col = 'groups'\n", 46 | "X = 'X:X'\n", 47 | "Y = 'Y:Y'" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "# calculate neighbors for each spot\n", 57 | "for spot in cells2[tissue_col].unique():\n", 58 | " tissue =cells2[cells2[tissue_col]==spot]\n", 59 | " fit = NearestNeighbors(n_neighbors=1).fit(tissue[[X,Y]].values)\n", 60 | " m = fit.kneighbors()[1]\n", 61 | " \n", 62 | " cells2.loc[tissue.index,'neigh_neigh'] = tissue.iloc[m[:,0],:][neigh_col].values\n", 63 | "cells2['neigh_neigh'] = cells2['neigh_neigh'].astype(int) " 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 4, 69 | "metadata": { 70 | "collapsed": true 71 | }, 72 | "outputs": [], 73 | "source": [ 74 | "#compute for each patient, in each tissue and neighborhood, the number of cells in that neighborhoood\n", 75 | "counts = cells2.groupby([group_col,patient_col,tissue_col,neigh_col]).apply(lambda x: len(x)).unstack()\n", 76 | "\n", 77 | "#compute for each patient, in each tissue and neighborhood: the count of how many of the cells in that neighborhood are next to a cell in the other neighborhood\n", 78 | "neighs = cells2.groupby([group_col,patient_col,tissue_col,neigh_col]).apply(lambda x:x['neigh_neigh'].value_counts(sort = False)).unstack()\n", 79 | "\n" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 5, 85 | "metadata": { 86 | "collapsed": true 87 | }, 88 | "outputs": [], 89 | "source": [ 90 | "#specify which neighborhoods you want to calculate\n", 91 | "neigh1,neigh2 = 0,4" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 6, 97 | "metadata": { 98 | "scrolled": true 99 | }, 100 | "outputs": [], 101 | "source": [ 102 | "# Comment out if you wish to average each spot for each patient\n", 103 | "N = neighs.sum(level = [group_col,patient_col,neigh_col])\n", 104 | "N[tissue_col] = [i[1] for i in N.index]\n", 105 | "neighs = N.set_index(tissue_col,append = True).reorder_levels([group_col,patient_col,tissue_col,neigh_col])" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 7, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "# index the values for the neighborhoods of interest\n", 115 | "ix = pd.IndexSlice\n", 116 | "\n", 117 | "#take the mean between the number of cells in neigh 1 that touch neigh2 and the number of cells in neigh2 that touch neigh1 \n", 118 | "#as not necessarily symmetric\n", 119 | "inters = pd.concat([neighs.loc[ix[:,:,:,[neigh1]],[neigh2]],neighs.loc[ix[:,:,:,[neigh2]],[neigh1]]],1).mean(level = [group_col,patient_col,tissue_col]).mean(1)\n", 120 | "\n", 121 | "#calculate the total number of cells in both neighborhoods\n", 122 | "wholes = neighs.sum(1).loc[ix[:,:,:,[neigh2,neigh1]]].unstack().sum(1)\n", 123 | "\n", 124 | "combo = pd.concat([inters,wholes],1).dropna((0))\n", 125 | "combo['ratio'] = combo[0]/combo[1]\n", 126 | "combo = combo.rename(columns = {0:'neighboring',1:'union',2:'ratio'})\n", 127 | "\n", 128 | "combo = combo.reset_index()\n" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 8, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "data": { 138 | "image/png": "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\nSUVORK5CYII=\n", 139 | "text/plain": [ 140 | "" 141 | ] 142 | }, 143 | "metadata": {}, 144 | "output_type": "display_data" 145 | } 146 | ], 147 | "source": [ 148 | "c = combo.reset_index()\n", 149 | "sns.stripplot(data = c, x = group_col, y ='ratio')\n", 150 | "plt.show()" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 9, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "a = c[c[group_col]==1]['ratio']" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 10, 165 | "metadata": { 166 | "collapsed": true 167 | }, 168 | "outputs": [], 169 | "source": [ 170 | "b = c[c[group_col]==2]['ratio']" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 11, 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "data": { 180 | "text/plain": [ 181 | "Ttest_indResult(statistic=-2.0816172422774324, pvalue=0.045211049299440066)" 182 | ] 183 | }, 184 | "execution_count": 11, 185 | "metadata": {}, 186 | "output_type": "execute_result" 187 | } 188 | ], 189 | "source": [ 190 | "from scipy import stats\n", 191 | "stats.ttest_ind(a, b)" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "metadata": { 198 | "collapsed": true 199 | }, 200 | "outputs": [], 201 | "source": [] 202 | } 203 | ], 204 | "metadata": { 205 | "kernelspec": { 206 | "display_name": "Python 3", 207 | "language": "python", 208 | "name": "python3" 209 | }, 210 | "language_info": { 211 | "codemirror_mode": { 212 | "name": "ipython", 213 | "version": 3 214 | }, 215 | "file_extension": ".py", 216 | "mimetype": "text/x-python", 217 | "name": "python", 218 | "nbconvert_exporter": "python", 219 | "pygments_lexer": "ipython3", 220 | "version": "3.6.8" 221 | }, 222 | "latex_envs": { 223 | "LaTeX_envs_menu_present": true, 224 | "autoclose": false, 225 | "autocomplete": true, 226 | "bibliofile": "biblio.bib", 227 | "cite_by": "apalike", 228 | "current_citInitial": 1, 229 | "eqLabelWithNumbers": true, 230 | "eqNumInitial": 1, 231 | "hotkeys": { 232 | "equation": "Ctrl-E", 233 | "itemize": "Ctrl-I" 234 | }, 235 | "labels_anchors": false, 236 | "latex_user_defs": false, 237 | "report_style_numbering": false, 238 | "user_envs_cfg": false 239 | } 240 | }, 241 | "nbformat": 4, 242 | "nbformat_minor": 2 243 | } 244 | -------------------------------------------------------------------------------- /Neighborhoods/Tensor Decomposition.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 8, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Populating the interactive namespace from numpy and matplotlib\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from tensorly.decomposition import non_negative_parafac,parafac,non_negative_tucker,partial_tucker,tucker\n", 18 | "from tensorly.regression import KruskalRegressor\n", 19 | "import tensorly as tl\n", 20 | "import numpy as np\n", 21 | "import pandas as pd\n", 22 | "import seaborn as sns\n", 23 | "from matplotlib import pyplot as plt\n", 24 | "%pylab inline" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 70, 30 | "metadata": { 31 | "collapsed": true 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "'''\n", 36 | "x needs to be a dataframe which says \n", 37 | "for each patient, neighborhood and cell type, how many cells of that type in that neighborhood there are\n", 38 | "\n", 39 | "cells of interest are the cells to be used in the decomposition\n", 40 | "CNs is the CNs to be used in the decomposition\n", 41 | "patients are the patients to be used in the decomposition\n", 42 | "'''\n", 43 | "\n", 44 | "x = pd.read_csv('main_fcs_csv.csv')\n", 45 | "neigh_col = 'neighborhood10'\n", 46 | "cells_of_interest = ['B Cells', 'CD4 T cells','CD8 T cells', 'TRegs']\n", 47 | "nbs = [0,2,3,4,5,6,7,8,9]\n", 48 | "patients = [1,2,3,4,5,6,7]" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 44, 54 | "metadata": { 55 | "collapsed": true 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "def build_tensor(patients,x,nbs,cells_of_interest):\n", 60 | " T = np.zeros((len(patients),len(nbs),len(cells_of_interest)))\n", 61 | " for i,nb in enumerate(nbs):\n", 62 | " for j,chk in enumerate(cells_of_interest):\n", 63 | " T[:,i,j] = x.loc[x['neighborhood10']==nb,:].set_index('patients').loc[patients,chk].fillna(0).values\n", 64 | " \n", 65 | " \n", 66 | " #normalize each patient's frequencies\n", 67 | " dat =np.nan_to_num(T/T.sum((1,2), keepdims = True))\n", 68 | " return dat\n" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 45, 74 | "metadata": { 75 | "collapsed": true 76 | }, 77 | "outputs": [], 78 | "source": [ 79 | "def decomposition_elbow(dat):\n", 80 | " pal = sns.color_palette('bright',10)\n", 81 | " palg = sns.color_palette('Greys',10)\n", 82 | " mat1 = np.zeros((5,15))\n", 83 | " #finding the elbow point\n", 84 | " for i in range(2,15):\n", 85 | " for j in range(1,5):\n", 86 | " facs_overall = non_negative_tucker(dat,rank=[j,i,i],random_state = 2336)\n", 87 | " mat1[j,i] = np.mean((dat- tl.tucker_to_tensor(tucker_tensor = (facs_overall[0],facs_overall[1])))**2)\n", 88 | "\n", 89 | " figsize(10,5)\n", 90 | " plt.plot(2+np.arange(13),mat1[2][2:],c = 'red',label = 'rank = (2,x,x)')\n", 91 | " plt.plot(2+np.arange(13),mat1[1][2:],c = 'blue',label = 'rank = (1,x,x)')\n", 92 | " plt.xlabel('x')\n", 93 | " plt.ylabel('error')\n", 94 | " plt.show()\n", 95 | " " 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 94, 101 | "metadata": { 102 | "collapsed": true 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "def tissue_module_plots(dat, person_rank,rank,nbs,cells_of_interest,random_state = 0):\n", 107 | " facs_overall = non_negative_tucker(dat,rank=[person_rank,rank,rank],random_state = random_state)\n", 108 | " print(facs_overall[0].shape)\n", 109 | " sns.heatmap(pd.DataFrame(facs_overall[1][1], index = nbs))\n", 110 | " plt.ylabel('CN')\n", 111 | " plt.xlabel('CN module')\n", 112 | " plt.title('CN modules')\n", 113 | " plt.show()\n", 114 | " \n", 115 | " sns.heatmap(pd.DataFrame(facs_overall[1][2], index = cells_of_interest))\n", 116 | " plt.ylabel('CT')\n", 117 | " plt.xlabel('CT module')\n", 118 | " plt.title('CT modules')\n", 119 | " plt.show()\n", 120 | " \n", 121 | " print('--------Tissue modules ---------')\n", 122 | " for i in range(person_rank):\n", 123 | " \n", 124 | " sns.heatmap(pd.DataFrame(facs_overall[0][i]))\n", 125 | " plt.ylabel('CN module')\n", 126 | " plt.xlabel('CT module')\n", 127 | " plt.title('Tissue module {}'.format(i))\n", 128 | " plt.show()\n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " return facs_overall" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 95, 138 | "metadata": { 139 | "collapsed": true 140 | }, 141 | "outputs": [], 142 | "source": [ 143 | "#use random data for sake of demonstration\n", 144 | "np.random.seed(123)\n", 145 | "dat = 10+np.random.normal([1]*140).reshape(len(patients),len(nbs),len(cells_of_interest))\n", 146 | "\n", 147 | "\n", 148 | "#dat = build_tensor(patients,x,nbs,cells_of_interest)" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 96, 154 | "metadata": { 155 | "scrolled": false 156 | }, 157 | "outputs": [ 158 | { 159 | "data": { 160 | "image/png": "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\n", 161 | "text/plain": [ 162 | "
" 163 | ] 164 | }, 165 | "metadata": { 166 | "needs_background": "light" 167 | }, 168 | "output_type": "display_data" 169 | } 170 | ], 171 | "source": [ 172 | "#compute elbow point for decomposition\n", 173 | "decomposition_elbow(dat)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 97, 179 | "metadata": {}, 180 | "outputs": [ 181 | { 182 | "name": "stdout", 183 | "output_type": "stream", 184 | "text": [ 185 | "(2, 3, 3)\n" 186 | ] 187 | }, 188 | { 189 | "data": { 190 | "image/png": "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\n", 191 | "text/plain": [ 192 | "
" 193 | ] 194 | }, 195 | "metadata": { 196 | "needs_background": "light" 197 | }, 198 | "output_type": "display_data" 199 | }, 200 | { 201 | "data": { 202 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAFNCAYAAAAaUIXQAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nO3debRlZXnn8e/PYpDIIAYwBigBLY2KBoUQR6KtGExURG0XahJMbKtdkSASbccFWmk7JholA7ZWWox2RNo4pCtIINhAIipaBWGwMBgsp7JoFJFBpYXiPv3H2SXHW3eoc+rse88+5/tx7cXZ4/NSC2s963mnVBWSJElddJ/lboAkSdKwTGQkSVJnmchIkqTOMpGRJEmdZSIjSZI6y0RGkiR1lomMpIElqSQPHfWzkjQoExlpjCR5SZINSX6Y5MYk/5jkyUne11z7YZK7ktzdd/6Py91uSVouJjLSmEhyGnAm8N+ABwIrgfcCx1fVK6tqz6ras7n/v7adV9Wzlq/VkrS8TGSkMZBkH2AN8Kqq+mRV/aiq7q6qf6iq1w3xvacm2ZzkvyT5blPdeV6S30jy1SS3JHlT3/O7JzkzyZbmODPJ7n33X9d8Y0uS35sV69Ik/6nv/GVJLpunXbsneVeSbyW5qak07dHc2y/JeUlubdr32ST+HSVpQf4lIY2HJwD3BT41wm/+QvPNA4HTgb8Gfgs4EngKcHqSw5pn3ww8HjgC+GXgaOAtAEmOA14LHAusAp6xE236E+BhTZyH9rUN4A+BzcD+9CpSbwLcQ0XSgkxkpPHw88DNVbV1hN+8G3h7Vd0NnAvsB/x5Vd1RVRuBjcBjmmdfCqypqu9W1feAtwG/3dx7EfDBqvpyVf0IeOswjUkS4BXAa6rqlqq6g1432Yl97X0Q8OCmGvXZcjM4SYswkZHGw/eB/ZLsMspvVtU9ze87m3/e1Hf/TmDP5vcvAt/su/fN5tq2e9+edW8Y+wM/B1zRdB/dClzQXAd4J3AD8E9JNiV5w5BxJE0RExlpPHwB+H/A85Yp/hbgwX3nK5trADcCB8+61+9H9BKUbX5hnhg300ueHlVV92+OfZoBzDSVoj+sqsOA5wCnJXn6cP86kqaFiYw0BqrqNnpjRc5qBuX+XJJdkzwryZ8uQRM+Crwlyf5J9mva8rfNvY8BL0vyyCQ/B5wx692rgOc3bX4o8PK5AlTVDL1xOu9JcgBAkgOT/Hrz+9lJHtp0Qd0O3NMckjQvExlpTFTVu4HT6A2y/R697pyTgb9fgvD/FdgAXANcC1zZXKOq/pHetPCL6XX9XDzr3fcAd9HrtvoQ8JEF4ry++cblSW4HPgM8vLm3qjn/Ib0K1Xur6tKd/PeSNOHiWDpJktRVVmQkSVJnmchIkqTOai2RSfKQbSuDNquMnpLk/m3FkyRJ06fNiswngHuaWQwfAA4FzmkxniRJmjJtJjIzzSqlJwBnVtVr6K3aKUmSNBKjXEV0truTvBg4id7iVgC7LvRCktXAaoA/O3zVkSetNO/R6Fy94YHL3QRNkJtWLPjXmTSUF2/5SJYy3t03bxp46vKu+x22pG1cTJsVmd+ltxHe26vq60kO5d4FtuZUVWur6qiqOsokRpIkLaa1ikxVXQec0nf+deAdbcWTJEkDmun+4tkjT2SSXAvMW6qqqsfMd0+SJC2hmlnuFuy0Nioyz27hm5IkadRmTGS2U1XfHPU3JUnS6JUVme0luYN7u5a2jWyu5ndV1d6jjilJkoZgRWZ7VbXXqL8pSZJaYEVmYUmeDKyqqg8m2Q/Yq5m9JEmSltsEzFpqc6+lM4DXA29sLu3GIuvISJKkJVQzgx87IMlxSa5PckOSN8xx/z1JrmqOrya5te/ePX331i0Wq82KzAnAY4ErAapqSxK7nSRJGhctjJFJsgI4CzgW2AysT7KuWV8OgGbbom3P/wG9fGGbO6vqiB2N1+bKvndVVdEM/E1yvxZjSZKkAVXNDHzsgKOBG6pqU1XdBZwLHL/A8y8GPjrsv0ObiczHkrwfuH+SVwCfAf66xXiSJGkQMzODH4s7EPh23/nm5tp2kjwYOBS4uO/yfZNsSHJ5kuctFqzNLQreleRY4Hbg4cDpVXVRW/EkSdKAhpi11L/Bc2NtVa3tf2SuSPN87kTg41XVP+p4ZTMc5TDg4iTXVtXX5mtPG+vIPBR4YFV9rklcLmquH5PkIQs1RpIkLaEhZi01ScvaBR7ZDBzcd34QsGWeZ08EXjXr+1uaf25Kcim98TPz5g5tdC2dCdwxx/UfN/ckSdI4aGfW0npgVZJDk+xGL1nZbvZRkocD+wJf6Lu2b5Ldm9/7AU8Crpv9br82upYOqaprZl+sqg1JDmkhniRJGkYLs5aqamuSk4ELgRXA2VW1MckaYENVbUtqXgyc20wM2uYRwPuTzNArtryjf7bTXNpIZO67wL09WognSZKG0dLKvlV1PnD+rGunzzp/6xzvfR549CCx2uhaWt/MUvoZSV4OXNFCPEmSNKXaqMicCnwqyUu5N3E5it7Kvie0EE+SJA3DTSO3V1U3AU9M8jTg8Obyp6vq4gVekyRJS+xnZz13U5vryFwCXNLW9yVJ0k5y92tJktRZdi1JkqTOsiKzY5pFbb4/a664JElaTkOs7DtuRj79Osnjk1ya5JNJHpvky8CXgZuSHDfqeJIkaUjtrOy7pNqoyPwV8CZgH3q7WT6rqi5P8kv0tum+oIWYkiRpUI6RmfubVfVPAEnWVNXlAFX1b8lcG2JKkqRlMYYVlkG1kcj0/6ncOeueY2QkSRoXVmTm9MtJbgcC7NH8pjlfaB8mSZK0lExktldVK0b9TUmSNHqu7CtJkrrLiowkSeosB/tKkqTOsiIjSZI6awIqMiNf2VeSJGmpWJGRJGla2bUkSZI6awK6lkxkJEmaVlZkJElSZ5nISJKkzrJrSZIkdZYVGUmS1FlWZCRJUmdZkZEkSZ1lRaY9d9+R5W6CJsyvvmrX5W6CJsh3Pvr95W6CtPOsyEiSpM4ykZEkSZ1Vtdwt2GkmMpIkTSsrMpIkqbNMZCRJUmc5a0mSJHXWBFRk7rPcDZAkSRqWFRlJkqaVs5YkSVJnTUDXkomMJEnTykRGkiR1lrOWJElSV9WMY2QkSVJX2bUkSZI6y64lSZLUWXYtSZKkzrJrSZIkdZaJjCRJ6ixX9pUkSZ1lRUaSJHXWBAz2dfdrSZKmVc0MfuyAJMcluT7JDUneMM8zL0pyXZKNSc7pu35Skn9vjpMWi2VFRpKkadVCRSbJCuAs4FhgM7A+ybqquq7vmVXAG4EnVdUPkhzQXH8AcAZwFFDAFc27P5gvnhUZSZKmVM3MDHzsgKOBG6pqU1XdBZwLHD/rmVcAZ21LUKrqu831XwcuqqpbmnsXAcctFMxERpIkjdKBwLf7zjc31/o9DHhYks8luTzJcQO8+zPsWpIkaVoN0bWUZDWwuu/S2qpa2//IHK/NDrQLsAp4KnAQ8Nkkh+/gu9t9SJIkTaMh9lpqkpa1CzyyGTi47/wgYMscz1xeVXcDX09yPb3EZjO95Kb/3UsXao9dS5IkTauZGvxY3HpgVZJDk+wGnAism/XM3wNPA0iyH72upk3AhcAzk+ybZF/gmc21eVmRkSRpWrWwIF5VbU1yMr0EZAVwdlVtTLIG2FBV67g3YbkOuAd4XVV9HyDJH9FLhgDWVNUtC8UzkZEkaVq1tCBeVZ0PnD/r2ul9vws4rTlmv3s2cPaOxjKRkSRpWg0xRmbcmMhIkjStJmCLAhMZSZKm1A4ucDfWTGQkSZpWVmQkSVJnmchIkqTOcrCvJEnqLCsykiSpq8pERpIkdZaJjCRJ6iynX0uSpM6yIiNJkjprAhKZ+yx3AyRJkoa1JBWZJPsCB1fVNUsRT5IkLa63CXW3tZbIJLkUeG4T4yrge0n+uaq227JbkiQtA7uWFrRPVd0OPB/4YFUdCTyjxXiSJGkQMzX4MWbaTGR2SfIg4EXAeTvyQpLVSTYk2fA/b9zSYtMkSVLN1MDHuGlzjMwa4ELgsqpan+Qw4N8XeqGq1gJrAf7vMU8dvz8tSZImyRgmJoNqLZGpqr8D/q7vfBPwgrbiSZKkAXV/PbzRJzJJ/hKYN8WrqlNGHVOSJA1uHLuKBtVGRWZDC9+UJEmjZiKzvar60Ki/KUmSWmDX0vaS/AMLdy09d9QxJUnS4Oxamtu7WvimJEkaNSsy26uqf972O8kewMqqun7UcSRJ0s6ZhIpMawviJXkOva0JLmjOj0iyrq14kiRpQDNDHGOmzZV93wocDdwKUFVXAYe0GE+SJA2gZgY/xk2bK/turarbkrQYQpIkDW0ME5NBtZnIfDnJS4AVSVYBpwCfbzGeJEkawDhWWAbVZtfSHwCPAn4CnAPcBpzaYjxJkjRl2txr6cfAm5tDkiSNGysy80tyUZL7953vm+TCtuJJkqTBONh3YftV1a3bTqrqB0kOaDGeJEkawDgmJoNqM5GZSbKyqr4FkOTBLLB1gSRJWlomMgt7M3BZkm0r/R4DrG4xniRJGkR1f4mUNgf7XpDkccDjgQCvqaqb24onSZIGY0VmEU3icl6bMSRJ0nBqxoqMJEnqKCsyc0iyS1VtHfV3JUnSaNUEjJFpYx2ZL7XwTUmSNGKuIzO37qd3kiRNAcfIzG3/JKfNd7Oq3t1CTEmSNKCagNXd2khkVgB7YmVGkqSxZkVmbjdW1ZoWvitJkkbIRGZu3f9TkSRpCti1NLent/BNSZI0YpNQkRn59OuqumXU35QkSZpLG+vISJKkDqjKwMeOSHJckuuT3JDkDQs898IkleSo5vyQJHcmuao53rdYrCXZoiDJc6tq3VLEkiRJO6aNBe6SrADOAo4FNgPrk6yrqutmPbcXcArwxVmf+FpVHbGj8drYouD5sy8BZyXZBaCqPjnqmJIkaXAz7WxRcDRwQ1VtAkhyLnA8cN2s5/4I+FPgtTsTrI2KzMeAC4Dvcu8MpvsBzwEKMJGRJGkMtLTX0oHAt/vONwO/2v9AkscCB1fVeUlmJzKHJvlX4HbgLVX12YWCtZHIPAF4B7AeeF9VVZKnVtXvthBLkiQNaZhZS0lWA6v7Lq2tqrX9j8wVqu/9+wDvAV42x3M3Aiur6vtJjgT+Psmjqur2+drTxqyl9fT6xXYDLk5yNH3/ApIkaTxUDXPU2qo6qu9YO+uzm4GD+84PArb0ne8FHA5cmuQbwOOBdUmOqqqfVNX3e22rK4CvAQ9b6N+hlcG+VTUD/HmSj9PLuiRJ0phpaR2Z9cCqJIcC3wFOBF7y05hVtwH7bTtPcinw2qrakGR/4JaquifJYcAqYNNCwVqdtVRV3wFe1GYMSZI0nDYG+1bV1iQnAxfS23/x7KramGQNsGGRWczHAGuSbAXuAV652Pp0rSQySU4CXg08vLn0FeAvqurDbcSTJEmDa2mwL1V1PnD+rGunz/PsU/t+fwL4xCCx2ph+/TvAqcBpwJX0Bv08DnhnEkxmJEkaD5Ow19KCg32T/NMQ3/x94ISquqSqbquqW6vqYuAFzT1JkjQGZioDH+NmsYrM/kN8c++q+sbsi1X1jSR7D/E9SZLUgra6lpbSYonMPnOs1PtT86zSe+cC31voniRJWkKT0LW0aCIDPJv5F7eZK5F5RJJr5rge4LAdbdiK3SfgT1dj5Z0fWO4WaJK8+Yr3LncTpJ02jl1Fg1oskflmVf3egN98xLCNkSRJS2cSupYWW9n34UmeNPtikqckecg87+wKHFRV3+w/gJUs0W7bkiRpcZMw2HexROaLwB1zXL8TOHOed84c4h1JkqSBLVYhOaCqthvv0iwjfMg87xwyxDuSJGmJTcJo1MUSmfsucG+PEb4jSZKW2Dh2FQ1qsa6l9UleMftikpcDV4zwHUmStMSqMvAxbharyJwKfCrJS7k3CTkK2A04YYTvSJKkJTaz3A0YgQUTmaq6CXhikqcBhzeXP91sOTCydyRJ0tKrOZeJ65Ydmg5dVZcAlwzy4WHekSRJS2dmAkb7uq6LJElTamZaKjKSJGnyTE3XkiRJmjwTP9hXkiRNLisykiSps6zISJKkzjKRkSRJnWXXkiRJ6qyZ7ucxJjKSJE0r15GRJEmdNQEL+y66+7UkSdLYsiIjSdKUctaSJEnqrJk4RkaSJHXUJIyRMZGRJGlK2bUkSZI6y3VkJElSZ7mOjCRJ6izHyEiSpM6ya0mSJHWWg30lSVJn2bUkSZI6y64lSZLUWXYtSZKkzjKRkSRJnVV2LUmSpK6yIiNJkjrLREaSJHXWJEy/vs9yN0CSJGlYVmQkSZpSriMjSZI6yzEykiSps0xkJElSZ03CYN8lSWSS7AscXFXXLEU8SZK0uEkYI9ParKUklybZO8kDgKuBDyZ5d1vxJEnSYGaGOHZEkuOSXJ/khiRvmOP+K5Ncm+SqJJcleWTfvTc2712f5NcXi9Xm9Ot9qup24PnAB6vqSOAZLcaTJEkDqCGOxSRZAZwFPAt4JPDi/kSlcU5VPbqqjgD+FHh38+4jgROBRwHHAe9tvjevNhOZXZI8CHgRcN6OvJBkdZINSTZ8ePONLTZNkiTNUAMfO+Bo4Iaq2lRVdwHnAsf3P9AUOra5H/fmSMcD51bVT6rq68ANzffm1eYYmTXAhcBlVbU+yWHAvy/0QlWtBdYCfO/YX5uEMUiSJI2tlmYtHQh8u+98M/Crsx9K8irgNGA34D/0vXv5rHcPXChYaxWZqvq7qnpMVf1+c76pql7QVjxJkjSYYbqW+ntPmmP1rM/ONYR4u+JEVZ1VVQ8BXg+8ZZB3+428IpPkLxcKWlWnjDqmJEka3DAVmf7ek3lsBg7uOz8I2LLA8+cC/33Id1vpWtrQwjclSdKItTT9ej2wKsmhwHfoDd59Sf8DSVZV1bbhJr/JvUNP1gHnNLOcfxFYBXxpoWAjT2Sq6kOj/qYkSRq9HRy8O5Cq2prkZHrjZFcAZ1fVxiRrgA1VtQ44OckzgLuBHwAnNe9uTPIx4DpgK/CqqrpnoXhtdC39Awt3LT131DElSdLg2ppVU1XnA+fPunZ63+9XL/Du24G372isNrqW3tXCNyVJ0oi519Icquqft/1OsgewsqquH3UcSZK0c9roWlpqbW5R8BzgKuCC5vyIJOvaiidJkqZPmyv7vpXeany3AlTVVcAhLcaTJEkDaGOLgqXW5sq+W6vqtmQCttaUJGkCOUZmYV9O8hJgRZJVwCnA51uMJ0mSBuAYmYX9Ab3dK38CnAPcBpzaYjxJkjQAu5YWUFU/Bt7cHJIkacxMQtdSm7OWLkpy/77zfZNc2FY8SZI0mBrif+OmzTEy+1XVrdtOquoHSQ5oMZ4kSRrAJFRk2kxkZpKsrKpvASR5MOPZvSZJ0lSahMG+bSYybwYuS7Jtpd9jgNUtxpMkSQPofhrT7mDfC5I8Dng8EOA1VXVzW/EkSdJgrMgsoklczmszhiRJGo5jZCRJUmeN4yykQY08kUmyS1VtHfV3JUnSaE1CRaaNdWS+1MI3JUnSiLmOzNzcJVKSpA6YhIpMG4nM/klOm+9mVb27hZiSJGlAMzV+FZZBtZHIrAD2xMqMJElqWRuJzI1VtaaF70qSpBHqfj3GMTKSJE0tF8Sb29Nb+KYkSRqxcZyFNKiRJzJVdcuovylJkkbPWUuSJKmzJqFrqY0F8UiyS9/vPZMcleQBbcSSJEnDmYQF8UaeyCR5GXBTkq8meRZwDfAnwNVJXjzqeJIkaTgzQxzjpo2upT8EHg7sBVwNPLaqvpbkgcBFwEdbiClJkgZULog3p3uq6mbg5iQ/rKqvAVTVTYkzsyVJGheTMEamjUTmW0n+mF5F5t+S/BnwSeAZwI0txJMkSUMYx66iQbUx2Pe3gNuBzcBzgS8AbwQeCLyshXiSJGkIkzDYt411ZG4H/rjv0sebQ5IkjZFJ6Fpqa/r1SUmuTPKj5tiQ5HfaiCVJkoZTVQMf42bkFZkmYTkVOA24kt7eS48D3pmEqvrwqGNKkqTBTcIYmTYG+/4+cEJVfaPv2sVJXgCcC5jISJI0BsZxzMug2khk9p6VxABQVd9IsncL8SRJ0hAcIzO3O4e8J0mSNJA2KjKPSHLNHNcDHNZCPEmSNIRxHLw7qFYSmVF85J6fuAqwRut1L1/uFmiS7PGLT1nuJmgCbb3rO0sabxK6ltpIZHYFHlhVn+u/mOQpwJYW4kmSpCFMwmDfNsbInAncMcf1O5t7kiRpDMxUDXyMmzYqModU1XZjZKpqQ5JDWognSZKGMH5pyeDaSGTuu8C9PVqIJ0mShjAJY2Ta6Fpan+QVsy8meTlwRQvxJEnSEGaogY9x00ZF5lTgU0leyr2Jy1HAbsAJLcSTJElDcPr1HKrqJuCJSZ4GHN5c/nRVXTzqWJIkaXjjWGEZVBsVGQCq6hLgkra+L0mSds4kTL9uLZGRJEnjbRK6ltoY7CtJkjqgrcG+SY5Lcn2SG5K8YY77xyS5MsnWJC+cde+eJFc1x7rFYlmRkSRpSrVRkUmyAjgLOBbYTG8287qquq7vsW8BLwNeO8cn7qyqI3Y0nomMJElTqqXBvkcDN1TVJoAk5wLHAz9NZKrqG829mZ0NZteSJElTqob43w44EPh23/nm5tqOum+SDUkuT/K8xR62IiNJ0pQaZu+kJKuB1X2X1lbV2v5H5nhtkEArq2pLksOAi5NcW1Vfm+9hExlJkrTDmqRl7QKPbAYO7js/CNgywPe3NP/clORS4LHAvImMXUuSJE2plrqW1gOrkhyaZDfgRGDR2UcASfZNsnvzez/gSfSNrZmLiYwkSVNqpmrgYzFVtRU4GbgQ+ArwsaramGRNkucCJPmVJJuB/wi8P8nG5vVHABuSXE1vUd13zJrttB27liRJmlJtrexbVecD58+6dnrf7/X0upxmv/d54NGDxDKRkSRpSg0z2HfcmMhIkjSl3GtJkiR1lhUZSZLUWVZkJElSZ1Xt9A4By85ERpKkKdXSXktLykRGkqQp1cbu10vNREaSpCllRUaSJHWWFRlJktRZTr+WJEmd5fRrSZLUWXYtSZKkznKwryRJ6qxJqMjcZ7kbIEmSNCwrMpIkTSlnLUmSpM6ahK4lExlJkqaUg30lSVJnWZGRJEmd5RgZSZLUWa7sK0mSOsuKjCRJ6izHyEiSpM6ya2kHJbkPsGdV3b4U8SRJ0uImoSLT2hYFSc5JsneS+wHXAdcneV1b8SRJ0mCqauBj3LS519IjmwrM84DzgZXAb7cYT5IkDaCGOMZNm11LuybZlV4i81dVdXeSBf8MkqwGVjen/7mq1rbYvomRZLV/VhoV/3vaMVtPX+4WdIf/TY2vrXd9J8vdhp2VtspESU4BXg9cDfwmvYrM31bVU1oJOMWSbKiqo5a7HZoM/vekUfO/KbWptYpMVf0F8Bd9l76Z5GltxZMkSdOntUQmyWlzXL4tyRVVdVVbcSVJ0vRoc7DvUcArgQObYzXwVOCvk/yXFuNOI/ueNUr+96RR878ptabNMTIXAi+oqh8253sCHwdOAK6oqke2EliSJE2NNisyK4G7+s7vBh5cVXcCP2kxriRJmhJtJjLnAJcnOSPJGcDngI/2LZCnnZTkuCTXJ7khyRuWuz3qtiRnJ/luki8vd1s0GZIcnOSSJF9JsjHJq5e7TZo8rXUtASQ5EngyEOCyqtrQWrApk2QF8FXgWGAzsB54cVWZJGooSY4Bfgh8uKoOX+72qPuSPAh4UFVdmWQv4Argef49pVFqsyIDsAdwe1WdSW/69aEtx5smRwM3VNWmqroLOBc4fpnbpA6rqn8BblnudmhyVNWNVXVl8/sO4Cv0Jn9II9PmXktn0FsQ743NpV2Bv20r3hQ6EPh23/lm/AtC0phKcgjwWOCLy9sSTZo2KzInAM8FfgRQVVuAvVqMN23mWlZ6HLfBkDTlmlmrnwBObfbgk0amzUTmruoNwCmAZpCvRmczcHDf+UHAlmVqiyTNqdlz7xPAR6rqk8vdHk2eNhOZjyV5P3D/JK8APgP8jxbjTZv1wKokhybZDTgRWLfMbZKkn0oS4APAV6rq3cvdHk2mtmctHQs8k143yIVVdVFrwaZQkt8AzgRWAGdX1duXuUnqsCQfpbf69n7ATcAZVfWBZW2UOi3Jk4HPAtcCM83lN1XV+cvXKk2aVhOZnwnUmy58YlV9ZEkCSpKkiTfyrqUkeyd5Y5K/SvLM9JwMbAJeNOp4kiRpeo28IpPkfwM/AL4APB3YF9gNeLW7XkuSpFFqI5G5tqoe3fxeAdwMrGwWQ5IkSRqZNmYt3b3tR1XdA3zdJEaSJLWhjYrMDL39WqA3W2kP4MfN76qqvUcaUJIkTa02KjJXV9XezbFXVe3S99skRtoJSX4hyblJvpbkuiTnJ/nlJFc1xy1Jvt78/kzLbflGkv0WeeZvkrywzXZImm67tPBNl8mXWtAsLvYp4ENVdWJz7Qhg76o6ojn/G+C8qvr4sjVUkpZQG4nMAUlOm++mqztKQ3sacHdVvW/bhUFmAjab9l0AXAY8Hrga+CDwNuAA4KVV9aUkDwDOBg6j1y28uqquSfLzwEeB/YEv0ez31Xz3vKo6vDl/LbBnVb11VvwjgXcDe9KbBPCyqrpxkD8ASZqtja6lFfT+otprnkPScA4HrtjJbzwU+HPgMcAvAS8Bngy8FnhT88zbgH+tqsc01z7cXD8DuKyqHktvO4yVOxq02W/nL4EXVtWR9BIlV6KWtNPaqMjcWFVrWviupJ339aq6FiDJRuD/VFUluRY4pHnmycALAKrq4iQ/n2Qf4Bjg+c31Tyf5wQBxH04vEbuo10PGCsBqjKSd1kYikxa+KQk2Ajs7cPYnfb9n+s5nuPfvg7n+P1yz/tlvKz9b3b3vHM8E2FhVT9jxpkrS4troWnp6C9+UBBcDuze7yQOQ5FeS/NqI4/wL8NLm+08Fbq6q22ddfxa9Vbuht8HkAU3lZnfg2XN883pg/yRPaN7fNcmjRtxuSVNo5IlMVd0y6m9K6i3CBJwAHNtMv94IvBXYMuJQbwWOSnIN8A7gpOb624BjklxJb1f7bzXtuhtYA3wROA/4tznafhe9atKfJHfSqhoAAABMSURBVLkauAp44ojbLWkKLdnu15IkSaPWRteSJEnSkjCRkSRJnWUiI0mSOstERpIkdZaJjCRJ6iwTGUmS1FkmMpIkqbNMZCRJUmf9f90J39mT+6LwAAAAAElFTkSuQmCC\n", 203 | "text/plain": [ 204 | "
" 205 | ] 206 | }, 207 | "metadata": { 208 | "needs_background": "light" 209 | }, 210 | "output_type": "display_data" 211 | }, 212 | { 213 | "name": "stdout", 214 | "output_type": "stream", 215 | "text": [ 216 | "--------Tissue modules ---------\n" 217 | ] 218 | }, 219 | { 220 | "data": { 221 | "image/png": "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\n", 222 | "text/plain": [ 223 | "
" 224 | ] 225 | }, 226 | "metadata": { 227 | "needs_background": "light" 228 | }, 229 | "output_type": "display_data" 230 | }, 231 | { 232 | "data": { 233 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiwAAAFNCAYAAAAjNzSLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAZlklEQVR4nO3de7BlZXnn8e+vGxASEIwgcrUdr6UMQgQmUYPgFSwJ0cEEpbwV0pnySkVTqJNSMfH2R26jmcJWCHiDKCoDhAwyQtRoQEBboBtQBlQ6oETBARwFmvPMH3u1bM+c3nuf02edvfbe3w+1ir3Xete7ntN1qvup572sVBWSJEldtmrcAUiSJA1jwiJJkjrPhEWSJHWeCYskSeo8ExZJktR5JiySJKnzTFikFZTknUk+Pu442pbkiCSblrutpNm13bgDkKZJknv7vv4GcB/wYPP9j6vq/Ssf1fRKciTwLuC3gbuqas14I5LUFiss0jKqqp23HMAPgWP6zn163PFNoZ8DZwB/Ou5AJLXLhEVaQUnek+RTzecdk3wqyU+T/CzJlUn2bK69JsnNSe5JckuSE+bf33xfk6SSbNd83zXJ6UluT/JvSf4iyeoBsXyuieGeJNcmeWKSdyS5I8mtSV7Q137vJOcnuTPJTUlO6ru2U5Izk9yVZCNw6LxnVZLH930/M8lfbCWuvZN8Psm/Nz/7m7f251lV36yqTwI3D/pzlzT5TFik8Xk1sCuwH/BI4L8Av0jym8B/A46uql2AZwDrR+zzLGAz8HjgYOAFwOsGtD8G+CTwCODbwMX0/l7YB3gv8NG+tmcDm4C9geOA9yd5bnPt3cDjmuOFzc+2aElWARcA32lieC5wcpIXLqU/SdPDhEUanwfoJSqPr6oHq+rqqrq7uTYHHJBkp6q6vao2DOusqc4cDZxcVT+vqjuAvwaOH3Db16rq4qraDHwO2AP4YFU9AJwDrEmyW5L9gGcBp1TVL6tqPfBx4JVNP38IvK+q7qyqW+klXEtxKLBHVb23qu6vqpuBjw35GSTNACfdSuPzSXrVlXOS7AZ8CvivVfXzJH8EvA04PcnXgbdW1Q1D+nsMsD1we5It51YBtw6458d9n38B/KSqHuz7DrAzvarKnVV1T1/7HwCHNJ/3nvecHwyJdWseA+yd5Gd951YDX1tif5KmhBUWaUyq6oGqOrWqnkJv2OfFwKuaaxdX1fOBvYAb6FUZoDfJ9Df6unl03+db6a1K2r2qdmuOh1fVU5ch3NuA30qyS9+5/YF/az7fTi/56r/W7/8OiLvfrcAtffHvVlW7VNWLtiF2SVPAhEUakyRHJvmPzaTYu+kNET2YZM8kv9/MZbkPuJeHlkavBw5Psn+SXYF3bOmvqm4HvgT8ZZKHJ1mV5HFJnr2tsTbDPN8APtBMFj4QOBHYsvLps8A7kjwiyb7Am+Z1sR54RZLVSY4CthbTN4G7k5zSTORdneSAJIcu1Lj5GXekV1lKE9sO2/bTSuoiExZpfB4NnEsvWbke+Aq9YaFVwFvpVTXupPeP++sBquoS4B+Aa4CrgQvn9fkqYAdgI3BX0/9eyxTvy4E1TVxfBN7dxANwKr1hoFvoJU2fnHfvW+hN8P0ZcAJw3kIPaIajjgEOavr6Cb25MrtuJabD6Q1dXUSvqvOL5vmSpkyqatwxSJIkDWSFRZIkdZ4JiyRJ6rzWljUneTJwLL3Nn4reuPf5VXV9W8+UJEnTqZUKS5JT6G06FXqz/q9sPp+d5O1tPFOSJE2vVibdJvku8NRmt8z+8zsAG6rqCcv+UEmSNLXaGhKao7fz5fzdLvdqri0oyVpgLUBW7/r0Vat+s6XwNIuOfvTB4w5BU+Rz5y7pdUnSQDse9rIMb7V8HvjJzYuuWmy/+39Y0Ri3aCthORn4cpLv8dB23fvTeyHbG7d2U1WtA9YBbLfDPq63liRJQEsJS1X9zyRPBA6jN+k29N7yemXfe0okSdI4zU3OP8mtrRKqqjng8rb6lyRJ26i2Okujc3xbsyRJs2rOhEWSJHVcWWGRJEmdZ4VFkiR1nhUWSZLUea4SkiRJnWeFRZIkdZ5zWCRJUte5SkiSJHWfFRZJktR5VlgkSVLnuUpIkiR1nhUWSZLUec5hkSRJnTdBFZZV4w5AkiRpGCsskiTNKoeEJElS11W5SkiSJHXdBM1hMWGRJGlWOSQkSZI6zwqLJEnqPHe6lSRJnWeFRZIkdZ5zWCRJUudZYZEkSZ1nhUWSJHWeCYskSeo6d7qVJEndN0EVFt/WLEnSrKq5xR9DJNkxyTeTfCfJhiSnLtDmYUn+IclNSa5IsmZYvyYskiTNqrm5xR/D3Qc8p6qeBhwEHJXkd+a1ORG4q6oeD/w18KFhnZqwSJI0q1qosFTPvc3X7Zuj5jU7Fjir+Xwu8NwkGdSvCYskSVpWSVYnWQ/cAVxSVVfMa7IPcCtAVW0G/g/wyEF9mrBIkjSrljAklGRtkqv6jrXzu62qB6vqIGBf4LAkB8xrslA1ZX4V5te4SkiSpFm1hJ1uq2odsG7Etj9L8s/AUcB1fZc2AfsBm5JsB+wK3DmoLysskiTNqhYm3SbZI8luzeedgOcBN8xrdj7w6ubzccClVWWFRZIkLaCdfVj2As5KsppeYeSzVXVhkvcCV1XV+cDpwCeT3ESvsnL8sE5NWCRJmlUtvPywqq4BDl7g/Lv6Pv8SeNli+jVhkSRpVk3QTrcmLJIkzaoWKixtMWGRJGlWWWGRJEmdZ4VFkiR1nhUWSZLUeSYskiSp8wbv1dYpJiySJM0qKyySJKnzTFgkSVLnuUpIkiR13gRVWHxbsyRJ6jwrLJIkzSpXCUmSpM6boCEhExZJkmaVCYskSeo8VwlJkqSuqznnsEiSpK5zSEiSJHWeQ0KSJKnzHBKSJEmd55CQJEnqPBMWSZLUee50K0mSOs8KiyRJ6jwn3UqSpM5zWfO2u/fKj407BE2Z1fs8edwhaIq8/pBTxh2CptDHvv+ylX2gFRZJktR1NUFzWFaNOwBJkqRhrLBIkjSrHBKSJEmd56RbSZLUeVZYJElS503QpFsTFkmSZpUVFkmS1HnOYZEkSZ1nhUWSJHXdJG0cZ8IiSdKsmqAKizvdSpI0q+Zq8ccQSfZLclmS65NsSPKWAW0PTfJgkuOG9WuFRZKkWdXOpNvNwFur6ltJdgGuTnJJVW3sb5RkNfAh4OJROrXCIknSrGqhwlJVt1fVt5rP9wDXA/ss0PRNwOeBO0YJ1QqLJEkzqlqew5JkDXAwcMW88/sALwGeAxw6Sl9WWCRJmlVLqLAkWZvkqr5j7UJdJ9mZXgXl5Kq6e97lvwFOqaoHRw3VCoskSbNqCcuaq2odsG5QmyTb00tWPl1VX1igySHAOUkAdgdelGRzVZ23tT5NWCRJmlUtDAmll4WcDlxfVX+1UJuqemxf+zOBCwclK2DCIknS7GpnDsszgVcC1yZZ35x7J7A/QFWdtpROTVgkSdKyqap/AbKI9q8ZpZ0JiyRJM6pqcna6NWGRJGlWTdDW/CYskiTNKhMWSZLUdW1vHLecTFgkSZpVJiySJKnzWnn3YTtMWCRJmlEOCUmSpO4zYZEkSZ3nkJAkSeo6h4QkSVL3WWGRJEldZ4VFkiR1nxUWSZLUdWXCIkmSOs+ERZIkdd0kVVhWjTsASZKkYaywSJI0qyaowmLCIknSjJqkISETFkmSZpQJiyRJ6jwTFkmS1H2VcUcwspFWCSV5TJLnNZ93SrJLu2FJkqS21dzij3EZmrAkOQk4F/hoc2pf4Lw2g5IkSe2ruSz6GJdRKixvAJ4J3A1QVd8DHtVmUJIkqX2TVGEZZQ7LfVV1f9LLqpJsB0zO6x0lSdKCaoLmsIySsHwlyTuBnZI8H3g9cEG7YUmSpLZN0iqhUYaE3g78O3At8MfARcCftRmUJElq3yTNYRlaYamqOeBjzSFJkqZETdAEj60mLEmuZcBclao6sJWIJEnSihhnxWSxBlVYXrxiUUiSpBU3FQlLVf1gJQORJEkrayqGhLZIcg8PDQ3tAGwP/LyqHt5mYJIkqV1TUWHZoqp+bRv+JH8AHNZaRJIkSfOM9C6hflV1HvCcFmKRJEkrqCqLPsZllCGhl/Z9XQUcgjvdSpI08SZp47hRdro9pu/zZuD7wLGtRCNJklbM3DRtzV9Vr12JQCRJ0sqaincJJfkwgzeOe3MrEUmSpBUxSauEBk26vQq4GtgR+G3ge81xEPBg+6FJkqQ2VS3+GCbJGUnuSHLdVq7vmuSCJN9JsiHJSCM5gzaOO6vp+DXAkVX1QPP9NOBLo3QuSZK6q6UKy5nAR4BPbOX6G4CNVXVMkj2AG5N8uqruH9TpKJNu9wZ2Ae5svu/cnJMkSROsjUm3VfXVJGsGNQF2SRJ6OcWd9Bb1DDRKwvJB4NtJLmu+Pxt4zwj3SZKkDhvTpNuPAOcDt9EriPxR1fAF1qOsEvr7JP8E/Cd6WdHbq+pH2xisJEkas6W8SyjJWmBt36l1VbVuEV28EFhPbxPaxwGXJPlaVd096KZRKizQ24r/95rPBVywiMAkSVIHLWVIqElOFpOgzPda4INVVcBNSW4Bngx8c9BNQ7fmT/JB4C3AxuZ4c5IPbEOgkiSpA8a0Nf8PgecCJNkTeBJw87CbRqmwvAg4aMv4UpKzgG8D71hKlEleW1V/v5R7JUnS8lnKkNAwSc4GjgB2T7IJeDewfe95dRrw58CZSa4FApxSVT8Z1u+oQ0K78dAqoV0XF/r/51TAhEWSpDFraZXQy4dcvw14wWL7HSVh+QAPrRIKcDhDqitJrtnaJWDPAff9aiLPR/7sjZx43NEjhCdJkpZiKrbm36Kqzk7yz8ChPFS6GbZKaE96s4Dvmnc+wDcGPOtXE3l++Z2LfCO0JEktmqqXHzb2aP6/GnhGEqrqCwPaXwjsXFXr519okh9JkqSRDU1YkpwBHAhsALZs7FLAVhOWqjpxwLVXLDJGSZLUgkkayhilwvI7VfWU1iORJEkrapKGhIbuwwL8axITFkmSpsyY9mFZklEqLGfRS1p+BNxHb+JsVdWBrUYmSZJaNfQFPh0ySsJyBvBK4Fom62eTJEkDFJMzJDRKwvLDqjq/9UgkSdKKmpugWbejJCw3JPkMvRce3rfl5JBlzZIkqePmpqzCshO9RKV/G92By5olSVL3TdWQUFW9diUCkSRJK2uSJqaOutOtJEmaMlNVYZEkSdPJCoskSeq8qUhYkrxq0I1V9YnlD0eSJK2UaRkSOnSBcwGOAfYBTFgkSZpgc5OTr2w9YamqN235nCTACcApwOXA+9oPTZIktWlq9mFJsh3wGuCtwBXAcVV14wrEJUmSWjZBG90OnMPyBuAtwJeBo6rqBysWlSRJUp9BFZYPA3cAzwIu6I0KAb6tWZKkqTAVq4SAx65YFJIkacXNZTrmsGwP7FlVX+8/meT3gNtajUqSJLVukuawrBpw7W+AexY4/4vmmiRJmmBzSzjGZVCFZU1VXTP/ZFVdlWRNaxFJkqQVMRX7sAA7Dri203IHIkmSVtYk7cMyaEjoyiQnzT+Z5ETg6vZCkiRJK6GWcIzLoArLycAXk5zAQwnKIcAOwEvaDkySJLVrKoaEqurHwDOSHAkc0Jz+x6q6dEUikyRJrZqWfVgAqKrLgMtWIBZJkrSCJmlZ89CERZIkTaepGBKSJEnTbaqGhCRJ0nQyYZEkSZ1XDglJkqSus8IiSZI6z4RFkiR13iQtax60Nb8kSVInmLBIkjSj5rL4Y5gkZyS5I8l1W7l+QpJrmuMbSZ42SqwmLJIkzai5JRwjOBM4asD1W4BnV9WBwJ8D60bp1DkskiTNqDYm3VbVV5OsGXD9G31fLwf2HaVfExZJkmZUBybdngj80ygNTVgkSZpRS3mXUJK1wNq+U+uqaqRhnXn9HEkvYXnWKO1NWCRJmlFLGRJqkpNFJyj9khwIfBw4uqp+Oso9JiySJM2ocQwJJdkf+ALwyqr67qj3mbBIkjSj5lpIWZKcDRwB7J5kE/BuYHuAqjoNeBfwSOC/JwHYXFWHDOvXhEWSpBnV0iqhlw+5/jrgdYvt14RFkqQZ1YFVQiMzYZEkaUb58kNJktR5S1nWPC4mLJIkzag2Jt22xYRFkqQZNTnpigmLJEkzyzkskiSp8yZpSGjVuAOQJEkaxgqLJEkzanLqKyYskiTNLOewSJKkzpukOSwmLJIkzajJSVdMWCRJmlkOCUmSpM6rCaqxmLBIkjSjrLBIkqTOc9KtJEnqvMlJV0xYJEmaWVZYJElS5zmHRZIkdZ6rhJbBZ1/0mXGHoCnz0lf8fNwhaIpc9cs7xh2CtM2ssEiSpM6zwiJJkjrPCoskSeq8uZqcCsuqcQcgSZI0jBUWSZJm1OTUV0xYJEmaWW4cJ0mSOs9VQpIkqfNcJSRJkjrPISFJktR5DglJkqTOc0hIkiR1Xk3QxnEmLJIkzSjnsEiSpM5zSEiSJHWek24lSVLnOSQkSZI6z0m3kiSp8yZpDsuqcQcgSZLGo5bw3yiSHJXkxiQ3JXn7Vtr8YZKNSTYk+cywPq2wSJI0o9qYw5JkNfB3wPOBTcCVSc6vqo19bZ4AvAN4ZlXdleRRw/q1wiJJkpbTYcBNVXVzVd0PnAMcO6/NScDfVdVdAFV1x7BOTVgkSZpRVbXoYwT7ALf2fd/UnOv3ROCJSb6e5PIkRw3r1CEhSZJm1FKGhJKsBdb2nVpXVev6myxw2/wHbQc8ATgC2Bf4WpIDqupnW3uuCYskSTNqKRvHNcnJugFNNgH79X3fF7htgTaXV9UDwC1JbqSXwFy5tU4dEpIkaUbNVS36GMGVwBOSPDbJDsDxwPnz2pwHHAmQZHd6Q0Q3D+rUhEWSpBlVSziG9lm1GXgjcDFwPfDZqtqQ5L1Jfr9pdjHw0yQbgcuAP62qnw7q1yEhSZJmVFtb81fVRcBF8869q+9zAX/SHCMxYZEkaUb5LiFJktR5vktIkiR1nhUWSZLUeUtZ1jwuJiySJM0oh4QkSVLnOSQkSZI6zwqLJEnqPCsskiSp85x0K0mSOm/EdwN1gu8SkiRJnWeFRZKkGeWQkCRJ6rxJGhIyYZEkaUZZYZEkSZ1nhUWSJHWeFRZJktR5VlgkSVLnWWGRJEmdVzU37hBGZsIiSdKM8l1CkiSp83xbsyRJ6jwrLJIkqfOssEiSpM5zWbMkSeo8lzVLkqTOc0hIkiR1npNuJUlS501ShWXVuAOQJEkaxgqLJEkzylVCkiSp8yZpSMiERZKkGeWkW0mS1HlWWCRJUuc5h0WSJHWeO91KkqTOs8IiSZI6zzkskiSp8yZpSKi1nW6TPDnJc5PsPO/8UW09U5Ikja6qFn2MSysJS5I3A/8DeBNwXZJj+y6/v41nSpKkxZmkhKWtIaGTgKdX1b1J1gDnJllTVX8LpKVnSpKkRZicASFIG9lSko1V9ZS+7zsD5wIbgedU1UFbuW8tsLb5uq6q1i17cFMoyVr/rLRc/H3ScvN3SsuhrYTlUuBPqmp937ntgDOAE6pq9bI/dIYluaqqDhl3HJoO/j5pufk7peXQ1qTbVwE/6j9RVZur6lXA4S09U5IkTalW5rBU1aYB177exjMlSdL0am1Zs1aUY8NaTv4+abn5O6Vt1socFkmSpOVkhUWSJHWeCcsES3JUkhuT3JTk7eOOR5MtyRlJ7khy3bhj0XRIsl+Sy5Jcn2RDkreMOyZNLoeEJlSS1cB3gecDm4ArgZdX1caxBqaJleRw4F7gE1V1wLjj0eRLshewV1V9K8kuwNXAH/j3lJbCCsvkOgy4qapurqr7gXOAY4fcI21VVX0VuHPccWh6VNXtVfWt5vM9wPXAPuONSpPKhGVy7QPc2vd9E/5FIKmjmte0HAxcMd5INKlMWCbXQu9kcnxPUuc0r2f5PHByVd097ng0mUxYJtcmYL++7/sCt40pFklaUJLt6SUrn66qL4w7Hk0uE5bJdSXwhCSPTbIDcDxw/phjkqRfSRLgdOD6qvqrccejyWbCMqGqajPwRuBiehPZPltVG8YblSZZkrOBfwWelGRTkhPHHZMm3jOBVwLPSbK+OV407qA0mVzWLEmSOs8KiyRJ6jwTFkmS1HkmLJIkqfNMWCRJUueZsEiSpM4zYZEmSJJHJzknyf9OsjHJRUme1rdk9M4ktzSf/1fLsXw/ye5D2pyZ5Lg245A0G7YbdwCSRtNswvVF4KyqOr45dxDw8Ko6qPl+JnBhVZ07tkAlqQVWWKTJcSTwQFWdtuVEVa2vqq+NcnOSNUluSPLxJNcl+XSS5yX5epLvJTmsafdbSc5Lck2Sy5Mc2Jx/ZJIvJfl2ko/SvM+q6fe6vue8Lcl7Fnj+05N8JcnVSS5Ostc2/WlImikmLNLkOAC4ehv7eDzwt8CBwJOBVwDPAt4GvLNpcyrw7ao6sDn3ieb8u4F/qaqD6b0GYv9RH9q8T+bDwHFV9XTgDOB92/izSJohDglJs+WWqroWIMkG4MtVVUmuBdY0bZ4F/GeAqrq0qazsChwOvLQ5/49J7lrEc59EL+G6pDeyxWrg9mX4eSTNCBMWaXJsALZ1Aut9fZ/n+r7P8dDfB1ngvpr3/36b+fVq7Y4LtAmwoap+d/RQJekhDglJk+NS4GFJTtpyIsmhSZ69zM/5KnBC0/8RwE+q6u55548GHtG0/zHwqKYS8zDgxQv0eSOwR5Lfbe7fPslTlzluSVPMhEWaENV7U+lLgOc3y5o3AO8BblvmR70HOCTJNcAHgVc3508FDk/yLeAFwA+buB4A3gtcAVwI3LBA7PfTqw59KMl3gPXAM5Y5bklTzLc1S5KkzrPCIkmSOs+ERZIkdZ4JiyRJ6jwTFkmS1HkmLJIkqfNMWCRJUueZsEiSpM4zYZEkSZ33/wAkccwepuG8TwAAAABJRU5ErkJggg==\n", 234 | "text/plain": [ 235 | "
" 236 | ] 237 | }, 238 | "metadata": { 239 | "needs_background": "light" 240 | }, 241 | "output_type": "display_data" 242 | } 243 | ], 244 | "source": [ 245 | "#compute CN modules, CT modules and couplings\n", 246 | "facs_overall = tissue_module_plots(dat,2,3,nbs,cells_of_interest)" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": null, 252 | "metadata": { 253 | "collapsed": true 254 | }, 255 | "outputs": [], 256 | "source": [] 257 | } 258 | ], 259 | "metadata": { 260 | "kernelspec": { 261 | "display_name": "Python 3", 262 | "language": "python", 263 | "name": "python3" 264 | }, 265 | "language_info": { 266 | "codemirror_mode": { 267 | "name": "ipython", 268 | "version": 3 269 | }, 270 | "file_extension": ".py", 271 | "mimetype": "text/x-python", 272 | "name": "python", 273 | "nbconvert_exporter": "python", 274 | "pygments_lexer": "ipython3", 275 | "version": "3.6.8" 276 | }, 277 | "latex_envs": { 278 | "LaTeX_envs_menu_present": true, 279 | "autoclose": false, 280 | "autocomplete": true, 281 | "bibliofile": "biblio.bib", 282 | "cite_by": "apalike", 283 | "current_citInitial": 1, 284 | "eqLabelWithNumbers": true, 285 | "eqNumInitial": 1, 286 | "hotkeys": { 287 | "equation": "Ctrl-E", 288 | "itemize": "Ctrl-I" 289 | }, 290 | "labels_anchors": false, 291 | "latex_user_defs": false, 292 | "report_style_numbering": false, 293 | "user_envs_cfg": false 294 | } 295 | }, 296 | "nbformat": 4, 297 | "nbformat_minor": 2 298 | } 299 | -------------------------------------------------------------------------------- /Neighborhoods/app_CRC_contacts.R: -------------------------------------------------------------------------------- 1 | # 2 | # This is a Shiny web application. You can run the application by clicking 3 | # the 'Run App' button above. 4 | # 5 | # Find out more about building applications with Shiny here: 6 | # 7 | # http://shiny.rstudio.com/ 8 | # 9 | # Originally implemented by Yury Goltsev goltsev@stanford.edu as described in https://doi.org/10.1016/j.cell.2018.07.010 10 | # Shiny app implemented by Janos Demeter jdemeter@stanford.edu 11 | 12 | pkgs <- list('shiny' = '1.3.2', 13 | 'shinyFiles' = '0.7.3', 14 | 'data.table' = '1.12.2', 15 | 'dplyr' = '0.8.3', 16 | 'deldir' = '0.1-21', 17 | 'doParallel' = '1.0.14', 18 | 'foreach' = '1.4.4', 19 | 'gplots' = '3.0.1.1', 20 | 'ggplot2' = '3.2.1', 21 | 'RColorBrewer' = '1.1-2', 22 | 'tidyverse' = '1.2.1', 23 | 'ComplexHeatmap' = '2.0.0', 24 | 'circlize' = '0.4.6', 25 | 'gtools' = '3.8.1', 26 | 'spatstat' = '1.60-1', 27 | 'ggpmisc' = '0.3.1') 28 | 29 | msg <- function(r, p, v, o){ 30 | x <- paste0('Please ', o, ' ', p, ' to at least version ', v, '.') 31 | if(is.na(r)){ 32 | r <- x 33 | } else { 34 | r <- c(r, x) 35 | } 36 | return(r) 37 | } 38 | 39 | exit <- function() { 40 | .Internal(.invokeRestart(list(NULL, NULL), NULL)) 41 | } 42 | 43 | instd <- as.data.frame(installed.packages(), stringsAsFactors = F) 44 | report <- NA_character_ 45 | 46 | for( p in attributes(pkgs)$names){ 47 | #print(p) 48 | if(p %in% instd$Package){ 49 | if(packageVersion(p) < pkgs[[p]]){ 50 | report <- msg(report, p, pkgs[[p]], 'update') 51 | } else { 52 | require(p, quietly = TRUE, character.only = TRUE) 53 | } 54 | } else { 55 | report <- msg(report, p, pkgs[[p]], 'install') 56 | } 57 | } 58 | 59 | if(!is.na(report)){ 60 | print(paste(report, collapse = '\n')) 61 | exit() 62 | } 63 | 64 | options(shiny.maxRequestSize=4000*1024^2) 65 | 66 | # Define UI for application that draws a histogram 67 | ui <- fluidPage( 68 | # Application title 69 | titlePanel("Niche Finder"), 70 | 71 | sidebarPanel( 72 | # select output directory 73 | shinyDirButton("dir", "Chose output directory", "Upload"), 74 | 75 | textInput('anncol', 76 | 'Cluster Column Name', 77 | 'ClusterName'), 78 | 79 | textInput('efactcol', 80 | 'Patient / Treatment Group Column Name', 81 | 'Groups'), 82 | 83 | textInput('regcol', 84 | 'Column Name for Regions', 85 | 'Spots'), 86 | 87 | textInput('patcol', 88 | 'Column Name for Patients', 89 | 'Patients'), 90 | 91 | textInput('xcol', 92 | 'X Coordinate Column', 93 | 'X'), 94 | 95 | textInput('ycol', 96 | 'Y Coordinate Column', 97 | 'Y'), 98 | 99 | numericInput('numclust', 100 | 'Number of niches', 101 | 10, 102 | min = 10, 103 | max = 200), 104 | 105 | 106 | numericInput('mincells', 107 | 'Minimum cell count (total)', 108 | 100, 109 | min = 50, 110 | max = 500), 111 | 112 | numericInput('convfactor', 113 | 'Conversion factor (um/pixel)', 114 | 0.37744, 115 | min = 0.05, 116 | max = 5), 117 | radioButtons('denshis', 'Distance graphs:', 118 | c('density' = 'dens', 119 | 'histogram' = 'hist'), 120 | inline = TRUE), 121 | 122 | # Input: Select a file ---- 123 | fileInput( 124 | "data_file", 125 | "Choose CSV File", 126 | multiple = FALSE, 127 | accept = c("text/csv", 128 | "text/comma-separated-values,text/plain", 129 | ".csv") 130 | ) 131 | ), 132 | 133 | mainPanel( 134 | 135 | verbatimTextOutput('path'), 136 | br(), 137 | textOutput('delmsg'), 138 | textOutput('pivotmsg'), 139 | textOutput('llhmsg'), 140 | textOutput('eqalmsg'), 141 | textOutput('globalmsg'), 142 | textOutput('nichemsg'), 143 | textOutput('profmsg'), 144 | textOutput('celldistmsg') 145 | ) 146 | ) 147 | 148 | 149 | # Define server logic required to draw a histogram 150 | server <- function(input, output) { 151 | 152 | x_coord_colname <- 'X:X' 153 | y_coord_colname <- 'Y:Y' 154 | ann_colname <- 'celltypes' 155 | tile_colname <- 'region' 156 | efact_colname <- 'efact' # experimental factor colname 157 | home_dir <- '~/mnt/driveB/jdemeter/project/nolanlab/' 158 | #home_dir <- 'E:/CHRISTIAN/For Janos' 159 | #home_dir <- 'D:/jdemeter/niche_finder' 160 | #cat('homedir = ', home_dir) 161 | perm_no <- 1000 162 | folders <- list(del = 'delauney_files', 163 | int = 'intermediate_files', 164 | niche = 'niche_heatmaps', 165 | ct = 'celltype_heatmaps', 166 | dist = 'celldist_plots') 167 | 168 | concat <- function(a, b, sep = '__'){ 169 | a <- unlist(a) 170 | b <- unlist(b) 171 | return(paste0(a, sep, b)) 172 | } 173 | 174 | clean_path <- function(txt){ 175 | gsub("[^a-zA-Z0-9\\.\\-]", "_", txt) 176 | } 177 | 178 | make_matr <- function(ay, sep = '___'){ 179 | matrix( 180 | apply(expand.grid(ay, ay), 181 | 1, 182 | paste, collapse = sep), 183 | nrow = length(ay) 184 | ) 185 | } 186 | 187 | mod_dset <- function(inp){ 188 | 189 | dt <- fread(inp$data_file$datapath) %>% 190 | mutate(!!x_coord_colname := get(inp$xcol), 191 | !!y_coord_colname := get(inp$ycol), 192 | !!tile_colname := as.character(get(inp$regcol)), 193 | !!ann_colname := get(inp$anncol)) %>% 194 | as.data.table 195 | 196 | if(inp$efactcol != 'none' & inp$efactcol %in% names(dt)){ 197 | dt <- dt %>% 198 | mutate(!!efact_colname := get(inp$efactcol)) %>% 199 | as.data.table 200 | } else { 201 | dt <- dt %>% 202 | mutate(!!efact_colname := 1) %>% 203 | as.data.table 204 | } 205 | 206 | if (!'XYcellid' %in% colnames(dt)) { 207 | if (all(c(x_coord_colname, y_coord_colname) %in% colnames(dt))){ 208 | dt[, XYcellid := concat(dt[[x_coord_colname]], dt[[y_coord_colname]])] 209 | } 210 | } 211 | 212 | dt[!grepl('^reg', get(tile_colname)), (tile_colname) := paste0('reg', get(tile_colname))] 213 | 214 | dt <- dt %>% 215 | select(tile_colname, x_coord_colname, y_coord_colname, ann_colname, 216 | XYcellid, efact_colname) %>% 217 | as.data.table 218 | 219 | # filter out regions with only 1 cell 220 | if(nrow(dt[, .N, tile_colname][N==1]) > 0){ 221 | singlets <- dt[, .N, tile_colname][N==1, ][[tile_colname]] 222 | dt <- dt[!get(tile_colname) %in% singlets] 223 | cat(file = stderr(), paste('removed tile(s):', singlets, '(singlets)\n')) 224 | } 225 | # filter out regions with 2 cells that share the same x/y coordinate 226 | if(nrow(dt[, .N, tile_colname][N==2]) > 0){ 227 | bad_tiles <- dt[, .N, tile_colname][N == 2, ][[tile_colname]] %>% unlist %>% unname 228 | for(t in bad_tiles){ 229 | if(length(unique(dt[get(tile_colname) == t, `X:X`])) == 1 | 230 | length(unique(dt[get(tile_colname) == t, `Y:Y`])) == 1){ 231 | dt <- dt[get(tile_colname) != t] 232 | cat(file = stderr(), paste('removed tile:', t,'(clashing coords for 2 cells)\n')) 233 | } 234 | } 235 | } 236 | 237 | # if regions are names like regNNN, make sure we don't have leading 0s 238 | if(all(grepl('^reg\\d+$', dt[[tile_colname]]))){ 239 | dt[, (tile_colname) := lapply(.SD, 240 | function(x){ 241 | paste0('reg', as.integer(gsub('^reg(\\d+)$', '\\1', x))) 242 | }), 243 | .SDcols = tile_colname] 244 | } 245 | 246 | # make sure each cell is unique: 247 | dt <- dt[, .SD[1,], .(region, XYcellid)] 248 | 249 | #cat(file=stderr(), paste('before: nrow=', nrow(dt), ', celltypes=', unique(dt$celltypes), '\n')) 250 | # filter out cell types, that have cell numbers < mincells 251 | dt <- dt[celltypes %in% dt[, .N, c('celltypes', efact_colname)][N > min_cells(), celltypes]] 252 | #cat(file=stderr(), paste('after: nrow=', nrow(dt), ', celltypes=', unique(dt$celltypes), '\n')) 253 | 254 | cat(file=stderr(), 'Imported dataset ...\n') 255 | cat(file=stderr(), file.path(path(), "dt.txt\n")) 256 | 257 | # cleanup folder 258 | # unlink(paste(path(), '*', sep = '/'), recursive = TRUE) 259 | 260 | # create subfolders 261 | for(f in folders){ 262 | dir.create(file.path(path(), f)) 263 | } 264 | 265 | fwrite(dt, file.path(path(), "dt.txt"), sep = '\t') 266 | return(dt) 267 | } 268 | 269 | # dir 270 | # cat('homedir=', home_dir) 271 | shinyDirChoose(input, 'dir', roots = c(home = home_dir), filetypes = c('', 'txt')) 272 | out_dir <- reactive({input$dir}) 273 | 274 | # path 275 | path <- reactiveVal(getwd()) 276 | observeEvent(ignoreNULL = TRUE, 277 | eventExpr = { 278 | input$dir 279 | }, 280 | handlerExpr = { 281 | if (!"path" %in% names(out_dir())) return() 282 | home <- normalizePath(home_dir) 283 | path(file.path(home, paste(unlist(out_dir()$path[-1]), collapse = .Platform$file.sep))) 284 | # create dir inside the selected folder 285 | path(file.path(path(), 286 | paste0('nf_', 287 | gsub(':', '', format(Sys.time(), "%d%m%y%X"))))) 288 | dir.create(path()) 289 | }) 290 | 291 | output$dir <- renderPrint(path) 292 | 293 | annot_col <- reactive({input$anncol}) 294 | tissue_col <- reactive({input$regcol}) 295 | efact_col <- reactive({input$efactcol}) 296 | x_col <- reactive({input$xcol}) 297 | y_col <- reactive({input$ycol}) 298 | pat_col <- reactive({input$patcol}) 299 | inputf_ori <- reactive({input$data_file$datapath}) 300 | dt <- reactive({mod_dset(input)}) 301 | output$path <- renderPrint(path()) 302 | no_clust <- reactive({input$numclust}) 303 | min_cells <- reactive({input$mincells}) 304 | conv_factor <- reactive({input$convfactor}) 305 | dens_his <- reactive({input$denshis}) 306 | 307 | output$delmsg <- renderPrint({ 308 | 309 | req(input$data_file) 310 | dt <- dt() 311 | outDir <- path() 312 | annotCol <- ann_colname #annot_col() 313 | tissueCol <- tile_colname #tissuve_col() 314 | tissues <- dt[[tissueCol]] %>% unique() 315 | 316 | cores <- detectCores() 317 | cl <- makeCluster(min(c(length(tissues), cores)), 318 | outfile = file.path(outDir, folders$int, 'dopar_log')) 319 | registerDoParallel(cl) 320 | 321 | summary_file <- file.path(outDir, folders$del, "deldir_summary.tsv") 322 | empty_dt <- data.table(x = integer(), y = integer(), n.tri = integer(), del.area = numeric(), 323 | del.wts = numeric(), n.tside = integer(), nbpt = integer(), 324 | dir.area = numeric(), dir.wts = numeric()) 325 | #file.create(summary_file) 326 | fwrite(empty_dt, summary_file, sep = '\t') 327 | re <<- foreach( 328 | i = 1:length(tissues), 329 | .combine = 'bind_rows', 330 | .export = c('concat'), 331 | .packages = c("deldir", 'data.table', 'dplyr') 332 | ) %dopar% { 333 | print(i) 334 | sub <- dt[get(tissueCol) == tissues[i],] 335 | tryCatch({ 336 | vtess <- deldir(sub[, `X:X`], sub[, `Y:Y`]) 337 | fwrite(vtess$summary %>% mutate(tissueCol = tissues[i]) %>% as.data.table, 338 | summary_file, append = TRUE, sep = '\t') 339 | result <- vtess$delsgs %>% select(-ind1,-ind2) %>% as.data.table 340 | result[, cell1ID := concat(result[, 1], result[, 2])] 341 | result[, cell2ID := concat(result[, 3], result[, 4])] 342 | 343 | result <- result %>% left_join(sub %>% select(annotCol, XYcellid), 344 | by = c('cell1ID' = 'XYcellid')) %>% as.data.table 345 | result <- result %>% left_join(sub %>% select(annotCol, XYcellid), 346 | by = c('cell2ID' = 'XYcellid')) %>% as.data.table 347 | 348 | result <- result[, c(1:4, 7:8)] 349 | colnames(result)[c(5, 6)] = c("cell1type", "cell2type") 350 | result <- rbind(result, result[, .(x1 = x2, y1 = y2, x2 = x1, y2 = y1, 351 | cell1type = cell2type, cell2type = cell1type)]) 352 | print('done') 353 | result[, ind := i] 354 | }, error = function(err){ 355 | data.table(x1 = integer(), y1 = integer(), x2 = integer(), y2 = integer(), 356 | cell1type = character(), cell2type = character(), ind = integer()) 357 | }) 358 | } 359 | stopCluster(cl) 360 | for (i in seq(length(tissues))) { 361 | fname <- file.path(outDir, folders$del, paste(tissues[i], 'Rdelaun.csv', sep = '_')) 362 | fwrite(re[ind == i, -'ind'], fname) 363 | } 364 | cat(file=stderr(), 'Finished Delauney graph calculations ... \n') 365 | print('Finished Delauney graph calculations') 366 | }) 367 | 368 | output$pivotmsg <- renderPrint({ 369 | 370 | req(input$data_file) 371 | outDir <- path() 372 | 373 | for (fname in dir(file.path(outDir, folders$del), full.names = TRUE)) { 374 | if (grepl('^reg.+Rdelaun.csv', basename(fname))) { 375 | #print(fname) 376 | delaun <- fread(fname, header = TRUE) 377 | 378 | pivot <- delaun %>% dcast(cell1type ~ cell2type, 379 | value.var = 'x1', 380 | fun.aggregate = length) %>% 381 | as.data.table 382 | 383 | # normalize values by dividing with row sums 384 | x <- rowSums(pivot[, 2:ncol(pivot)]) 385 | pivotnorm <- bind_cols(pivot[, .(cell1type)], 386 | sapply(2:ncol(pivot), 387 | function(i) { 388 | pivot[[i]] / x[i - 1] 389 | }) %>% as.data.table) 390 | 391 | orifname <- file.path(outDir, folders$int, paste0("pivotORI_", basename(fname))) 392 | normfname <- file.path(outDir, folders$int, paste0("pivotNORM_", basename(fname))) 393 | fwrite(pivot, orifname) 394 | fwrite(pivotnorm, normfname) 395 | } 396 | } 397 | cat(file=stderr(), 'Done with pivot tables ...\n') 398 | print('Finished creating pivot tables.') 399 | }) 400 | 401 | output$llhmsg <- renderPrint({ 402 | 403 | req(input$data_file) 404 | outDir <- path() 405 | 406 | for (fname in dir(file.path(outDir, folders$int), full.names = TRUE)) { 407 | if (grepl('^pivotORI_reg.*Rdelaun.csv$', basename(fname))) { 408 | 409 | #reading input file 410 | pivot <- fread(fname) 411 | 412 | # remove dirt column/row 413 | pivot <- pivot[cell1type != "dirt", ] 414 | if('dirt' %in% colnames(pivot)){ 415 | pivot <- pivot[, -'dirt'] 416 | } 417 | 418 | # make matrix from data 419 | ma <- as.matrix(pivot[, -'cell1type']) 420 | rownames(ma) <- pivot$cell1type 421 | colnames(ma) <- colnames(pivot)[-1] 422 | 423 | # normalize values by rowsum and totalsums 424 | summa <- sum(ma) 425 | hornorm <- unname(rowSums(ma)) / summa 426 | 427 | for (i in seq(nrow(ma))) { 428 | for (j in seq(ncol(ma))) { 429 | ma[i, j] = ma[i, j] / (hornorm[i] * hornorm[j] * summa) 430 | } 431 | } 432 | 433 | ma <- as.data.table(ma, keep.rownames = T) 434 | colnames(ma)[1] <- 'pairs' 435 | 436 | llhfname <- file.path(outDir, 437 | folders$int, 438 | paste("lglikelihood_mx_from", basename(fname), sep = '_')) 439 | fwrite(ma, llhfname) 440 | } 441 | } 442 | cat(file=stderr(), 'Finished likelihood calculations ...\n') 443 | print('finished lglikelihood calculations.') 444 | }) 445 | 446 | output$eqalmsg <- renderPrint({ 447 | 448 | req(input$data_file) 449 | cluster_or_reorder <- 'reorder' 450 | outDir <- path() 451 | combo <- data.table() 452 | 453 | for(fname in dir(file.path(outDir, folders$int), full.names = TRUE)){ 454 | if(grepl('^lglikelihood_mx_from_pivotORI_reg.+_Rdelaun.csv$', basename(fname))){ 455 | 456 | fstub <- gsub('\\.csv', '', basename(fname)) 457 | llhtab <- fread(fname) 458 | a <- as.matrix(llhtab[, -1]) 459 | ##################### done reading and re-formatting the matrix 460 | b <- make_matr(colnames(llhtab)[-1]) 461 | #####################done making the matrix of row and column names for the interaction matrix 462 | b <- b[!lower.tri(b)] 463 | ##################done unfolding the row names for the upper triangle of the interaction matrix 464 | a <- a[!lower.tri(a)] 465 | ##################done unfolding the values for the upper triangle of the interaction matrix 466 | if (nrow(combo) == 0){ 467 | combo <- data.table(annotation = b, fname = a) 468 | colnames(combo)[2] <- fstub 469 | ################done naming a row after file 470 | } else { 471 | c <- data.table(annotation = b, fname = a) 472 | colnames(c)[2] <- fstub 473 | combo <- full_join(combo, c, by = "annotation") %>% as.data.table 474 | } 475 | } 476 | } 477 | 478 | combo[, leftcell := gsub('^(.+)___.+$', '\\1', annotation)] 479 | combo[, rightcell := gsub('^(.+)___(.+)$', '\\2', annotation)] 480 | setcolorder(combo, c('leftcell', 'rightcell', colnames(combo)[!colnames(combo) %in% c('leftcell', 'rightcell')])) 481 | 482 | combo <- unique(combo) 483 | 484 | breaks <- seq(0, 4, length.out = 1000) 485 | gradient1 <- colorpanel( sum( breaks[-1]<=1 ), "#0030FF", "white" ) 486 | gradient2 <- colorpanel( sum( breaks[-1]>1 ), "white", "#FF5020" ) 487 | hm.colors <- c(gradient1,gradient2) 488 | 489 | dd <- function(c) as.dist(1 / (c + 5)) 490 | data_ref <- function(dt, col){ 491 | z <- dt %>% 492 | dcast(leftcell ~ rightcell, value.var = col, fill = 1) %>% 493 | as.data.table 494 | setcolorder(z, c('leftcell', colnames(z)[colnames(z) != 'leftcell'])) 495 | z <- z[order(leftcell)] %>% as.data.frame 496 | rownames(z) <- z[, 1] 497 | z <- as.matrix(z[, 2:ncol(z)]) 498 | z[is.na(z)] <- 0 499 | return( z ) 500 | } 501 | 502 | if (cluster_or_reorder =="reorder"){ 503 | print ("reordering") 504 | 505 | # order of rows is based on one of the files 506 | z <- data_ref(combo, colnames(combo)[4]) 507 | # safer to pring to disposable file 508 | png(filename = file.path(outDir, folders$ct, 'waste.png'), height = 1500, width = 2000, res = 300, pointsize = 10) 509 | hmstore <- heatmap.2(z, distfun = dd) 510 | dev.off() 511 | unlink(file.path(outDir, folders$ct, 'waste.png')) 512 | } 513 | 514 | for(i in seq(4, ncol(combo))){ 515 | 516 | z <- combo %>% 517 | dcast(leftcell ~ rightcell, value.var = colnames(combo)[i], fill = 1) %>% 518 | as.data.table 519 | 520 | setcolorder(z, c('leftcell', colnames(z)[colnames(z) != 'leftcell'])) 521 | z <- z[order(leftcell)] %>% as.data.frame 522 | rownames(z) <- z[, 1] 523 | z <- as.matrix(z[, 2:ncol(z)]) 524 | 525 | pngfile <- file.path(outDir, folders$ct, paste(colnames(combo)[i], 'png', sep='.')) 526 | png(filename = pngfile,height = 1500, width = 2000, res = 300, pointsize = 10) 527 | if(cluster_or_reorder =="reorder"){ 528 | z <- z[hmstore$rowInd, hmstore$rowInd] 529 | heatmap.2(z, col=hm.colors, trace="none", lmat=rbind(4:3,2:1), 530 | breaks=breaks, margins=c(15,15), cexRow=0.4, 531 | cexCol=0.4, lwid=c(1.5,2.0), Colv = FALSE, Rowv = FALSE, 532 | dendrogram = 'none') 533 | } else { 534 | heatmap.2(z, distfun=dd, col=hm.colors, trace="none", lmat=rbind(4:3,2:1), 535 | breaks=breaks, margins=c(15,15), cexRow=0.4, cexCol=0.4, lwid=c(1.5,2.0)) 536 | } 537 | dev.off() 538 | } 539 | cat(file = stderr(), 'Ready with clustering of llh by region ...\n') 540 | print('ready with clustering of llh by region') 541 | }) 542 | 543 | output$globalmsg <- renderPrint({ 544 | 545 | # create heatmap of cluster1 vs cluster2 using likelihood ratio and frequency metrics 546 | # also, do this by groups (samples = values of expt factor) 547 | 548 | req(input$data_file) 549 | 550 | outDir <- path() 551 | dtmap <- dt() %>% 552 | select(tile_colname, efact_colname) %>% 553 | unique %>% 554 | as.data.table 555 | 556 | combo <- data.table() 557 | 558 | for(fname in dir(file.path(outDir, folders$del), full.names = TRUE)){ 559 | if(grepl('^reg.+Rdelaun.csv', basename(fname))){ 560 | d <- fread(fname) 561 | region <- gsub('^(reg\\d+)_.*$', '\\1', basename(fname)) 562 | d[, reg := region] 563 | combo <- bind_rows(combo, d) 564 | } 565 | } 566 | 567 | combo <- combo %>% 568 | left_join(dtmap, by = c('reg' = 'region')) %>% 569 | as.data.table 570 | 571 | comb_res <- function(a, b){ 572 | full_join(a, b, by = c('cell1type', 'cell2type', 'efact')) %>% as.data.table 573 | } 574 | 575 | scramble_cols <- function(dt, efs, n = perm_no){ 576 | 577 | cores <- detectCores() 578 | cl <- makeCluster(cores, outfile = file.path(outDir, folders$int, 'dopar_log')) 579 | registerDoParallel(cl) 580 | 581 | set.seed <- 12345 582 | 583 | re <- foreach( 584 | i = 1:n, 585 | .combine = 'comb_res', 586 | .inorder = FALSE, 587 | .export = c('calc_metrics'), 588 | .packages = c('data.table', 'dplyr') 589 | ) %dopar% { 590 | dtx <- copy(dt) 591 | for(ef in efs){ 592 | dtx[efact == ef, `:=`(cell1type = sample(cell1type, replace = T), cell2type = sample(cell2type, replace = T))] 593 | } 594 | dtx <- calc_metrics(dtx, flag = 2) 595 | dtx <- dtx[, .(cell1type, cell2type, efact, llh)] 596 | #print(dtx) 597 | dtx 598 | } 599 | stopCluster(cl) 600 | colnames(re)[4:ncol(re)] <- paste0('llh', seq(1, ncol(re)-3)) 601 | 602 | # make sure we have all permutations or celltypes for all efacts 603 | full_re <- data.table() 604 | for(ef in efs){ 605 | full_re <- bind_rows(full_re, unique(dt$cell1type) %>% permutations(length(.), 2, ., repeats.allowed = T) %>% 606 | as.data.table %>% 607 | unite('key', V1, V2, sep = '__', remove = FALSE) %>% 608 | rename('cell1type' = V1, 'cell2type' = V2) %>% 609 | mutate(efact = ef) %>% 610 | as.data.table) 611 | } 612 | 613 | re <- full_re %>% 614 | full_join(re %>% unite('key', cell1type, cell2type, sep = '__', remove = FALSE), 615 | by = c('key', 'cell1type', 'cell2type', 'efact')) %>% 616 | select(-key) %>% 617 | as.data.table 618 | 619 | invisible(re) 620 | } 621 | 622 | calc_metrics <- function(dt, flag = 1){ 623 | dtx <- dt[, .(ints_cl12 = .N), .(cell1type, cell2type, efact)] %>% 624 | left_join(dt[, .(ints_cl1 = .N), .(cell1type, efact)], 625 | by = c('cell1type', 'efact')) %>% # interaction counts per cell1 626 | left_join(dt[, .(ints_cl2 = .N), .(cell2type, efact)], 627 | by = c('cell2type', 'efact')) %>% # interaction counts per cell2 628 | left_join(dt[, .(ints_total = .N), efact], by = 'efact') %>% # all interactions 629 | # calculate likelihood ratio for interactions 630 | mutate(llh = (1.0 * ints_cl12 * ints_total) / (1.0 * ints_cl1 * ints_cl2)) %>% 631 | as.data.table 632 | if(flag == 1){ 633 | dtx <- dtx %>% 634 | # count cells 635 | left_join(dt[, .(cell_cl12 = nrow(unique(.SD[, .(x1,y1,region)]))), .(cell1type, cell2type, efact)], 636 | by = c('cell1type', 'cell2type', 'efact')) %>% 637 | left_join(dt[, .(cell_cl21 = nrow(unique(.SD[, .(x2,y2,region)]))), .(cell1type, cell2type, efact)], 638 | by = c('cell1type', 'cell2type', 'efact')) %>% 639 | left_join(dt[, .(cell_cl1 = nrow(unique(.SD[, .(x1,y1,region)]))), .(cell1type, efact)], 640 | by = c('cell1type', 'efact')) %>% 641 | left_join(dt[, .(cell_cl2 = nrow(unique(.SD[, .(x2,y2,region)]))), .(cell2type, efact)], 642 | by = c('cell2type', 'efact')) %>% 643 | # calculate interaction frequency 644 | mutate(`cl12_cl1` = 1.0 * ints_cl12/ints_cl1) %>% 645 | as.data.table 646 | } 647 | invisible(dtx) 648 | } 649 | 650 | combox <- calc_metrics(combo) 651 | fwrite(combox, file.path(outDir, folders$ct, 'cell1type_vs_cell2type_llh.txt'), sep = '\t') 652 | 653 | efacts <- sort(unique(combox$efact)) 654 | 655 | llh_bg <- scramble_cols(combo, efacts) 656 | fwrite(llh_bg, file.path(outDir, folders$ct, 'scrambled_data.txt'), sep = '\t') 657 | 658 | cell_count_max <- round(max(combox$cell_cl1)) 659 | llh_max <- max(abs(range(log2(combox$llh)))) * 0.7 660 | llh_min <- -1 * llh_max 661 | mats <- list() 662 | dfs <- list() 663 | 664 | for (ef in efacts) { 665 | 666 | # calculate p-values 667 | z <- combox[efact == ef, .(cell1type, cell2type, llh)] %>% 668 | left_join(llh_bg[efact == ef, -'efact'], by = c('cell1type', 'cell2type')) %>% 669 | as.data.table 670 | 671 | z[, mean := apply(.SD, 1, mean, na.rm = T), .SDcols = grep('^llh\\d', colnames(z))] 672 | z[, sd := apply(.SD, 1, sd, na.rm = T), .SDcols = grep('^llh\\d', colnames(z))] 673 | z[, qt := pnorm(llh, mean, sd)] 674 | z[, pval1 := pmin(2-2*qt, 2*qt, na.rm = T)] 675 | z[, padj1 := p.adjust(pval1, method = 'bonferroni')] 676 | z[, pval := apply(.SD, 1, function(x){ 677 | if(sum(!is.na(x[-1]))>0){ 678 | F <- ecdf(x[-1]) 679 | min(F(x[1])*2, 2-2*F(x[1])) 680 | } else { 681 | NA_real_ 682 | }}), .SDcols = grep('^llh', colnames(z))] 683 | z[, padj := lapply(.SD, p.adjust, method = 'BH'), .SDcols = 'pval'] 684 | # z <- z[, .(cell1type, cell2type, padj)] 685 | # pvmin <- -1*(z$padj[z$padj > 0]%>% log10 %>% min %>% floor) 686 | 687 | # pvalues in this matrix 688 | zmat <- z %>% 689 | dcast(cell1type ~ cell2type, value.var = 'padj1') %>% 690 | melt(id.var = 'cell1type', 691 | variable.name = 'cell2type', 692 | value.name = 'padj') %>% 693 | mutate(lpadj = case_when(!is.na(padj) ~ -log10(padj), 694 | #padj > 0 ~ -log10(padj), 695 | #padj == 0 ~ -log10(padj), #pvmin, 696 | is.na(padj) ~ 0.)) %>% 697 | as.data.table %>% 698 | #mutate(tpadj = as.character(round(lpadj,1))) %>% 699 | dcast(cell1type ~ cell2type, value.var = 'lpadj') %>% 700 | dplyr::select(sort(colnames(.))) %>% 701 | arrange(cell1type) %>% 702 | as.data.frame %>% 703 | column_to_rownames('cell1type') %>% 704 | #select(noquote(colnames(.))) %>% # This now gives an error 705 | as.matrix 706 | 707 | # p-values as text in this matrix 708 | zmatt <- z %>% 709 | dcast(cell1type ~ cell2type, value.var = 'padj1') %>% 710 | melt(id.var = 'cell1type', 711 | variable.name = 'cell2type', 712 | value.name = 'padj') %>% 713 | mutate(lpadj = case_when(!is.na(padj) ~ -log10(padj), 714 | #padj > 0 ~ -log10(padj), 715 | #padj == 0 ~ -log10(padj), #pvmin, 716 | is.na(padj) ~ 0.)) %>% 717 | mutate(tpadj = as.character(round(lpadj, 2))) %>% 718 | as.data.table %>% 719 | dcast(cell1type ~ cell2type, value.var = 'tpadj') %>% 720 | dplyr::select(sort(colnames(.))) %>% 721 | arrange(cell1type) %>% 722 | as.data.frame %>% 723 | column_to_rownames('cell1type') %>% 724 | #select(noquote(colnames(.))) %>% # this now gives an error 725 | as.matrix 726 | 727 | # likelihood ratio values 728 | mat <- combox[efact == ef,] %>% 729 | dcast(cell1type ~ cell2type, value.var = 'llh') %>% 730 | melt(id.var = 'cell1type', 731 | variable.name = 'cell2type', 732 | value.name = 'llh') %>% 733 | mutate(lllh = case_when(llh > 0 ~ log2(llh), 734 | is.na(llh) ~ 0)) %>% 735 | as.data.table %>% 736 | dcast(cell1type ~ cell2type, value.var = 'lllh') %>% 737 | dplyr::select(sort(colnames(.))) %>% 738 | arrange(cell1type) %>% 739 | as.data.frame %>% 740 | column_to_rownames('cell1type') %>% 741 | #select(noquote(colnames(.))) %>% # this now gives an error 742 | as.matrix 743 | 744 | mats[[length(mats)+1]] <- mat 745 | 746 | # cell counts for top bar 747 | df <- unique(combox[efact == ef, .(cell_count = cell_cl1, cell1type)]) %>% 748 | as.data.frame(stringsAsFactors = FALSE) %>% 749 | right_join(data.frame(cell1type = rownames(mat), stringsAsFactors = FALSE), by = 'cell1type') %>% 750 | arrange(cell1type) %>% 751 | column_to_rownames('cell1type') 752 | 753 | # top color bar for cell numbers 754 | ha <- HeatmapAnnotation(df = df, 755 | col = list(cell_count = colorRamp2(c(0, cell_count_max), 756 | c("white", "red")))) 757 | 758 | dfs[[length(dfs)+1]] <- df 759 | 760 | ef_postfix <- ifelse(length(efacts)==1 & ef==1, '', paste0('_', clean_path(ef))) 761 | pdffile <- file.path(outDir, folders$ct, paste0('cell1type_vs_cell2type_llh', ef_postfix, '.pdf')) 762 | pdf(file = pdffile, width = 11, height = 10) 763 | print( 764 | mat %>% 765 | Heatmap( 766 | cluster_columns = FALSE, 767 | cluster_rows = FALSE, 768 | na_col = 'grey90', 769 | col = circlize::colorRamp2(seq(from = llh_min, to = llh_max, length.out = 100), 770 | colorRampPalette(rev(brewer.pal(n= 7, 'RdYlBu')))(100)), 771 | top_annotation = ha, 772 | column_title = paste('likelihood ratios for sample:', ef), 773 | heatmap_legend_param = list(title = 'log2 lhr', at = c(round(llh_min), 0, round(llh_max))), 774 | # add pval to graph 775 | cell_fun = function(j, i, x, y, width, height, fill) { 776 | if(!is.na(zmat[i, j]) & zmat[i,j] > -log10(0.01)) 777 | grid.text(zmatt[i, j], x, y, gp = gpar(fontsize = 5)) 778 | } 779 | ) 780 | ) 781 | dev.off() 782 | } 783 | 784 | ##### plot comparison plots 785 | matdiffs <- list() 786 | matps <- list() 787 | matpst <- list() 788 | dfdiffs <- list() 789 | set.seed(12345) 790 | if(length(efacts) > 1){ 791 | comparisons <- combinations(length(efacts), 2, v = efacts) 792 | for(r in 1:nrow(comparisons)){ 793 | a <- comparisons[r,1] 794 | b <- comparisons[r,2] 795 | mata <- mats[[which(efacts == a)]] 796 | matb <- mats[[which(efacts == b)]] 797 | 798 | isect <- intersect(rownames(mata), rownames(matb)) 799 | mata <- subset(mata, rownames(mata) %in% isect) %>% .[, isect] 800 | matb <- subset(matb, rownames(matb) %in% isect) %>% .[, isect] 801 | 802 | matdiffs[[r]] <- matb - mata 803 | dfdiffs[[r]] <- dfs[[which(efacts == a)]] %>% as.data.table(keep.rownames = T) %>% 804 | inner_join(dfs[[which(efacts == b)]] %>% as.data.table(keep.rownames = T), 805 | by = 'rn', 806 | suffix = c(paste0('_', a), paste0('_', b))) %>% 807 | column_to_rownames('rn') 808 | 809 | # get distr of randomized log ratios 810 | difllh <- matb %>% 811 | as.data.table(keep.rownames = T) %>% 812 | melt(id.vars = 'rn') %>% 813 | left_join(mata %>% 814 | as.data.table(keep.rownames = T) %>% 815 | melt(id.vars = 'rn'), 816 | by = c('rn', 'variable'), 817 | suffix = c('.b', '.a')) %>% 818 | mutate(llh = value.b - value.a) %>% 819 | select(-value.a, -value.b) %>% 820 | unite('key', rn, variable, sep = '__') %>% 821 | arrange(key) %>% 822 | as.data.table 823 | 824 | difllh <- difllh %>% left_join( 825 | (llh_bg[efact == b & cell1type %in% isect & cell2type %in% isect, -'efact'] %>% 826 | unite('rn', cell1type, cell2type, sep = '__') %>% 827 | arrange(rn) %>% 828 | column_to_rownames('rn') %>% 829 | as.matrix %>% 830 | log2 831 | - 832 | llh_bg[efact == a & cell1type %in% isect & cell2type %in% isect, -'efact'] %>% 833 | unite('rn', cell1type, cell2type, sep = '__') %>% 834 | arrange(rn) %>% 835 | column_to_rownames('rn') %>% 836 | as.matrix %>% 837 | log2 838 | ) %>% 839 | as.data.table(keep.rownames = TRUE), by = c('key' = 'rn')) %>% 840 | as.data.table 841 | 842 | difllh[, mean := apply(.SD, 1, mean, na.rm = T), .SDcols = grep('^llh\\d', colnames(difllh))] 843 | difllh[, sd := apply(.SD, 1, sd, na.rm = T), .SDcols = grep('^llh\\d', colnames(difllh))] 844 | difllh[, qt := pnorm(llh, mean, sd)] 845 | difllh[, pval1 := pmin(2-2*qt, 2*qt, na.rm = T)] 846 | difllh[, padj1 := p.adjust(pval1, method = 'bonferroni')] 847 | difllh[, pval := apply(.SD, 1, function(x){ 848 | if(sum(!is.na(x[-1]))>0){ 849 | F <- ecdf(x[-1]) 850 | min(F(x[1])*2, 2-2*F(x[1]))} 851 | else{ 852 | NA_real_ 853 | }}), .SDcols = grep('^llh', colnames(difllh))] 854 | difllh[, padj := lapply(.SD, p.adjust, method = 'BH'), .SDcols = 'pval'] 855 | 856 | matps[[r]] <- difllh[, .(key, padj = padj1)] %>% 857 | separate(key, into = c('cell1type', 'cell2type'), sep = '__') %>% 858 | mutate(lpadj = case_when(is.na(padj) ~ 0., 859 | !is.na(padj) ~ -log10(padj))) %>% 860 | dcast(cell1type ~ cell2type, value.var = 'lpadj') %>% 861 | as.data.table %>% 862 | dplyr::select(sort(colnames(.))) %>% 863 | arrange(cell1type) %>% 864 | as.data.frame %>% 865 | column_to_rownames('cell1type') %>% 866 | as.matrix 867 | 868 | matpst[[r]] <- difllh[, .(key, padj = padj1)] %>% 869 | separate(key, into = c('cell1type', 'cell2type'), sep = '__') %>% 870 | mutate(lpadj = case_when(is.na(padj) ~ 0.00, 871 | !is.na(padj) ~ -log10(padj))) %>% 872 | mutate(tpadj = as.character(round(lpadj, 2))) %>% 873 | as.data.table %>% 874 | dcast(cell1type ~ cell2type, value.var = 'tpadj') %>% 875 | dplyr::select(sort(colnames(.))) %>% 876 | arrange(cell1type) %>% 877 | as.data.frame %>% 878 | column_to_rownames('cell1type') %>% 879 | as.matrix 880 | # 881 | # diffba <- ecdf( 882 | # (sample(as.numeric(matb), size = 100000, replace = T) 883 | # - 884 | # sample(as.numeric(mata), size = 100000, replace = T))) 885 | # 886 | # adjusted pvals for observed log-ratios 887 | # matps[[r]] <- matrix( 888 | # #p.adjust( 889 | # sapply(as.numeric(matdiffs[[r]]), 890 | # function(x){ 891 | # min(c(diffba(x)*2, (1-diffba(x))*2)) 892 | # }), 893 | # # method = 'BH'), 894 | # nrow = nrow(mata)) # %>% log10 895 | # 896 | #matdiffs[[r]] 897 | #colnames(dfdiffs[[length(dfdiffs)]]) <- c(a,b) 898 | } 899 | 900 | gmin <- min(sapply(matdiffs, min)) 901 | gmax <- max(sapply(matdiffs, max)) 902 | 903 | for(k in 1:nrow(comparisons)){ 904 | 905 | colmap <- list() 906 | cname1 <- colnames(dfdiffs[[k]])[1] 907 | cname2 <- colnames(dfdiffs[[k]])[2] 908 | colmap[[cname1]] <- colorRamp2(c(0, cell_count_max), c("white", "red")) 909 | colmap[[cname2]] <- colorRamp2(c(0, cell_count_max), c("white", "red")) 910 | legmap <- list() 911 | legmap[[cname1]] <- list(title = 'Cell Count') 912 | legmap[[cname2]] <- list(title = 'Cell Count') 913 | 914 | ha <- HeatmapAnnotation(df = dfdiffs[[k]], col = colmap, 915 | annotation_legend_param = legmap, 916 | show_legend = c(TRUE, FALSE)) 917 | 918 | pdffile <- file.path(outDir, folders$ct, paste0('cell1type_vs_cell2type_x', 919 | paste(comparisons[k,2], 920 | comparisons[k,1], 921 | sep = '_vs_'), '.pdf')) 922 | pdf(file = pdffile, width = 11, height = 10) 923 | print( 924 | matdiffs[[k]] %>% 925 | Heatmap( 926 | cluster_columns = TRUE, 927 | cluster_rows = TRUE, 928 | na_col = 'grey90', 929 | col = circlize::colorRamp2(seq(from = gmin, to = gmax, length.out = 100), 930 | colorRampPalette(rev(brewer.pal(n= 7, 'BrBG')))(100)), 931 | top_annotation = ha, 932 | column_title = paste('log2fc for likelihood ratios:', 933 | comparisons[k,2], 'vs.', comparisons[k,1]), 934 | heatmap_legend_param = list(title = 'l2fc lhr'), 935 | rect_gp = gpar(col = "grey80", lwd = 1), 936 | cell_fun = function(j, i, x, y, width, height, fill) { 937 | if(!is.na(matps[[k]][i, j]) & matps[[k]][i,j] > -log10(0.001)){ 938 | grid.text(matpst[[k]][i, j], x, y, gp = gpar(fontsize = 5)) 939 | }} 940 | ) 941 | ) 942 | dev.off() 943 | } 944 | 945 | } 946 | cat(file=stderr(), 'Ready with clustering of llh globally ...\n') 947 | print('ready with clustering of llh globally') 948 | }) 949 | 950 | # niches -------------------------------------------------------------------------------------- 951 | 952 | output$nichemsg <- renderPrint({ 953 | 954 | req(input$data_file) 955 | outDir <- path() 956 | dt <- dt() 957 | annotCol <- ann_colname #annot_col() 958 | tissueCol <- tile_colname #tissue_col() 959 | efactCol <- efact_colname 960 | 961 | ef2reg_map <- dt[, .(region, efact)] %>% unique %>% .[order(efact, region)] 962 | efacts <- unique(ef2reg_map$efact) 963 | 964 | upperTrivec <- function(dtab){ 965 | dtab <- as.matrix(dtab) 966 | sapply(1:(nrow(dtab)), function(i){dtab[i,seq(i, nrow(dtab), 1)]}) %>% unlist 967 | } 968 | 969 | for(j in 1:length(efacts)){ 970 | 971 | reg_counts <- dcast(dt[get(efactCol) == efacts[j]], 972 | as.formula(paste(annotCol, '~', tissueCol)), 973 | value.var = 'X:X', fun.aggregate = length) 974 | 975 | setcolorder(reg_counts, 976 | c(annotCol, 977 | paste0('reg', sort(as.integer(gsub('reg', '', colnames(reg_counts)[-1])))))) 978 | 979 | tissues <- dt[get(efactCol) == efacts[j],][[ tissueCol ]] %>% 980 | unlist %>% 981 | unique %>% 982 | str_replace('reg', '') %>% 983 | as.integer %>% 984 | sort() %>% 985 | paste0('reg', .) 986 | 987 | combo <- data.table() 988 | 989 | for (i in seq(1, length(tissues))){ 990 | 991 | #print(tissues[i]) 992 | 993 | #reading lglilyhd_input file 994 | lglklyhd_input <- fread( 995 | file.path( 996 | outDir, 997 | folders$int, 998 | paste("lglikelihood_mx_from_pivotORI", tissues[i], "Rdelaun.csv", sep = "_") 999 | )) %>% 1000 | filter(pairs != 'dirt') %>% 1001 | select(colnames(.)[colnames(.) != 'dirt']) %>% 1002 | as.data.frame %>% 1003 | column_to_rownames(colnames(.)[1]) %>% 1004 | as.matrix 1005 | 1006 | ori_intcount_input <- fread( 1007 | file.path( 1008 | outDir, 1009 | folders$int, 1010 | paste("pivotORI", tissues[i], "Rdelaun.csv", sep = "_") 1011 | )) %>% 1012 | filter(cell1type != 'dirt') %>% 1013 | select(colnames(.)[colnames(.) != 'dirt']) %>% 1014 | as.data.frame %>% 1015 | column_to_rownames(colnames(.)[1]) %>% 1016 | as.matrix 1017 | 1018 | b <- make_matr(colnames(ori_intcount_input)) 1019 | 1020 | leftcell <- matrix( 1021 | apply( 1022 | expand.grid(colnames(ori_intcount_input), 1023 | colnames(ori_intcount_input)), 1024 | 1, 1025 | function(x){ 1026 | x <- unlist(x) 1027 | reg_counts[get(annotCol) == x[1],][[tissues[i]]] 1028 | }) %>% unlist, 1029 | nrow = ncol(ori_intcount_input) 1030 | ) 1031 | 1032 | rightcell <- matrix( 1033 | apply( 1034 | expand.grid(colnames(ori_intcount_input), 1035 | colnames(ori_intcount_input)), 1036 | 1, 1037 | function(x){ 1038 | x <- unlist(x) 1039 | reg_counts[get(annotCol) == x[2],][[tissues[i]]] 1040 | }) %>% unlist, 1041 | nrow = ncol(ori_intcount_input) 1042 | ) 1043 | 1044 | combo1 <- data.table(a = upperTrivec(b), 1045 | b = upperTrivec(leftcell), 1046 | c = upperTrivec(rightcell), 1047 | d = upperTrivec(ori_intcount_input), 1048 | e = upperTrivec(lglklyhd_input)) 1049 | 1050 | colnames(combo1) <- c('annotation', 1051 | paste(tissues[i],"leftcell_count", sep="_"), 1052 | paste(tissues[i],"rightcell_count", sep="_"), 1053 | paste(tissues[i],"ori_int_count", sep="_"), 1054 | paste(tissues[i],"likelihood_ratio", sep="_")) 1055 | 1056 | if(nrow(combo) > 0){ 1057 | combo <- combo %>% full_join(combo1, by = 'annotation') %>% as.data.table 1058 | } else{ 1059 | combo <- combo1 1060 | } 1061 | } 1062 | 1063 | fwrite(combo, file.path(outDir, folders$int, paste0( 1064 | "new_full_pairwise_analysis_UPPERtr_", 1065 | efacts[j], 1066 | ".csv"))) 1067 | } 1068 | cat( file = stderr(), 'Ready with new full pairwise analysis ...\n') 1069 | print('Ready with new full pairwise analysis.') 1070 | }) 1071 | 1072 | # profiles ------------------------------------------------------------------------------------ 1073 | 1074 | output$profmsg <- renderPrint({ 1075 | 1076 | req(input$data_file) 1077 | outDir <- path() 1078 | dt <- dt() 1079 | annotCol <- ann_colname #annot_col() 1080 | tissueCol <- tile_colname #tissue_col() 1081 | tissues <- dt[[tissueCol]] %>% unique 1082 | normProfs <- data.table() 1083 | noClust <- no_clust() 1084 | ef2reg_map <- dt[, .(efact, region)] %>% unique %>% .[order(efact)] 1085 | efacts <- sort(unique(ef2reg_map$efact)) 1086 | row_order <- character() 1087 | for(ef in efacts){ 1088 | z <- ef2reg_map[efact == ef, region] 1089 | z <- paste0('reg', sort(as.integer(gsub('reg', '', z)))) 1090 | row_order <- c(row_order, z) 1091 | } 1092 | 1093 | for(fname in dir(file.path(outDir, folders$del), full.names = TRUE)){ 1094 | if(grepl('^reg.+_Rdelaun.csv$', basename(fname))){ 1095 | #print(fname) 1096 | input <- fread(fname) 1097 | regionid <- strsplit(basename(fname), "_" )[[1]][1] 1098 | 1099 | input[, cell := apply(input, 1, function(x){ 1100 | x <- unlist(x) 1101 | paste(regionid, as.integer(x[1]), as.integer(x[2]), sep = '_') 1102 | })] 1103 | input[, dummy := 1] 1104 | profiles <- dcast(input, 1105 | cell ~ cell2type, 1106 | value.var = 'dummy', 1107 | fun.aggregate = length) %>% as.data.table 1108 | 1109 | profiles_norm <- bind_cols(profiles[, .(cell)], (profiles[, -1]/rowSums(profiles[, -1]))) 1110 | fwrite(profiles_norm, file.path(outDir, folders$int, paste0(regionid, "_neighborprofiles.csv"))) 1111 | normProfs <- bind_rows(normProfs, profiles_norm) %>% replace(., is.na(.), 0) %>% as.data.table 1112 | } 1113 | } 1114 | 1115 | cat(file=stderr(), 'Made cell profiles ...\n') 1116 | print('Made cell profiles.') 1117 | 1118 | res_km <- kmeans(normProfs[, -'cell'], noClust, iter.max = 100) 1119 | 1120 | d <- t(res_km$centers) # celltypes vs clusters 1121 | b <- data.table(cluster = res_km$cluster, cellID = normProfs$cell) 1122 | 1123 | dt <- dt %>% unite('cellID', tissueCol, `X:X`, `Y:Y`, sep = '_', remove = FALSE) 1124 | 1125 | # add cluster id to ori data 1126 | dt <- dt %>% left_join(b, by = "cellID") %>% as.data.table %>% .[!is.na(cluster)] 1127 | fwrite(dt, file.path(outDir, 'dt_with_clusters.txt'), sep = '\t') 1128 | 1129 | quantile.range <- quantile(as.numeric(res_km$centers), probs = seq(0, 1, 0.01)) 1130 | #quantile.range <- quantile(as.numeric(log(d + min(d[d>0]))), probs = seq(0, 1, 0.01)) 1131 | palette.breaks <- seq(quantile.range["5%"], quantile.range["98%"], 1132 | (quantile.range["98%"]- quantile.range["5%"])/1000) 1133 | 1134 | ## use http://colorbrewer2.org/ to find optimal divergent color palette (or set own) 1135 | color.palette <- colorRampPalette(c("black","blue","green"))(length(palette.breaks) - 1) 1136 | 1137 | png(file.path(outDir, folders$niche, paste("niches",'png',sep='.')), 1138 | height=1100, width=2000, res = 300, pointsize=10) 1139 | 1140 | heatmap.2(d, labCol=colnames(d), dendrogram="none", col=color.palette, trace="none", 1141 | breaks=palette.breaks, margins=c(3,20), cexRow=0.7, cexCol=0.5) 1142 | dev.off() 1143 | 1144 | # to prevent printing to the plots pane, create a file we don't need 1145 | png(file.path(outDir, folders$niche, paste("waste",'png',sep='.')), 1146 | height=1100, width=2000, res = 300, pointsize=10) 1147 | d <- d[,order(as.numeric(colnames(d)))] 1148 | 1149 | nichemap <- heatmap.2(d, labCol=colnames(d), dendrogram="none", col=color.palette, 1150 | trace="none", breaks=palette.breaks, margins=c(3,20), 1151 | cexRow=0.7, cexCol=0.5) 1152 | dev.off() 1153 | unlink(file.path(outDir, folders$niche, paste("waste",'png',sep='.'))) 1154 | 1155 | nicheorder <- nichemap$colInd 1156 | cellorder <- nichemap$rowInd 1157 | 1158 | d <- d[,nicheorder] 1159 | d <- d[cellorder,] 1160 | d <- d[dim(d)[1]:1,] 1161 | 1162 | # profiles in niches 1163 | per_region_niches <- dt %>% select(tissueCol, annotCol, nichename = cluster) %>% as.data.table 1164 | per_region_niches[, dummy := 1] 1165 | 1166 | # cell count per region + celltype for each cluster (columns) 1167 | cells_in_niches <- dcast(per_region_niches, 1168 | as.formula(paste(tissueCol, '+', annotCol, '~', 'nichename')), 1169 | value.var = 'dummy', 1170 | fun.aggregate = sum) 1171 | 1172 | setcolorder(cells_in_niches, c(1,2,2+nicheorder)) 1173 | 1174 | cells_in_niches_norm <- bind_cols(cells_in_niches[,1:2], 1175 | cells_in_niches[, -c(1:2)] / rowSums(cells_in_niches[, -c(1:2)])) 1176 | 1177 | fwrite(cells_in_niches, file.path(outDir, folders$int, paste("cells_in_niches.cst",sep = ""))) 1178 | fwrite(cells_in_niches_norm, file.path(outDir, folders$int, paste("cells_in_niches_norm.cst",sep = ""))) 1179 | 1180 | celltypes <- unique(cells_in_niches_norm[[annotCol]]) %>% unlist %>% unname 1181 | 1182 | 1183 | color.palette <- circlize::colorRamp2(seq(from = 0, to = 1, length.out = 100), 1184 | colorRampPalette(c("royalblue4","yellow", 'red'))(100)) 1185 | 1186 | # cell types across clusters by region 1187 | for (index in 1:length(celltypes)){ 1188 | #print(index) 1189 | addon <- cells_in_niches_norm[get(annotCol) == celltypes[index], ] %>% 1190 | select(region, colnames(d)) %>% 1191 | column_to_rownames(colnames(.)[1]) %>% 1192 | as.matrix() %>% .[row_order[row_order %in% rownames(.)], ] 1193 | kaugm <- rbind(d, addon) # what is '5' 1194 | 1195 | df <- data.frame(samples = c(rep(annotCol, dim(d)[1]), ef2reg_map[region %in% rownames(addon), efact]), 1196 | stringsAsFactors = FALSE) 1197 | sidebar_col <- c( 'red', 'green') 1198 | if(length(unique(df$samples)) > 2){ 1199 | sidebar_col <- brewer.pal(length(unique(df$samples)), 'Set1') 1200 | } 1201 | 1202 | names(sidebar_col) <- unique(df$samples) 1203 | 1204 | ha <- HeatmapAnnotation(df = df, which = 'row', col = list(samples = sidebar_col)) 1205 | 1206 | png(file.path(outDir, 1207 | folders$niche, 1208 | paste0(clean_path(celltypes[index]),".png")), 1209 | height=6000, width=4500, res = 300, pointsize=10) 1210 | print( 1211 | ha + Heatmap(kaugm/rowSums(kaugm), 1212 | row_order = rownames(kaugm), 1213 | column_order = colnames(kaugm), 1214 | col = color.palette, 1215 | row_split = factor(df$samples, levels = df$samples %>% unique), 1216 | cluster_row_slices = F, 1217 | column_title = paste('Niche composition of regions for :', celltypes[index]), 1218 | heatmap_legend_param = list(title = 'fraction')) 1219 | ) 1220 | dev.off() 1221 | } 1222 | cat(file = stderr(), 'Finished niche clustering ...\n') 1223 | print('Finished niche clustering') 1224 | }) 1225 | 1226 | } 1227 | 1228 | # Run the application 1229 | shinyApp(ui = ui, server = server) 1230 | 1231 | -------------------------------------------------------------------------------- /Neighborhoods/cca_cleaned_up.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Populating the interactive namespace from numpy and matplotlib\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import numpy as np\n", 18 | "import pandas as pd\n", 19 | "from matplotlib import pyplot as plt\n", 20 | "from scipy.stats import pearsonr,spearmanr\n", 21 | "import itertools\n", 22 | "from sklearn.cross_decomposition import CCA\n", 23 | "import networkx as nx\n", 24 | "import seaborn as sns\n", 25 | "%pylab inline" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 69, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "cells = pd.read_pickle('cells2_salil')\n", 35 | "patient_to_group_dict = cells.loc[:,['patients','groups']].drop_duplicates().set_index('patients').to_dict()['groups']\n", 36 | "group1_patients = [a for a,gp in patient_to_group_dict.items() if gp==1]\n", 37 | "group2_patients = [a for a,gp in patient_to_group_dict.items() if gp==2]" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 70, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "# select which neighborhoods and functional subsets\n", 47 | "cns = [0,2,3,4,5,6,7,8,9]\n", 48 | "subsets = ['CD8+ICOS+','CD8+Ki67+','CD8+PD-1+','Treg-Ki67+']" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 71, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "#log (1e-3 + neighborhood specific cell type frequency) of functional subsets) ('nsctf')\n", 58 | "nsctf = np.log(1e-3 + cells.groupby(['patients','neighborhood10'])[subsets].mean().reset_index().set_index(['neighborhood10','patients']))" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "# CCA relative to permutations for group1" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 76, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "cca = CCA(n_components=1,max_iter = 5000)\n", 75 | "func = pearsonr\n", 76 | "\n", 77 | "# set number of permutation params\n", 78 | "n_perms = 5000\n", 79 | "stats_group1 = {}\n", 80 | "\n", 81 | "for cn_i in cns:\n", 82 | " for cn_j in cns:\n", 83 | " if cn_i < cn_j:\n", 84 | "\n", 85 | " #concat dfs\n", 86 | " combined = pd.concat([nsctf.loc[cn_i].loc[group1_patients],nsctf.loc[cn_j].loc[group1_patients]], axis = 1).dropna(axis = 0, how = 'any')\n", 87 | " x = combined.iloc[:,:len(subsets)].values\n", 88 | " y = combined.iloc[:,len(subsets):].values\n", 89 | "\n", 90 | " arr = np.zeros(n_perms)\n", 91 | "\n", 92 | " #compute the canonical correlation achieving components with respect to observed data\n", 93 | " ccx,ccy = cca.fit_transform(x,y)\n", 94 | " stats_group1[cn_i,cn_j] = (pearsonr(ccx[:,0],ccy[:,0])[0],arr)\n", 95 | "\n", 96 | " #initialize array for perm values\n", 97 | "\n", 98 | " for i in range(n_perms):\n", 99 | " idx = np.arange(len(x))\n", 100 | " np.random.shuffle(idx)\n", 101 | " # compute with permuted data\n", 102 | " cc_permx,cc_permy = cca.fit_transform(x[idx],y)\n", 103 | " arr[i] = pearsonr(cc_permx[:,0],cc_permy[:,0])[0]\n", 104 | "\n", 105 | "\n", 106 | "\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "### visualization" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 96, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "data": { 123 | "image/png": "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\n", 124 | "text/plain": [ 125 | "
" 126 | ] 127 | }, 128 | "metadata": { 129 | "needs_background": "light" 130 | }, 131 | "output_type": "display_data" 132 | } 133 | ], 134 | "source": [ 135 | "g1 = nx.Graph()\n", 136 | "for cn_pair, cc in stats_group1.items():\n", 137 | " s,t = cn_pair\n", 138 | " obs, perms = cc\n", 139 | " p =np.mean(obs>perms)\n", 140 | " if p>0.9 :\n", 141 | " g1.add_edge(s,t, weight = p)\n", 142 | " \n", 143 | " \n", 144 | "pal = sns.color_palette('bright',10)\n", 145 | "dash = {True: '-', False: ':'}\n", 146 | "pos=nx.drawing.nx_pydot.pydot_layout(g1,prog='neato')\n", 147 | "for k,v in pos.items():\n", 148 | " x,y = v\n", 149 | " plt.scatter([x],[y],c = [pal[k]], s = 200,zorder = 3)\n", 150 | " plt.text(x,y, k, fontsize = 10, zorder = 10,ha = 'center', va = 'center')\n", 151 | "\n", 152 | "\n", 153 | "atrs = nx.get_edge_attributes(g1, 'weight') \n", 154 | "for e0,e1 in g1.edges():\n", 155 | " p = atrs[e0,e1]\n", 156 | " plt.plot([pos[e0][0],pos[e1][0]],[pos[e0][1],pos[e1][1]], c= 'black',alpha = 3*p**3,linewidth = 3*p**3)\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "metadata": {}, 162 | "source": [ 163 | "# CCA relative to permutations for group2" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 77, 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "cca = CCA(n_components=1,max_iter = 5000)\n", 173 | "func = pearsonr\n", 174 | "\n", 175 | "# set number of permutation params\n", 176 | "n_perms = 5000\n", 177 | "stats_group2 = {}\n", 178 | "\n", 179 | "for cn_i in cns:\n", 180 | " for cn_j in cns:\n", 181 | " if cn_i < cn_j:\n", 182 | "\n", 183 | " #concat dfs\n", 184 | " combined = pd.concat([nsctf.loc[cn_i].loc[group2_patients],nsctf.loc[cn_j].loc[group2_patients]], axis = 1).dropna(axis = 0, how = 'any')\n", 185 | " x = combined.iloc[:,:len(subsets)].values\n", 186 | " y = combined.iloc[:,len(subsets):].values\n", 187 | "\n", 188 | " arr = np.zeros(n_perms)\n", 189 | "\n", 190 | " #compute the canonical correlation achieving components\n", 191 | " ccx,ccy = cca.fit_transform(x,y)\n", 192 | " stats_group2[cn_i,cn_j] = (pearsonr(ccx[:,0],ccy[:,0])[0],arr)\n", 193 | "\n", 194 | " #initialize array for perm values\n", 195 | "\n", 196 | " for i in range(n_perms):\n", 197 | " idx = np.arange(len(x))\n", 198 | " np.random.shuffle(idx)\n", 199 | " cc_permx,cc_permy = cca.fit_transform(x[idx],y)\n", 200 | " arr[i] = pearsonr(cc_permx[:,0],cc_permy[:,0])[0]\n", 201 | "\n", 202 | "\n", 203 | "\n" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "### visualization" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 97, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "data": { 220 | "image/png": "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\n", 221 | "text/plain": [ 222 | "
" 223 | ] 224 | }, 225 | "metadata": { 226 | "needs_background": "light" 227 | }, 228 | "output_type": "display_data" 229 | } 230 | ], 231 | "source": [ 232 | "g2 = nx.Graph()\n", 233 | "for cn_pair, cc in stats_group2.items():\n", 234 | " s,t = cn_pair\n", 235 | " obs, perms = cc\n", 236 | " p =np.mean(obs>perms)\n", 237 | " if p>0.9 :\n", 238 | " g2.add_edge(s,t, weight = p)\n", 239 | " \n", 240 | " \n", 241 | "pal = sns.color_palette('bright',10)\n", 242 | "dash = {True: '-', False: ':'}\n", 243 | "pos=nx.drawing.nx_pydot.pydot_layout(g2,prog='neato')\n", 244 | "for k,v in pos.items():\n", 245 | " x,y = v\n", 246 | " plt.scatter([x],[y],c = [pal[k]], s = 200,zorder = 3)\n", 247 | " plt.text(x,y, k, fontsize = 10, zorder = 10,ha = 'center', va = 'center')\n", 248 | "\n", 249 | "\n", 250 | "atrs = nx.get_edge_attributes(g2, 'weight') \n", 251 | "for e0,e1 in g2.edges():\n", 252 | " p = atrs[e0,e1]\n", 253 | " plt.plot([pos[e0][0],pos[e1][0]],[pos[e0][1],pos[e1][1]], c= 'black',alpha = 3*p**3,linewidth = 3*p**3)\n" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [] 262 | } 263 | ], 264 | "metadata": { 265 | "kernelspec": { 266 | "display_name": "Python 3", 267 | "language": "python", 268 | "name": "python3" 269 | }, 270 | "language_info": { 271 | "codemirror_mode": { 272 | "name": "ipython", 273 | "version": 3 274 | }, 275 | "file_extension": ".py", 276 | "mimetype": "text/x-python", 277 | "name": "python", 278 | "nbconvert_exporter": "python", 279 | "pygments_lexer": "ipython3", 280 | "version": "3.7.4" 281 | } 282 | }, 283 | "nbformat": 4, 284 | "nbformat_minor": 4 285 | } 286 | -------------------------------------------------------------------------------- /Neighborhoods/voronoi.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import seaborn as sns 4 | # from shapely.ops import polygonize,unary_union 5 | from shapely.geometry import MultiPoint, Point, Polygon 6 | from scipy.spatial import Voronoi 7 | 8 | 9 | 10 | def voronoi_finite_polygons_2d(vor, radius=None): 11 | """ 12 | adapted from https://stackoverflow.com/questions/20515554/colorize-voronoi-diagram/20678647#20678647 3.18.2019 13 | 14 | 15 | Reconstruct infinite voronoi regions in a 2D diagram to finite 16 | regions. 17 | 18 | Parameters 19 | ---------- 20 | vor : Voronoi 21 | Input diagram 22 | radius : float, optional 23 | Distance to 'points at infinity'. 24 | 25 | Returns 26 | ------- 27 | regions : list of tuples 28 | Indices of vertices in each revised Voronoi regions. 29 | vertices : list of tuples 30 | Coordinates for revised Voronoi vertices. Same as coordinates 31 | of input vertices, with 'points at infinity' appended to the 32 | end. 33 | 34 | """ 35 | 36 | if vor.points.shape[1] != 2: 37 | raise ValueError("Requires 2D input") 38 | 39 | new_regions = [] 40 | new_vertices = vor.vertices.tolist() 41 | 42 | center = vor.points.mean(axis=0) 43 | if radius is None: 44 | radius = vor.points.ptp().max() 45 | 46 | # Construct a map containing all ridges for a given point 47 | all_ridges = {} 48 | for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices): 49 | all_ridges.setdefault(p1, []).append((p2, v1, v2)) 50 | all_ridges.setdefault(p2, []).append((p1, v1, v2)) 51 | 52 | # Reconstruct infinite regions 53 | for p1, region in enumerate(vor.point_region): 54 | vertices = vor.regions[region] 55 | 56 | if all(v >= 0 for v in vertices): 57 | # finite region 58 | new_regions.append(vertices) 59 | continue 60 | 61 | # reconstruct a non-finite region 62 | ridges = all_ridges[p1] 63 | new_region = [v for v in vertices if v >= 0] 64 | 65 | for p2, v1, v2 in ridges: 66 | if v2 < 0: 67 | v1, v2 = v2, v1 68 | if v1 >= 0: 69 | # finite ridge: already in the region 70 | continue 71 | 72 | # Compute the missing endpoint of an infinite ridge 73 | 74 | t = vor.points[p2] - vor.points[p1] # tangent 75 | t /= np.linalg.norm(t) 76 | n = np.array([-t[1], t[0]]) # normal 77 | 78 | midpoint = vor.points[[p1, p2]].mean(axis=0) 79 | direction = np.sign(np.dot(midpoint - center, n)) * n 80 | far_point = vor.vertices[v2] + direction * radius 81 | 82 | new_region.append(len(new_vertices)) 83 | new_vertices.append(far_point.tolist()) 84 | 85 | # sort region counterclockwise 86 | vs = np.asarray([new_vertices[v] for v in new_region]) 87 | c = vs.mean(axis=0) 88 | angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0]) 89 | new_region = np.array(new_region)[np.argsort(angles)] 90 | 91 | # finish 92 | new_regions.append(new_region.tolist()) 93 | 94 | return new_regions, np.asarray(new_vertices) 95 | 96 | def plot_voronoi(points,colors,invert_y = True,edge_color = 'facecolor',line_width = .1,alpha = 1,size_max=np.inf): 97 | 98 | # spot_samp = spot#.sample#(n=100,random_state = 0) 99 | # points = spot_samp[['X:X','Y:Y']].values 100 | # colors = [sns.color_palette('bright')[i] for i in spot_samp['neighborhood10']] 101 | 102 | if invert_y: 103 | points[:,1] = max(points[:,1])-points[:,1] 104 | vor = Voronoi(points) 105 | 106 | regions, vertices = voronoi_finite_polygons_2d(vor) 107 | 108 | pts = MultiPoint([Point(i) for i in points]) 109 | mask = pts.convex_hull 110 | new_vertices = [] 111 | if type(alpha)!=list: 112 | alpha = [alpha]*len(points) 113 | areas = [] 114 | for i,(region,alph) in enumerate(zip(regions,alpha)): 115 | polygon = vertices[region] 116 | shape = list(polygon.shape) 117 | shape[0] += 1 118 | p = Polygon(np.append(polygon, polygon[0]).reshape(*shape)).intersection(mask) 119 | areas+=[p.area] 120 | if p.area 0: 153 | neigh_alpha = .3 154 | else: 155 | neigh_alpha = 1 156 | 157 | voronoi_kwargs = {**{'alpha':neigh_alpha},**voronoi_kwargs} 158 | scatter_kwargs = {**{'s':50,'alpha':1,'marker':'.'},**scatter_kwargs} 159 | 160 | plt.figure(figsize = figsize) 161 | colors = [voronoi_palette[i] for i in spot[voronoi_hue]] 162 | a = plot_voronoi(spot[[X,Y]].values, 163 | colors,#[{0:'white',1:'red',2:'purple'}[i] for i in spot['color']], 164 | **voronoi_kwargs) 165 | 166 | if len(c)>0: 167 | if 'c' not in scatter_kwargs: 168 | colors = [scatter_palette[i] for i in c[scatter_hue]] 169 | scatter_kwargs['c'] = colors 170 | 171 | plt.scatter(x = c[X],y = (max(spot[Y])-c[Y].values), 172 | **scatter_kwargs 173 | ) 174 | plt.axis('off'); 175 | return a 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeighborhoodCoordination 2 | Code accompanying our manuscript "Coordinated cellular neighborhoods orchestrate antitumoral immunity at the colorectal cancer invasive front" 3 | 4 | ## Introduction 5 | This repo consists of two parts: 1) Code used to generate the results for our publication “Coordinated cellular neighborhoods orchestrate antitumoral immunity at the colorectal cancer invasive front” and 2) Useful functions from our paper that we have generalized to make more user-friendly for the broader scientific community. These functions can be found in the “Neighborhoods” directory. 6 | 7 | The following describes how to navigate the code we provide in the “Neighborhoods” directory. The code is implemented in python3 using jupyter notebooks. Some experience with installing python packages will be required to get up and running. Only a familiarity with python is required to follow and adapt these functions for your own use. 8 | 9 | ## Installation 10 | We recommend using the free package manager Anaconda (https://www.anaconda.com) to help install the packages required to use these scripts. 11 | • Jupyter is most easily installed through Anaconda. Otherwise, one can follow the instructions here https://jupyter.org/ 12 | • The used scripts require very few dependencies. Packages required: 13 | o statsmodels (0.11.1) 14 | o numpy (1.18.1) 15 | o pandas (1.0.1) 16 | o jupyter (1.0.0) 17 | o seaborn (0.9.0) 18 | o scikitlearn (0.22.1) 19 | o tensorly (0.6.0) 20 | o shapely (1.7.0) 21 | 22 | 23 | ## Functions 24 | 1. The single-cell file (with patient, TMA core, neighborhood10 and ClusterName annotations) is in the Mendeley data link provided in the manuscript. Any time you see read in of 'cells2_salil', this can be replaced with read in of the Mendeley data file. 25 | 26 | 2. Neighborhood Identification: This notebook walks a user through identifying Cellular Neighborhoods in high parameter imaging data as described in our publication. 27 | 28 | 3. Voronoi Generation: This notebook helps visualize the Cellular Neighborhoods on the tissue and allows the user to overlay a collection of cells over these neighborhoods to explore their spatial distribution. 29 | 30 | 4. tensor_decomposition_cleaned_up: This notebook describes how to perform tensor decomposition after each single cell has been allocated to a Cellular Neighborhood and Cell Type. This has been updated to start from the Mendeley upload. 31 | 32 | 5. Neighborhood Mixing: This notebook allows the user to describe the spatial contacts between two Cellular Neighborhoods of interest. 33 | 34 | 6. Cell Type Differential Enrichment: This notebook allows a user to identify cell types that are differentially enriched within specific cellular neighborhoods across a set of conditions or clinical groups. 35 | 36 | 7. app_CRC_contacts.R: A Shiny application for computing contacts between different cell types. 37 | 38 | -------------------------------------------------------------------------------- /paper_submission.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nolanlab/NeighborhoodCoordination/d6e309c6404c00d26b80ee0bad72a3a36794ed33/paper_submission.zip --------------------------------------------------------------------------------