Документ взят из кэша поисковой машины. Адрес оригинального документа : http://kodomo.fbb.msu.ru/hg/allpy/annotate/316122b6b70e/allpy/base.py
Дата изменения: Unknown
Дата индексирования: Sun Mar 2 07:03:46 2014
Кодировка:
allpy: allpy/base.py annotate

allpy

annotate allpy/base.py @ 313:316122b6b70e

Changed interfaces of allpy.fasta, updated allpy.base accordingly * Renamed allpy.fasta.parse_fasta -> parse_file, save_fasta -> save_file * parse_file returns a list of 3-tuples instead of dict
author Daniil Alexeyevsky <me.dendik@gmail.com>
date Thu, 16 Dec 2010 23:42:14 +0300
parents 5417b9d6b63e
children 3347b5f31477
rev   line source
me@261 1 import sys
me@261 2
me@261 3 import config
me@284 4 import fasta
me@261 5 from graph import Graph
me@260 6 import data.codes
me@260 7
me@306 8 default_gaps = set((".", "-", "~"))
me@306 9 """Set of characters to recoginze as gaps when parsing alignment."""
me@306 10
me@260 11 class MonomerType(object):
me@260 12 """Class of monomer types.
me@260 13
me@260 14 Each MonomerType object represents a known monomer type, e.g. Valine,
me@260 15 and is referenced to by each instance of monomer in a given sequence.
me@260 16
me@260 17 - `name`: full name of monomer type
me@260 18 - `code1`: one-letter code
me@260 19 - `code3`: three-letter code
me@260 20 - `is_modified`: either of True or False
me@260 21
me@260 22 class atributes:
me@260 23
me@260 24 - `by_code1`: a mapping from one-letter code to MonomerType object
me@260 25 - `by_code3`: a mapping from three-letter code to MonomerType object
me@260 26 - `by_name`: a mapping from monomer name to MonomerType object
me@260 27 - `instance_type`: class of Monomer objects to use when creating new
me@260 28 objects; this must be redefined in descendent classes
me@260 29
me@260 30 All of the class attributes MUST be redefined when subclassing.
me@260 31 """
me@260 32
me@260 33 by_code1 = {}
me@260 34 by_code3 = {}
me@260 35 by_name = {}
me@260 36 instance_type = None
me@260 37
me@260 38 def __init__(self, name="", code1="", code3="", is_modified=False):
me@310 39 super(MonomerType, self).__init__()
me@260 40 self.name = name.capitalize()
me@260 41 self.code1 = code1.upper()
me@260 42 self.code3 = code3.upper()
me@260 43 self.is_modified = bool(is_modified)
me@260 44 if not is_modified:
me@260 45 self.by_code1[self.code1] = self
me@260 46 self.by_code3[code3] = self
me@260 47 self.by_name[name] = self
me@260 48 # We duplicate distinguished long names into MonomerType itself,
me@260 49 # so that we can use MonomerType.from_code3 to create the relevant
me@260 50 # type of monomer.
me@260 51 MonomerType.by_code3[code3] = self
me@260 52 MonomerType.by_name[name] = self
me@260 53
me@260 54 @classmethod
me@260 55 def _initialize(cls, type_letter, codes=data.codes.codes):
me@260 56 """Create all relevant instances of MonomerType.
me@260 57
me@260 58 `type_letter` is either of:
me@260 59
me@260 60 - 'p' for protein
me@260 61 - 'd' for DNA
me@260 62 - 'r' for RNA
me@260 63
me@260 64 `codes` is a table of monomer codes
me@260 65 """
me@260 66 for type, code1, is_modified, code3, name in codes:
me@260 67 if type == type_letter:
me@260 68 cls(name, code1, code3, is_modified)
me@260 69
me@260 70 @classmethod
me@260 71 def from_code1(cls, code1):
me@260 72 """Return monomer type by one-letter code."""
me@260 73 return cls.by_code1[code1.upper()]
me@260 74
me@260 75 @classmethod
me@260 76 def from_code3(cls, code3):
me@260 77 """Return monomer type by three-letter code."""
me@260 78 return cls.by_code3[code3.upper()]
me@260 79
me@260 80 @classmethod
me@260 81 def from_name(cls, name):
me@260 82 """Return monomer type by name."""
me@260 83 return cls.by_name[name.capitalize()]
me@260 84
me@260 85 def instance(self):
me@260 86 """Create a new monomer of given type."""
me@260 87 return self.instance_type(self)
me@260 88
me@260 89 def __eq__(self, other):
me@260 90 if hasattr(other, "type"):
me@260 91 return self is other.type
me@260 92 return self is other
me@260 93
me@260 94 class Monomer(object):
me@260 95 """Monomer object.
me@260 96
me@260 97 attributes:
me@260 98
me@260 99 - `type`: type of monomer (a MonomerType object)
me@260 100
me@282 101 class attributes:
me@282 102
me@282 103 - `monomer_type`: either MonomerType or one of it's subclasses, it is used
me@282 104 when creating new monomers. It SHOULD be redefined when subclassing
me@282 105 Monomer.
me@260 106 """
me@260 107 monomer_type = MonomerType
me@260 108
me@260 109 def __init__(self, type):
me@310 110 super(Monomer, self).__init__()
me@260 111 self.type = type
me@260 112
me@260 113 @classmethod
me@260 114 def from_code1(cls, code1):
me@260 115 return cls(cls.monomer_type.by_code1[code1.upper()])
me@260 116
me@260 117 @classmethod
me@260 118 def from_code3(cls, code3):
me@260 119 return cls(cls.monomer_type.by_code3[code3.upper()])
me@260 120
me@260 121 @classmethod
me@260 122 def from_name(cls, name):
me@260 123 return cls(cls.monomer_type.by_name[name.capitalize()])
me@260 124
me@260 125 def __eq__(self, other):
me@260 126 if hasattr(other, "type"):
me@260 127 return self.type is other.type
me@260 128 return self.type is other
bnagaev@239 129
bnagaev@239 130 class Sequence(list):
me@274 131 """Sequence of Monomers.
bnagaev@243 132
me@274 133 This behaves like list of monomer objects. In addition to standard list
me@274 134 behaviour, Sequence has the following attributes:
me@270 135
me@274 136 * name -- str with the name of the sequence
me@274 137 * description -- str with description of the sequence
me@274 138 * source -- str denoting source of the sequence
me@266 139
me@274 140 Any of them may be empty (i.e. hold empty string)
me@275 141
me@275 142 Class attributes:
me@282 143
me@275 144 * monomer_type -- type of monomers in sequence, must be redefined when
me@275 145 subclassing
me@274 146 """
me@270 147
me@275 148 monomer_type = Monomer
me@270 149
me@275 150 name = ''
me@275 151 description = ''
me@275 152 source = ''
me@275 153
me@275 154 def __init__(self, sequence=[], name=None, description=None, source=None):
me@275 155 super(Sequence, self).__init__(sequence)
me@275 156 if hasattr(sequence, 'name'):
me@275 157 vars(self).update(vars(sequence))
me@275 158 if name:
me@275 159 self.name = name
me@275 160 if description:
me@275 161 self.description = description
me@275 162 if source:
me@275 163 self.source = source
me@270 164
me@262 165 def __str__(self):
me@275 166 """Returns sequence in one-letter code."""
me@275 167 return ''.join(monomer.code1 for monomer in self)
me@270 168
me@273 169 @classmethod
me@273 170 def from_string(cls, string, name='', description=''):
me@273 171 """Create sequences from string of one-letter codes."""
me@273 172 monomer = cls.monomer_type.from_code1
me@273 173 monomers = [monomer(letter) for letter in string]
me@273 174 return cls(monomers, name, description)
me@262 175
me@284 176 @classmethod
me@284 177 def from_fasta(cls, file):
me@284 178 """Read sequence from FASTA file.
me@286 179
me@284 180 File must contain exactly one sequence.
me@284 181 """
me@313 182 sequence, = fasta.parse_file(file)
me@313 183 name, description, body = sequence
me@313 184 return cls(body, name, description, file.name)
me@284 185
me@295 186 class Alignment(object):
me@295 187 """Alignment. It is a list of Columns."""
bnagaev@249 188
me@287 189 sequence_type = Sequence
me@289 190 """Type of sequences in alignment. SHOULD be redefined when subclassing."""
me@288 191
me@289 192 sequences = None
me@289 193 """Ordered list of sequences in alignment. Read, but DO NOT FIDDLE!"""
bnagaev@249 194
me@287 195 def __init__(self):
me@287 196 """Initialize empty alignment."""
me@287 197 super(Alignment, self).__init__()
me@287 198 self.sequences = []
me@295 199 self.columns = []
me@282 200
me@299 201 # Alignment modification methods
me@299 202 # ==============================
me@299 203
me@294 204 def append_sequence(self, sequence):
me@294 205 """Add sequence to alignment.
me@294 206
me@294 207 If sequence is too short, pad it with gaps on the right.
me@294 208 """
me@294 209 self.sequences.append(sequence)
me@294 210 for i, monomer in enumerate(sequence):
me@302 211 self.column_at(i)[sequence] = monomer
me@294 212
me@306 213 def append_gapped_line(self, line, name='', description='', source='',
me@306 214 gaps=default_gaps):
me@287 215 """Add row from a line of one-letter codes and gaps."""
me@313 216 Sequence = self.sequence_type
me@306 217 not_gap = lambda (i, char): char not in gaps
me@306 218 without_gaps = util.remove_each(line, gaps)
me@306 219 sequence = Sequence(without_gaps, name, description, source)
me@303 220 # The following line has some simple magic:
me@303 221 # 1. attach natural numbers to monomers
me@303 222 # 2. delete gaps
me@303 223 # 3. attach numbers again
me@303 224 # This way we have a pair of numbers attached to monomer:
me@303 225 # - it's position in alignment (the first attached number, j)
me@303 226 # - it's position in sequence (the second attached number, i)
me@287 227 for i, (j, char) in enumerate(filter(not_gap, enumerate(line))):
me@313 228 self.column_at(j)[sequence] = sequence[i]
me@287 229 self.sequences.append(sequence)
me@287 230
me@302 231 def column_at(self, n):
me@302 232 """Return column by index. Create required new columns if required.
me@302 233
me@302 234 Do NOT use this method, unless you are sure it is what you want.
me@302 235 """
me@302 236 for i in range(len(self.columns), n + 1):
me@302 237 self.columns.append(Column())
me@302 238 return self.columns[n]
me@302 239
me@299 240 # Alignment IO methods
me@299 241 # ====================
me@299 242
me@287 243 @classmethod
me@306 244 def from_fasta(cls, file, gaps=default_gaps):
me@287 245 """Create new alignment from FASTA file."""
me@287 246 self = cls()
me@313 247 for (name, description, body) in fasta.parse_file(file):
me@306 248 self.append_gapped_line(body, name, description, gaps)
me@287 249 return self
bnagaev@249 250
me@292 251 def to_fasta(self, file):
me@292 252 """Write alignment in FASTA file as sequences with gaps."""
me@292 253 def char(monomer):
me@292 254 if monomer:
me@292 255 return monomer.code1
me@292 256 return "-"
me@292 257 for row in self.rows_as_lists():
me@292 258 seq = row.sequence
me@292 259 line = "".join(map(char, row))
me@292 260 fasta.save_file(file, line, seq.name, seq.description)
me@292 261
me@299 262 # Data access methods for alignment
me@299 263 # =================================
me@299 264
me@299 265 def rows(self):
me@299 266 """Return list of rows (temporary objects) in alignment.
me@299 267
me@299 268 Each row is a dictionary of { column : monomer }.
me@299 269
me@299 270 For gap positions there is no key for the column in row.
me@299 271
me@299 272 Each row has attribute `sequence` pointing to the sequence the row is
me@299 273 describing.
me@299 274
me@299 275 Modifications of row have no effect on the alignment.
me@299 276 """
me@299 277 # For now, the function returns a list rather than iterator.
me@299 278 # It is yet to see, whether memory performance here becomes critical,
me@299 279 # or is random access useful.
me@299 280 rows = []
me@299 281 for sequence in self.sequences:
me@299 282 row = util.UserDict()
me@299 283 row.sequence = sequence
me@299 284 for column in self.columns:
me@299 285 if sequence in column:
me@299 286 row[column] = column[sequence]
me@299 287 rows.append(row)
me@299 288 return rows
me@299 289
me@299 290 def rows_as_lists(self):
me@299 291 """Return list of rows (temporary objects) in alignment.
me@299 292
me@299 293 Each row here is a list of either monomer or None (for gaps).
me@299 294
me@299 295 Each row has attribute `sequence` pointing to the sequence of row.
me@299 296
me@299 297 Modifications of row have no effect on the alignment.
me@299 298 """
me@299 299 rows = []
me@299 300 for sequence in self.sequences:
me@299 301 row = util.UserList()
me@299 302 row.sequence = sequence
me@299 303 for column in self.columns:
me@299 304 row.append(column.get(sequence))
me@299 305 rows.append(row)
me@299 306 return rows
me@299 307
me@299 308 def columns_as_lists(self):
me@299 309 """Return list of columns (temorary objects) in alignment.
me@299 310
me@299 311 Each column here is a list of either monomer or None (for gaps).
me@299 312
me@299 313 Items of column are sorted in the same way as alignment.sequences.
me@299 314
me@299 315 Modifications of column have no effect on the alignment.
me@299 316 """
me@299 317 columns = []
me@299 318 for column in self.columns:
me@299 319 col = []
me@299 320 for sequence in self.sequences:
me@299 321 col.append(column.get(sequence))
me@299 322 columns.append(col)
me@299 323 return columns
me@299 324
me@300 325 class Column(dict):
me@300 326 """Column of alignment.
me@300 327
me@300 328 Column is a dict of { sequence : monomer }.
me@300 329
me@300 330 For sequences that have gaps in current row, given key is not present in
me@300 331 the column.
me@300 332 """
me@300 333 pass
me@300 334
me@307 335 class Block(object):
me@307 336 """Block of alignment.
me@301 337
me@307 338 Block is intersection of a set of columns & a set of rows. Most of blocks
me@307 339 look like rectangular part of alignment if you shuffle alignment rows the
me@307 340 right way.
me@261 341 """
me@270 342
me@307 343 alignment = None
me@307 344 """Alignment the block belongs to."""
me@270 345
me@307 346 sequences = ()
me@307 347 """List of sequences in block."""
me@307 348
me@307 349 columns = ()
me@307 350 """List of columns in block."""
me@307 351
me@307 352 def __init__(self, alignment, sequences=None, columns=None):
me@307 353 """Build new block from alignment.
me@307 354
me@307 355 If sequences are not given, the block uses all sequences in alignment.
me@307 356
me@307 357 If columns are not given, the block uses all columns in alignment.
me@307 358
me@307 359 In both cases we use exactly the list used in alignment, thus, if new
me@307 360 sequences or columns are added to alignment, the block tracks this too.
me@261 361 """
me@310 362 super(Block, self).__init__()
me@307 363 if sequences is None:
me@307 364 sequences = alignment.sequences
me@307 365 if colums is None:
me@307 366 columns = alignment.columns
me@261 367 self.alignment = alignment
me@261 368 self.sequences = sequences
me@261 369 self.positions = positions
me@270 370
me@312 371 def flush_left(self):
me@312 372 """Move all monomers to the left, gaps to the right within block."""
me@312 373 padding = [None] * len(self.columns)
me@312 374 for row in self.rows_as_lists():
me@312 375 sequence = row.sequence
me@312 376 row = filter(None, row) + padding
me@312 377 for monomer, column in zip(row, self.columns):
me@312 378 if monomer:
me@312 379 column[sequence] = monomer
me@312 380 elif sequence in column:
me@312 381 del column[sequence]
me@312 382
me@312 383
me@260 384 # vim: set ts=4 sts=4 sw=4 et: