OSCR

Unique phenotypic and T cell receptor characteristics of CD8<sup>+</sup> T cells accumulated in the brains of Alzheimer's disease mice.

Code ↔ Paper

3 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 3 matches
  1. [1] § Results › Phenotypic characterization of CXCR6-related and AD-associated CD8+ T cell clusters ↔ notebooks/natImmuno2023_tau10x.ipynb, lines 889–944 · score 0.64 · Gzma, Klrg1, Sell, Socs1, Ccr7, Cd69
  2. [2] § Materials and methods › AIMS analysis ↔ aims_immune/aims_cli.py, lines 216–288 · score 0.59 · GitHub, CDR loops, downloaded, page, metadata, matrix
  3. [3] § Results › Unique biophysical profiles of clonally expanded TCRs in AD-associated cluster 1 ↔ aims_immune/aims_cli.py, lines 290–351 · score 0.51 · amino acids, biophysical properties, dimensional, encoded, Molecule, loop

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,705 lines · 81 KB · MIT · 2 matches

  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. # # Welcome to the AIMS CLI
  4. # # Section 0: Loading in Modules and Defining Figure Formatting
  5. # This first cell is just loading in all of the necessary python modules (which you should have already installed) and defining figure font, size, etc.
  6. # In[1]:
  7. import numpy as np
  8. from matplotlib import cm
  9. import matplotlib.pyplot as pl
  10. from matplotlib import rcParams
  11. from matplotlib import rc
  12. from matplotlib.lines import Line2D
  13. from mpl_toolkits import mplot3d
  14. import pandas
  15. import os
  16. from aims_immune import aims_loader as aimsLoad
  17. from aims_immune import aims_analysis as aims
  18. from aims_immune import aims_classification as classy
  19. import matplotlib.gridspec as gridspec
  20. from sklearn.utils import resample
  21. import argparse
  22. import distutils
  23. # This bit is for that figure formatting. Change font and font size if desired
  24. font = {'family' : 'Arial',
  25. 'weight' : 'bold',
  26. 'size' : 20}
  27. COLOR = 'black'
  28. rcParams['text.color'] = 'black'
  29. rcParams['axes.labelcolor'] = COLOR
  30. rcParams['xtick.color'] = COLOR
  31. rcParams['ytick.color'] = COLOR
  32. rc('font', **font)
  33. # Lastly this custom colormap is for
  34. import matplotlib as mpl
  35. upper = mpl.cm.jet(np.arange(256))
  36. lower = np.ones((int(256/4),4))
  37. for i in range(3):
  38. lower[:,i] = np.linspace(1, upper[0,i], lower.shape[0])
  39. cmap = np.vstack(( lower, upper ))
  40. cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0])
  41. # Taking a crack at writing a parser
  42. ###############################################################################################
  43. def main():
  44. parser = argparse.ArgumentParser(description = 'Run the AIMS Pipeline in a Single Command')
  45. parser.add_argument("-dd", "--datDir",help="Data Directory [string]",required=False,default='./',type=str)
  46. parser.add_argument("-od", "--outputDir",help="Output Directory [string]",required=False, default='AIMS_out',type=str)
  47. parser.add_argument("-m","--molecule",help="Molecule of interest (Ig, MSA, or Peptide) [string]",required=True,type=str)
  48. parser.add_argument("-f","--fileNames",help="Name of files [list of strings]",required=True,type=str,nargs='*')
  49. # No default for datName, need to account for this in the "main" instance
  50. parser.add_argument("-n","--datNames",help="Name of the datasets [list of strings]",required=False,default=[],nargs='*',type=str)
  51. parser.add_argument("-a","--align",help="Sequence alignment scheme [string]",required=False,default='center',type=str)
  52. # Need to fix this so the default is the shape of the input file
  53. parser.add_argument("-nl","--numLoop",help="Number of loops for Ig [int]",required=False,default=1,type=int)
  54. parser.add_argument("-dp","--dropDup",help="Drop duplicate sequences? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  55. parser.add_argument("-p","--parallel",help="Turn on Parallel Processing? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  56. parser.add_argument("-s","--subset",help="Take a subset of input data? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  57. parser.add_argument("-ss","--subStart",help="Start points for data subset [list of ints]",required=False,default=[],type=int,nargs='*')
  58. parser.add_argument("-se","--subEnd",help="End points for data subset [list of ints]",required=False,default=[],type=int,nargs='*')
  59. parser.add_argument("-bp","--bulgePad",help="Padding for bulge format [int]",required=False,default=8,type=int)
  60. parser.add_argument("-np","--normProp",help="Normalize biophysical properties? [msuv,zscore, or 0to1]",required=False,default='msuv',type=str)
  61. parser.add_argument("-rn","--REnorm",help="Renormalize BPHYS mat by entropy? [T/F]",required=False,default=True,type=distutils.util.strtobool)
  62. parser.add_argument("-cd","--clustData",help="Data format for dim. red. and clustering [string]",required=False,default='parse',type=str)
  63. parser.add_argument("-pa","--projAlg",help="Algorithm for data projection [string]",required=False,default='pca',type=str)
  64. parser.add_argument("-us","--umapSeed",help="Random seed for UMAP projection [int]",required=False,default=[],type=int)
  65. parser.add_argument("-c","--clustAlg",help="Clustering algorithm of choice [string]",required=False,default='optics',type=str)
  66. parser.add_argument("-cs","--clustSize",help="Set cluster size for clustering algorithm [string]",required=False,default=10,type=int)
  67. # Need to come back to the metadata section soon
  68. parser.add_argument("-mf","--metaForm",help="Format of metadata [string]",required=False,default='category',type=str)
  69. parser.add_argument("-mn","--metaName",help="Name of the incorporated metadata [string]",required=False,default='meta',type=str)
  70. ##################################################
  71. # NEW FEATURES!!!! Woohoo
  72. parser.add_argument("-ds","--DOstats",help="Run statistics for relevant analysis? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  73. parser.add_argument("-db","--DOboot",help="Run bootstrapping for relevant analysis? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  74. parser.add_argument("-gd","--GETdist",help="Run AIMSdist calculations? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  75. parser.add_argument("-pd","--PARdist",help="Make AIMSdist calc parallel? [T/F] (must specify --GETdist True)",required=False,default=False,type=distutils.util.strtobool)
  76. parser.add_argument("-pp","--Plotprops",help="Plot biophysical properties for clusters? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  77. parser.add_argument("-bt","--boots",help="How many bootstrap replicase should run? [int]",required=False,default=1000,type=int)
  78. parser.add_argument("-mi","--MIboots",help="How many MI bootstrap replicas should run? [int]",required=False,default=10,type=int)
  79. parser.add_argument("-aa","--AAorder",help="Order of amino acids for figures [single string of 20]",required=False,default='',type=str)
  80. parser.add_argument("-cc","--colors",help="Color selection for scatter/line plots [list of strings]",required = False,default=['purple','orange'],type=str,nargs='*')
  81. ##################################################
  82. # # # # # # # # # # # #
  83. parser.add_argument("-sp","--showProj",help="Show 2D or 3D data projections [string]",required=False,default='both',type=str)
  84. parser.add_argument("-sc","--showClust",help="Show metadata or clustering [string]",required=False,default='both',type=str)
  85. parser.add_argument("-nb","--normBar",help="Normalize cluster purity bar plots? [T/F]",required=False,default=True,type=distutils.util.strtobool, nargs=1)
  86. parser.add_argument("-as","--analysisSel",help="Select the data subset type to further analyze [cluster or metadata]",required=False,default='cluster',type=str)
  87. parser.add_argument("-lo","--seqlogo",help = "Want to create seqlogo plots? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  88. parser.add_argument("-ln","--logoNum",help = "What sequence length do you want to generate seqlogos for? [int]",required=False,default=14,type=int)
  89. parser.add_argument("-sv","--saveSeqs",help = "Want to save clustered sequences in separate files? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  90. parser.add_argument("-sd","--selDat",help="Specify which precisely which data subsets you want to further analyze [list of ints]",
  91. required=False, type=int, nargs='*', default=[0,1])
  92. parser.add_argument("-p1","--prop1",help="Biophysical property of choice to analyze [int]", required=False,default=1,type=int)
  93. parser.add_argument("-p2","--prop2",help="Biophysical property of choice to analyze [int]", required=False,default=2,type=int)
  94. parser.add_argument("-ms","--matSize",help="Matrix size for linear discriminant analysis [int]", required=False,default=10,type=int)
  95. # Debugging functions:
  96. parser.add_argument("-sl","--showLabel",help="Show on projections? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  97. ############################################
  98. # More new features, gotta do what we gotta do:
  99. parser.add_argument("-co","--clustOnly",help="Stop running the script after clustering? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  100. parser.add_argument("-sf","--saveFmt",help="Which format do you want figures saved as? [png or pdf]",required=False,default='pdf',type=str)
  101. parser.add_argument("-sac","--saveAllClusts",help="Save the sequences for all clusters generated? [T/F]",required=False,default=False,type=distutils.util.strtobool)
  102. ############################################
  103. args = parser.parse_args()
  104. return(args)
  105. ###############################################################################################
  106. # All of the
  107. def run():
  108. #try:
  109. args = main()
  110. #except:
  111. # print("Error, check yourself (more error handling to come)")
  112. # A few of the argparse options don't have default entries
  113. # because we are dependent on them being defined
  114. ###############################################################################################
  115. #### Definition of ALL variables used in this notebook: Need to let users add them in with flags
  116. # For now the defaults are commented out so we can compare/contrast any issues
  117. datDir = args.datDir #"app_data"
  118. outputDir = args.outputDir #"AIMS_out"
  119. # Look to the notebooks for the other examples [ig vs msa vs peptide]
  120. molecule = args.molecule #'Ig'
  121. fileName = args.fileNames #["siv_tl8.csv","siv_cm9.csv"]
  122. datName = args.datNames #["TL8","CM9"]
  123. if len(datName) != len(fileName):
  124. datName = []
  125. for i in np.arange(len(fileName)):
  126. datName.append("dat"+str(i))
  127. num_loop = args.numLoop #1
  128. drop_duplicates = args.dropDup #True
  129. parallel_process = args.parallel #False
  130. # Note, you can't really take a subset of peptide data
  131. subset = args.subset #True
  132. subset_starts = args.subStart #[164,214,275,327]
  133. subset_ends = args.subEnd #[214,275,327,376]
  134. # The "align" option only changes alignments for Ig, not MSA encodings
  135. align = args.align #'bulge'
  136. # This "pad" value only changes things if your alignemnt is "bulge"
  137. # Otherwise keep it defined as 8, again another dummy variable that you need to keep
  138. pad = args.bulgePad #6
  139. # Specifically normalization for the bphys property matrix
  140. normalize = args.normProp # True
  141. renormalize = args.REnorm # True
  142. # For the clustering
  143. dchoice = args.clustData #'parse'
  144. reduce = args.projAlg #'pca'
  145. # set an optional umap_seed
  146. umap_seed = args.umapSeed #69
  147. # Clustering specifics
  148. clust = args.clustAlg #'optics'
  149. # This will be an all in one variable for whichever algorithm
  150. # minimum cluster size for optics, EPS for DBSCAN, and NClust for kmeans
  151. clust_size = args.clustSize #10
  152. # Incorporating metadata
  153. meta_form = args.metaForm # 'category'
  154. ############################################
  155. # New features! Woohoo
  156. DOstats = args.DOstats # False
  157. bootstrap = args.DOboot # False
  158. boots = args.boots # 1000
  159. MIboots = args.MIboots # 10
  160. AAorder = args.AAorder # 'WFMLIVPYHAGSTDECNQRK'
  161. if len(AAorder) == 20:
  162. custom_key = True
  163. elif len(AAorder) == 0:
  164. custom_key = False
  165. else:
  166. print("ERROR: Custom Key Wrong Length!")
  167. # Break out early with this error
  168. quit()
  169. colors = args.colors
  170. GETdist= args.GETdist
  171. parallel_dist = args.PARdist
  172. plot_props = args.Plotprops
  173. ############################################
  174. # More new features, gotta do what we gotta do:
  175. clustOnly = args.clustOnly # False
  176. saveFmt = args.saveFmt # pdf
  177. saveAllClusts = args.saveAllClusts
  178. ############################################
  179. ######### Do you want to show 2D projection, 3D, or both?##################
  180. proj_show = args.showProj # 'both'
  181. clust_show = args.showClust # 'both'
  182. # This is more of a debug function
  183. show_labels = args.showLabel #False
  184. meta_name = args.metaName # 'Dset'
  185. # Do you want to noramlize cluster_purity barplots
  186. norm = args.normBar #True
  187. # Decide if you want to analyze the data by cluster or by metadata.
  188. subset_sel = args.analysisSel #'cluster' # other option is 'metadata'
  189. # Do you want to save each individual cluster of sequences as an AIMS-compatible file?
  190. seqlogo = args.seqlogo #False
  191. save_subSeqs = args.saveSeqs #False
  192. seqlogo_size = args.logoNum #14
  193. # Which data subsets do you want to use
  194. sub_sels = args.selDat #[0,1]
  195. # Biophysical properties shown for positional average
  196. prop1 = args.prop1 # 1
  197. prop2 = args.prop2 # 2
  198. # Matsize for the linear discriminant analysis
  199. matSize= args.matSize #10
  200. # THEN, we need a whole bunch of functions so users don't have to sit through this forever.
  201. # Specifically, ways to only run portions of the analysis repeatedly or to save progress
  202. # Big one will be an option to save the "bigass matrix" and then load it back in to skip that step
  203. ###############################################################################################
  204. # # Section 1: Analysis Mode Definition and Pre-Processing
  205. # This section is critical for loading in data and either utilizing the AIMS Ig (TCR and Antibody), AIMS Peptide (Eluted from MHC or otherwise), or AIMS MSA (all other molecules) analysis modules
  206. # Trying to make it as easy as possible for new users to get a grasp of how AIMS works, while also reducing some clutter on the GitHub page. If you preferred older versions of AIMS, where each type of analysis was a completely different Jupyter notebook, you can click "tags" on the GitHub and download version 0.7.5 or earlier.
  207. # Note that this has important implications for the way that your data is read in. Only three options, "Ig","MSA", or "Peptide". MSA should work for every type of molecule, even Ig molecules if you really wanted to. Would be a little messy though due to poor conservation of CDR3 loops
  208. # Even if you only have a single file to analyze, it is required to define your path in a list (should be clearer below in the code)
  209. # The below cell is where we finally require users to input their own information for their files. How much info depends on the molecules of interest
  210. # In[2]:
  211. #####################
  212. """
  213. Throughout this script, I will try to include comments like this where explanations are important
  214. Use the below information to alter this code for your own personal analysis
  215. - molecule is either "Ig","MSA", or "peptide"
  216. - datDir is the location of your data
  217. - outputDir is where figures will be saved
  218. -If you need help understanding how to define these "Dirs" go to the ReadTheDocs website
  219. - fileName is a list of the full dataset filenames
  220. - datName is a more human readable title for the data
  221. """
  222. #####################
  223. try:
  224. os.listdir(outputDir)
  225. except:
  226. os.makedirs(outputDir)
  227. # # In the Below Cell We Convert Our Sequences to an AIMS-Readable Format
  228. # If there are downstream issues in the first section where figures are generated (Section 2) come back here and check formatting of the outputs here
  229. # # In this Section We Also Determine if We Want Subsets of the Data to be Taken
  230. # For instance, you may be interested in only a select region of the MSA or only a subset of the included CDR loops
  231. # In the example subset we provide, we are selecting out regions of the MHC MSA that correspond to consensus alignments of the MHC alpha-helices and beta-strands
  232. # In[3]:
  233. if len(fileName) != len(datName):
  234. print("A mistake! You don't have the proper number of labels for your files")
  235. elif molecule.lower() == 'ig':
  236. for i in np.arange(len(fileName)):
  237. seq_pre = aimsLoad.Ig_loader(datDir+'/'+fileName[i],label=datName[i],loops=num_loop,drop_degens = drop_duplicates)
  238. if i == 0:
  239. seqPRE = seq_pre
  240. else:
  241. seqPRE = pandas.concat([seqPRE,seq_pre],axis=1)
  242. seqF = seqPRE
  243. elif molecule.lower() == 'peptide':
  244. for i in np.arange(len(fileName)):
  245. seq_pre = aimsLoad.pep_loader(datDir+'/'+fileName[i],label=datName[i])
  246. if i == 0:
  247. seqPRE = seq_pre
  248. else:
  249. seqPRE = pandas.concat([seqPRE,seq_pre],axis=1)
  250. seqF = seqPRE
  251. elif molecule.lower() == 'msa':
  252. for i in np.arange(len(fileName)):
  253. seq_pre = aimsLoad.msa_loader(datDir+'/'+fileName[i],label=datName[i],drop_dups = drop_duplicates)
  254. if i == 0:
  255. seqAll = seq_pre
  256. else:
  257. seqAll = pandas.concat([seqAll,seq_pre],axis=1)
  258. # Have to reshape our sequences
  259. seqs = np.array(seqAll.loc[0].values).reshape(1,len(seqAll.loc[0].values))
  260. seqPRE = pandas.DataFrame(seqs)
  261. seqPRE.columns = seqAll.columns
  262. if subset:
  263. seqF = aims.get_msa_sub(seqPRE,subset_starts,subset_ends)
  264. else:
  265. seqF = seqPRE
  266. # Save our FASTA headers as metadata. May be useful downstream or not
  267. metaF = seqAll.loc[1]
  268. # In[4]:
  269. mat_size = aims.get_sequence_dimension(np.array(seqF))[0]
  270. # General changes that need to be done for every type of molecule
  271. AA_num_key = aims.get_props()[1]
  272. if num_loop != 1:
  273. for i in np.arange(len(mat_size)):
  274. if i == 0:
  275. xtick_loc = [mat_size[i]/2]
  276. else:
  277. pre_loc = sum(mat_size[:i])
  278. xtick_loc = xtick_loc + [mat_size[i]/2 + pre_loc]
  279. else:
  280. xtick_loc = mat_size/2
  281. # # Section 2: Sequence Visualization via AIMS Matrix Encoding
  282. # If looking at Ig molecules, you can decide if you would like to align to the center, left, or right of each sequence.
  283. # There is a 3rd option, "bulge" which aligns the germline regions of CDR3 (and other loops) and then center aligns what is left.
  284. # Change the 'align' variable to one of these four. Pretty easy to visualize each time you do so in below matrix
  285. # New little added bit to create a custom amino acid order
  286. if custom_key:
  287. my_AA_key = [a for a in AAorder]
  288. else:
  289. # My AA key here is the "standard" AIMS key that has been used in previous papers
  290. # Note changing the key doesn't change anything BUT the ordering of Amino acids in some figures
  291. my_AA_key=['A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V']
  292. # Just in case you want to do MSA analysis, you need to use "my_AA_key_dash"
  293. my_AA_key_dash = my_AA_key + ['-']
  294. # In[5]:
  295. if molecule.lower() == 'ig':
  296. seq_MIpre = aims.gen_tcr_matrix(np.array(seqF),AA_key = my_AA_key,key = AA_num_key, giveSize = mat_size, alignment = align, bulge_pad=pad)
  297. elif molecule.lower() == 'peptide':
  298. seq_MIpre = aims.gen_tcr_matrix(np.array(seqF),AA_key = my_AA_key,key = AA_num_key, giveSize = mat_size, alignment = align, bulge_pad=pad)
  299. elif molecule.lower() == 'msa':
  300. AA_num_key_dash = np.hstack((AA_num_key,[0]))
  301. seq_MIpre = aims.gen_MSA_matrix(np.array(seqF),AA_key_dash = my_AA_key_dash,key = AA_num_key_dash, giveSize = mat_size)
  302. seq_MIf = pandas.DataFrame(np.transpose(seq_MIpre),columns = seqF.columns)
  303. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  304. x = ax[0,0].imshow(np.transpose(seq_MIf), interpolation='nearest', aspect='auto',cmap=cmap)
  305. pl.colorbar(x)
  306. ax[0,0].set_ylabel('Sequence Number')
  307. ######
  308. # It will help to have vertical dashed black lines to guide the viewer
  309. seq1_len = np.shape(seqF)[1]
  310. Numclones = int(seq1_len)
  311. if type(mat_size) != int:
  312. for i in np.arange(len(mat_size)-1):
  313. ax[0,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(Numclones),np.arange(Numclones),'k--',linewidth = 3)
  314. #######
  315. ax[0,0].set_xlabel('Sequence Position')
  316. if saveFmt.lower() == 'png':
  317. pl.savefig(outputDir+'/AIMS_mat.png',format='png',dpi=600)
  318. else:
  319. pl.savefig(outputDir+'/AIMS_mat.pdf',format='pdf')
  320. pl.close()
  321. # # Section 3: Calculate our Biophysical Property Matrices
  322. # Depending on what type of analysis you are doing, this will likely be the slowest step in this entire notebook
  323. # The only thing that you *might* need to change in the below code is the decision to normalize the biophysical properties or not. Default is to normalize
  324. # In[6]:
  325. # Process this new matrix and apply biophysical propery "masks"
  326. # This has to be changed from the binary case, because we aren't looking for differences
  327. dsetF = seqF.values
  328. if molecule.lower() == 'ig':
  329. special = ''
  330. elif molecule.lower() == 'peptide':
  331. special = ''
  332. elif molecule.lower() == 'msa':
  333. special = 'MSA'
  334. #################### PARALLEL PROCESSING TO CREATE BIG MATRIX #######################
  335. if parallel_process:
  336. import multiprocessing as mp
  337. def boot_it(data):
  338. bigass = classy.get_bigass_matrix(dsetF[:,data[0]:data[1]],AA_key=my_AA_key,AA_key_dash=my_AA_key_dash, giveSize = mat_size, alignment = align,special = special, norm=normalize,bulge_pad=pad)
  339. return(bigass)
  340. def do_boot(data):
  341. with mp.Pool() as pool:
  342. results = pool.map(boot_it, data)
  343. return(results)
  344. if __name__ == "__main__":
  345. # Probably a smarter way to calculate #seqs per node, but do 100 for now
  346. final = aims.gen_splits(splitMat = seq_MIf, splitSize = 100)
  347. big_pre = do_boot(final)
  348. total_mat = np.concatenate(big_pre, axis = 0)
  349. else:
  350. #################### Or, Don't Parallelize to CREATE BIG MATRIX #######################
  351. bigass = classy.get_bigass_matrix(dsetF,AA_key=my_AA_key,AA_key_dash=my_AA_key_dash, giveSize = mat_size, alignment = align, norm = normalize,special=special,bulge_pad=pad )
  352. total_mat = bigass
  353. # Generate a large list of property names and matrix positions so you can pinpoint strong
  354. # contributors to discrimating features between datasets or clusters
  355. prop_list_old = ['Phobic1','Charge','Phobic2','Bulk','Flex','Kid1','Kid2','Kid3','Kid4','Kid5','Kid6','Kid7','Kid8','Kid9','Kid10']
  356. prop_list_new = ['Hot'+str(b+1) for b in range(46)]
  357. prop_names = prop_list_old + prop_list_new
  358. num_locs = int(np.shape(total_mat)[1]/61)
  359. Bigass_names = []
  360. for i in prop_names:
  361. for j in np.arange(num_locs):
  362. Bigass_names = Bigass_names + [ i + '-' + str(j) ]
  363. ########################################################################################
  364. if renormalize:
  365. entropy_pre,freq_pre,cov_pre = aims.calculate_shannon(np.transpose(seq_MIf.values))
  366. repeat_ent = []
  367. for i in np.arange(len(prop_names)):
  368. repeat_ent = repeat_ent + [2**entropy_pre]
  369. refactor = np.array(repeat_ent).reshape(61*len(entropy_pre))
  370. pp_mat = total_mat*refactor
  371. else:
  372. pp_mat = total_mat
  373. ##########################################################################################
  374. # Drop Highly Correlated Vectors and Vectors where entry=0 for all entries
  375. ###### Currently drop vectors with over 0.75 corr. coef. ################
  376. full_big = pandas.DataFrame(pp_mat,columns = Bigass_names)
  377. drop_zeros = [column for column in full_big.columns if all(full_big[column] == 0 )]
  378. y = full_big.drop(full_big[drop_zeros], axis=1)
  379. z_pre = np.abs(np.corrcoef(np.transpose(y)))
  380. z = pandas.DataFrame(z_pre,columns=y.columns,index=y.columns)
  381. # Select upper triangle of correlation matrix
  382. upper = z.where(np.triu(np.ones(z.shape), k=1).astype(bool))
  383. # If you did want to change that corr. coef. cutoff, do so here
  384. to_drop = [column for column in upper.columns if ( any(upper[column] > 0.75) ) ]
  385. # Your final product of a parsed matrix
  386. parsed_mat = y.drop(y[to_drop], axis=1)
  387. # This is a new, important variable to account for the cases where renormalization
  388. # is used. We need non-renormed data for downstream repertoire characterization
  389. NonNorm_big = pandas.DataFrame(total_mat,columns = Bigass_names)
  390. # Let's have some default metadata we can pull from later
  391. tokenized_dset = []
  392. for i in np.arange(len(datName)):
  393. for j in seqF.columns:
  394. if str(j).find(datName[i]) != -1:
  395. tokenized_dset.append(i)
  396. token_df = pandas.DataFrame(tokenized_dset,columns=['ID'])
  397. IDed_full_big = pandas.concat([full_big,token_df],axis=1)
  398. # Lastly, create a good-ole traditional averaged bphys property matrix
  399. # i.e. each sequence gets a single value for averaged charge, averaged flexibility, etc...
  400. posLen,seqLen = np.shape(seq_MIf)
  401. # The 61 is hardcoded here because it is our number of properties. Eventually we will let users define which properties to use
  402. seq_bigReshape = np.array(full_big).reshape(seqLen,61,posLen)
  403. # # Section 4: Sequence Projection & Clustering
  404. # # The next few cells are particularly powerful for isolating interesting populations in the dataset using PCA, UMAP, and KMeans Clustering
  405. # This is really a section where you should take your time and toggle some of these settings. Look at your data using PCA and UMAP
  406. # Try to use "full", "parse", or "avg" biophysical properties for each entry as input into the dimensionality reduction
  407. # The below cell is important to define, so we set it apart from the rest of the code
  408. # In[7]:
  409. # DEFINE WHICH FORM OF THE DATASET YOU WOULD LIKE TO ANALYZE HERE
  410. # Perform dim. red. on the whole dataset? enter "full"
  411. # Want to do it on a dataset with highly correlated vectors removed? enter "parse"
  412. # Lastly, can just do it on a matrix of the per-sequence average over 61 props? enter "avg"
  413. # # The below section then uses the above information to actually calculate these things
  414. #
  415. # There is a lot of opportunity to go EVEN deeper into the code here to tweak your analysis. Important to do if you really care about the data. Alter things like:
  416. # - Nclust for Kmeans
  417. # - min_samples for OPTICS
  418. # - eps for DBSCAN
  419. # - Setting random seeds for UMAP (see the ReadTheDocs for the disclaimer for using UMAP)
  420. # In[8]:
  421. # Don't change these if statements, change the stuff below that
  422. if dchoice.lower() == 'full':
  423. chosen_dset = full_big
  424. elif dchoice.lower() == 'parse':
  425. chosen_dset = parsed_mat
  426. elif dchoice.lower() == 'avg':
  427. chosen_dset = np.average(seq_bigReshape,axis=2)
  428. if reduce == 'pca':
  429. from sklearn.decomposition import PCA
  430. pca = PCA(n_components=3, svd_solver='full')
  431. final=pca.fit_transform(chosen_dset)
  432. transform = pandas.DataFrame(np.transpose(final),columns = seq_MIf.columns)
  433. print("PCA Explained Variance Ratio:")
  434. print(pca.explained_variance_ratio_)
  435. elif reduce == 'umap':
  436. import umap
  437. if isinstance(umap_seed,int):
  438. reducer = umap.UMAP(n_components=3, n_neighbors = 25,random_state=umap_seed)
  439. else:
  440. reducer = umap.UMAP(n_components=3, n_neighbors = 25)
  441. final = reducer.fit_transform(chosen_dset)
  442. transform = pandas.DataFrame(np.transpose(final),columns = seq_MIf.columns)
  443. # Cluster the results:
  444. import sklearn.cluster as cluster
  445. clust_input = np.array(np.transpose(transform))
  446. if clust == 'kmean':
  447. NClust = clust_size
  448. clusts = cluster.KMeans(n_clusters=NClust).fit_predict(clust_input)
  449. elif clust == 'optics':
  450. clusts = cluster.OPTICS(min_samples=clust_size).fit_predict(clust_input)
  451. elif clust == 'dbscan':
  452. clusts = cluster.DBSCAN(min_samples=clust_size).fit_predict(clust_input)
  453. cluster_dset = pandas.DataFrame(clusts,columns=['cluster'])
  454. # # SECTION 5: Metadata Incorporation
  455. # By default, we'll assume that the user DOES NOT have any metadata to add (and the code reflects this), but there are sections to show how metadata could be incorporated are included in the code. The ReadTheDocs will be updated soon to go into more detail here for you to add custom metadata
  456. # In[9]:
  457. # We can incorporate metadata either defining a categorical map or a quantitative map
  458. ###################################################################################
  459. # meta_form is either "category" or "quant". Check ReadTheDocs if more descriptions are needed
  460. # If using default metadata (i.e. loaded file), it should be "category"
  461. ###################################################################################
  462. if meta_form == 'category':
  463. # This is the easiest default metadata definition.
  464. # Just based on the files that were loaded in (useless if only 1 file)
  465. metapre = token_df
  466. # Convert the metadata from numbers to strings
  467. meta_conv = []
  468. for i in token_df.values:
  469. meta_conv.append(datName[i[0]])
  470. metadat = pandas.DataFrame(meta_conv)
  471. # However, you can also read in your own metadata, or build it from scratch
  472. # JUST MAKE SURE YOUR METADATA IS A PANDAS DATAFRAME IN THE END
  473. elif meta_form == 'quant':
  474. # This "quantitative" metadata will just count from 1 to the length of the dataset,
  475. # coloring the points on the plot in order.
  476. metaPRE = np.arange(len(token_df))
  477. metadat = pandas.DataFrame(metaPRE)
  478. # But, you could add things like MFI, binding affinity, or GEX data for a particular gene
  479. # From there, not much should change unless you want to give a unique name to your metadata
  480. meta_map = aims.encode_meta(metadat)
  481. meta_map.columns = [meta_name]
  482. meta_leg = metadat.drop_duplicates().values
  483. # We also need to define our clusters more clearly
  484. clust_map = cluster_dset
  485. clust_leg = [a[0] for a in cluster_dset.drop_duplicates().sort_values('cluster').values]
  486. clust_name = 'cluster'
  487. # # Section 6: Plotting Clustering and (Optional) Metadata
  488. # The previous section was just for calculating these things. We are now going to provide users with a few different options to visualize their data
  489. #
  490. # By default we show 2D and 3D cluster projections with metadata and clustered data but give users control over which is shown
  491. # In[10]:
  492. # Could optionally plot other data or change legends if you would like
  493. chosen_map1 = clust_map; leg1 = clust_leg
  494. chosen_map2 = meta_map; leg2 = meta_leg
  495. # Define a colormap to color metadata. Can change this if you want, look at matplotlib colormap options
  496. cmapF = pl.get_cmap('tab20b')
  497. # So there's no reason to have a function for this, other than to make this notebook look a bit prettier
  498. # Really just defining a bunch of stuff repeatedly with if statements
  499. fig3d,plotloc,plottype,plotem,legends,dattype = aims.get_plotdefs(clust_show,proj_show,chosen_map1,chosen_map2,leg1,leg2)
  500. # Now plot the actual stuff, and save the object handles in a list
  501. colorhandle= []; ax=[]
  502. for i in np.arange(len(plotloc)):
  503. #print(dattype[i])
  504. if dattype[i] == 'clust':
  505. if clust == 'kmean':
  506. cmap_use = pl.get_cmap('rainbow')
  507. else:
  508. cmap_use = cmap
  509. else:
  510. cmap_use = cmapF
  511. if plottype[i] == '3d':
  512. ax.append(fig3d.add_subplot(plotloc[i],projection=plottype[i]))
  513. # So the 3D plot has shading, which means instead we're better off splitting the data up
  514. colorhandle.append(ax[i].scatter(clust_input[:,0],clust_input[:,1],clust_input[:,2],c = plotem[i].values.reshape(len(plotem[i]),), cmap=cmap_use))
  515. ax[i].set_xlabel('AX1',labelpad=20); ax[i].set_ylabel('AX2',labelpad=20); ax[i].set_zlabel('AX3',labelpad=15)
  516. else:
  517. ax.append(fig3d.add_subplot(plotloc[i]))
  518. colorhandle.append(ax[i].scatter(clust_input[:,0],clust_input[:,1],c = plotem[i].values.reshape(len(plotem[i]),), cmap=cmap_use))
  519. ax[i].set_xlabel('AX1'); ax[i].set_ylabel('AX1')
  520. # This code is somewhat wild just for getting a properly color-coded legend in there...
  521. fig3d.canvas.draw()
  522. # We use the metadata mapped colors down the line, so if you aren't plotting it then you need to save some other way
  523. need_meta = True
  524. for i in np.arange(len(legends)):
  525. cmap_pre = pandas.DataFrame(colorhandle[i].get_facecolors())
  526. if dattype[i] == 'clust':
  527. mapDF = pandas.concat([clust_map,cmap_pre],axis=1)
  528. mapped_colors= mapDF.sort_values('cluster').drop_duplicates('cluster').values[:,1:]
  529. else:
  530. need_meta = False
  531. mapDF = pandas.concat([meta_map,cmap_pre],axis=1)
  532. mapped_colors= mapDF.sort_values(meta_name).drop_duplicates(meta_name).values[:,1:]
  533. keep_map = mapped_colors
  534. # Do two things at once here. Also add in axis labels
  535. # Don't plot duplicate legends
  536. if i > 0:
  537. if dattype[i] == dattype[i-1]:
  538. continue
  539. if len(legends[i]) > 6:
  540. # Don't show exceedingly long legends
  541. continue
  542. else:
  543. legend_elements = []
  544. # This line won't do anything if the array is properly shaped, will do stuff it if isn't.
  545. legends[i] = np.array(legends[i]).reshape(len(legends[i]))
  546. for j in np.arange(len(legends[i])):
  547. element = [Line2D([0], [0], marker='o', color='w', label=legends[i][j],markerfacecolor=mapped_colors[j], markersize=10)]
  548. legend_elements = legend_elements+element
  549. ax[i].legend(bbox_to_anchor=(0.5, 1.1),handles=legend_elements,loc='upper center',ncol=len(legends[i]),title=meta_name)
  550. if need_meta:
  551. cmap_discrete = cmapF(np.linspace(0, 1, len(clust_input)))
  552. cmap_pre = pandas.DataFrame(cmap_discrete)
  553. mapDF = pandas.concat([meta_map,cmap_pre],axis=1)
  554. mapped_colors= mapDF.sort_values(meta_name).drop_duplicates(meta_name).values[:,1:]
  555. keep_map = mapped_colors
  556. if show_labels:
  557. # ONLY show this for a 2D plot
  558. for num in np.arange(len(ax)):
  559. if plottype[num] == '2d':
  560. break
  561. a = 0; plot1 = clust_input[:,0]; plot2 = clust_input[:,1]
  562. plot_labels = seqF.columns.values
  563. for i,j in zip(plot1,plot2):
  564. ax[num].annotate(str(plot_labels[a]),xy=(i,j),fontsize=14)
  565. a+=1
  566. # Note, we never save these as a PDF because explicitly drawing a vector for each point is very slow/unwieldy
  567. pl.savefig(outputDir+'/AIMS_projections.png',format='png',dpi=600)
  568. pl.close()
  569. # # Section 7: Quantify Cluster Compositions
  570. # This step might be meaningless if you don't have ANY metadata to go off of, and/or are not comparing/contrasting two datasets. This is fine, you should still run this step to make sure that everything is properly defined
  571. #
  572. # In[11]:
  573. cmap3 = pl.get_cmap('tab20b')
  574. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(14,8))
  575. # Just in case your metadata has weird indices:
  576. meta_map.index = cluster_dset.index
  577. final_breakdown = pandas.concat([cluster_dset,meta_map],axis=1)
  578. a = 0
  579. for i in np.sort(final_breakdown['cluster'].drop_duplicates()):
  580. if i == -1:
  581. continue # Dont count the unclustered
  582. sub_clust = final_breakdown[final_breakdown['cluster'] == i]
  583. if len(sub_clust) == 0:
  584. continue
  585. bottom=0
  586. for j in sub_clust[meta_name].drop_duplicates().values:
  587. sub_sub = sub_clust[sub_clust[meta_name] == j]
  588. if norm:
  589. pl.bar(a,len(sub_sub)/len(sub_clust),bottom = bottom,color=keep_map[int(j)],edgecolor='black')
  590. bottom += len(sub_sub)/len(sub_clust)
  591. else:
  592. pl.bar(a,len(sub_sub),bottom = bottom,color=keep_map[int(j)],edgecolor='black')
  593. bottom += len(sub_sub)
  594. a = a+1
  595. if len(meta_leg) < 6:
  596. meta_legF = np.array(meta_leg).reshape(len(meta_leg))
  597. legend_elements= []
  598. for j in np.arange(len(meta_leg)):
  599. element = [Line2D([0], [0], marker='o', color='w', label=meta_legF[j],markerfacecolor=keep_map[j], markersize=10)]
  600. legend_elements = legend_elements+element
  601. pl.legend(handles=legend_elements,ncol=len(meta_leg))
  602. pl.xlabel('Cluster Number')
  603. if norm:
  604. pl.ylabel('Fraction Per Cluster')
  605. else:
  606. pl.ylabel('Count Per Cluster')
  607. cluster_purity = aims.calc_cluster_purity(final_breakdown,meta_name)
  608. print("Cluster Purities:")
  609. print(np.transpose(cluster_purity))
  610. # Really not sure why *just* this file is mad being saved as a pdf.
  611. # Had to put this line in to try to fix it though.
  612. if saveFmt.lower() == 'png':
  613. pl.savefig(outputDir+'/AIMS_clusterQuant.png',format='png',dpi=600)
  614. else:
  615. try:
  616. pl.savefig(outputDir+'/AIMS_clusterQuant.pdf',format='pdf')
  617. except:
  618. pl.savefig(outputDir+'/AIMS_clusterQuant.png',format='png',dpi=600)
  619. pl.close()
  620. ###### CALCULATE CLUSTER SIGNIFICANCE ###########
  621. fig, ax = pl.subplots(1, 2,squeeze=False,figsize=(16,6))
  622. cluster_purity,purity_pVal = aims.calc_cluster_purity(final_breakdown,meta_name)
  623. x = ax[0,0].imshow(cluster_purity,interpolation='nearest', aspect='auto',vmax = 1,cmap='plasma')
  624. y = ax[0,1].imshow(purity_pVal,interpolation='nearest', aspect='auto',vmax = 0.05,cmap='Greys_r')
  625. ax[0,0].set_xlabel('MetaData #'); ax[0,1].set_xlabel('MetaData #')
  626. ax[0,0].set_ylabel('Cluster #'); ax[0,1].set_ylabel('Cluster #')
  627. ax[0,0].set_title('Cluster Purity'); ax[0,1].set_title('Cluster Significance')
  628. pl.colorbar(x,ax=ax[0,0]); pl.colorbar(y,ax=ax[0,1])
  629. if saveFmt.lower() == 'png':
  630. pl.savefig(outputDir+'/AIMS_clusterPurity.png',format='png',dpi=600)
  631. else:
  632. pl.savefig(outputDir+'/AIMS_clusterPurity.pdf',format='pdf')
  633. pl.close()
  634. # # Section 8: Defining Data Subsets of Interest to Further Characterize Repertoire
  635. # VERY IMPORTANT STEP here for all downstream analysis. Define whether you want to compare/contrast clustered sequences or analyze sequences based upon the associated metadata
  636. # In[12]:
  637. # # Then, Plot This Dataset of Interest and Visualize How Similar/Different Each Look
  638. # In[13]:
  639. # NEW FEATURE!!!
  640. # You can view biophysical properties as
  641. # a function of cluster or metadata here,
  642. # rather than just the sequence colors
  643. #########################################
  644. # Recommend changing colors if showing diff props
  645. showProp = 1 #1=Charge, 2=phob
  646. # Show lines separating major groups?
  647. show_lines = True
  648. #########################################
  649. if subset_sel.lower() == 'cluster':
  650. chosen_map = clust_map; chosen_name = clust_name
  651. elif subset_sel.lower() == 'metadata':
  652. chosen_map = meta_map; chosen_name = meta_name
  653. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  654. for i in np.sort(chosen_map[chosen_name].drop_duplicates()):
  655. if i == -1:
  656. continue
  657. if plot_props:
  658. sub_props = seq_bigReshape[:,showProp]
  659. subbDF = pandas.DataFrame(np.transpose(sub_props))
  660. subbDF.columns = seq_MIf.columns
  661. pre_clust = subbDF[subbDF.columns[chosen_map[chosen_map[chosen_name] == i].index]]
  662. else:
  663. pre_clust = seq_MIf[seq_MIf.columns[chosen_map[chosen_map[chosen_name] == i].index]]
  664. clustID = np.transpose(pandas.DataFrame(i*np.ones(np.shape(pre_clust)[1])))
  665. clustID.columns = pre_clust.columns
  666. pre_clustF = pandas.concat([pre_clust,clustID],axis=0)
  667. if i == 0:
  668. clustered = pre_clustF
  669. else:
  670. clustered = pandas.concat([clustered, pre_clustF],axis = 1)
  671. if show_lines:
  672. ax[0,0].plot(np.arange(len(seq_MIf)),np.ones(len(seq_MIf))*(np.shape(clustered)[1]),'black',linewidth = 3)
  673. ax[0,0].set_ylabel('Sequence Number')
  674. if type(mat_size) != int:
  675. for i in np.arange(len(mat_size)-1):
  676. ax[0,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(np.shape(clustered)[1]),np.arange(np.shape(clustered)[1]),'k--',linewidth = 3)
  677. if plot_props:
  678. ttt = np.transpose(np.array(clustered))[:,:-1]
  679. scaledd = np.max([np.abs(np.min(ttt)),np.abs(np.max(ttt))])
  680. xyz = ax[0,0].imshow(np.transpose(np.array(clustered))[:,:-1], interpolation='nearest', aspect='auto',cmap='bwr',vmin=-scaledd,vmax=scaledd)
  681. else:
  682. xyz = ax[0,0].imshow(np.transpose(np.array(clustered))[:,:-1], interpolation='nearest', aspect='auto',cmap=cmap)
  683. pl.colorbar(xyz)
  684. if saveFmt.lower() == 'png':
  685. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_subViz.png',format='png',dpi=600)
  686. else:
  687. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_subViz.pdf',format='pdf')
  688. pl.close()
  689. if saveAllClusts:
  690. dset = seqF
  691. # Stole this code from further down, but we don't want to deal with all
  692. # of the plotting stuff. Redefine chosen map in case users picked "metadata"
  693. chosen_map = clust_map; chosen_name = clust_name
  694. # Have this as a debug step to make sure we're printing enough textfiles.
  695. #print(np.shape(chosen_map.drop_duplicates().values))
  696. sub_sels = chosen_map.drop_duplicates().values
  697. for i in sub_sels:
  698. # Look at umap dset or pca dset
  699. # !!!!! NOTE: THIS MIGHT BE AN ERROR IN SOME LATER VERSIONS OF PYTHON
  700. # !!!! I THINK THEY CHANGE HOW THEY HANDLE DATAFRAMES -> values
  701. sub_MI = seq_MIf[seq_MIf.columns[chosen_map[chosen_map[chosen_name] == i[0]].index]]
  702. sub_seqs = np.transpose(dset[sub_MI.columns])
  703. sub_seqs.to_csv(outputDir+'/cluster'+str(i[0])+'_all.txt',header=None,index=None)
  704. # Stop running the code early for just the clustering aspect
  705. # Could probably add another of these to stop other analysis as well.
  706. if clustOnly:
  707. print('Stopping code after clustering per user definition')
  708. quit()
  709. ###############################################################
  710. # NEW: AIMSDIST!
  711. if GETdist:
  712. get_distClusts = True
  713. for i in chosen_map.sort_values(chosen_name).drop_duplicates().values:
  714. if i == -1:
  715. continue
  716. sub_MI_temp = seq_MIf[seq_MIf.columns[chosen_map[chosen_map[chosen_name] == i[0]].index]]
  717. sub_seqs_temp = np.transpose(seqF[sub_MI_temp.columns])
  718. if i == 0:
  719. sorted_seqs = sub_seqs_temp
  720. else:
  721. sorted_seqs = pandas.concat([sorted_seqs,sub_seqs_temp])
  722. ########################################################################################################3
  723. if parallel_dist:
  724. import multiprocessing as mp
  725. def boot_it(data):
  726. if data[0][0] == data[1][0]:
  727. dist_temp = aims.calc_AIMSdist(sorted_seqs[data[0][0]:data[0][1]])
  728. else:
  729. dist_temp = aims.calc_AIMSdist(sorted_seqs[data[0][0]:data[0][1]],sorted_seqs[data[1][0]:data[1][1]])
  730. return(data,dist_temp)
  731. def do_boot(data):
  732. with mp.Pool() as pool:
  733. results = pool.map(boot_it, data)
  734. return(results)
  735. if __name__ == "__main__":
  736. # Probably a smarter way to calculate #seqs per node, but do 100 for now
  737. xx = aims.prep_distCalc(sorted_seqs)
  738. dist_pre = do_boot(xx)
  739. dist_matF = np.zeros((len(sorted_seqs),len(sorted_seqs)))
  740. for i in np.arange(len(dist_pre)):
  741. # Set 1 will be our x-axis of the matrix
  742. # set 2 will be our y-axis of the matrix
  743. set1 = dist_pre[i][0][0]
  744. set2 = dist_pre[i][0][1]
  745. # Set 3 is then the data that goes in that space
  746. set3 = dist_pre[i][1]
  747. # Need to fill both the matrix entry and the
  748. # transpose of that entry!!!
  749. dist_matF[set1[0]:set1[1],set2[0]:set2[1]] = set3
  750. dist_matF[set2[0]:set2[1],set1[0]:set1[1]] = np.transpose(set3)
  751. dists = dist_matF
  752. else:
  753. dists = aims.calc_AIMSdist(sorted_seqs)
  754. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(10,8))
  755. x = pl.imshow(np.transpose(dists), interpolation='nearest', aspect='auto',vmin=0,vmax=11)
  756. # Optionally can get back distance clusters:
  757. if get_distClusts:
  758. distance_clusters = aims.get_distClusts(dists,metadat,max_d=5)
  759. distance_clusters.to_csv(outputDir+'/dist_clust.csv',index=False)
  760. pl.colorbar(x)
  761. if saveFmt.lower() == 'png':
  762. pl.savefig(outputDir+'/AIMSdist.png',format='png',dpi=600)
  763. else:
  764. pl.savefig(outputDir+'/AIMSdist.pdf',format='pdf')
  765. pl.close
  766. # # Section 9: Isolation of Individual Groups for Downstream Characterization
  767. # Run the next cell to visualize your clusters of choice, and then go through the remainder of the AIMS modules
  768. #
  769. # Selecting the sub_sels [0,1] will select either the first two clusters or the first two metadata entries. Remember that python is 0-indexed, so if you want to look at a very specific metadata cluster then make sure you take that into account! You can *technically* visualize every cluster all at once, but that is REALLY not recommended
  770. # In[14]:
  771. # So now that we've done some clustering, pick out the most interesting or sections of the data:
  772. # OPTIONALLY YOU CAN VISUALIZE/ANALYZE EVERY CLUSTER. STRONGLY NOT RECOMMENDED IF YOU HAVE MANY CLUSTERS
  773. ##sub_sels = np.arange(len(chosen_map))
  774. # Redefine the raw sequences so we can manipulate them if needed
  775. dset = seqF
  776. fig, ax = pl.subplots(len(sub_sels), 1,squeeze=False,figsize=(16,4*len(sub_sels)))
  777. label=[]
  778. a = 0
  779. for i in sub_sels:
  780. # Look at umap dset or pca dset
  781. sub_MI = seq_MIf[seq_MIf.columns[chosen_map[chosen_map[chosen_name] == i].index]]
  782. sub_seqs = np.transpose(dset[sub_MI.columns])
  783. if subset_sel.lower() == 'metadata':
  784. label.append(meta_legF[i])
  785. else:
  786. label.append('cluster'+str(i))
  787. ax[a,0].imshow(np.transpose(sub_MI), interpolation='nearest', aspect='auto',cmap=cmap)
  788. datlen = np.shape(sub_MI)[1]
  789. datID = np.transpose(pandas.DataFrame(datlen*[a]))
  790. datID.columns = sub_MI.columns
  791. sub_matPRE = pandas.concat([sub_MI,datID],axis=0)
  792. if a == 0:
  793. sub_matF = sub_matPRE
  794. sub_seqF = sub_seqs
  795. else:
  796. sub_matF = pandas.concat([sub_matF,sub_matPRE],axis=1)
  797. sub_seqF = pandas.concat([sub_seqF,sub_seqs],axis=0)
  798. a+=1
  799. if save_subSeqs:
  800. sub_seqs.to_csv(outputDir+'/'+label[i]+'_all.txt',header=None,index=None)
  801. if seqlogo:
  802. seqlogo1 = sub_seqs[sub_seqs[0].str.len() == seqlogo_size]
  803. seqlogo1.to_csv(outputDir+'/'+label[i]+'_logo.txt',header=None,index=None)
  804. print("Visualizing the subsets: "+str([str(a) for a in label]))
  805. if saveFmt.lower() == 'png':
  806. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_selected.png',format='png',dpi=600)
  807. else:
  808. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_selected.pdf',format='pdf')
  809. pl.close()
  810. # # Leaving This Here in Case You Have Seqlogo Installed
  811. # Can be useful to visualize some sequences in this way. This code has not been tested in a while, and has not been tested at all for MSA analysis
  812. # In[15]:
  813. # # Section 10: Generate Subsets of Matrices We Have Calculated Previously
  814. # This will save some time compared to how things were done before.
  815. # In[16]:
  816. # define "RE" variables so we don't mess with any variables upstream (in case you want to re-run)
  817. full_big_re = NonNorm_big; full_big_re.index = seq_MIf.columns
  818. parsed_mat_re = parsed_mat; parsed_mat_re.index = seq_MIf.columns
  819. # use the transpose of the sub_mat to find the
  820. ref_sub = np.transpose(sub_matF)
  821. ref_sub.columns = np.arange(len(sub_matF))
  822. sub_big = full_big_re.loc[sub_matF.columns]
  823. sub_parsed = parsed_mat_re.loc[sub_matF.columns]
  824. # # Section 11: Position Sensitive Biophysical Properties for Every Clone in the Dataset
  825. # Here, we can visualize the how similar or dissimilar the biophysical properties are for each sequence within a given repertoire.
  826. #
  827. # Right now we show only the charge and the hydropathy, but you can change "prop1" in either cell to visualize a different property (see ReadTheDocs for more info)
  828. # In[17]:
  829. # Generate the position sensitive charge across all clones in the dataset
  830. # Which property will you want to look at down the line? 1 = charge, 2 = hydrophobicity... see full list in eLife paper (Boughter et al. 2020)
  831. fig, axs = pl.subplots(1, len(sub_sels),squeeze=False,figsize=(10*len(sub_sels),8))
  832. for dat in np.arange(len(sub_sels)):
  833. take_sub = ref_sub[ref_sub[len(sub_matF)-1] == dat].index
  834. take_big = sub_big.loc[take_sub]
  835. temp_bigReshape = np.array(take_big).reshape(len(take_big),61,posLen)
  836. min_temp = np.min(temp_bigReshape[:,prop1,:])
  837. max_temp = np.max(temp_bigReshape[:,prop1,:])
  838. if abs(min_temp) > abs(max_temp):
  839. propMin = min_temp
  840. propMax = -min_temp
  841. elif abs(max_temp) > abs(min_temp):
  842. propMin = -max_temp
  843. propMax = max_temp
  844. else:
  845. propMin = min_temp
  846. propMax = max_temp
  847. x = axs[0,dat].imshow(temp_bigReshape[:,prop1,:],interpolation='nearest', aspect='auto',cmap=cm.seismic, vmin = propMin, vmax = propMax)
  848. axs[0,dat].set_xlabel('Sequence Position'); axs[0,dat].set_ylabel('Sequence Number')
  849. axs[0,dat].set_title(str(label[sub_sels[dat]]) + ' - Charge')
  850. fig.colorbar(x, ax=axs[0,dat])
  851. if saveFmt.lower() == 'png':
  852. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_charge.png',format='png',dpi=600)
  853. else:
  854. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_charge.pdf',format='pdf')
  855. pl.close()
  856. # In[18]:
  857. fig, axs = pl.subplots(1, len(sub_sels),squeeze=False,figsize=(10*len(sub_sels),8))
  858. for dat in np.arange(len(sub_sels)):
  859. take_sub = ref_sub[ref_sub[len(sub_matF)-1] == dat].index
  860. take_big = sub_big.loc[take_sub]
  861. temp_bigReshape = np.array(take_big).reshape(len(take_big),61,posLen)
  862. min_temp = np.min(temp_bigReshape[:,prop2,:])
  863. max_temp = np.max(temp_bigReshape[:,prop2,:])
  864. if abs(min_temp) > abs(max_temp):
  865. propMin = min_temp
  866. propMax = -min_temp
  867. elif abs(max_temp) > abs(min_temp):
  868. propMin = -max_temp
  869. propMax = max_temp
  870. else:
  871. propMin = min_temp
  872. propMax = max_temp
  873. x = axs[0,dat].imshow(temp_bigReshape[:,prop2,:],interpolation='nearest', aspect='auto',cmap=cm.BrBG, vmin = propMin, vmax = propMax)
  874. axs[0,dat].set_xlabel('Sequence Position'); axs[0,dat].set_ylabel('Sequence Number')
  875. axs[0,dat].set_title(str(label[sub_sels[dat]]) + ' - Hydropathy')
  876. fig.colorbar(x, ax=axs[0,dat])
  877. if saveFmt.lower() == 'png':
  878. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_hydropathy.png',format='png',dpi=600)
  879. else:
  880. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_hydropathy.pdf',format='pdf')
  881. pl.close()
  882. # # Section 12: Averaged Biophysical Properties for Each Group of Interest
  883. # Look at biophysical properties averaged over clones AND position
  884. #
  885. # Note for old users of the software, you might get different looking results because originally I normalized vectors to unit length, but NOT 0 mean. I now do both.
  886. # In[19]:
  887. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  888. x_axis = np.array([-0.2,0.9,2,3.1])
  889. # We want to exclude prop0 (the simple 1-21 AA representation entries)
  890. full_avg = []; full_std = []; plot_lab = []
  891. a=0
  892. for dat in np.arange(len(sub_sels)):
  893. sin_avg =[]; sin_std = []
  894. b=0
  895. for prop in np.arange(4):
  896. propF = prop+1
  897. take_sub = ref_sub[ref_sub[len(sub_matF)-1] == dat].index
  898. take_big = sub_big.loc[take_sub]
  899. temp_bigReshape = np.array(take_big).reshape(len(take_big),61,posLen)
  900. if bootstrap:
  901. prop_avg = []
  902. for i in np.arange(boots):
  903. re_big = resample(temp_bigReshape)
  904. prop_avg.append(np.average(re_big[:,propF,:]))
  905. fin_avg = np.average(prop_avg,axis=0)
  906. fin_std = np.std(prop_avg,axis=0)
  907. if b == 0:
  908. plot_lab.append(pl.bar(x_axis[b]+a/len(sub_sels), fin_avg,yerr=fin_std,width=1/len(sub_sels),alpha=0.5,color=colors[dat]))
  909. else:
  910. pl.bar(x_axis[b]+a/len(sub_sels), fin_avg,yerr=fin_std,width=1/len(sub_sels),alpha=0.5,color=colors[dat])
  911. else:
  912. # We don't want to plot prop1, we want to plot the rest of them
  913. plot_avg = np.average(np.average(temp_bigReshape[:,propF,:],axis=1))
  914. plot_std = np.std(np.std(temp_bigReshape[:,propF,:],axis=1))
  915. sin_avg.append(plot_avg)
  916. sin_std.append(plot_std)
  917. b+=1
  918. a+=1
  919. if bootstrap == False:
  920. full_avg.append(sin_avg)
  921. full_std.append(sin_std)
  922. if bootstrap == False:
  923. for i in np.arange(len(sub_sels)):
  924. ax[0,0].bar(x_axis+i/len(sub_sels), full_avg[i],yerr = full_std[i],alpha = 0.5, width = 1/len(sub_sels),color=colors[i])
  925. #ax[0,0].bar(x_axis+i/len(sub_sels), full_avg[i],alpha = 0.5, width = 1/len(sub_sels))
  926. ax[0,0].legend(label)
  927. else:
  928. ax[0,0].legend(plot_lab,label)
  929. ax[0,0].set_xticks([0.2,1.3,2.4,3.5])
  930. ax[0,0].set_xticklabels(['Charge','Hydrophobicity','Bulkiness','Flexibility'])
  931. ax[0,0].set_xlabel('Biophysical Property')
  932. ax[0,0].set_ylabel('Normalized Property Value')
  933. if saveFmt.lower() == 'png':
  934. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_netAvgProp.png',format='png',dpi=600)
  935. else:
  936. pl.savefig(outputDir+'/AIMS_'+subset_sel+'_netAvgProp.pdf',format='pdf')
  937. pl.close()
  938. ##########################################################################
  939. # Calculating statistical significance
  940. if DOstats:
  941. prop_names = ['Charge','Hydrophobicity','Bulkiness','Flexibility']
  942. take_sub1 = ref_sub[ref_sub[len(sub_matF)-1] == 0].index
  943. take_big1 = sub_big.loc[take_sub1]
  944. temp_bigReshape1 = np.array(take_big1).reshape(len(take_big1),61,posLen)
  945. take_sub2 = ref_sub[ref_sub[len(sub_matF)-1] == 1].index
  946. take_big2 = sub_big.loc[take_sub2]
  947. temp_bigReshape2 = np.array(take_big2).reshape(len(take_big2),61,posLen)
  948. # Significance for Bar plots
  949. bar_sig = []; bar_names = []
  950. for i in np.arange(4):
  951. propF = i+1
  952. data1 = np.average(temp_bigReshape1[:,propF,:],axis=1)
  953. data2 = np.average(temp_bigReshape2[:,propF,:],axis=1)
  954. p = aims.do_statistics(data1,data2,num_reps=10000,test='average')
  955. bar_sig = bar_sig + [p]
  956. bar_names = bar_names + [prop_names[i]]
  957. print('p-value for '+prop_names[i]+' bar plot: '+str(p))
  958. BARsigF = np.vstack((bar_names,bar_sig))
  959. pandas.DataFrame(BARsigF).to_csv(outputDir+'/bar_sig.csv',index=False)
  960. # # Section 13: Position-Sensitive Averaged Biophysical Properties
  961. # Here we effectively average the figures from section 11 over the y-axes, giving a general idea of trends in the biophysical properties of within-group sequences
  962. #
  963. # NOTE: This figure frequently looks chaotic/hard to parse for MSA analysis. More useful for Ig analysis
  964. # In[20]:
  965. # Now get the position sensitive avarege biophysical properties
  966. fig, ax = pl.subplots(2, 1,squeeze=False,figsize=(14,10))
  967. prop_sel = [1,2]
  968. full_avg = []; full_std = []
  969. for dat in np.arange(len(sub_sels)):
  970. a=0
  971. for prop in prop_sel:
  972. take_sub = ref_sub[ref_sub[len(sub_matF)-1] == dat].index
  973. take_big = sub_big.loc[take_sub]
  974. temp_bigReshape = np.array(take_big).reshape(len(take_big),61,posLen)
  975. if bootstrap:
  976. prop_avg = []
  977. for i in np.arange(boots):
  978. re_big = resample(temp_bigReshape)
  979. prop_avg.append(np.average(re_big[:,prop,:],axis=0))
  980. fin_avg = np.average(prop_avg,axis=0)
  981. fin_std = np.std(prop_avg,axis=0)
  982. ax[a,0].plot(fin_avg,marker='o',linewidth=2.5,color=colors[dat])
  983. ax[a,0].fill_between(np.arange(len(fin_avg)),fin_avg+fin_std,fin_avg-fin_std,alpha=0.3,color=colors[dat])
  984. else:
  985. take_sub = ref_sub[ref_sub[len(sub_matF)-1] == dat].index
  986. take_big = sub_big.loc[take_sub]
  987. temp_bigReshape = np.array(take_big).reshape(len(take_big),61,posLen)
  988. plot_avg = np.average(temp_bigReshape[:,prop,:],axis=0)
  989. plot_std = np.std(temp_bigReshape[:,prop,:],axis=0)
  990. ax[a,0].plot(plot_avg,marker='o',linewidth=2.5,color=colors[dat])
  991. ax[a,0].fill_between(np.arange(len(plot_avg)),plot_avg+plot_std,plot_avg-plot_std,alpha=0.3,color=colors[dat])
  992. a+=1
  993. # Draw some nice lines to guide
  994. y11, y12 = ax[0,0].get_ylim();y21, y22 = ax[1,0].get_ylim()
  995. if type(mat_size) != int:
  996. for i in np.arange(len(mat_size)-1):
  997. ax[0,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(y11,y12,100),'black',linewidth = 3)
  998. ax[1,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(y21,y22,100),'black',linewidth = 3)
  999. legend_elements=[]
  1000. for j in np.arange(len(sub_sels)):
  1001. element = [Line2D([0], [0], marker='o', color='w', label=label[j],markerfacecolor=colors[j], markersize=10)]
  1002. legend_elements = legend_elements+element
  1003. pl.legend(handles=legend_elements,ncol=len(sub_sels))
  1004. ax[0,0].set_ylabel('Normalized Charge')
  1005. ax[1,0].set_ylabel('Normalized Hydropathy')
  1006. if type(mat_size)==int:
  1007. ax[0,0].set_xlim([-0.5,mat_size-0.5])
  1008. ax[1,0].set_xlim([-0.5,mat_size-0.5])
  1009. else:
  1010. ax[0,0].set_xlim([-0.5,sum(mat_size)-0.5])
  1011. ax[1,0].set_xlim([-0.5,sum(mat_size)-0.5])
  1012. pl.xlabel('Sequence Position')
  1013. if saveFmt.lower() == 'png':
  1014. pl.savefig(outputDir+'/AIMS_posSensAvg.png',format='png',dpi=600)
  1015. else:
  1016. pl.savefig(outputDir+'/AIMS_posSensAvg.pdf',format='pdf')
  1017. pl.close()
  1018. ##########################################################################
  1019. # Calculating statistical significance
  1020. if DOstats:
  1021. fig, ax = pl.subplots(2, 1,squeeze=False,figsize=(16,10))
  1022. take_sub1 = ref_sub[ref_sub[len(sub_matF)-1] == 0].index
  1023. take_big1 = sub_big.loc[take_sub1]
  1024. temp_bigReshape1 = np.array(take_big1).reshape(len(take_big1),61,posLen)
  1025. take_sub2 = ref_sub[ref_sub[len(sub_matF)-1] == 1].index
  1026. take_big2 = sub_big.loc[take_sub2]
  1027. temp_bigReshape2 = np.array(take_big2).reshape(len(take_big2),61,posLen)
  1028. a=0
  1029. for propF in prop_sel:
  1030. #Significance for position-sensitive
  1031. data1 = temp_bigReshape1[:,propF,:]
  1032. data2 = temp_bigReshape2[:,propF,:]
  1033. p = aims.do_statistics(data1,data2,num_reps=10000,test='average')
  1034. ax[a,0].plot(p,color='black',marker='o')
  1035. ax[a,0].plot(np.arange(np.shape(data1)[1]),np.ones(np.shape(data1)[1])*0.05,linewidth=3,color='red')
  1036. a+=1
  1037. y11, y12 = ax[0,0].get_ylim();y21, y22 = ax[1,0].get_ylim()
  1038. if type(mat_size) != int:
  1039. for i in np.arange(len(mat_size)-1):
  1040. ax[0,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(y11,y12,100),'black',linewidth = 3,linestyle='--')
  1041. ax[1,0].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(y21,y22,100),'black',linewidth = 3,linestyle='--')
  1042. pl.xlabel('Sequence Position')
  1043. ax[0,0].set_ylabel('Charge p-value')
  1044. ax[1,0].set_ylabel('Hydropathy p-value')
  1045. if saveFmt.lower() == 'png':
  1046. pl.savefig(outputDir+'/AIMS_PosSens_pval.png',format='png',dpi=600)
  1047. else:
  1048. pl.savefig(outputDir+'/AIMS_PosSens_pval.pdf',format='pdf')
  1049. pl.close()
  1050. # # Section 14: Information Theoretic Calculations
  1051. # Use the Shannon Entropy and Mutual Information to quantify the diversity and inter-relations between the amino acids used in each sequence within a group
  1052. # NOTE: These metrics are more useful for large datasets, less so far small datasets from DBSCAN or OPTICS identified clustered
  1053. # Also NOTE: Again, these plots can be a bit hard to interpret for longer MSA sequences. Just too many points to read.
  1054. # In[21]:
  1055. # Calculate the Shannon Entropy, a proxy for diversity
  1056. fig = pl.figure(figsize=(16,8))
  1057. gs = gridspec.GridSpec(2, 1,height_ratios=[1,4])
  1058. ax1 = pl.subplot(gs[0])
  1059. ax2 = pl.subplot(gs[1])
  1060. poses = len(seq_MIf)
  1061. entropy = []; frequencies = []; coverage=[]
  1062. for dat in np.arange(len(sub_sels)):
  1063. temp_MI = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == dat].index].iloc[0:-1]
  1064. if bootstrap:
  1065. boot_entropy = []; boot_frequencies = []; boot_cov = []
  1066. for i in np.arange(boots):
  1067. re_MI = resample(np.transpose(np.array(temp_MI)))
  1068. entropy_pre,freq_pre,cov_pre = aims.calculate_shannon(re_MI)
  1069. boot_entropy.append(entropy_pre); boot_frequencies.append(freq_pre)
  1070. boot_cov.append(cov_pre)
  1071. ent_avg = np.average(boot_entropy,axis=0)
  1072. ent_std = np.std(boot_entropy,axis=0)
  1073. freq_avg = np.average(boot_frequencies,axis=0)
  1074. cov_avg = np.average(boot_cov,axis=0)
  1075. ax2.plot(ent_avg,marker='o',linewidth=2.5,color=colors[dat])
  1076. pl.fill_between(np.arange(len(ent_avg)),ent_avg+ent_std,ent_avg-ent_std,alpha=0.3,color=colors[dat])
  1077. entropy.append(ent_avg); frequencies.append(freq_avg); coverage.append(1-cov_avg)
  1078. else:
  1079. entropy_pre,freq_pre,cov_pre = aims.calculate_shannon(np.transpose(np.array(temp_MI)))
  1080. ax2.plot(entropy_pre,marker='o',linewidth=2.5,color=colors[dat])
  1081. entropy.append(entropy_pre); frequencies.append(freq_pre); coverage.append(1-cov_pre)
  1082. ax1.imshow(coverage,aspect='auto',interpolation='nearest',cmap='Greys')
  1083. legend_elements=[]
  1084. for j in np.arange(len(sub_sels)):
  1085. element = [Line2D([0], [0], marker='o', color='w', label=label[j],markerfacecolor=colors[j], markersize=10)]
  1086. legend_elements = legend_elements+element
  1087. pl.legend(handles=legend_elements,ncol=len(sub_sels))
  1088. pl.xlabel('Sequence Position'); pl.ylabel('Shannon Entropy (Bits)')
  1089. if type(mat_size) != int:
  1090. for i in np.arange(len(mat_size)-1):
  1091. ax2.plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(0,4.2,100),'black',linewidth = 3)
  1092. if saveFmt.lower() == 'png':
  1093. pl.savefig(outputDir+'/AIMS_entropy.png',format='png',dpi=600)
  1094. else:
  1095. pl.savefig(outputDir+'/AIMS_entropy.pdf',format='pdf')
  1096. pl.close()
  1097. ############# Do stats? #############
  1098. if DOstats:
  1099. data1 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 0].index].iloc[0:-1]
  1100. data2 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 1].index].iloc[0:-1]
  1101. p_ent = aims.do_statistics(data1,data2,num_reps=1000,test='function',test_func=aims.calculate_shannon,func_val = 0)
  1102. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(10,8))
  1103. pl.plot(p_ent,color='black',marker='o',linewidth=2.5)
  1104. pl.plot(np.arange(len(p_ent)),np.ones(len(p_ent))*0.05,color='red',linewidth=3)
  1105. pl.xlabel('Sequence Position')
  1106. pl.ylabel('p-value')
  1107. if saveFmt.lower() == 'png':
  1108. pl.savefig(outputDir+'/aims_entropy_pval.png',format='png',dpi=600)
  1109. else:
  1110. pl.savefig(outputDir+'/aims_entropy_pval.pdf',format='pdf')
  1111. pl.close()
  1112. # # NOTE: The Mutual Information Calculation Can Be Quite Slow
  1113. # Especially the case for MSAs with many amino acids.
  1114. # In[22]:
  1115. # And then the mutual information:
  1116. fig, ax = pl.subplots(1, len(sub_sels),squeeze=False,figsize=(14,5*len(sub_sels)))
  1117. poses = len(seq_MIf)
  1118. MI = []; ent_cond = []; count = []; MIMax = []
  1119. for dat in np.arange(len(sub_sels)):
  1120. temp_MI = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == dat].index]
  1121. MI_pre,ent_cond_pre,count_pre = aims.calculate_MI(np.transpose(np.array(temp_MI)))
  1122. MIMax.append(np.max(MI_pre))
  1123. MI.append(MI_pre); ent_cond.append(ent_cond_pre); count.append(count_pre)
  1124. ax[0,dat].set_title(label[dat])
  1125. maxmax = np.max(MIMax)
  1126. for dat in np.arange(len(sub_sels)):
  1127. ax[0,dat].imshow(MI[dat],vmin=0,vmax=maxmax,cmap=cm.Greys)
  1128. # Help Guide the eyes a bit
  1129. if type(mat_size) != int:
  1130. for i in np.arange(len(mat_size)-1):
  1131. for j in np.arange(len(sub_sels)):
  1132. ax[0,j].plot( (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(0,poses,100),'black',linewidth = 3)
  1133. ax[0,j].plot( np.linspace(0,poses,100), (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100) ,'black',linewidth = 3)
  1134. if saveFmt.lower() == 'png':
  1135. pl.savefig(outputDir+'/AIMS_MI.png',format='png',dpi=600)
  1136. else:
  1137. pl.savefig(outputDir+'/AIMS_MI.pdf',format='pdf')
  1138. pl.close()
  1139. #####################################################
  1140. # Amino acid frequency comparison for each individual dataset
  1141. # Calculate the probabilities of seeing each amino acid at each position
  1142. fig, ax = pl.subplots(1, 2,squeeze=False,figsize=(18,10))
  1143. #pl.title(str(label[0])+ ' AA Frequency - ' + str(label[1]) + ' AA Frequency')
  1144. AA_key=['A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V']
  1145. freq1 = pandas.DataFrame(frequencies[0][:,1:])
  1146. freq2 = pandas.DataFrame(frequencies[1][:,1:])
  1147. # Remember that the "frequencies" are calculated with your custom
  1148. # key in mind! So you need to carry that down here
  1149. if custom_key:
  1150. freq1.columns = my_AA_key
  1151. freq2.columns = my_AA_key
  1152. fin_key = my_AA_key
  1153. else:
  1154. freq1.columns = AA_key
  1155. freq2.columns = AA_key
  1156. fin_key = AA_key
  1157. x=ax[0,0].pcolormesh(freq1,vmin=0,vmax=0.25,cmap=cm.Greys)
  1158. x=ax[0,1].pcolormesh(freq2,vmin=0,vmax=0.25,cmap=cm.Greys)
  1159. #pl.colorbar(x); pl.ylabel('Sequence Position')
  1160. xax=pl.setp(ax,xticks=np.arange(20)+0.5,xticklabels=fin_key)
  1161. place=0
  1162. if type(mat_size) == int:
  1163. pl.plot(np.arange(21),place*np.ones(21),'black')
  1164. else:
  1165. for i in mat_size:
  1166. place += i
  1167. ax[0,0].plot(np.arange(21),place*np.ones(21),'black')
  1168. ax[0,1].plot(np.arange(21),place*np.ones(21),'black')
  1169. ax[0,0].set_xlabel("Amino Acid")
  1170. ax[0,1].set_xlabel("Amino Acid")
  1171. ax[0,0].set_ylabel("Sequence Position")
  1172. ax[0,1].set_ylabel("Sequence Position")
  1173. #pl.colorbar(x)
  1174. if saveFmt.lower() == 'png':
  1175. pl.savefig(outputDir+'/AIMS_freq_sides.png',format='png',dpi=600)
  1176. else:
  1177. pl.savefig(outputDir+'/AIMS_freq_sides.pdf',format='pdf')
  1178. pl.close()
  1179. # # Section 15: Binary Comparisons Between Datasets
  1180. # Anything from here on requires a comparison between only two datasets. This can be two clusters, two loaded in files, two metadata subsets, whatever.
  1181. #
  1182. # You can continue on from the previous sections even if you were analyzing multiple datasets. The analysis will just, by default, compare only the first two datasets
  1183. # In[23]:
  1184. # A bit easier to look at the DIFFERENCE in mutual information:
  1185. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(14,10))
  1186. max_temp = np.max(MI[0]-MI[1]); min_temp = np.min(MI[0]-MI[1])
  1187. if abs(min_temp) > abs(max_temp):
  1188. propMin = min_temp
  1189. propMax = -min_temp
  1190. elif abs(max_temp) > abs(min_temp):
  1191. propMin = -max_temp
  1192. propMax = max_temp
  1193. else:
  1194. propMin = min_temp
  1195. propMax = max_temp
  1196. x = pl.imshow(MI[0] - MI[1], cmap=cm.PuOr, vmin = propMin, vmax = propMax)
  1197. pl.colorbar(x); pl.title(str(label[0])+ ' MI - ' + str(label[1]) + ' MI')
  1198. # Help Guide the eyes a bit
  1199. if type(mat_size) != int:
  1200. for i in np.arange(len(mat_size)-1):
  1201. ax[0,0].plot((mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100),np.linspace(0,poses,100),'black',linewidth = 3)
  1202. ax[0,0].plot( np.linspace(0,poses,100), (mat_size[i] + sum(mat_size[:i]) - 0.5) * np.ones(100) ,'black',linewidth = 3)
  1203. pl.xlabel('Sequence Position'); pl.ylabel('Sequence Position')
  1204. if saveFmt.lower() == 'png':
  1205. pl.savefig(outputDir+'/AIMS_MIdiff.png',format='png',dpi=600)
  1206. else:
  1207. pl.savefig(outputDir+'/AIMS_MIdiff.pdf',format='pdf')
  1208. pl.close()
  1209. ###############################################################
  1210. # Do stats?
  1211. if DOstats:
  1212. data1 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 0].index].iloc[0:-1]
  1213. data2 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 1].index].iloc[0:-1]
  1214. p_MI = aims.do_statistics(data1,data2,num_reps=MIboots,test='function',test_func=aims.calculate_MI,func_val = 0)
  1215. dim1, dim2 = np.shape(p_MI)
  1216. alpha = 0.05
  1217. empty_mat_mi = np.zeros((dim1,dim2))
  1218. for a in np.arange(dim1):
  1219. for b in np.arange(dim2):
  1220. if p_MI[a,b] < alpha:
  1221. empty_mat_mi[a,b] = 1
  1222. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(10,8))
  1223. x= pl.imshow(empty_mat_mi,cmap=cm.Greys)
  1224. pl.xlabel('Sequence Position')
  1225. pl.ylabel('Sequence Position')
  1226. if saveFmt.lower() == 'png':
  1227. pl.savefig(outputDir+'/AIMS_MIdiff_sig.png',format='png',dpi=600)
  1228. else:
  1229. pl.savefig(outputDir+'/AIMS_MIdiff_sig.pdf',format='pdf')
  1230. pl.close()
  1231. # In[24]:
  1232. # Calculate the probabilities of seeing each amino acid at each position
  1233. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  1234. pl.title(str(label[0])+ ' AA Frequency - ' + str(label[1]) + ' AA Frequency')
  1235. freqMax = np.max(freq1.values-freq2.values); freqMin = np.min(freq1.values-freq2.values)
  1236. freqBound = max(abs(freqMax),abs(freqMin))
  1237. x=ax[0,0].pcolormesh(freq1-freq2,vmin=-freqBound,vmax=freqBound,cmap=cm.PuOr)
  1238. AA_key=['A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V']
  1239. pl.colorbar(x); pl.ylabel('Sequence Position')
  1240. xax=pl.setp(ax,xticks=np.arange(20)+0.5,xticklabels=fin_key)
  1241. place=0
  1242. if type(mat_size) == int:
  1243. pl.plot(np.arange(21),place*np.ones(21),'black')
  1244. else:
  1245. for i in mat_size:
  1246. place += i
  1247. pl.plot(np.arange(21),place*np.ones(21),'black')
  1248. if saveFmt.lower() == 'png':
  1249. pl.savefig(outputDir+'/AIMS_freqDiff.png',format='png',dpi=600)
  1250. else:
  1251. pl.savefig(outputDir+'/AIMS_freqDiff.pdf',format='pdf')
  1252. pl.close()
  1253. if DOstats:
  1254. data1 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 0].index].iloc[0:-1]
  1255. data2 = sub_matF[ref_sub[ref_sub[len(sub_matF)-1] == 1].index].iloc[0:-1]
  1256. p_freq = aims.do_statistics(data1,data2,num_reps=1000,test='function',test_func=aims.calculate_shannon,func_val = 1)
  1257. dim1, dim2 = np.shape(p_freq)
  1258. alpha = 0.05
  1259. AA_key=['A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V']
  1260. empty_mat = np.zeros((dim1,dim2))
  1261. for a in np.arange(dim1):
  1262. for b in np.arange(dim2):
  1263. if p_freq[a,b] < alpha:
  1264. empty_mat[a,b] = 1
  1265. # Need stuff to optionally shift around the x-axis:
  1266. empty_frame = pandas.DataFrame(empty_mat[:,1:])
  1267. if custom_key:
  1268. empty_frame.columns = my_AA_key
  1269. else:
  1270. empty_frame.columns = AA_key
  1271. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(10,8))
  1272. x = pl.pcolormesh(empty_frame,cmap=cm.Greys)
  1273. xax=pl.setp(ax,xticks=np.arange(20)+0.5,xticklabels=fin_key)
  1274. pl.ylabel('Sequence Position')
  1275. pl.xlabel('Amino Acid')
  1276. if saveFmt.lower() == 'png':
  1277. pl.savefig(outputDir+'/aa_diff_statSig.png',format='png',dpi=600)
  1278. else:
  1279. pl.savefig(outputDir+'/aa_diff_statSig.pdf',format='pdf')
  1280. pl.close()
  1281. # # Alright Now We Need to Bring Back in Linear Discriminant Analysis
  1282. # In[25]:
  1283. bigass1 = full_big_re.loc[ref_sub[ref_sub[len(sub_matF)-1] == 0].index]
  1284. seq1_len = len(bigass1)
  1285. bigass1.index = np.arange(seq1_len)
  1286. bigass2 = full_big_re.loc[ref_sub[ref_sub[len(sub_matF)-1] == 1].index]
  1287. seq2_len = len(bigass2)
  1288. bigass2.index = np.arange(seq2_len)
  1289. ######### Run the actual classification here ###############
  1290. # Change the matSize variable to alter the number of vectors used to generate a classification
  1291. bigF,weights,acc_all,mda_all,final,top_names = classy.do_linear_split(bigass1,bigass2,got_big=True,matSize=matSize)
  1292. ############################################################
  1293. import seaborn as sns
  1294. fig = pl.figure(figsize = (12, 12))
  1295. dset = ["Linear Discriminant Analysis" for x in range(seq1_len+seq2_len)]
  1296. reacts = [label[0] for x in range(seq1_len)] + [label[1] for x in range(seq2_len)]
  1297. d1 = {'Dataset': dset, 'Linear Discriminant 1': mda_all.reshape(len(mda_all)),
  1298. 'Reactivity' : reacts}
  1299. df1 = pandas.DataFrame(data=d1)
  1300. sns.set(style="white", color_codes=True,font_scale=1.5)
  1301. sns.swarmplot(x="Dataset", y="Linear Discriminant 1", data=df1, hue = 'Reactivity', palette = "Dark2")
  1302. print("Classification Accuracy")
  1303. print(acc_all)
  1304. if saveFmt.lower() == 'png':
  1305. pl.savefig(outputDir+'/AIMS_LDA.png',format='png',dpi=600)
  1306. else:
  1307. pl.savefig(outputDir+'/AIMS_LDA.pdf',format='pdf')
  1308. pl.close()
  1309. # In[26]:
  1310. # Show the top properties that differentiate the two populations
  1311. # show_top = how many of these top values do you want to show? don't recommend more than ~5
  1312. # solely due to how busy the figure gets
  1313. # Again, see eLife paper for biophysical property definitions
  1314. import seaborn as sns
  1315. show_top = 5
  1316. dset_parse = final[top_names[0:show_top]]
  1317. dset_ID_pre1 = bigF['ID']
  1318. dset_ID_pre2 = dset_ID_pre1.replace(1.0,label[0])
  1319. dset_ID = dset_ID_pre2.replace(2.0,label[1])
  1320. bigass_parse_dset = pandas.concat([dset_parse,dset_ID],axis = 1)
  1321. try:
  1322. sns.pairplot(bigass_parse_dset,hue = 'ID')
  1323. if saveFmt.lower() == 'png':
  1324. pl.savefig(outputDir+'/AIMS_pairplot.png',format='png',dpi=600)
  1325. else:
  1326. pl.savefig(outputDir+'/AIMS_pairplot.pdf',format='pdf')
  1327. pl.close()
  1328. except:
  1329. print("Pairplot failed")
  1330. # # Detailed Amino Acid Frequency Breakdowns
  1331. # This feature is most useful in peptide analysis, but could be useful for general repertoire comparisons
  1332. # In[27]:
  1333. # Need to get back just our sequences of interest.
  1334. # Need to get back just our sequences of interest.
  1335. seq1 = sub_seqF.loc[ref_sub[ref_sub[len(sub_matF)-1] == 0].index]
  1336. seq2 = sub_seqF.loc[ref_sub[ref_sub[len(sub_matF)-1] == 1].index]
  1337. # Calculate both position-insensitive amino acid frequency and digram frequencies
  1338. # Can either normalize to the # of sequences or total number of AA (num_seq or num_AA)
  1339. AA_freq_all1, digram_all1 = aims.full_AA_freq(seq1,norm='num_AA')
  1340. AA_freq_all2, digram_all2 = aims.full_AA_freq(seq2,norm='num_AA')
  1341. freqAll1 = np.transpose(pandas.DataFrame(AA_freq_all1))
  1342. freqAll1.columns = AA_key
  1343. freqAll2 = np.transpose(pandas.DataFrame(AA_freq_all2))
  1344. freqAll2.columns = AA_key
  1345. freqAll1_df = freqAll1[fin_key]
  1346. freqAll2_df = freqAll2[fin_key]
  1347. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  1348. ax[0,0].bar(np.arange(len(AA_freq_all1)),freqAll1_df.values[0], color=colors[0],alpha=0.5)
  1349. ax[0,0].bar(np.arange(len(AA_freq_all2)),freqAll2_df.values[0],color=colors[1],alpha=0.5)
  1350. xax=pl.setp(ax,xticks=np.arange(20),xticklabels=fin_key)
  1351. ax[0,0].legend([label[0],label[1]])
  1352. pl.ylabel('Frequency')
  1353. pl.xlabel('Amino Acid')
  1354. if saveFmt.lower() == 'png':
  1355. pl.savefig(outputDir+'/AIMS_AAnetProb.png',format='png',dpi=600)
  1356. else:
  1357. pl.savefig(outputDir+'/AIMS_AAnetProb.pdf',format='pdf')
  1358. pl.close()
  1359. #########################################################
  1360. if DOstats:
  1361. data1 = seqF[ref_sub[ref_sub[len(sub_matF)-1] == 0].index]
  1362. data2 = seqF[ref_sub[ref_sub[len(sub_matF)-1] == 1].index]
  1363. p_freq = aims.do_statistics(data1,data2,num_reps=boots,test='function',test_func=aims.full_AA_freq,func_val = 0)
  1364. p_df = np.transpose(pandas.DataFrame(p_freq))
  1365. p_df.columns = AA_key
  1366. p_df_fin = p_df[fin_key]
  1367. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  1368. pl.plot(p_df_fin.values[0],marker='o',linewidth=3,color='black')
  1369. pl.plot(np.arange(20),np.ones(20)*0.05,linewidth=3,color='red')
  1370. xax=pl.setp(ax,xticks=np.arange(20),xticklabels=fin_key)
  1371. if saveFmt.lower() == 'png':
  1372. pl.savefig(outputDir+'/AIMS_AAnetProb_pval.png',format='png',dpi=600)
  1373. else:
  1374. pl.savefig(outputDir+'/AIMS_AAnetProb_pval.pdf',format='pdf')
  1375. pl.close()
  1376. # # Plot the Digram Frequencies Per Sequence (or per AA)
  1377. # There can occasionally be interesting information in these digram patterns, particularly if there are notabe differences between populations.
  1378. #
  1379. # Note, while the matrix could possibly appear symmetric, it need not be so. The y-axis gives the first amino acid in the digram, the x-axis gives the second. So on the x,y coordinate map, E (x-axis) and D (y-axis) gives the frequency of the digram DE.
  1380. # In[28]:
  1381. fig, ax = pl.subplots(int(len(label)/2), 2,squeeze=False,figsize=(16,12))
  1382. plot_max = np.max([np.max(digram_all1),np.max(digram_all2)])
  1383. ax[0,0].set_title(str(label[0])+ ' Digram Frequency')
  1384. ax[0,1].set_title(str(label[1])+ ' Digram Frequency')
  1385. # Again for potentially altering the AA axis how you want:
  1386. digAll1 = pandas.DataFrame(digram_all1)
  1387. digAll1.columns = AA_key; digAll1.index = AA_key
  1388. digAll2 = pandas.DataFrame(digram_all2)
  1389. digAll2.columns = AA_key; digAll2.index = AA_key
  1390. digF1 = digAll1[fin_key].loc[fin_key]
  1391. digF2 = digAll2[fin_key].loc[fin_key]
  1392. x1 = ax[0,0].imshow(digF1, cmap = cm.Greys,vmin=0,vmax=plot_max)
  1393. x2 = ax[0,1].imshow(digF2, cmap = cm.Greys,vmin=0,vmax=plot_max)
  1394. xax=pl.setp(ax[0,0],xticks=np.arange(20),xticklabels=fin_key)
  1395. yax=pl.setp(ax[0,0],yticks=np.arange(20),yticklabels=fin_key)
  1396. xax=pl.setp(ax[0,1],xticks=np.arange(20),xticklabels=fin_key)
  1397. yax=pl.setp(ax[0,1],yticks=np.arange(20),yticklabels=fin_key)
  1398. fig.colorbar(x1, ax=ax[0, 0], shrink=0.5)
  1399. fig.colorbar(x2, ax=ax[0, 1], shrink=0.5)
  1400. if saveFmt.lower() == 'png':
  1401. pl.savefig(outputDir+'/AIMS_digram_sep.png',format='png',dpi=600)
  1402. else:
  1403. pl.savefig(outputDir+'/AIMS_digram_sep.pdf',format='pdf')
  1404. pl.close()
  1405. # In[29]:
  1406. # Look at the difference in these digram frequencies
  1407. # For now, hard to say if these are necessarily significant,
  1408. # more downstream analysis is needed to tease out conclusions
  1409. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(16,8))
  1410. pl.title('Peptide Digram Difference')
  1411. min_temp = np.min(digram_all2-digram_all1)
  1412. max_temp = np.max(digram_all2-digram_all1)
  1413. if abs(min_temp) > abs(max_temp):
  1414. propMin = min_temp
  1415. propMax = -min_temp
  1416. elif abs(max_temp) > abs(min_temp):
  1417. propMin = -max_temp
  1418. propMax = max_temp
  1419. else:
  1420. propMin = min_temp
  1421. propMax = max_temp
  1422. zzz = pl.imshow(digF1-digF2, vmin = propMin, vmax = propMax, cmap = cm.PuOr)
  1423. xax=pl.setp(ax,xticks=np.arange(20),xticklabels=fin_key)
  1424. yax=pl.setp(ax,yticks=np.arange(20),yticklabels=fin_key)
  1425. ax[0,0].set_ylabel('First Amino Acid')
  1426. ax[0,0].set_xlabel('Second Amino Acid')
  1427. pl.colorbar(zzz)
  1428. if saveFmt.lower() == 'png':
  1429. pl.savefig(outputDir+'/AIMS_digramDiff.png',format='png',dpi=600)
  1430. else:
  1431. pl.savefig(outputDir+'/AIMS_digramDiff.pdf',format='pdf')
  1432. pl.close()
  1433. ##################################################
  1434. # Do statistics?
  1435. if DOstats:
  1436. data1 = seqF[ref_sub[ref_sub[len(sub_matF)-1] == 0].index]
  1437. data2 = seqF[ref_sub[ref_sub[len(sub_matF)-1] == 1].index]
  1438. p_digram = aims.do_statistics(data1,data2,num_reps=1000,test='function',test_func=aims.full_AA_freq,func_val = 1)
  1439. dim1, dim2 = np.shape(p_digram)
  1440. alpha = 0.05
  1441. empty_mat_digram = np.zeros((dim1,dim2))
  1442. for a in np.arange(dim1):
  1443. for b in np.arange(dim2):
  1444. if p_digram[a,b] < alpha:
  1445. empty_mat_digram[a,b] = 1
  1446. fin_key = my_AA_key
  1447. empty_dig = pandas.DataFrame(empty_mat_digram)
  1448. empty_dig.columns = AA_key; empty_dig.index = AA_key
  1449. empty_digF = empty_dig[fin_key].loc[fin_key]
  1450. fig, ax = pl.subplots(1, 1,squeeze=False,figsize=(10,8))
  1451. pl.title('Peptide Digram Difference Stat. Sig.')
  1452. x= pl.imshow(empty_digF,cmap=cm.Greys)
  1453. xax=pl.setp(ax,xticks=np.arange(20),xticklabels=fin_key)
  1454. xax=pl.setp(ax,yticks=np.arange(20),yticklabels=fin_key)
  1455. ax[0,0].set_ylabel('First Amino Acid')
  1456. ax[0,0].set_xlabel('Second Amino Acid')
  1457. if saveFmt.lower() == 'png':
  1458. pl.savefig(outputDir+'/digram_diff_sig.png',format='png',dpi=600)
  1459. else:
  1460. pl.savefig(outputDir+'/digram_diff_sig.pdf',format='pdf')
  1461. pl.close()
  1462. if __name__ == '__main__':
  1463. x=run()
  1464. print(x)

aims_cli.py at commit a179d9a, under MIT · at the source

Overview

Authors: Wang Zhihuan1, Emi Furusawa-Nishii1, Sachiko Miyake1
  1. Department of Immunology, Juntendo University Faculty of Medicine,2-1-1 Hongo, Bunkyo-ku, Tokyo, 113-8421 Japan
Institutions: Juntendo University (Japan)
Journal: Scientific reports, volume 16, issue 1, article 12518
Dates: received 29 October 2025; accepted 29 January 2026; published online 7 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41598-026-38351-8 · PMID 41794902 · PMCID PMC13087195 · OpenAlex W7134133663
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), Alzheimer's / dementia (population), cellular / molecular (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions
Keywords: Alzheimer’s disease, Aging, CD8+ tissue-resident memory T cells, C-X-C chemokine receptor type 6, Computational biology and bioinformatics, Immunology, Neuroscience
MeSH: Alzheimer Disease*, Brain*, CD8-Positive T-Lymphocytes*, Receptors, Antigen, T-Cell*, Aging, Animals, Disease Models, Animal, Memory T Cells, Mice, Phenotype, Receptors, CXCR6 (* major topic)
Topic: Neuroinflammation and Neurodegeneration Mechanisms (Neurology, Neuroscience), according to OpenAlex
Funding: Visionary Council on the Moonshot Research and Development Program (JPMJMS2024-6)
Citations: cited by 2 papers (Europe PMC); 32 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.

ctboughter/AIMS_manuscripts

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 2772b8ecd6a47a17cce64c2d1d7028511798ca9a, 29 September 2023
Languages: Jupyter (12), Python (9), Shell (2)
Size: 176 files, 23 scripts
Software Heritage: not archived
Found in: the text, “AIMS analysis”
Holds: README, license file, 12 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (20 files), pandas (19 files), scikit-learn (14 files), Matplotlib (12 files), seaborn (8 files), Biopython (3 files), SciPy (3 files), UMAP (3 files)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
25 files

ctboughter/AIMS

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: a179d9a29882c5bbb886257e6c104d53ede773ae, 24 June 2026
Languages: Python (10), Jupyter (1)
Size: 167 files, 11 scripts
Software Heritage: not archived
Found in: the text, “AIMS analysis”
Holds: README, license file, environment (pyproject.toml, docs/source/requirements.txt), documentation, 1 notebook
Not found: CITATION.cff, tests, continuous integration
Tools: NumPy (6 files), pandas (6 files), Matplotlib (5 files), scikit-learn (5 files), SciPy (3 files), seaborn (3 files), UMAP (3 files), Biopython (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
13 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 34 scripts, each with its path and the digest of its content;
  • 3 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • it says that the code is available on request

Read it in the paper: doi.org/10.1038/s41598-026-38351-8.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 7 keywords, 11 MeSH terms, 1 funder, 32 references.

Cite

This paper

Zhihuan, W., Furusawa-Nishii, E., & Miyake, S. (2026). Unique phenotypic and T cell receptor characteristics of CD8&lt;sup&gt;+&lt;/sup&gt; T cells accumulated in the brains of Alzheimer's disease mice. Scientific reports, 16(1), 12518. https://doi.org/10.1038/s41598-026-38351-8

BibTeX

@article{zhihuan2026unique,
author = {Zhihuan, Wang and Furusawa-Nishii, Emi and Miyake, Sachiko},
title = {{Unique phenotypic and T cell receptor characteristics of CD8\&lt;sup\&gt;+\&lt;/sup\&gt; T cells accumulated in the brains of Alzheimer's disease mice}},
journal = {Scientific reports},
year = {2026},
month = mar,
volume = {16},
number = {1},
pages = {12518},
publisher = {Nature Publishing Group},
issn = {2045-2322},
doi = {10.1038/s41598-026-38351-8},
url = {https://doi.org/10.1038/s41598-026-38351-8},
pmid = {41794902},
pmcid = {PMC13087195}
}

RIS

TY - JOUR
AU - Zhihuan, Wang
AU - Furusawa-Nishii, Emi
AU - Miyake, Sachiko
TI - Unique phenotypic and T cell receptor characteristics of CD8&lt;sup&gt;+&lt;/sup&gt; T cells accumulated in the brains of Alzheimer's disease mice
T2 - Scientific reports
J2 - Sci Rep
PY - 2026
DA - 2026/03/07
VL - 16
IS - 1
SP - 12518
SN - 2045-2322
PB - Nature Publishing Group
DO - 10.1038/s41598-026-38351-8
UR - https://doi.org/10.1038/s41598-026-38351-8
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41598-026-38351-8",
"type": "article-journal",
"title": "Unique phenotypic and T cell receptor characteristics of CD8&lt;sup&gt;+&lt;/sup&gt; T cells accumulated in the brains of Alzheimer's disease mice",
"container-title": "Scientific reports",
"author": [
{
"family": "Zhihuan",
"given": "Wang"
},
{
"family": "Furusawa-Nishii",
"given": "Emi"
},
{
"family": "Miyake",
"given": "Sachiko"
}
],
"container-title-short": "Sci Rep",
"volume": "16",
"issue": "1",
"page": "12518",
"DOI": "10.1038/s41598-026-38351-8",
"PMID": "41794902",
"PMCID": "PMC13087195",
"ISSN": "2045-2322",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41598-026-38351-8",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
7
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41593-026-02293-1 [code]
Optics-free spatial genomics for mapping mammalian brain aging by IRISeq.
Journal: Nature neuroscience
In common: Biopython, UMAP, seaborn, 5 other tools, mouse, cellular / molecular, 1 reference
[2] doi:10.1038/s44318-026-00818-9 [code]
FAM134B-mediated ER-phagy degrades APP and suppresses Alzheimer's disease pathology.
Journal: The EMBO journal
In common: Biopython, UMAP, seaborn, 5 other tools, Alzheimer's / dementia, mouse, cellular / molecular
[3] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Biopython, UMAP, seaborn, 5 other tools, mouse, cellular / molecular
[4] doi:10.1038/s41592-026-03057-2 [code]
CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.
Journal: Nature methods
In common: Biopython, UMAP, seaborn, 5 other tools, mouse
[5] doi:10.1038/s41467-026-76675-1 [code]
Long-read proteogenomic atlas of human neuronal differentiation reveals isoform diversity informing neurodevelopmental risk mechanisms.
Journal: Nature communications
In common: Biopython, UMAP, seaborn, 5 other tools
[6] doi:10.64898/2026.03.30.714220 [code]
An integrated single cell and spatial omics atlas of human prenatal development
Journal: bioRxiv (preprint)
In common: Biopython, UMAP, seaborn, 5 other tools
[7] doi:10.1186/s13059-026-04177-w [code]
Genomic sequence evolution underlying human neocortical interareal diversification.
Journal: Genome biology
In common: UMAP, seaborn, scikit-learn, 4 other tools, mouse, cellular / molecular, 1 reference
[8] doi:10.1126/sciadv.aed2952 [code]
Activation of transposable elements is linked to a region- and cell type-specific interferon response in Parkinson's disease.
Journal: Science advances
In common: Biopython, UMAP, seaborn, 4 other tools, cellular / molecular
[9] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: UMAP, seaborn, scikit-learn, 4 other tools, cellular / molecular, 1 reference
[10] doi:10.1038/s41467-026-75700-7 [code]
Gene regulatory innovations from transposable elements in primate cerebellum development.
Journal: Nature communications
In common: Biopython, seaborn, scikit-learn, 4 other tools, mouse, cellular / molecular

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.