Package Bio :: Package SubsMat
[hide private]
[frames] | no frames]

Source Code for Package Bio.SubsMat

  1  """Substitution matrices, log odds matrices, and operations on them. 
  2  """ 
  3  import re 
  4  import sys 
  5  import copy 
  6  import math 
  7  # BioPython imports 
  8  from Bio import Alphabet 
  9  from Bio.SubsMat import FreqTable 
 10  # from Bio.Tools import statfns 
 11   
 12  log = math.log 
 13  # Matrix types 
 14  NOTYPE = 0 
 15  ACCREP = 1 
 16  OBSFREQ = 2 
 17  SUBS = 3 
 18  EXPFREQ = 4 
 19  LO = 5 
 20  EPSILON = 0.00000000000001 
21 -class BadMatrix(Exception):
22 """Exception raised when verifying a matrix"""
23 - def __str__(self):
24 return "Bad Matrix"
25 BadMatrixError = BadMatrix() 26 27 # 5/2001 added the following: 28 # * Methods for subtraction, addition and multiplication of matrices 29 # * Generation of an expected frequency table from an observed frequency matrix 30 # * Calculation of linear correlation coefficient between two matrices. Needs 31 # Bio.Tools.statfns 32 # * Calculation of relative entropy is now done using the _make_relative_entropy method 33 # and is stored in the member self.relative_entropy 34 # * Calculation of entropy is now done using the _make_entropy method and is stored in 35 # the member self.entropy 36 # * Jensen-Shannon distance between the distributions from which the matrices are 37 # derived. This is a distance function based on the distribution's entropies. 38 # 39 # Substitution matrix routines 40 # Iddo Friedberg idoerg@cc.huji.ac.il 41 # Biopython license applies (http://biopython.org) 42 # 43 # General: 44 # ------- 45 # You should have python 2.0 or above. 46 # http://www.python.org 47 # You should have biopython (http://biopython.org) installed. 48 # 49 # This module provides a class and a few routines for generating 50 # substitution matrices, similar ot BLOSUM or PAM matrices, but based on 51 # user-provided data. 52 # The class used for these matrices is SeqMat 53 # 54 # Matrices are implemented as a user dictionary. Each index contains a 55 # 2-tuple, which are the two residue/nucleotide types replaced. The value 56 # differs according to the matrix's purpose: e.g in a log-odds frequency 57 # matrix, the value would be log(Pij/(Pi*Pj)) where: 58 # Pij: frequency of substitution of letter (residue/nucletide) i by j 59 # Pi, Pj: expected frequencies of i and j, respectively. 60 # 61 # Usage: 62 # ----- 63 # The following section is layed out in the order by which most people wish 64 # to generate a log-odds matrix. Of course, interim matrices can be 65 # generated and investigated. Most people just want a log-odds matrix, 66 # that's all. 67 # 68 # Generating an Accepted Replacement Matrix: 69 # ----------------------------------------- 70 # Initially, you should generate an accepted replacement matrix 71 # (ARM) from your data. The values in ARM are the _counted_ number of 72 # replacements according to your data. The data could be a set of pairs 73 # or multiple alignments. So for instance if Alanine was replaced by 74 # Cysteine 10 times, and Cysteine by Alanine 12 times, the corresponding 75 # ARM entries would be: 76 # ['A','C']: 10, ['C','A'] 12 77 # as order doesn't matter, user can already provide only one entry: 78 # ['A','C']: 22 79 # A SeqMat instance may be initialized with either a full (first 80 # method of counting: 10, 12) or half (the latter method, 22) matrices. A 81 # Full protein alphabet matrix would be of the size 20x20 = 400. A Half 82 # matrix of that alphabet would be 20x20/2 + 20/2 = 210. That is because 83 # same-letter entries don't change. (The matrix diagonal). Given an 84 # alphabet size of N: 85 # Full matrix size:N*N 86 # Half matrix size: N(N+1)/2 87 # 88 # If you provide a full matrix, the constructore will create a half-matrix 89 # automatically. 90 # If you provide a half-matrix, make sure 91 # of a (low, high) sorted order in the keys: there should only be 92 # a ('A','C') not a ('C','A'). 93 # 94 # Internal functions: 95 # 96 # Generating the observed frequency matrix (OFM): 97 # ---------------------------------------------- 98 # Use: OFM = _build_obs_freq_mat(ARM) 99 # The OFM is generated from the ARM, only instead of replacement counts, it 100 # contains replacement frequencies. 101 # Generating an expected frequency matrix (EFM): 102 # --------------------------------------------- 103 # Use: EFM = _build_exp_freq_mat(OFM,exp_freq_table) 104 # exp_freq_table: should be a freqTableC instantiation. See freqTable.py for 105 # detailed information. Briefly, the expected frequency table has the 106 # frequencies of appearance for each member of the alphabet 107 # Generating a substitution frequency matrix (SFM): 108 # ------------------------------------------------ 109 # Use: SFM = _build_subs_mat(OFM,EFM) 110 # Accepts an OFM, EFM. Provides the division product of the corresponding 111 # values. 112 # Generating a log-odds matrix (LOM): 113 # ---------------------------------- 114 # Use: LOM=_build_log_odds_mat(SFM[,logbase=10,factor=10.0,roundit=1]) 115 # Accepts an SFM. logbase: base of the logarithm used to generate the 116 # log-odds values. factor: factor used to multiply the log-odds values. 117 # roundit: default - true. Whether to round the values. 118 # Each entry is generated by log(LOM[key])*factor 119 # And rounded if required. 120 # 121 # External: 122 # --------- 123 # In most cases, users will want to generate a log-odds matrix only, without 124 # explicitly calling the OFM --> EFM --> SFM stages. The function 125 # build_log_odds_matrix does that. User provides an ARM and an expected 126 # frequency table. The function returns the log-odds matrix 127 #
128 -class SeqMat(dict):
129 """A Generic sequence matrix class 130 The key is a 2-tuple containing the letter indices of the matrix. Those 131 should be sorted in the tuple (low, high). Because each matrix is dealt 132 with as a half-matrix.""" 133
134 - def _alphabet_from_matrix(self):
135 ab_dict = {} 136 s = '' 137 for i in self.keys(): 138 ab_dict[i[0]] = 1 139 ab_dict[i[1]] = 1 140 letters_list = ab_dict.keys() 141 letters_list.sort() 142 for i in letters_list: 143 s = s + i 144 self.alphabet.letters = s
145
146 - def __init__(self,data=None, alphabet=None, 147 mat_type=NOTYPE,mat_name='',build_later=0):
148 # User may supply: 149 # data: matrix itself 150 # mat_type: its type. See below 151 # mat_name: its name. See below. 152 # alphabet: an instance of Bio.Alphabet, or a subclass. If not 153 # supplied, constructor builds its own from that matrix.""" 154 # build_later: skip the matrix size assertion. User will build the 155 # matrix after creating the instance. Constructor builds a half matrix 156 # filled with zeroes. 157 158 assert type(mat_type) == type(1) 159 assert type(mat_name) == type('') 160 161 # "data" may be: 162 # 1) None --> then self.data is an empty dictionary 163 # 2) type({}) --> then self.data takes the items in data 164 # 3) An instance of SeqMat 165 # This whole creation-during-execution is done to avoid changing 166 # default values, the way Python does because default values are 167 # created when the function is defined, not when it is created. 168 assert (type(data) == type({}) or isinstance(data,dict) or 169 data == None) 170 if data == None: 171 data = {} 172 else: 173 self.update(data) 174 if alphabet == None: 175 alphabet = Alphabet.Alphabet() 176 assert Alphabet.generic_alphabet.contains(alphabet) 177 self.alphabet = alphabet 178 179 # If passed alphabet is empty, use the letters in the matrix itself 180 if not self.alphabet.letters: 181 self._alphabet_from_matrix() 182 # Assert matrix size: half or full 183 if not build_later: 184 N = len(self.alphabet.letters) 185 assert len(self) == N**2 or len(self) == N*(N+1)/2 186 self.ab_list = list(self.alphabet.letters) 187 self.ab_list.sort() 188 # type can be: ACCREP, OBSFREQ, SUBS, EXPFREQ, LO 189 self.mat_type = mat_type 190 # Names: a string like "BLOSUM62" or "PAM250" 191 self.mat_name = mat_name 192 if build_later: 193 self._init_zero() 194 else: 195 # Convert full to half if matrix is not already a log-odds matrix 196 if self.mat_type != LO: 197 self._full_to_half() 198 self._correct_matrix() 199 self.sum_letters = {} 200 self.relative_entropy = 0
201
202 - def _correct_matrix(self):
203 keylist = self.keys() 204 for key in keylist: 205 if key[0] > key[1]: 206 self[(key[1],key[0])] = self[key] 207 del self[key]
208
209 - def _full_to_half(self):
210 """ 211 Convert a full-matrix to a half-matrix 212 """ 213 # For instance: two entries ('A','C'):13 and ('C','A'):20 will be summed 214 # into ('A','C'): 33 and the index ('C','A') will be deleted 215 # alphabet.letters:('A','A') and ('C','C') will remain the same. 216 217 N = len(self.alphabet.letters) 218 # Do nothing if this is already a half-matrix 219 if len(self) == N*(N+1)/2: 220 return 221 for i in self.ab_list: 222 for j in self.ab_list[:self.ab_list.index(i)+1]: 223 if i != j: 224 self[j,i] = self[j,i] + self[i,j] 225 del self[i,j]
226
227 - def _init_zero(self):
228 for i in self.ab_list: 229 for j in self.ab_list[:self.ab_list.index(i)+1]: 230 self[j,i] = 0.
231
232 - def make_relative_entropy(self,obs_freq_mat):
233 """if this matrix is a log-odds matrix, return its entropy 234 Needs the observed frequency matrix for that""" 235 ent = 0. 236 if self.mat_type == LO: 237 for i in self.keys(): 238 ent += obs_freq_mat[i]*self[i]/log(2) 239 elif self.mat_type == SUBS: 240 for i in self.keys(): 241 if self[i] > EPSILON: 242 ent += obs_freq_mat[i]*log(self[i])/log(2) 243 else: 244 raise TypeError("entropy: substitution or log-odds matrices only") 245 self.relative_entropy = ent
246 #
247 - def make_entropy(self):
248 self.entropy = 0 249 for i in self.keys(): 250 if self[i] > EPSILON: 251 self.entropy += self[i]*log(self[i])/log(2) 252 self.entropy = -self.entropy
253 - def letter_sum(self,letter):
254 assert letter in self.alphabet.letters 255 sum = 0. 256 for i in self.keys(): 257 if letter in i: 258 if i[0] == i[1]: 259 sum += self[i] 260 else: 261 sum += (self[i] / 2.) 262 return sum
263
264 - def all_letters_sum(self):
265 for letter in self.alphabet.letters: 266 self.sum_letters[letter] = self.letter_sum(letter)
267 - def print_full_mat(self,f=None,format="%4d",topformat="%4s", 268 alphabet=None,factor=1,non_sym=None):
269 f = f or sys.stdout 270 # create a temporary dictionary, which holds the full matrix for 271 # printing 272 assert non_sym == None or type(non_sym) == type(1.) or \ 273 type(non_sym) == type(1) 274 full_mat = copy.copy(self) 275 for i in self: 276 if i[0] != i[1]: 277 full_mat[(i[1],i[0])] = full_mat[i] 278 if not alphabet: 279 alphabet = self.ab_list 280 topline = '' 281 for i in alphabet: 282 topline = topline + topformat % i 283 topline = topline + '\n' 284 f.write(topline) 285 for i in alphabet: 286 outline = i 287 for j in alphabet: 288 if alphabet.index(j) > alphabet.index(i) and non_sym is not None: 289 val = non_sym 290 else: 291 val = full_mat[i,j] 292 val *= factor 293 if val <= -999: 294 cur_str = ' ND' 295 else: 296 cur_str = format % val 297 298 outline = outline+cur_str 299 outline = outline+'\n' 300 f.write(outline)
301
302 - def print_mat(self,f=None,format="%4d",bottomformat="%4s", 303 alphabet=None,factor=1):
304 """Print a nice half-matrix. f=sys.stdout to see on the screen 305 User may pass own alphabet, which should contain all letters in the 306 alphabet of the matrix, but may be in a different order. This 307 order will be the order of the letters on the axes""" 308 309 f = f or sys.stdout 310 if not alphabet: 311 alphabet = self.ab_list 312 bottomline = '' 313 for i in alphabet: 314 bottomline = bottomline + bottomformat % i 315 bottomline = bottomline + '\n' 316 for i in alphabet: 317 outline = i 318 for j in alphabet[:alphabet.index(i)+1]: 319 try: 320 val = self[j,i] 321 except KeyError: 322 val = self[i,j] 323 val *= factor 324 if val == -999: 325 cur_str = ' ND' 326 else: 327 cur_str = format % val 328 329 outline = outline+cur_str 330 outline = outline+'\n' 331 f.write(outline) 332 f.write(bottomline)
333 - def __sub__(self,other):
334 """ returns a number which is the subtraction product of the two matrices""" 335 mat_diff = 0 336 for i in self.keys(): 337 mat_diff += (self[i] - other[i]) 338 return mat_diff
339 - def __mul__(self,other):
340 """ returns a matrix for which each entry is the multiplication product of the 341 two matrices passed""" 342 new_mat = copy.copy(self) 343 for i in self.keys(): 344 new_mat[i] *= other[i] 345 return new_mat
346 - def __sum__(self, other):
347 new_mat = copy.copy(self) 348 for i in self.keys(): 349 new_mat[i] += other[i] 350 return new_mat
351
352 -def _build_obs_freq_mat(acc_rep_mat):
353 """ 354 build_obs_freq_mat(acc_rep_mat): 355 Build the observed frequency matrix, from an accepted replacements matrix 356 The accRep matrix should be generated by the user. 357 """ 358 # Note: acc_rep_mat should already be a half_matrix!! 359 sum = 0. 360 for i in acc_rep_mat.values(): 361 sum += i 362 obs_freq_mat = SeqMat(alphabet=acc_rep_mat.alphabet,build_later=1) 363 for i in acc_rep_mat.keys(): 364 obs_freq_mat[i] = acc_rep_mat[i]/sum 365 obs_freq_mat.mat_type = OBSFREQ 366 return obs_freq_mat
367
368 -def _exp_freq_table_from_obs_freq(obs_freq_mat):
369 exp_freq_table = {} 370 for i in obs_freq_mat.alphabet.letters: 371 exp_freq_table[i] = 0. 372 for i in obs_freq_mat.keys(): 373 if i[0] == i[1]: 374 exp_freq_table[i[0]] += obs_freq_mat[i] 375 else: 376 exp_freq_table[i[0]] += obs_freq_mat[i] / 2. 377 exp_freq_table[i[1]] += obs_freq_mat[i] / 2. 378 return FreqTable.FreqTable(exp_freq_table,FreqTable.FREQ)
379
380 -def _build_exp_freq_mat(exp_freq_table):
381 """Build an expected frequency matrix 382 exp_freq_table: should be a FreqTable instance 383 """ 384 exp_freq_mat = SeqMat(alphabet=exp_freq_table.alphabet,build_later=1) 385 for i in exp_freq_mat.keys(): 386 if i[0] == i[1]: 387 exp_freq_mat[i] = exp_freq_table[i[0]]**2 388 else: 389 exp_freq_mat[i] = 2.0*exp_freq_table[i[0]]*exp_freq_table[i[1]] 390 exp_freq_mat.mat_type = EXPFREQ 391 return exp_freq_mat
392 # 393 # Build the substitution matrix 394 #
395 -def _build_subs_mat(obs_freq_mat,exp_freq_mat):
396 """ Build the substitution matrix """ 397 if obs_freq_mat.ab_list != exp_freq_mat.ab_list: 398 raise ValueError("Alphabet mismatch in passed matrices") 399 subs_mat = SeqMat(obs_freq_mat) 400 for i in obs_freq_mat.keys(): 401 subs_mat[i] = obs_freq_mat[i]/exp_freq_mat[i] 402 403 subs_mat.mat_type = SUBS 404 return subs_mat
405 406 # 407 # Build a log-odds matrix 408 #
409 -def _build_log_odds_mat(subs_mat,logbase=2,factor=10.0,round_digit=0,keep_nd=0):
410 """_build_log_odds_mat(subs_mat,logbase=10,factor=10.0,round_digit=1): 411 Build a log-odds matrix 412 logbase=2: base of logarithm used to build (default 2) 413 factor=10.: a factor by which each matrix entry is multiplied 414 round_digit: roundoff place after decimal point 415 keep_nd: if true, keeps the -999 value for non-determined values (for which there 416 are no substitutions in the frequency substitutions matrix). If false, plants the 417 minimum log-odds value of the matrix in entries containing -999 418 """ 419 lo_mat = SeqMat(subs_mat) 420 for i in subs_mat.keys(): 421 if subs_mat[i] < EPSILON: 422 lo_mat[i] = -999 423 else: 424 lo_mat[i] = round(factor*log(subs_mat[i])/log(logbase),round_digit) 425 lo_mat.mat_type = LO 426 mat_min = min(lo_mat.values()) 427 if not keep_nd: 428 for i in lo_mat.keys(): 429 if lo_mat[i] <= -999: 430 lo_mat[i] = mat_min 431 return lo_mat
432 433 # 434 # External function. User provides an accepted replacement matrix, and, 435 # optionally the following: expected frequency table, log base, mult. factor, 436 # and rounding factor. Generates a log-odds matrix, calling internal SubsMat 437 # functions. 438 #
439 -def make_log_odds_matrix(acc_rep_mat,exp_freq_table=None,logbase=2, 440 factor=1.,round_digit=9,keep_nd=0):
441 obs_freq_mat = _build_obs_freq_mat(acc_rep_mat) 442 if not exp_freq_table: 443 exp_freq_table = _exp_freq_table_from_obs_freq(obs_freq_mat) 444 exp_freq_mat = _build_exp_freq_mat(exp_freq_table) 445 subs_mat = _build_subs_mat(obs_freq_mat, exp_freq_mat) 446 lo_mat = _build_log_odds_mat(subs_mat,logbase,factor,round_digit,keep_nd) 447 lo_mat.make_relative_entropy(obs_freq_mat) 448 return lo_mat
449 -def observed_frequency_to_substitution_matrix(obs_freq_mat):
450 exp_freq_table = _exp_freq_table_from_obs_freq(obs_freq_mat) 451 exp_freq_mat = _build_exp_freq_mat(exp_freq_table) 452 subs_mat = _build_subs_mat(obs_freq_mat, exp_freq_mat) 453 return subs_mat
454 -def read_text_matrix(data_file,mat_type=NOTYPE):
455 matrix = {} 456 tmp = data_file.read().split("\n") 457 table=[] 458 for i in tmp: 459 table.append(i.split()) 460 # remove records beginning with ``#'' 461 for rec in table[:]: 462 if (rec.count('#') > 0): 463 table.remove(rec) 464 465 # remove null lists 466 while (table.count([]) > 0): 467 table.remove([]) 468 # build a dictionary 469 alphabet = table[0] 470 j = 0 471 for rec in table[1:]: 472 # print j 473 row = alphabet[j] 474 # row = rec[0] 475 if re.compile('[A-z\*]').match(rec[0]): 476 first_col = 1 477 else: 478 first_col = 0 479 i = 0 480 for field in rec[first_col:]: 481 col = alphabet[i] 482 matrix[(row,col)] = float(field) 483 i += 1 484 j += 1 485 # delete entries with an asterisk 486 for i in matrix.keys(): 487 if '*' in i: del(matrix[i]) 488 ret_mat = SeqMat(matrix,mat_type=mat_type) 489 return ret_mat
490 491 diagNO = 1 492 diagONLY = 2 493 diagALL = 3 494
495 -def two_mat_relative_entropy(mat_1,mat_2,logbase=2,diag=diagALL):
496 rel_ent = 0. 497 key_list_1 = mat_1.keys(); key_list_2 = mat_2.keys() 498 key_list_1.sort(); key_list_2.sort() 499 key_list = [] 500 sum_ent_1 = 0.; sum_ent_2 = 0. 501 for i in key_list_1: 502 if i in key_list_2: 503 key_list.append(i) 504 if len(key_list_1) != len(key_list_2): 505 506 sys.stderr.write("Warning:first matrix has more entries than the second\n") 507 if key_list_1 != key_list_2: 508 sys.stderr.write("Warning: indices not the same between matrices\n") 509 for key in key_list: 510 if diag == diagNO and key[0] == key[1]: 511 continue 512 if diag == diagONLY and key[0] != key[1]: 513 continue 514 if mat_1[key] > EPSILON and mat_2[key] > EPSILON: 515 sum_ent_1 += mat_1[key] 516 sum_ent_2 += mat_2[key] 517 518 for key in key_list: 519 if diag == diagNO and key[0] == key[1]: 520 continue 521 if diag == diagONLY and key[0] != key[1]: 522 continue 523 if mat_1[key] > EPSILON and mat_2[key] > EPSILON: 524 val_1 = mat_1[key] / sum_ent_1 525 val_2 = mat_2[key] / sum_ent_2 526 # rel_ent += mat_1[key] * log(mat_1[key]/mat_2[key])/log(logbase) 527 rel_ent += val_1 * log(val_1/val_2)/log(logbase) 528 return rel_ent
529 530 ## Gives the linear correlation coefficient between two matrices 531 #def two_mat_correlation(mat_1, mat_2): 532 # Wait for the statistical package before uncommenting this 533 # 534 # corr_list = [] 535 # assert mat_1.ab_list == mat_2.ab_list 536 # for ab_pair in mat_1.keys(): 537 # try: 538 # corr_list.append((mat_1[ab_pair], mat_2[ab_pair])) 539 # except KeyError: 540 # sys.stderr.write("Error:two_mat_correlation: %s is not a common key\n" % 541 # mat_1) 542 # return statfns.corr(corr_list) 543 544 # Jensen-Shannon Distance 545 # Need to input observed frequency matrices
546 -def two_mat_DJS(mat_1,mat_2,pi_1=0.5,pi_2=0.5):
547 assert mat_1.ab_list == mat_2.ab_list 548 assert pi_1 > 0 and pi_2 > 0 and pi_1< 1 and pi_2 <1 549 assert not (pi_1 + pi_2 - 1.0 > EPSILON) 550 sum_mat = SeqMat(build_later=1) 551 sum_mat.ab_list = mat_1.ab_list 552 for i in mat_1.keys(): 553 sum_mat[i] = pi_1 * mat_1[i] + pi_2 * mat_2[i] 554 sum_mat.make_entropy() 555 mat_1.make_entropy() 556 mat_2.make_entropy() 557 # print mat_1.entropy, mat_2.entropy 558 dJS = sum_mat.entropy - pi_1 * mat_1.entropy - pi_2 *mat_2.entropy 559 return dJS
560 561 """ 562 This isn't working yet. Boo hoo! 563 def two_mat_print(mat_1, mat_2, f=None,alphabet=None,factor_1=1, factor_2=1, 564 format="%4d",bottomformat="%4s",topformat="%4s", 565 topindent=7*" ", bottomindent=1*" "): 566 f = f or sys.stdout 567 if not alphabet: 568 assert mat_1.ab_list == mat_2.ab_list 569 alphabet = mat_1.ab_list 570 len_alphabet = len(alphabet) 571 print_mat = {} 572 topline = topindent 573 bottomline = bottomindent 574 for i in alphabet: 575 bottomline += bottomformat % i 576 topline += topformat % alphabet[len_alphabet-alphabet.index(i)-1] 577 topline += '\n' 578 bottomline += '\n' 579 f.write(topline) 580 for i in alphabet: 581 for j in alphabet: 582 print_mat[i,j] = -999 583 diag_1 = {}; diag_2 = {} 584 for i in alphabet: 585 for j in alphabet[:alphabet.index(i)+1]: 586 if i == j: 587 diag_1[i] = mat_1[(i,i)] 588 diag_2[i] = mat_2[(alphabet[len_alphabet-alphabet.index(i)-1], 589 alphabet[len_alphabet-alphabet.index(i)-1])] 590 else: 591 if i > j: 592 key = (j,i) 593 else: 594 key = (i,j) 595 mat_2_key = [alphabet[len_alphabet-alphabet.index(key[0])-1], 596 alphabet[len_alphabet-alphabet.index(key[1])-1]] 597 # print mat_2_key 598 mat_2_key.sort(); mat_2_key = tuple(mat_2_key) 599 # print key ,"||", mat_2_key 600 print_mat[key] = mat_2[mat_2_key] 601 print_mat[(key[1],key[0])] = mat_1[key] 602 for i in alphabet: 603 outline = i 604 for j in alphabet: 605 if i == j: 606 if diag_1[i] == -999: 607 val_1 = ' ND' 608 else: 609 val_1 = format % (diag_1[i]*factor_1) 610 if diag_2[i] == -999: 611 val_2 = ' ND' 612 else: 613 val_2 = format % (diag_2[i]*factor_2) 614 cur_str = val_1 + " " + val_2 615 else: 616 if print_mat[(i,j)] == -999: 617 val = ' ND' 618 elif alphabet.index(i) > alphabet.index(j): 619 val = format % (print_mat[(i,j)]*factor_1) 620 else: 621 val = format % (print_mat[(i,j)]*factor_2) 622 cur_str = val 623 outline += cur_str 624 outline += bottomformat % (alphabet[len_alphabet-alphabet.index(i)-1] + 625 '\n') 626 f.write(outline) 627 f.write(bottomline) 628 """ 629