File Coverage

blib/lib/Bio/Phylo/Matrices/MatrixRole.pm
Criterion Covered Total %
statement 378 1247 30.3
branch 79 398 19.8
condition 13 120 10.8
subroutine 41 110 37.2
pod 33 80 41.2
total 544 1955 27.8


line stmt bran cond sub pod time code
1             package Bio::Phylo::Matrices::MatrixRole;
2 13     13   95 use strict;
  13         27  
  13         364  
3 13     13   64 use warnings;
  13         23  
  13         289  
4 13     13   757 use Data::Dumper;
  13         10155  
  13         769  
5 13     13   75 use base qw'Bio::Phylo::Matrices::TypeSafeData Bio::Phylo::Taxa::TaxaLinker';
  13         28  
  13         4244  
6 13     13   4020 use Bio::Phylo::Models::Substitution::Binary;
  13         32  
  13         439  
7 13     13   4429 use Bio::Phylo::Models::Substitution::Dna;
  13         46  
  13         487  
8 13     13   3031 use Bio::Phylo::Util::OptionalInterface 'Bio::Align::AlignI';
  13         38  
  13         73  
9 13     13   82 use Bio::Phylo::Util::CONSTANT qw':objecttypes /looks_like/';
  13         26  
  13         3444  
10 13     13   88 use Bio::Phylo::Util::Exceptions qw'throw';
  13         21  
  13         508  
11 13     13   70 use Bio::Phylo::NeXML::Writable;
  13         26  
  13         92  
12 13     13   3430 use Bio::Phylo::Matrices::Datum;
  13         35  
  13         88  
13 13     13   84 use Bio::Phylo::IO qw(parse unparse);
  13         28  
  13         642  
14 13     13   73 use Bio::Phylo::Factory;
  13         26  
  13         65  
15             my $LOADED_WRAPPERS = 0;
16             {
17             my $CONSTANT_TYPE = _MATRIX_;
18             my $CONSTANT_CONTAINER = _MATRICES_;
19             my $logger = __PACKAGE__->get_logger;
20             my $factory = Bio::Phylo::Factory->new;
21              
22             =head1 NAME
23              
24             Bio::Phylo::Matrices::MatrixRole - Extra behaviours for a character state matrix
25              
26             =head1 SYNOPSIS
27              
28             use Bio::Phylo::Factory;
29             my $fac = Bio::Phylo::Factory->new;
30              
31             # instantiate taxa object
32             my $taxa = $fac->create_taxa;
33             for ( 'Homo sapiens', 'Pan paniscus', 'Pan troglodytes' ) {
34             $taxa->insert( $fac->create_taxon( '-name' => $_ ) );
35             }
36              
37             # instantiate matrix object, 'standard' data type. All categorical
38             # data types follow semantics like this, though with different
39             # symbols in lookup table and matrix
40             my $standard_matrix = $fac->create_matrix(
41             '-type' => 'STANDARD',
42             '-taxa' => $taxa,
43             '-lookup' => {
44             '-' => [],
45             '0' => [ '0' ],
46             '1' => [ '1' ],
47             '?' => [ '0', '1' ],
48             },
49             '-labels' => [ 'Opposable big toes', 'Opposable thumbs', 'Not a pygmy' ],
50             '-matrix' => [
51             [ 'Homo sapiens' => '0', '1', '1' ],
52             [ 'Pan paniscus' => '1', '1', '0' ],
53             [ 'Pan troglodytes' => '1', '1', '1' ],
54             ],
55             );
56              
57             # note: complicated constructor for mixed data!
58             my $mixed_matrix = Bio::Phylo::Matrices::Matrix->new(
59              
60             # if you want to create 'mixed', value for '-type' is array ref...
61             '-type' => [
62              
63             # ...with first field 'mixed'...
64             'mixed',
65              
66             # ...second field is an array ref...
67             [
68              
69             # ...with _ordered_ key/value pairs...
70             'dna' => 10, # value is length of type range
71             'standard' => 10, # value is length of type range
72              
73             # ... or, more complicated, value is a hash ref...
74             'rna' => {
75             '-length' => 10, # value is length of type range
76              
77             # ...value for '-args' is an array ref with args
78             # as can be passed to 'unmixed' datatype constructors,
79             # for example, here we modify the lookup table for
80             # rna to allow both 'U' (default) and 'T'
81             '-args' => [
82             '-lookup' => {
83             'A' => [ 'A' ],
84             'C' => [ 'C' ],
85             'G' => [ 'G' ],
86             'U' => [ 'U' ],
87             'T' => [ 'T' ],
88             'M' => [ 'A', 'C' ],
89             'R' => [ 'A', 'G' ],
90             'S' => [ 'C', 'G' ],
91             'W' => [ 'A', 'U', 'T' ],
92             'Y' => [ 'C', 'U', 'T' ],
93             'K' => [ 'G', 'U', 'T' ],
94             'V' => [ 'A', 'C', 'G' ],
95             'H' => [ 'A', 'C', 'U', 'T' ],
96             'D' => [ 'A', 'G', 'U', 'T' ],
97             'B' => [ 'C', 'G', 'U', 'T' ],
98             'X' => [ 'G', 'A', 'U', 'T', 'C' ],
99             'N' => [ 'G', 'A', 'U', 'T', 'C' ],
100             },
101             ],
102             },
103             ],
104             ],
105             );
106              
107             # prints 'mixed(Dna:1-10, Standard:11-20, Rna:21-30)'
108             print $mixed_matrix->get_type;
109              
110             =head1 DESCRIPTION
111              
112             This module defines a container object that holds
113             L<Bio::Phylo::Matrices::Datum> objects. The matrix
114             object inherits from L<Bio::Phylo::Listable>, so the
115             methods defined there apply here.
116              
117             =head1 METHODS
118              
119             =head2 CONSTRUCTOR
120              
121             =over
122              
123             =item new()
124              
125             Matrix constructor.
126              
127             Type : Constructor
128             Title : new
129             Usage : my $matrix = Bio::Phylo::Matrices::Matrix->new;
130             Function: Instantiates a Bio::Phylo::Matrices::Matrix
131             object.
132             Returns : A Bio::Phylo::Matrices::Matrix object.
133             Args : -type => optional, but if used must be FIRST argument,
134             defines datatype, one of dna|rna|protein|
135             continuous|standard|restriction|[ mixed => [] ]
136              
137             -taxa => optional, link to taxa object
138             -lookup => character state lookup hash ref
139             -labels => array ref of character labels
140             -matrix => two-dimensional array, first element of every
141             row is label, subsequent are characters
142              
143             =cut
144              
145             sub new : Constructor {
146              
147             # could be child class
148 38     38 1 1607 my $class = shift;
149              
150             # notify user
151 38         255 $logger->info("constructor called for '$class'");
152 38 100       144 if ( not $LOADED_WRAPPERS ) {
153 11 0 0 0 0 24 eval do { local $/; <DATA> };
  11 0 0 0 0 52  
  11 0 0 0 0 34569  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0   0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
154 11 50       58 die $@ if $@;
155 11         30 $LOADED_WRAPPERS++;
156             }
157              
158             # go up inheritance tree, eventually get an ID
159 38         383 my $self = $class->SUPER::new(
160             '-characters' => $factory->create_characters,
161             '-listener' => \&_update_characters,
162             @_
163             );
164 38         157 return $self;
165 13     13   79 }
  13         29  
  13         68  
166              
167             =item new_from_bioperl()
168              
169             Matrix constructor from Bio::Align::AlignI argument.
170              
171             Type : Constructor
172             Title : new_from_bioperl
173             Usage : my $matrix =
174             Bio::Phylo::Matrices::Matrix->new_from_bioperl(
175             $aln
176             );
177             Function: Instantiates a
178             Bio::Phylo::Matrices::Matrix object.
179             Returns : A Bio::Phylo::Matrices::Matrix object.
180             Args : An alignment that implements Bio::Align::AlignI
181              
182             =cut
183              
184             sub new_from_bioperl {
185 0     0 1 0 my ( $class, $aln, @args ) = @_;
186 0 0       0 if ( looks_like_instance( $aln, 'Bio::Align::AlignI' ) ) {
187 0         0 $aln->unmatch;
188 0         0 $aln->map_chars( '\.', '-' );
189 0         0 my @seqs = $aln->each_seq;
190 0         0 my ( $type, $missing, $gap, $matchchar );
191 0 0       0 if ( $seqs[0] ) {
192 0   0     0 $type =
193             $seqs[0]->alphabet
194             || $seqs[0]->_guess_alphabet
195             || 'dna';
196             }
197             else {
198 0         0 $type = 'dna';
199             }
200 0   0     0 my $self = $factory->create_matrix(
      0        
      0        
201             '-type' => $type,
202             '-special_symbols' => {
203             '-missing' => $aln->missing_char || '?',
204             '-matchchar' => $aln->match_char || '.',
205             '-gap' => $aln->gap_char || '-',
206             },
207             @args
208             );
209              
210             # XXX create raw getter/setter pairs for annotation,
211             # accession, consensus_meta source
212 0         0 for my $field ( qw(description accession id annotation consensus_meta score source) ) {
213 0         0 $self->$field( $aln->$field );
214             }
215 0         0 my $to = $self->get_type_object;
216 0         0 for my $seq (@seqs) {
217 0         0 my $datum = Bio::Phylo::Matrices::Datum->new_from_bioperl( $seq,
218             '-type_object' => $to );
219 0         0 $self->insert($datum);
220             }
221 0         0 return $self;
222             }
223             else {
224 0         0 throw 'ObjectMismatch' => 'Not a bioperl alignment!';
225             }
226             }
227              
228             =back
229              
230             =head2 MUTATORS
231              
232             =over
233              
234             =item set_special_symbols
235              
236             Sets three special symbols in one call
237              
238             Type : Mutator
239             Title : set_special_symbols
240             Usage : $matrix->set_special_symbols(
241             -missing => '?',
242             -gap => '-',
243             -matchchar => '.'
244             );
245             Function: Assigns state labels.
246             Returns : $self
247             Args : Three args (with distinct $x, $y and $z):
248             -missing => $x,
249             -gap => $y,
250             -matchchar => $z
251             Notes : This method is here to ensure
252             you don't accidentally use the
253             same symbol for missing AND gap
254              
255             =cut
256              
257             sub set_special_symbols {
258 0     0 1 0 my $self = shift;
259 0         0 my %args;
260 0 0 0     0 if ( ( @_ == 1 && ref( $_[0] ) eq 'HASH' && ( %args = %{ $_[0] } ) )
  0   0     0  
      0        
261             || ( %args = looks_like_hash @_ ) )
262             {
263 0 0 0     0 if ( !defined $args{'-missing'}
      0        
264             || !defined $args{'-gap'}
265             || !defined $args{'-matchchar'} )
266             {
267 0         0 throw 'BadArgs' =>
268             'Need -missing => $x, -gap => $y, -matchchar => $z arguments, not '
269             . "@_";
270             }
271 0         0 my %values = map { $_ => 1 } values %args;
  0         0  
272 0         0 my @values = keys %values;
273 0 0       0 if ( scalar @values < 3 ) {
274 0         0 throw 'BadArgs' => 'Symbols must be distinct, not '
275             . join( ', ', values %args );
276             }
277 0   0     0 my %old_special_symbols = (
      0        
      0        
278             ( $self->get_missing || '?' ) => 'set_missing',
279             ( $self->get_gap || '-' ) => 'set_gap',
280             ( $self->get_matchchar || '.' ) => 'set_matchchar',
281             );
282             my %new_special_symbols = (
283             $args{'-missing'} => 'set_missing',
284             $args{'-gap'} => 'set_gap',
285 0         0 $args{'-matchchar'} => 'set_matchchar',
286             );
287 0         0 my %dummies;
288 0         0 while (%new_special_symbols) {
289 0         0 for my $sym ( keys %new_special_symbols ) {
290 0 0       0 if ( not $old_special_symbols{$sym} ) {
    0          
291 0         0 my $method = $new_special_symbols{$sym};
292 0         0 $self->$method($sym);
293 0         0 delete $new_special_symbols{$sym};
294             }
295             elsif ( $old_special_symbols{$sym} eq
296             $new_special_symbols{$sym} )
297             {
298 0         0 delete $new_special_symbols{$sym};
299             }
300             else {
301 0         0 DUMMY: for my $dummy (qw(! @ $ % ^ & *)) {
302 0 0 0     0 if ( !$new_special_symbols{$dummy}
      0        
303             && !$old_special_symbols{$dummy}
304             && !$dummies{$dummy} )
305             {
306 0         0 my $method = $old_special_symbols{$sym};
307 0         0 $self->$method($dummy);
308 0         0 $dummies{$dummy} = 1;
309 0         0 delete $old_special_symbols{$sym};
310 0         0 $old_special_symbols{$dummy} = $method;
311 0         0 last DUMMY;
312             }
313             }
314             }
315             }
316             }
317             }
318 0         0 return $self;
319             }
320              
321             =item set_charlabels()
322              
323             Sets argument character labels.
324              
325             Type : Mutator
326             Title : set_charlabels
327             Usage : $matrix->set_charlabels( [ 'char1', 'char2', 'char3' ] );
328             Function: Assigns character labels.
329             Returns : $self
330             Args : ARRAY, or nothing (to reset);
331              
332             =cut
333              
334             sub set_charlabels {
335 4     4 1 11 my ( $self, $charlabels ) = @_;
336              
337             # it's an array ref, but what about its contents?
338 4 50 0     13 if ( looks_like_instance( $charlabels, 'ARRAY' ) ) {
    0          
339 4         6 for my $label ( @{$charlabels} ) {
  4         12  
340 15 50       28 if ( ref $label ) {
341 0         0 throw 'BadArgs' =>
342             "charlabels must be an array ref of scalars";
343             }
344             }
345             }
346              
347             # it's defined but not an array ref
348             elsif ( defined $charlabels
349             && !looks_like_instance( $charlabels, 'ARRAY' ) )
350             {
351 0         0 throw 'BadArgs' => "charlabels must be an array ref of scalars";
352             }
353              
354             # there might be more labels than currently existing characters.
355             # here we add however more are needed
356 4         16 my $characters = $self->get_characters;
357 4         10 my $missing = scalar(@{$charlabels}) - scalar(@{$characters->get_entities});
  4         9  
  4         14  
358 4         13 for my $i ( 1 .. $missing ) {
359 4         24 $characters->insert($factory->create_character);
360             }
361              
362             # it's either a valid array ref, or nothing, i.e. a reset
363 4         9 my @characters = @{ $characters->get_entities };
  4         11  
364 4         8 for my $i ( 0 .. $#{$charlabels} ) {
  4         11  
365 15         47 $characters[$i]->set_name( $charlabels->[$i] );
366             }
367 4         11 return $self;
368             }
369              
370             =item set_raw()
371              
372             Set contents using two-dimensional array argument.
373              
374             Type : Mutator
375             Title : set_raw
376             Usage : $matrix->set_raw( [ [ 'taxon1' => 'acgt' ], [ 'taxon2' => 'acgt' ] ] );
377             Function: Syntax sugar to define $matrix data contents.
378             Returns : $self
379             Args : A two-dimensional array; first dimension contains matrix rows,
380             second dimension contains taxon name / character string pair.
381              
382             =cut
383              
384             sub set_raw {
385 12     12 1 34 my ( $self, $raw ) = @_;
386 12 50       34 if ( defined $raw ) {
387 12 50       44 if ( looks_like_instance( $raw, 'ARRAY' ) ) {
388 12         24 my @rows;
389 12         22 for my $row ( @{$raw} ) {
  12         31  
390 46 50       108 if ( defined $row ) {
391 46 50       121 if ( looks_like_instance( $row, 'ARRAY' ) ) {
392             my $matrixrow = $factory->create_datum(
393             '-type_object' => $self->get_type_object,
394             '-name' => $row->[0],
395 46         161 '-char' => join( ' ', @$row[ 1 .. $#{$row} ] ),
  46         404  
396             );
397 46         174 push @rows, $matrixrow;
398             }
399             else {
400 0         0 throw 'BadArgs' =>
401             "Raw matrix row must be an array reference";
402             }
403             }
404             }
405 12         75 $self->clear;
406 12         79 $self->insert($_) for @rows;
407             }
408             else {
409 0         0 throw 'BadArgs' => "Raw matrix must be an array reference";
410             }
411             }
412 12         39 return $self;
413             }
414              
415             =back
416              
417             =head2 ACCESSORS
418              
419             =over
420              
421             =item get_special_symbols()
422              
423             Retrieves hash ref for missing, gap and matchchar symbols
424              
425             Type : Accessor
426             Title : get_special_symbols
427             Usage : my %syms = %{ $matrix->get_special_symbols };
428             Function: Retrieves special symbols
429             Returns : HASH ref, e.g. { -missing => '?', -gap => '-', -matchchar => '.' }
430             Args : None.
431              
432             =cut
433              
434             sub get_special_symbols {
435 0     0 1 0 my $self = shift;
436             return {
437 0         0 '-missing' => $self->get_missing,
438             '-matchchar' => $self->get_matchchar,
439             '-gap' => $self->get_gap
440             };
441             }
442              
443             =item get_charlabels()
444              
445             Retrieves character labels.
446              
447             Type : Accessor
448             Title : get_charlabels
449             Usage : my @charlabels = @{ $matrix->get_charlabels };
450             Function: Retrieves character labels.
451             Returns : ARRAY
452             Args : None.
453              
454             =cut
455              
456             sub get_charlabels {
457 4     4 1 38 my $self = shift;
458             my @labels =
459 4         10 map { $_->get_name } @{ $self->get_characters->get_entities };
  9         31  
  4         19  
460 4         16 return \@labels;
461             }
462              
463             =item get_nchar()
464              
465             Calculates number of characters.
466              
467             Type : Accessor
468             Title : get_nchar
469             Usage : my $nchar = $matrix->get_nchar;
470             Function: Calculates number of characters (columns) in matrix (if the matrix
471             is non-rectangular, returns the length of the longest row).
472             Returns : INT
473             Args : none
474              
475             =cut
476              
477             sub get_nchar {
478 214     214 1 335 my $self = shift;
479 214         310 my $nchar = 0;
480 214         290 for my $row ( @{ $self->get_entities } ) {
  214         463  
481 639   50     1312 my $offset = ( $row->get_position || 1 ) - 1;
482 639         900 my $rowlength = scalar @{ $row->get_entities };
  639         1131  
483 639         890 $rowlength += $offset;
484 639 100       1273 $nchar = $rowlength if $rowlength > $nchar;
485             }
486 214         513 return $nchar;
487             }
488              
489             =item get_ntax()
490              
491             Calculates number of taxa (rows) in matrix.
492              
493             Type : Accessor
494             Title : get_ntax
495             Usage : my $ntax = $matrix->get_ntax;
496             Function: Calculates number of taxa (rows) in matrix
497             Returns : INT
498             Args : none
499              
500             =cut
501              
502 16     16 1 824 sub get_ntax { scalar @{ shift->get_entities } }
  16         47  
503              
504             =item get_raw()
505              
506             Retrieves a 'raw' (two-dimensional array) representation of the matrix's contents.
507              
508             Type : Accessor
509             Title : get_raw
510             Usage : my $rawmatrix = $matrix->get_raw;
511             Function: Retrieves a 'raw' (two-dimensional array) representation
512             of the matrix's contents.
513             Returns : A two-dimensional array; first dimension contains matrix rows,
514             second dimension contains taxon name and characters.
515             Args : NONE
516              
517             =cut
518              
519             sub get_raw {
520 5     5 1 9 my $self = shift;
521 5         9 my @raw;
522 5         8 for my $row ( @{ $self->get_entities } ) {
  5         14  
523 22         29 my @row;
524 22         45 push @row, $row->get_name;
525 22         49 my @char = $row->get_char;
526 22         117 push @row, @char;
527 22         59 push @raw, \@row;
528             }
529 5         13 return \@raw;
530             }
531              
532             =item get_ungapped_columns()
533              
534             Type : Accessor
535             Title : get_ungapped_columns
536             Usage : my @ungapped = @{ $matrix->get_ungapped_columns };
537             Function: Retrieves the zero-based column indices of columns without gaps
538             Returns : An array reference with zero or more indices (i.e. integers)
539             Args : NONE
540              
541             =cut
542              
543             sub get_ungapped_columns {
544 1     1 1 19 my $self = shift;
545 1         5 my $raw = $self->get_raw;
546 1         3 my $nchar = $self->get_nchar;
547 1         2 my @ungapped;
548 1         6 my $gap = $self->get_gap;
549 1         3 for my $i ( 1 .. $nchar ) {
550 29         47 my %seen;
551 29         41 for my $row ( @$raw ) {
552 116         133 my $c = $row->[$i];
553 116         147 $seen{$c}++;
554             }
555 29 100       53 push @ungapped, $i - 1 unless $seen{$gap};
556             }
557 1         11 return \@ungapped;
558             }
559              
560             =item get_invariant_columns()
561              
562             Type : Accessor
563             Title : get_invariant_columns
564             Usage : my @invariant = @{ $matrix->get_invariant_columns };
565             Function: Retrieves the zero-based column indices of invariant columns
566             Returns : An array reference with zero or more indices (i.e. integers)
567             Args : Optional:
568             -gap => if true, counts the gap symbol (probably '-') as a variant
569             -missing => if true, counts the missing symbol (probably '?') as a variant
570              
571             =cut
572              
573             sub get_invariant_columns {
574 2     2 1 1419 my ( $self, %args ) = @_;
575 2         5 my $raw = $self->get_raw;
576 2         5 my $nchar = $self->get_nchar;
577 2         6 my $gap = $self->get_gap;
578 2         8 my $miss = $self->get_missing;
579 2         4 my @invariant;
580 2         5 for my $i ( 1 .. $nchar ) {
581 58         67 my %seen;
582 58         75 for my $row ( @$raw ) {
583 232         283 my $c = uc $row->[$i];
584 232         281 $seen{$c}++;
585             }
586 58         97 my @states = keys %seen;
587 58 100       84 @states = grep { $_ ne $gap } @states unless $args{'-gap'};
  45         83  
588 58 50       100 @states = grep { $_ ne $miss } @states unless $args{'-missing'};
  80         131  
589 58 100 100     184 push @invariant, $i - 1 if @states and @states == 1;
590             }
591 2         19 return \@invariant;
592             }
593              
594              
595             =back
596              
597             =head2 CALCULATIONS
598              
599             =over
600              
601             =item calc_indel_sizes()
602              
603             Calculates size distribution of insertions or deletions
604              
605             Type : Calculation
606             Title : calc_indel_sizes
607             Usage : my %sizes = %{ $matrix->calc_indel_sizes };
608             Function: Calculates the size distribution of indels.
609             Returns : HASH
610             Args : Optional:
611             -trim => if true, disregards indels at start and end
612             -insertions => if true, counts insertions, if false, counts deletions
613              
614             =cut
615              
616             sub calc_indel_sizes {
617 0     0 1 0 my ($self,%args) = @_;
618 0         0 my $ntax = $self->get_ntax;
619 0         0 my $nchar = $self->get_nchar;
620 0         0 my $raw = $self->get_raw;
621 0         0 my $gap = $self->get_gap;
622 0         0 my @indels;
623              
624             # iterate over rows
625 0         0 for my $row ( @$raw ) {
626 0         0 my $previous;
627             my @row_indels;
628              
629             # iterate over columns
630 0         0 for my $i ( 1 .. $nchar ) {
631              
632             # focal cell is a gap
633 0 0       0 if ( $row->[$i] eq $gap ) {
634 0 0       0 if ( $previous ) {
635              
636             # we're extending an indel
637 0 0       0 if ( $previous eq $gap ) {
638 0         0 $row_indels[-1]->{'end'} = $i;
639             }
640              
641             # we're starting an indel
642             else {
643 0         0 push @row_indels, { 'start' => $i };
644             }
645             }
646              
647             # sequence starts with an indel
648             else {
649 0         0 push @row_indels, { 'start' => $i };
650             }
651             }
652             else {
653             # gap of length 1 is closed: start==end
654 0 0 0     0 if ( $previous and $previous eq $gap ){
655 0         0 $row_indels[-1]->{'end'} = $i;
656             }
657             }
658 0         0 $previous = $row->[$i];
659             }
660              
661             # flatten the lists
662 0         0 push @indels, @row_indels;
663             }
664              
665             # remove tops and tails
666 0 0       0 if ( $args{'-trim'} ) {
667 0         0 @indels = grep { $_->{'start'} > 1 } @indels;
  0         0  
668 0         0 @indels = grep { $_->{'end'} < $nchar } @indels;
  0         0  
669             }
670              
671             # count sizes
672 0         0 my %sizes;
673 0         0 for my $i ( 1 .. $nchar ) {
674 0         0 my @starting_here = grep { $_->{'start'} == $i } @indels;
  0         0  
675 0 0       0 if ( @starting_here ) {
676 0         0 for my $j ( $i + 1 .. $nchar ) {
677 0         0 my @ending_here = grep { $_->{'end'} == $j } @starting_here;
  0         0  
678 0 0       0 if ( @ending_here ) {
679 0         0 my $size = $j - $i;
680              
681             # more taxa have an indel here than don't. crudely, we
682             # say this is probably an insertion
683 0 0       0 if ( scalar(@ending_here) > ( $ntax / 2 ) ) {
684 0 0       0 $sizes{$size}++ if $args{'-insertions'};
685             }
686             # it's a deletion
687             else {
688 0 0       0 $sizes{$size}++ if not $args{'-insertions'};
689             }
690             }
691             }
692             }
693             }
694 0         0 return \%sizes;
695             }
696              
697              
698             =item calc_prop_invar()
699              
700             Calculates proportion of invariant sites.
701              
702             Type : Calculation
703             Title : calc_prop_invar
704             Usage : my $pinvar = $matrix->calc_prop_invar;
705             Function: Calculates proportion of invariant sites.
706             Returns : Scalar: a number
707             Args : Optional:
708             # if true, counts missing (usually the '?' symbol) as a state
709             # in the final tallies. Otherwise, missing states are ignored
710             -missing => 1
711             # if true, counts gaps (usually the '-' symbol) as a state
712             # in the final tallies. Otherwise, gap states are ignored
713             -gap => 1
714              
715             =cut
716              
717             sub calc_prop_invar {
718 1     1 1 1614 my $self = shift;
719 1         6 my $raw = $self->get_raw;
720 1         3 my $ntax = $self->get_ntax;
721 1   50     3 my $nchar = $self->get_nchar || 1;
722 1         5 my %args = looks_like_hash @_;
723 1         2 my @symbols_to_ignore;
724 1         2 for my $sym (qw(missing gap)) {
725 2 50       7 if ( not exists $args{"-${sym}"} ) {
726 2         3 my $method = "get_${sym}";
727 2         7 push @symbols_to_ignore, $self->$method;
728             }
729             }
730 1         2 my $invar_count;
731 1         5 for my $i ( 1 .. $nchar ) {
732 4         5 my %seen;
733 4         8 for my $j ( 0 .. ( $ntax - 1 ) ) {
734 12         31 $seen{ $raw->[$j]->[$i] }++;
735             }
736 4         7 delete @seen{@symbols_to_ignore};
737 4         10 my @symbols_in_column = keys %seen;
738 4 100       11 $invar_count++ if scalar(@symbols_in_column) <= 1;
739             }
740 1         7 return $invar_count / $nchar;
741             }
742              
743             =item calc_state_counts()
744              
745             Calculates occurrences of states.
746              
747             Type : Calculation
748             Title : calc_state_counts
749             Usage : my %counts = %{ $matrix->calc_state_counts };
750             Function: Calculates occurrences of states.
751             Returns : Hashref: keys are states, values are counts
752             Args : Optional - one or more states to focus on
753              
754             =cut
755              
756             sub calc_state_counts {
757 4     4 1 1691 my $self = shift;
758 4         12 my %totals;
759 4         10 for my $row ( @{ $self->get_entities } ) {
  4         24  
760 18         91 my $counts = $row->calc_state_counts(@_);
761 18         41 for my $state ( keys %{$counts} ) {
  18         70  
762 60         179 $totals{$state} += $counts->{$state};
763             }
764             }
765 4         22 return \%totals;
766             }
767              
768             =item calc_state_frequencies()
769              
770             Calculates the frequencies of the states observed in the matrix.
771              
772             Type : Calculation
773             Title : calc_state_frequencies
774             Usage : my %freq = %{ $object->calc_state_frequencies() };
775             Function: Calculates state frequencies
776             Returns : A hash, keys are state symbols, values are frequencies
777             Args : Optional:
778             # if true, counts missing (usually the '?' symbol) as a state
779             # in the final tallies. Otherwise, missing states are ignored
780             -missing => 1
781             # if true, counts gaps (usually the '-' symbol) as a state
782             # in the final tallies. Otherwise, gap states are ignored
783             -gap => 1
784             Comments: Throws exception if matrix holds continuous values
785              
786             =cut
787              
788             sub calc_state_frequencies {
789 2     2 1 3 my $self = shift;
790 2         4 my %result;
791 2         4 for my $row ( @{ $self->get_entities } ) {
  2         5  
792 5         21 my $freqs = $row->calc_state_frequencies(@_);
793 5         8 for my $state ( keys %{$freqs} ) {
  5         9  
794 20         34 $result{$state} += $freqs->{$state};
795             }
796             }
797 2         4 my $total = 0;
798 2         10 $total += $_ for values %result;
799 2 50       7 if ( $total > 0 ) {
800 2         4 for my $state ( keys %result ) {
801 8         13 $result{$state} /= $total;
802             }
803             }
804 2         5 return \%result;
805             }
806              
807             =item calc_distinct_site_patterns()
808              
809             Identifies the distinct distributions of states for all characters and
810             counts their occurrences. Returns an array-of-arrays, where the first cell
811             of each inner array holds the occurrence count, the second cell holds the
812             pattern, i.e. an array of states. For example, for a matrix like this:
813              
814             taxon1 GTGTGTGTGTGTGTGTGTGTGTG
815             taxon2 AGAGAGAGAGAGAGAGAGAGAGA
816             taxon3 TCTCTCTCTCTCTCTCTCTCTCT
817             taxon4 TCTCTCTCTCTCTCTCTCTCTCT
818             taxon5 AAAAAAAAAAAAAAAAAAAAAAA
819             taxon6 CGCGCGCGCGCGCGCGCGCGCGC
820             taxon7 AAAAAAAAAAAAAAAAAAAAAAA
821              
822             The following data structure will be returned:
823              
824             [
825             [ 12, [ 'G', 'A', 'T', 'T', 'A', 'C', 'A' ] ],
826             [ 11, [ 'T', 'G', 'C', 'C', 'A', 'G', 'A' ] ]
827             ]
828              
829             The patterns are sorted from most to least frequently occurring, the states
830             for each pattern are in the order of the rows in the matrix. (In other words,
831             the original matrix can more or less be reconstructed by inverting the patterns,
832             and multiplying them by their occurrence, although the order of the columns
833             will be lost.)
834              
835             Type : Calculation
836             Title : calc_distinct_site_patterns
837             Usage : my $patterns = $object->calc_distinct_site_patterns;
838             Function: Calculates distinct site patterns.
839             Returns : A multidimensional array, see above.
840             Args : NONE
841             Comments:
842              
843             =cut
844              
845             sub calc_distinct_site_patterns {
846 1     1 1 25 my ( $self, $indices ) = @_;
847 1         4 my $raw = $self->get_raw;
848 1         4 my $nchar = $self->get_nchar;
849 1         4 my $ntax = $self->get_ntax;
850 1         2 my ( %pattern, %index );
851 1         4 for my $i ( 1 .. $nchar ) {
852 23         27 my @column;
853 23         35 for my $j ( 0 .. ( $ntax - 1 ) ) {
854 161         203 push @column, $raw->[$j]->[$i];
855             }
856 23         40 my $col_pattern = join ' ', @column;
857 23         31 $pattern{$col_pattern}++;
858 23 100       58 $index{$col_pattern} = [] if not $index{$col_pattern};
859 23         24 push @{ $index{$col_pattern} }, $i - 1;
  23         45  
860             }
861 1         3 my @pattern_array;
862 1         3 for my $key ( keys %pattern ) {
863 2         9 my @column = split / /, $key;
864 2 50       5 if ( $indices ) {
865 0         0 push @pattern_array, [ $pattern{$key}, \@column, $index{$key} ];
866             }
867             else {
868 2         9 push @pattern_array, [ $pattern{$key}, \@column ];
869             }
870             }
871 1         7 return [ sort { $b->[0] <=> $a->[0] } @pattern_array ];
  1         15  
872             }
873              
874             =item calc_gc_content()
875              
876             Calculates the G+C content as a fraction on the total
877              
878             Type : Calculation
879             Title : calc_gc_content
880             Usage : my $fraction = $obj->calc_gc_content;
881             Function: Calculates G+C content
882             Returns : A number between 0 and 1 (inclusive)
883             Args : Optional:
884             # if true, counts missing (usually the '?' symbol) as a state
885             # in the final tallies. Otherwise, missing states are ignored
886             -missing => 1
887             # if true, counts gaps (usually the '-' symbol) as a state
888             # in the final tallies. Otherwise, gap states are ignored
889             -gap => 1
890             Comments: Throws 'BadArgs' exception if matrix holds anything other than DNA
891             or RNA. The calculation also takes the IUPAC symbol S (which is C|G)
892             into account, but no other symbols (such as V, for A|C|G);
893              
894             =cut
895              
896             sub calc_gc_content {
897 1     1 1 10 my $self = shift;
898 1         4 my $type = $self->get_type;
899 1 50       6 if ( $type !~ /^(?:d|r)na/i ) {
900 0         0 throw 'BadArgs' => "Matrix doesn't contain nucleotides";
901             }
902 1         5 my $freq = $self->calc_state_frequencies;
903 1         2 my $total = 0;
904 1         3 for (qw(c C g G s S)) {
905 6 100       13 $total += $freq->{$_} if exists $freq->{$_};
906             }
907 1         15 return $total;
908             }
909              
910             =item calc_median_sequence()
911              
912             Calculates the median character sequence of the matrix
913              
914             Type : Calculation
915             Title : calc_median_sequence
916             Usage : my $seq = $obj->calc_median_sequence;
917             Function: Calculates median sequence
918             Returns : Array in list context, string in scalar context
919             Args : Optional:
920             -ambig => if true, uses ambiguity codes to summarize equally frequent
921             states for a given character. Otherwise picks a random one.
922             -missing => if true, keeps the missing symbol (probably '?') if this
923             is the most frequent for a given character. Otherwise strips it.
924             -gaps => if true, keeps the gap symbol (probably '-') if this is the most
925             frequent for a given character. Otherwise strips it.
926             Comments: The intent of this method is to provide a crude approximation of the most
927             commonly occurring sequences in an alignment, for example as a starting
928             sequence for a sequence simulator. This gives you something to work with if
929             ancestral sequence calculation is too computationally intensive and/or not
930             really necessary.
931              
932             =cut
933              
934             sub calc_median_sequence {
935 0     0 1 0 my ( $self, %args ) = @_;
936 0         0 my $to = $self->get_type_object;
937 0         0 my $type = $self->get_type;
938 0 0       0 if ( $type =~ /continuous/i ) {
939 0         0 throw 'BadArgs' => 'No median sequence calculation for continuous data (yet)';
940             }
941             else {
942 0         0 my $raw = $self->get_raw;
943 0         0 my $ntax = $self->get_ntax;
944 0         0 my $nchar = $self->get_nchar;
945 0         0 my $gap = $self->get_gap;
946 0         0 my $miss = $self->get_missing;
947 0         0 my @seq;
948 0         0 for my $i ( 1 .. $nchar ) {
949 0         0 my %seen;
950 0         0 for my $row ( @$raw ) {
951 0         0 my $c = uc $row->[$i];
952 0         0 $seen{$c}++;
953             }
954 0         0 my @sorted = sort { $seen{$b} <=> $seen{$a} } keys %seen;
  0         0  
955 0         0 my $max = $seen{$sorted[0]};
956 0         0 my @top;
957 0         0 my $j = 0;
958 0   0     0 while ( $sorted[$j] and $seen{$sorted[$j]} == $max ) {
959 0         0 push @top, $sorted[$j];
960 0         0 $j++;
961             }
962 0 0       0 if ( @top == 1 ) {
963 0         0 push @seq, @top;
964             }
965             else {
966 0 0       0 if ( $args{'-ambig'} ) {
967 0         0 push @seq, $to->get_symbol_for_states(@top);
968             }
969             else {
970 0         0 push @seq, shift @top;
971             }
972             }
973             }
974 0 0       0 if ( not $args{'-gaps'} ) {
975 0         0 @seq = grep { $_ ne $gap } @seq;
  0         0  
976             }
977 0 0       0 if ( not $args{'-missing'} ) {
978 0         0 @seq = grep { $_ ne $miss } @seq;
  0         0  
979             }
980 0 0       0 return wantarray ? @seq : join '', @seq;
981             }
982             }
983              
984             =back
985              
986             =head2 METHODS
987              
988             =over
989              
990             =item keep_chars()
991              
992             Creates a cloned matrix that only keeps the characters at
993             the supplied (zero-based) indices.
994              
995             Type : Utility method
996             Title : keep_chars
997             Usage : my $clone = $object->keep_chars([6,3,4,1]);
998             Function: Creates spliced clone.
999             Returns : A spliced clone of the invocant.
1000             Args : Required, an array ref of integers
1001             Comments: The columns are retained in the order in
1002             which they were supplied.
1003              
1004             =cut
1005              
1006             sub keep_chars {
1007 4     4 1 11 my ( $self, $indices_array_ref ) = @_;
1008 4         8 my @indices = @{$indices_array_ref};
  4         8  
1009 4         20 my $clone = $self->clone;
1010 4         8 for my $seq ( @{ $clone->get_entities } ) {
  4         13  
1011 16         49 $seq->keep_entities( \@indices );
1012 16         21 my @anno = @{ $seq->get_annotations };
  16         34  
1013 16 50       37 if (@anno) {
1014 0         0 my @re_anno = @anno[@indices];
1015 0         0 $seq->set_annotations(@re_anno);
1016             }
1017             }
1018 4         14 $clone->get_characters->keep_entities(\@indices);
1019 4         27 return $clone;
1020             }
1021              
1022             =item prune_chars()
1023              
1024             Creates a cloned matrix that omits the characters at
1025             the supplied (zero-based) indices.
1026              
1027             Type : Utility method
1028             Title : prune_chars
1029             Usage : my $clone = $object->prune_chars([6,3,4,1]);
1030             Function: Creates spliced clone.
1031             Returns : A spliced clone of the invocant.
1032             Args : Required, an array ref of integers
1033             Comments: The columns are retained in the order in
1034             which they were supplied.
1035              
1036             =cut
1037              
1038             sub prune_chars {
1039 3     3 1 20 my ( $self, $indices ) = @_;
1040 3         8 my $nchar = $self->get_nchar;
1041 3         8 my %indices = map { $_ => 1 } @{$indices};
  7         18  
  3         7  
1042 3         6 my @keep;
1043 3         10 for my $i ( 0 .. ( $nchar - 1 ) ) {
1044 13 100       32 push @keep, $i if not exists $indices{$i};
1045             }
1046 3         17 return $self->keep_chars( \@keep );
1047             }
1048              
1049             =item prune_invariant()
1050              
1051             Creates a cloned matrix that omits the characters for which all taxa
1052             have the same state (or missing);
1053              
1054             Type : Utility method
1055             Title : prune_invariant
1056             Usage : my $clone = $object->prune_invariant;
1057             Function: Creates spliced clone.
1058             Returns : A spliced clone of the invocant.
1059             Args : None
1060             Comments: The columns are retained in the order in
1061             which they were supplied.
1062              
1063             =cut
1064              
1065             sub prune_invariant {
1066 1     1 1 10 my $self = shift;
1067 1         3 my $nchar = $self->get_nchar;
1068 1         4 my $missing = $self->get_missing;
1069 1         2 my @indices;
1070 1         3 for my $i ( 0 .. ( $nchar - 1 ) ) {
1071 5         7 my %seen;
1072 5         6 ROW: for my $row ( @{ $self->get_entities } ) {
  5         12  
1073 25         44 my $state = $row->get_by_index($i);
1074 25 100       50 next ROW if $state eq $missing;
1075 14         27 $seen{ $state } = 1;
1076             }
1077 5         13 my @states = keys %seen;
1078 5 100       13 push @indices, $i if scalar(@states) <= 1;
1079             }
1080 1         5 return $self->prune_chars(\@indices);
1081             }
1082              
1083             =item prune_uninformative()
1084              
1085             Creates a cloned matrix that omits all uninformative characters. Uninformative
1086             are considered characters where all non-missing values are either invariant
1087             or autapomorphies.
1088              
1089             Type : Utility method
1090             Title : prune_uninformative
1091             Usage : my $clone = $object->prune_uninformative;
1092             Function: Creates spliced clone.
1093             Returns : A spliced clone of the invocant.
1094             Args : None
1095             Comments: The columns are retained in the order in
1096             which they were supplied.
1097              
1098             =cut
1099              
1100             sub prune_uninformative {
1101 1     1 1 13 my $self = shift;
1102 1         4 my $nchar = $self->get_nchar;
1103 1         4 my $ntax = $self->get_ntax;
1104 1         5 my $missing = $self->get_missing;
1105 1         3 my @indices;
1106 1         5 for my $i ( 0 .. ( $nchar - 1 ) ) {
1107 5         7 my %seen;
1108 5         7 for my $row ( @{ $self->get_entities } ) {
  5         12  
1109 25         52 my $state = $row->get_by_index($i);
1110 25         42 $seen{ $state }++;
1111             }
1112 5         12 my @states = keys %seen;
1113 5 100       10 push @indices, $i if scalar(@states) <= 1;
1114 5 100       11 if ( scalar(@states) > 1 ) {
1115 4         6 my $seen_informative;
1116 4   100     12 my $non_missing = $ntax - ( $seen{$missing} || 0 );
1117 4         6 my @informative_maybe;
1118 4         6 for my $state ( @states ) {
1119 10 100       14 if ( $seen{$state} == 1 ) {
1120 4         7 $non_missing--;
1121             }
1122             else {
1123 6         12 push @informative_maybe, $state;
1124             }
1125             }
1126 4         6 for my $state ( @informative_maybe ) {
1127 6 100       12 $seen_informative++ if $seen{$state} < $non_missing;
1128             }
1129 4 100       13 push @indices, $i unless $seen_informative;
1130             }
1131             }
1132 1         5 return $self->prune_chars(\@indices);
1133             }
1134              
1135             =item prune_missing_and_gaps()
1136              
1137             Creates a cloned matrix that omits all characters for which the invocant only
1138             has missing and/or gap states.
1139              
1140             Type : Utility method
1141             Title : prune_missing_and_gaps
1142             Usage : my $clone = $object->prune_missing_and_gaps;
1143             Function: Creates spliced clone.
1144             Returns : A spliced clone of the invocant.
1145             Args : None
1146             Comments: The columns are retained in the order in
1147             which they were supplied.
1148              
1149             =cut
1150              
1151             sub prune_missing_and_gaps {
1152 0     0 1 0 my $self = shift;
1153 0         0 my $nchar = $self->get_nchar;
1154 0         0 my $to = $self->get_type_object;
1155 0         0 my $m = $to->get_missing;
1156 0         0 my $g = $to->get_gap;
1157 0         0 my @indices;
1158 0         0 for my $i ( 0 .. ( $nchar - 1 ) ) {
1159 0         0 my %col;
1160             $self->visit(sub{
1161 0     0   0 my $row = shift;
1162 0         0 my $state = $row->get_by_index($i);
1163 0 0 0     0 if ( $state ne $m and $state ne $g ) {
1164 0         0 $col{$state} = 1;
1165             }
1166 0 0       0 push @indices, $i if not keys %col;
1167 0         0 });
1168             }
1169 0         0 return $self->prune_chars(\@indices);
1170             }
1171              
1172             =item bootstrap()
1173              
1174             Creates bootstrapped clone.
1175              
1176             Type : Utility method
1177             Title : bootstrap
1178             Usage : my $bootstrap = $object->bootstrap;
1179             Function: Creates bootstrapped clone.
1180             Returns : A bootstrapped clone of the invocant.
1181             Args : Optional, a subroutine reference that returns a random
1182             integer between 0 (inclusive) and the argument provided
1183             to it (exclusive). The default implementation is to use
1184             sub { int( rand( shift ) ) }, a user might override this
1185             by providing an implementation with a better random number
1186             generator.
1187             Comments: The bootstrapping algorithm uses perl's random number
1188             generator to create a new series of indices (without
1189             replacement) of the same length as the original matrix.
1190             These indices are first sorted, then applied to the
1191             cloned sequences. Annotations (if present) stay connected
1192             to the resampled cells.
1193              
1194             =cut
1195              
1196             sub bootstrap {
1197 0     0   0 my $self = shift;
1198 0   0 0   0 my $gen = shift || sub { int( rand(shift) ) };
  0         0  
1199 0         0 my $nchar = $self->get_nchar;
1200 0         0 my @indices;
1201 0         0 push @indices, $gen->($nchar) for ( 1 .. $nchar );
1202 0         0 @indices = sort { $a <=> $b } @indices;
  0         0  
1203 0         0 return $self->keep_chars( \@indices );
1204             }
1205              
1206             =item jackknife()
1207              
1208             Creates jackknifed clone.
1209              
1210             Type : Utility method
1211             Title : jackknife
1212             Usage : my $bootstrap = $object->jackknife(0.5);
1213             Function: Creates jackknifed clone.
1214             Returns : A jackknifed clone of the invocant.
1215             Args : * Required, a number between 0 and 1, representing the
1216             fraction of characters to jackknife.
1217             * Optional, a subroutine reference that returns a random
1218             integer between 0 (inclusive) and the argument provided
1219             to it (exclusive). The default implementation is to use
1220             sub { int( rand( shift ) ) }, a user might override this
1221             by providing an implementation with a better random number
1222             generator.
1223             Comments: The jackknife algorithm uses perl's random number
1224             generator to create a new series of indices of cells to keep.
1225             These indices are first sorted, then applied to the
1226             cloned sequences. Annotations (if present) stay connected
1227             to the resampled cells.
1228              
1229             =cut
1230              
1231             sub jackknife {
1232 0     0 1 0 my ( $self, $prop ) = @_;
1233 0 0 0     0 if ( not looks_like_number $prop or $prop >= 1 or $prop < 0 ) {
      0        
1234 0         0 throw 'BadNumber' =>
1235             "Jackknifing proportion must be a number between 0 and 1";
1236             }
1237 0   0 0   0 my $gen = $_[2] || sub { int( rand(shift) ) };
  0         0  
1238 0         0 my $nchar = $self->get_nchar;
1239 0         0 my ( %indices, @indices );
1240 0         0 while ( scalar keys %indices < ( $nchar - int( $nchar * $prop ) ) ) {
1241 0         0 $indices{ $gen->($nchar) } = 1;
1242             }
1243 0         0 @indices = sort { $a <=> $b } keys %indices;
  0         0  
1244 0         0 return $self->keep_chars( \@indices );
1245             }
1246              
1247             =item replicate()
1248              
1249             Creates simulated replicate.
1250              
1251             Type : Utility method
1252             Title : replicate
1253             Usage : my $replicate = $matrix->replicate($tree);
1254             Function: Creates simulated replicate.
1255             Returns : A simulated replicate of the invocant.
1256             Args : Tree to simulate the characters on.
1257             Optional:
1258             -seed => a random integer seed
1259             -model => an object of class Bio::Phylo::Models::Substitution::Dna or
1260             Bio::Phylo::Models::Substitution::Binary
1261             -random_rootseq => start DNA sequence simulation from random ancestral sequence
1262             instead of the median sequence in the alignment.
1263              
1264             Comments: Requires Statistics::R, with 'ape', 'phylosim', 'phangorn' and 'phytools'.
1265             If model is not given as argument, it will be estimated.
1266              
1267             =cut
1268              
1269             sub replicate {
1270 0     0 1 0 my ($self,%args) = @_;
1271              
1272 0         0 my $tree = $args{'-tree'};
1273 0 0       0 if ( not looks_like_object $tree, _TREE_ ) {
1274 0         0 throw 'BadArgs' => "Need tree as argument";
1275             }
1276              
1277 0         0 my $type = $self->get_type;
1278 0 0       0 if ( $type =~ /dna/i ) {
    0          
1279             return $self->_replicate_dna(
1280             '-tree' => $tree,
1281             '-model' => $args{'-model'},
1282             '-seed' => $args{'-seed'},
1283 0         0 '-random_rootseq' => $args{'-random_rootseq'}
1284             );
1285             }
1286             elsif ( $type =~ /standard/i ) {
1287             return $self->_replicate_binary(
1288             '-tree' => $tree,
1289             '-model' => $args{'-model'},
1290 0         0 '-seed' => $args{'-seed'}
1291             );
1292             }
1293             else {
1294 0         0 throw 'BadArgs' => "Can't replicate $type matrices (yet?)";
1295             }
1296             }
1297            
1298             sub _replicate_dna {
1299 0     0   0 my ($self,%args) = @_;
1300            
1301 0         0 my $seed = $args{'-seed'};
1302 0         0 my $tree = $args{'-tree'};
1303 0         0 my $model = $args{'-model'};
1304 0         0 my $random_rootseq = $args{'-random_rootseq'};
1305            
1306 0 0       0 if ( scalar @{ $self->get_entities } < 3 ) {
  0         0  
1307 0         0 $logger->warn("Cannot replicate a matrix with less than three elements.");
1308 0         0 return;
1309             }
1310              
1311             # we will need 'ape', 'phylosim' (and 'phangorn' for model testing)
1312 0 0       0 if ( looks_like_class 'Statistics::R' ) {
1313              
1314             # instantiate R
1315 0         0 my $R = Statistics::R->new;
1316 0 0       0 $R->run(qq[set.seed($seed)]) if $seed;
1317 0         0 $R->run(q[options(device=NULL)]);
1318 0         0 $R->run(q[require('ape')]);
1319 0         0 $R->run(q[phylosim <- require('phylosim')]);
1320 0         0 $R->run(q[PSIM_FAST<-TRUE]);
1321             # check if phylosim (and therefore ape) is installed
1322 0 0       0 if ( $R->get(q[phylosim]) eq 'FALSE' ) {
1323 0         0 $logger->warn('R package phylosim must be installed to replicate alignment.');
1324 0         0 return;
1325             }
1326              
1327             # pass in the tree, scale it so that its length sums to 1.
1328             # in combination with a gamma function and/or invariant sites
1329             # this should give us sequences that are reasonably realistic:
1330             # not overly divergent.
1331 0         0 my $newick = $tree->to_newick;
1332 0         0 $R->run(qq[phylo <- read.tree(text="$newick")]);
1333 0         0 $R->run(q[tree <- PhyloSim(phylo)]);
1334 0         0 $R->run(q[scaleTree(tree,1/tree$treeLength)]);
1335 0         0 $R->run(q[t <- tree$phylo]);
1336              
1337             # run the model test if model not given as argument
1338 0 0       0 if ( ! $model ) {
1339 0         0 $logger->info("no model given as argument, determining model with phangorn's modelTest");
1340 0         0 $model = 'Bio::Phylo::Models::Substitution::Dna'->modeltest(
1341             '-matrix' => $self,
1342             '-tree' => $tree
1343             );
1344             }
1345             # prepare data for processes
1346 0         0 my @ungapped = @{ $self->get_ungapped_columns };
  0         0  
1347 0         0 my @invariant = @{ $self->get_invariant_columns };
  0         0  
1348 0         0 my %deletions = %{ $self->calc_indel_sizes( '-trim' => 0 ) };
  0         0  
1349 0         0 my %insertions = %{ $self->calc_indel_sizes( '-trim' => 0, '-insertions' => 1 ) };
  0         0  
1350              
1351             # set ancestral (root) sequence
1352 0         0 my $ancestral;
1353 0         0 my @alphabet = ('A', 'T', 'C', 'G');
1354 0 0       0 if ( $random_rootseq ) {
1355             # start from random sequence if requested
1356 0         0 $logger->debug('simulating from random ancestral sequence');
1357 0         0 $ancestral .= $alphabet[ rand @alphabet ] for 1..$self->length;
1358             }
1359             else {
1360 0         0 $logger->debug('simulating from median ancestral sequence');
1361             # start from median sequence
1362 0         0 $ancestral = $self->calc_median_sequence;
1363             # phylosim does not accept ambiguity characters, replace with random nucleotides
1364 0         0 $ancestral =~ s/[^ATCG]/$alphabet[rand @alphabet]/g;
1365             }
1366 0         0 $logger->debug("ancestral sequence for simulation : $ancestral");
1367 0         0 $R->run(qq[root.seq=NucleotideSequence(string='$ancestral')]);
1368              
1369 0         0 my $m = ref($model);
1370 0 0       0 if ( $m=~/([^\:\:]+$)/ ) {
1371 0         0 $m = $1;
1372             }
1373             # mapping between model names (can differ between Bio::Phylo and phylosim)
1374 0         0 my %models = ('JC69'=>'JC69', 'GTR'=>'GTR', 'F81'=>'F81', 'HKY85'=>'HKY', 'K80'=>'K80');
1375 0   0     0 my $type = $models{$m} || 'GTR';
1376              
1377             # collect model specific parameters in string passed to R
1378 0         0 my $model_params = '';
1379 0 0       0 if ( $type =~ /(?:F81|GTR|K80|HKY)/ ) {
1380 0         0 $logger->debug("setting base frequencies for substitution model");
1381 0         0 $model_params .= 'base.freqs=c(' . join(',', @{$model->get_pi}) . ')';
  0         0  
1382             }
1383 0 0       0 if ( $type =~ /GTR/ ) {
1384 0         0 $logger->debug("setting rate params for GTR model");
1385             # (watch out for different column order in phangorn's and phylosim's Q matrix!)
1386 0         0 my $a = $model->get_rate('C', 'T');
1387 0         0 my $b = $model->get_rate('A', 'T');
1388 0         0 my $c = $model->get_rate('G', 'T');
1389 0         0 my $d = $model->get_rate('A', 'C');
1390 0         0 my $e = $model->get_rate('G', 'C');
1391 0         0 my $f = $model->get_rate('G', 'A');
1392 0         0 $model_params .= ", rate.params=list(a=$a, b=$b, c=$c, d=$d, e=$e, f=$f)";
1393             }
1394 0 0       0 if ( $type =~ /(?:K80|HKY)/) {
1395 0         0 $logger->debug("setting kappa parameter for substitution model");
1396 0         0 my $kappa = $model->get_kappa;
1397             # get transition and transversion rates from kappa,
1398             # scale with number of nucleotides to obtain similar Q matrices
1399 0         0 my $alpha = $kappa * 4;
1400 0         0 my $beta = 4;
1401 0         0 $model_params .= ", rate.params=list(Alpha=$alpha, Beta=$beta)";
1402             }
1403             # create model for phylosim
1404 0         0 $R->run(qq[model <- $type($model_params)]);
1405             # set parameters for indels
1406 0 0       0 if ( keys %deletions ) {
1407             # deletions
1408 0         0 my @del_sizes = keys %deletions;
1409 0         0 my $del_total = 0;
1410 0         0 $del_total += $_ for values (%deletions);
1411 0         0 my @del_probs = map {$_/$del_total} values %deletions;
  0         0  
1412 0         0 my $size_str = 'c(' . join(',', @del_sizes) . ')';
1413 0         0 my $prob_str = 'c(' . join(',', @del_probs) . ')';
1414 0         0 my $rate = scalar(keys %deletions) / $self->get_nchar;
1415 0         0 $logger->debug("Setting deletion rate to $rate");
1416 0         0 $R->run(qq[attachProcess(root.seq, DiscreteDeletor(rate=$rate, sizes=$size_str, probs=$prob_str))]);
1417             }
1418 0 0       0 if ( keys %insertions ) {
1419             # insertions
1420 0         0 my @ins_sizes = keys %insertions;
1421 0         0 my $ins_total = 0;
1422 0         0 $ins_total += $_ for values (%insertions);
1423 0         0 my @ins_probs = map {$_/$ins_total} values %insertions;
  0         0  
1424 0         0 my $size_str = 'c(' . join(',', @ins_sizes) . ')';
1425 0         0 my $prob_str = 'c(' . join(',', @ins_probs) . ')';
1426 0         0 my $maxsize = (sort { $b <=> $a } keys(%insertions))[0];
  0         0  
1427 0         0 my $rate = scalar(keys %insertions) / $self->get_nchar;
1428 0         0 $logger->debug("Setting insertion rate to $rate");
1429 0         0 $R->run(qq[i <- DiscreteInsertor(rate=$rate, sizes=$size_str, probs=$prob_str)]);
1430 0         0 $R->run(qq[template <- NucleotideSequence(length=$maxsize,processes=list(list(model)))]);
1431 0         0 $R->run(q[i$templateSeq <- template]);
1432 0         0 $R->run(qq[attachProcess(root.seq, i)]);
1433             }
1434              
1435             # specify model that evolves root sequence
1436 0         0 $R->run(q[attachProcess(root.seq, model)]);
1437              
1438             # set invariant sites
1439 0 0       0 if ( scalar @invariant ) {
1440 0   0     0 my $pinvar = $model->get_pinvar || scalar(@invariant)/$self->get_nchar;
1441 0 0       0 if ( $pinvar == 1){
1442 0         0 my $epsilon = 0.01;
1443 0         0 $pinvar -= $epsilon;
1444             }
1445             # set a high value for gamma, then we approximate the empirical number of invariant sites
1446 0         0 $R->run(qq[plusInvGamma(root.seq,model,pinv=$pinvar,shape=1e10)]);
1447             }
1448              
1449             # run the simulation
1450 0         0 $R->run(q[sim <- PhyloSim(root.seq=root.seq, phylo=t)]);
1451 0         0 $R->run(q[Simulate(sim)]);
1452              
1453             # get alignment as Fasta string
1454 0         0 my $aln = $R->get(q[paste('>', t$tip.label, '\n', apply(sim$alignment, 1, paste, collapse='')[t$tip.label], '\n', collapse='', sep='')]);
1455 0         0 $aln =~ s/\\n/\n/g;
1456              
1457             # create matrix
1458 0         0 my $project = parse(
1459             '-string' => $aln,
1460             '-format' => 'fasta',
1461             '-type' => 'dna',
1462             '-as_project' => 1,
1463             );
1464              
1465 0         0 my ($matrix) = @{ $project->get_items(_MATRIX_) };
  0         0  
1466              
1467 0         0 return $matrix;
1468             }
1469             }
1470              
1471             sub _replicate_binary {
1472 0     0   0 my ($self,%args) = @_;
1473              
1474 0         0 my $seed = $args{'-seed'};
1475 0         0 my $tree = $args{'-tree'};
1476              
1477             # we will need both 'ape' and 'phytools'
1478 0 0       0 if ( looks_like_class 'Statistics::R' ) {
1479 0         0 my $pattern = $self->calc_distinct_site_patterns('with_indices_of_patterns');
1480 0         0 my $characters = $self->get_characters;
1481 0         0 $logger->info("matrix has ".scalar(@$pattern)." distinct patterns");
1482              
1483             # instantiate R
1484 0         0 my $newick = $tree->to_newick;
1485 0         0 my $R = Statistics::R->new;
1486 0 0       0 $R->run(qq[set.seed($seed)]) if $seed;
1487 0         0 $R->run(q[library("ape")]);
1488 0         0 $R->run(q[library("phytools")]);
1489 0         0 $R->run(qq[phylo <- read.tree(text="$newick")]);
1490              
1491             # start the matrix
1492 0         0 my @matrix;
1493 0         0 push @matrix, [] for 1 .. $self->get_ntax;
1494              
1495             # iterate over distinct site patterns
1496 0         0 for my $p ( @$pattern ) {
1497 0         0 my $i = $p->[2]->[0];
1498 0         0 my %pat = map { $_ => 1 } @{ $p->[1] };
  0         0  
  0         0  
1499 0         0 my $states;
1500              
1501             # if this column is completely invariant, the model
1502             # estimator is unhappy, so we just copy it over
1503 0 0       0 if ( keys(%pat) == 1 ) {
1504 0         0 $logger->info("column is invariant, copying verbatim");
1505 0         0 $states = $p->[1];
1506             }
1507             else {
1508 0         0 my $model = $args{'-model'};
1509 0 0       0 if ( ! $model ) {
1510 0         0 $logger->info("going to test model for column(s) $i..*");
1511 0         0 $model = Bio::Phylo::Models::Substitution::Binary->modeltest(
1512             '-tree' => $tree,
1513             '-matrix' => $self,
1514             '-char' => $characters->get_by_index($i),
1515             );
1516             }
1517             # pass model to R
1518 0         0 my ( $fw, $rev ) = ( $model->get_forward, $model->get_reverse );
1519              
1520             # add epsilon if one of the rates is zero, otherwise sim.history chokes
1521 0 0       0 $fw = 1e-6 if $fw == 0;
1522 0 0       0 $rev = 1e-6 if $rev == 0;
1523 0         0 $logger->info("0 -> 1 ($fw), 1 -> 0 ($rev)");
1524              
1525 0         0 my $rates = [ 0, $fw, $rev, 0 ];
1526 0         0 $R->set( 'rates' => $rates );
1527 0         0 $R->run(qq[Q<-matrix(rates,2,2,byrow=TRUE)]);
1528 0         0 $R->run(qq[rownames(Q)<-colnames(Q)<-c("0","1")]);
1529 0         0 $R->run(qq[diag(Q)<--rowSums(Q)]);
1530              
1531             # simulate character on tree, get states
1532 0         0 $R->run(qq[tt<-sim.history(phylo,Q,message=FALSE)]);
1533 0         0 $R->run(qq[states<-as.double(getStates(tt,"tips"))]);
1534 0         0 $states = $R->get(q[states]);
1535             }
1536              
1537             # add states to matrix
1538 0         0 my @indices = @{ $p->[2] };
  0         0  
1539 0         0 for my $row ( 0 .. $#{ $states } ) {
  0         0  
1540 0         0 my $value = $states->[$row];
1541 0         0 for my $col ( @indices ) {
1542 0         0 $matrix[$row]->[$col] = $value;
1543             }
1544             }
1545             }
1546              
1547             # create matrix
1548 0         0 my $i = 0;
1549             $tree->visit_depth_first(
1550             '-pre' => sub {
1551 0     0   0 my $node = shift;
1552 0 0       0 if ( $node->is_terminal ) {
1553 0         0 unshift @{ $matrix[$i++] }, $node->get_name
  0         0  
1554             }
1555             }
1556 0         0 );
1557 0         0 $logger->info(Dumper(\@matrix));
1558 0         0 return $factory->create_matrix(
1559             '-type' => $self->get_type,
1560             '-raw' => \@matrix,
1561             );
1562             }
1563             }
1564              
1565             =item insert()
1566              
1567             Insert argument in invocant.
1568              
1569             Type : Listable method
1570             Title : insert
1571             Usage : $matrix->insert($datum);
1572             Function: Inserts $datum in $matrix.
1573             Returns : Modified object
1574             Args : A datum object
1575             Comments: This method re-implements the method by the same
1576             name in Bio::Phylo::Listable
1577              
1578             =cut
1579              
1580             sub insert {
1581 120     120 1 266 my ( $self, $obj ) = @_;
1582 120         177 my $obj_container;
1583 120         190 eval { $obj_container = $obj->_container };
  120         350  
1584 120 100 66     437 if ( $@ || $obj_container != $self->_type ) {
1585 1         4 throw 'ObjectMismatch' => 'object not a datum object!';
1586             }
1587 119         699 $logger->info("inserting '$obj' in '$self'");
1588 119 50       326 if ( !$self->get_type_object->is_same( $obj->get_type_object ) ) {
1589 0         0 throw 'ObjectMismatch' => 'object is of wrong data type';
1590             }
1591 119         468 my $taxon1 = $obj->get_taxon;
1592 119   66     465 my $mname = $self->get_name || $self->get_internal_name;
1593 119         217 for my $ents ( @{ $self->get_entities } ) {
  119         314  
1594 224 50       437 if ( $obj->get_id == $ents->get_id ) {
1595 0         0 throw 'ObjectMismatch' => 'row already inserted';
1596             }
1597 224 100       482 if ($taxon1) {
1598 1         6 my $tname = $taxon1->get_name;
1599 1         3 my $taxon2 = $ents->get_taxon;
1600 1 50 33     8 if ( $taxon2 && $taxon1->get_id == $taxon2->get_id ) {
1601 0         0 my $tmpl = 'Note: a row linking to %s already exists in matrix %s';
1602 0         0 $logger->info(sprintf $tmpl,$tname,$mname);
1603             }
1604             }
1605             }
1606 119         412 $self->SUPER::insert($obj);
1607 119         467 return $self;
1608             }
1609              
1610             =begin comment
1611              
1612             Validates the object's contents.
1613              
1614             Type : Method
1615             Title : validate
1616             Usage : $obj->validate
1617             Function: Validates the object's contents
1618             Returns : True or throws Bio::Phylo::Util::Exceptions::InvalidData
1619             Args : None
1620             Comments: This method implements the interface method by the same
1621             name in Bio::Phylo::Matrices::TypeSafeData
1622              
1623             =end comment
1624              
1625             =cut
1626              
1627             sub _validate {
1628 0     0   0 shift->visit( sub { shift->validate } );
  73     73   482  
1629             }
1630              
1631             =item compress_lookup()
1632              
1633             Removes unused states from lookup table
1634              
1635             Type : Method
1636             Title : validate
1637             Usage : $obj->compress_lookup
1638             Function: Removes unused states from lookup table
1639             Returns : $self
1640             Args : None
1641              
1642             =cut
1643              
1644             sub compress_lookup {
1645 0     0 1 0 my $self = shift;
1646 0         0 my $to = $self->get_type_object;
1647 0         0 my $lookup = $to->get_lookup;
1648 0         0 my %seen;
1649 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
1650 0         0 my @char = $row->get_char;
1651 0         0 $seen{$_}++ for (@char);
1652             }
1653 0         0 for my $state ( keys %{$lookup} ) {
  0         0  
1654 0 0       0 if ( not exists $seen{$state} ) {
1655 0         0 delete $lookup->{$state};
1656             }
1657             }
1658 0         0 $to->set_lookup($lookup);
1659 0         0 return $self;
1660             }
1661              
1662             =item check_taxa()
1663              
1664             Validates taxa associations.
1665              
1666             Type : Method
1667             Title : check_taxa
1668             Usage : $obj->check_taxa
1669             Function: Validates relation between matrix and taxa block
1670             Returns : Modified object
1671             Args : None
1672             Comments: This method implements the interface method by the same
1673             name in Bio::Phylo::Taxa::TaxaLinker
1674              
1675             =cut
1676              
1677             sub check_taxa {
1678 48     48 1 74 my $self = shift;
1679              
1680             # is linked to taxa
1681 48 100       114 if ( my $taxa = $self->get_taxa ) {
1682             my %taxa =
1683 44         66 map { $_->get_internal_name => $_ } @{ $taxa->get_entities };
  186         371  
  44         147  
1684 44         102 ROW_CHECK: for my $row ( @{ $self->get_entities } ) {
  44         102  
1685 116 100       237 if ( my $taxon = $row->get_taxon ) {
1686 105 50       190 next ROW_CHECK if exists $taxa{ $taxon->get_name };
1687             }
1688 11         35 my $name = $row->get_name;
1689 11 50       28 if ( exists $taxa{$name} ) {
1690 11         78 $row->set_taxon( $taxa{$name} );
1691             }
1692             else {
1693 0         0 my $taxon = $factory->create_taxon( -name => $name );
1694 0         0 $taxa{$name} = $taxon;
1695 0         0 $taxa->insert($taxon);
1696 0         0 $row->set_taxon($taxon);
1697             }
1698             }
1699             }
1700              
1701             # not linked
1702             else {
1703 4         9 for my $row ( @{ $self->get_entities } ) {
  4         17  
1704 16         30 $row->set_taxon();
1705             }
1706             }
1707 48         108 return $self;
1708             }
1709              
1710             =item make_taxa()
1711              
1712             Creates a taxa block from the objects contents if none exists yet.
1713              
1714             Type : Method
1715             Title : make_taxa
1716             Usage : my $taxa = $obj->make_taxa
1717             Function: Creates a taxa block from the objects contents if none exists yet.
1718             Returns : $taxa
1719             Args : NONE
1720              
1721             =cut
1722              
1723             sub make_taxa {
1724 1     1 1 8 my $self = shift;
1725 1 50       8 if ( my $taxa = $self->get_taxa ) {
1726 0         0 return $taxa;
1727             }
1728             else {
1729 1         2 my %taxa;
1730 1         8 my $taxa = $factory->create_taxa;
1731 1         3 for my $row ( @{ $self->get_entities } ) {
  1         4  
1732 2         6 my $name = $row->get_internal_name;
1733 2 50       6 if ( not $taxa{$name} ) {
1734 2         10 $taxa{$name} = $factory->create_taxon( '-name' => $name );
1735             }
1736             }
1737 1 50       4 if ( keys %taxa ) {
1738 1         6 $taxa->insert( map { $taxa{$_} } sort { $a cmp $b } keys %taxa );
  2         9  
  1         4  
1739             }
1740 1         11 $self->set_taxa($taxa);
1741 1         4 return $taxa;
1742             }
1743             }
1744              
1745             =back
1746              
1747             =head2 SERIALIZERS
1748              
1749             =over
1750              
1751             =item to_xml()
1752              
1753             Serializes matrix to nexml format.
1754              
1755             Type : Format convertor
1756             Title : to_xml
1757             Usage : my $data_block = $matrix->to_xml;
1758             Function: Converts matrix object into a nexml element structure.
1759             Returns : Nexml block (SCALAR).
1760             Args : Optional:
1761             -compact => 1 (for compact representation of matrix)
1762              
1763             =cut
1764              
1765             sub to_xml {
1766 0     0 1 0 my $self = shift;
1767 0         0 $logger->debug("writing $self to xml");
1768 0         0 my ( %args, $ids_for_states );
1769 0 0       0 if (@_) {
1770 0         0 %args = @_;
1771             }
1772              
1773             # creating opening tag
1774 0         0 my $type = $self->get_type;
1775 0 0       0 my $verbosity = $args{'-compact'} ? 'Seqs' : 'Cells';
1776 0         0 my $xsi_type = 'nex:' . ucfirst($type) . $verbosity;
1777 0         0 $self->set_attributes( 'xsi:type' => $xsi_type );
1778 0         0 my $xml = $self->get_xml_tag;
1779 0         0 $logger->debug("created opening tag $xml");
1780              
1781             # normalizing symbol table
1782 0         0 my $normalized = $self->_normalize_symbols;
1783 0         0 $logger->debug("normalized symbols");
1784              
1785             # the format block
1786 0         0 $xml .= '<format>';
1787 0         0 $logger->debug($xml);
1788 0         0 my $to = $self->get_type_object;
1789 0         0 $ids_for_states = $to->get_ids_for_states(1);
1790              
1791             # write state definitions
1792             # this calls Datatype::to_xml method
1793             #
1794 0         0 $xml .= $to->to_xml( $normalized, $self->get_polymorphism );
1795 0         0 $logger->debug($xml);
1796 0         0 $xml .= $self->get_characters->to_xml;
1797 0         0 $xml .= "\n</format>";
1798              
1799             # the matrix block
1800 0         0 $xml .= "\n<matrix>";
1801             my @char_ids =
1802 0         0 map { $_->get_xml_id } @{ $self->get_characters->get_entities };
  0         0  
  0         0  
1803              
1804             # write rows
1805 0         0 my $special = $self->get_type_object->get_ids_for_special_symbols(1);
1806 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
1807 0         0 $xml .= "\n"
1808             . $row->to_xml(
1809             '-states' => $ids_for_states,
1810             '-chars' => \@char_ids,
1811             '-symbols' => $normalized,
1812             '-special' => $special,
1813             %args,
1814             );
1815             }
1816 0         0 $xml .= "\n</matrix>";
1817 0         0 $xml .= "\n" . sprintf( '</%s>', $self->get_tag );
1818 0         0 return $xml;
1819             }
1820              
1821             # what this does:
1822             # numerical states are their own keys; also want numerical
1823             # representations for non-numerical states..."normalization"
1824             # provides this mapping....
1825             sub _normalize_symbols {
1826 0     0   0 my $self = shift;
1827 0         0 $logger->debug("normalizing symbols");
1828 0 0       0 if ( $self->get_type =~ /^standard$/i ) {
1829 0         0 my $to = $self->get_type_object;
1830 0         0 my $lookup = $self->get_lookup;
1831 0         0 my @states = keys %{$lookup};
  0         0  
1832 0 0       0 if ( my @letters = sort { $a cmp $b } grep { /[a-z]/i } @states ) {
  0         0  
  0         0  
1833 0         0 my @numbers = sort { $a <=> $b } grep { /^\d+$/ } @states;
  0         0  
  0         0  
1834 0         0 my $i = $numbers[-1];
1835 0         0 my %map = map { $_ => ++$i } @letters;
  0         0  
1836 0         0 return \%map;
1837             }
1838             else {
1839 0         0 return {};
1840             }
1841             }
1842             else {
1843 0         0 return {};
1844             }
1845             }
1846              
1847             =item to_nexus()
1848              
1849             Serializes matrix to nexus format.
1850              
1851             Type : Format convertor
1852             Title : to_nexus
1853             Usage : my $data_block = $matrix->to_nexus;
1854             Function: Converts matrix object into a nexus data block.
1855             Returns : Nexus data block (SCALAR).
1856             Args : The following options are available:
1857              
1858             # if set, writes TITLE & LINK tokens
1859             '-links' => 1
1860              
1861             # if set, writes block as a "data" block (deprecated, but used by mrbayes),
1862             # otherwise writes "characters" block (default)
1863             -data_block => 1
1864              
1865             # if set, writes "RESPECTCASE" token
1866             -respectcase => 1
1867              
1868             # if set, writes "GAPMODE=(NEWSTATE or MISSING)" token
1869             -gapmode => 1
1870              
1871             # if set, writes "MSTAXA=(POLYMORPH or UNCERTAIN)" token
1872             -polymorphism => 1
1873              
1874             # if set, writes character labels
1875             -charlabels => 1
1876              
1877             # if set, writes state labels
1878             -statelabels => 1
1879              
1880             # if set, writes mesquite-style charstatelabels
1881             -charstatelabels => 1
1882              
1883             # by default, names for sequences are derived from $datum->get_name, if
1884             # 'internal' is specified, uses $datum->get_internal_name, if 'taxon'
1885             # uses $datum->get_taxon->get_name, if 'taxon_internal' uses
1886             # $datum->get_taxon->get_internal_name, if $key, uses $datum->get_generic($key)
1887             -seqnames => one of (internal|taxon|taxon_internal|$key)
1888              
1889             =cut
1890              
1891             sub to_nexus {
1892 1     1 1 3 my $self = shift;
1893 1         5 $logger->info("writing to nexus: $self");
1894 1         3 my %args = @_;
1895 1         2 my $nchar = $self->get_nchar;
1896             my $string = sprintf "BEGIN %s;\n",
1897 1 50       7 $args{'-data_block'} ? 'DATA' : 'CHARACTERS';
1898 1         15 $string .=
1899             "[! Characters block written by "
1900             . ref($self) . " "
1901             . $self->VERSION . " on "
1902             . localtime() . " ]\n";
1903              
1904             # write links
1905 1 50       5 if ( $args{'-links'} ) {
1906 0         0 $string .= sprintf "\tTITLE %s;\n", $self->get_internal_name;
1907 0 0       0 $string .= sprintf "\tLINK TAXA=%s;\n",
1908             $self->get_taxa->get_internal_name
1909             if $self->get_taxa;
1910             }
1911              
1912             # dimensions token line - data block defines NTAX, characters block doesn't
1913 1 50       3 if ( $args{'-data_block'} ) {
1914 0         0 $string .= "\tDIMENSIONS NTAX=" . $self->get_ntax() . ' ';
1915 0         0 $string .= 'NCHAR=' . $nchar . ";\n";
1916             }
1917             else {
1918 1         4 $string .= "\tDIMENSIONS NCHAR=" . $nchar . ";\n";
1919             }
1920              
1921             # format token line
1922 1         7 $string .= "\tFORMAT DATATYPE=" . $self->get_type();
1923             $string .= ( $self->get_respectcase ? " RESPECTCASE" : "" )
1924 1 0       166 if $args{'-respectcase'}; # mrbayes no like
    50          
1925 1 50       5 $string .= " MATCHCHAR=" . $self->get_matchchar if $self->get_matchchar;
1926 1         6 $string .= " MISSING=" . $self->get_missing();
1927 1 50       101 $string .= " GAP=" . $self->get_gap() if $self->get_gap();
1928 1         2 $string .= ";\n";
1929              
1930             # options token line (mrbayes no like)
1931 1 50 33     6 if ( $args{'-gapmode'} or $args{'-polymorphism'} ) {
1932 0         0 $string .= "\tOPTIONS ";
1933             $string .=
1934             "GAPMODE=" . ( $self->get_gapmode ? "NEWSTATE " : "MISSING " )
1935 0 0       0 if $args{'-gapmode'};
    0          
1936             $string .=
1937             "MSTAXA="
1938             . ( $self->get_polymorphism ? "POLYMORPH " : "UNCERTAIN " )
1939 0 0       0 if $args{'-polymorphism'};
    0          
1940 0         0 $string .= ";\n";
1941             }
1942              
1943             # charlabels token line
1944 1 50       4 if ( $args{'-charlabels'} ) {
1945 0         0 my $charlabels;
1946 0 0       0 if ( my @labels = @{ $self->get_charlabels } ) {
  0         0  
1947 0         0 my $i = 1;
1948 0         0 for my $label (@labels) {
1949 0 0       0 $charlabels .=
1950             $label =~ /\s/
1951             ? "\n\t\t [$i] '$label'"
1952             : "\n\t\t [$i] $label";
1953 0         0 $i++;
1954             }
1955 0         0 $string .= "\tCHARLABELS$charlabels\n\t;\n";
1956             }
1957             }
1958              
1959             # statelabels token line
1960 1 50       3 if ( $args{'-statelabels'} ) {
1961 0         0 my $statelabels;
1962 0 0       0 if ( my @labels = @{ $self->get_statelabels } ) {
  0         0  
1963 0         0 my $i = 1;
1964 0         0 for my $labelset (@labels) {
1965 0         0 $statelabels .= "\n\t\t $i";
1966 0         0 for my $label ( @{$labelset} ) {
  0         0  
1967 0 0       0 $statelabels .=
1968             $label =~ /\s/
1969             ? "\n\t\t\t'$label'"
1970             : "\n\t\t\t$label";
1971 0         0 $i++;
1972             }
1973 0         0 $statelabels .= ',';
1974             }
1975 0         0 $string .= "\tSTATELABELS$statelabels\n\t;\n";
1976             }
1977             }
1978              
1979             # charstatelabels token line
1980 1 50       3 if ( $args{'-charstatelabels'} ) {
1981 0         0 my @charlabels = @{ $self->get_charlabels };
  0         0  
1982 0         0 my @statelabels = @{ $self->get_statelabels };
  0         0  
1983 0 0 0     0 if ( @charlabels and @statelabels ) {
1984 0         0 my $charstatelabels;
1985 0         0 my $nlabels = $self->get_nchar - 1;
1986 0         0 for my $i ( 0 .. $nlabels ) {
1987 0         0 $charstatelabels .= "\n\t\t" . ( $i + 1 );
1988 0 0       0 if ( my $label = $charlabels[$i] ) {
1989 0 0       0 $charstatelabels .=
1990             $label =~ /\s/ ? " '$label' /" : " $label /";
1991             }
1992             else {
1993 0         0 $charstatelabels .= " ' ' /";
1994             }
1995 0 0       0 if ( my $labelset = $statelabels[$i] ) {
1996 0         0 for my $label ( @{$labelset} ) {
  0         0  
1997 0 0       0 $charstatelabels .=
1998             $label =~ /\s/ ? " '$label'" : " $label";
1999             }
2000             }
2001             else {
2002 0         0 $charstatelabels .= " ' '";
2003             }
2004 0 0       0 $charstatelabels .= ',' if $i < $nlabels;
2005             }
2006 0         0 $string .= "\tCHARSTATELABELS$charstatelabels\n\t;\n";
2007             }
2008             }
2009              
2010             # ...and write matrix!
2011 1         2 $string .= "\tMATRIX\n";
2012 1         3 my $length = 0;
2013 1         2 foreach my $datum ( @{ $self->get_entities } ) {
  1         3  
2014 3 100       13 $length = length( $datum->get_nexus_name )
2015             if length( $datum->get_nexus_name ) > $length;
2016             }
2017 1         2 $length += 4;
2018 1         2 my $sp = ' ';
2019 1         2 foreach my $datum ( @{ $self->get_entities } ) {
  1         3  
2020 3         4 $string .= "\t\t";
2021              
2022             # construct name
2023 3         4 my $name;
2024 3 50 0     7 if ( not $args{'-seqnames'} ) {
    0          
    0          
2025 3         6 $name = $datum->get_nexus_name;
2026             }
2027             elsif ( $args{'-seqnames'} =~ /^internal$/i ) {
2028 0         0 $name = $datum->get_nexus_name;
2029             }
2030             elsif ( $args{'-seqnames'} =~ /^taxon/i and $datum->get_taxon ) {
2031 0 0       0 if ( $args{'-seqnames'} =~ /^taxon_internal$/i ) {
    0          
2032 0         0 $name = $datum->get_taxon->get_nexus_name;
2033             }
2034             elsif ( $args{'-seqnames'} =~ /^taxon$/i ) {
2035 0         0 $name = $datum->get_taxon->get_nexus_name;
2036             }
2037             }
2038             else {
2039 0         0 $name = $datum->get_generic( $args{'-seqnames'} );
2040             }
2041 3 50       7 $name = $datum->get_nexus_name if not $name;
2042 3         8 $string .= $name . ( $sp x ( $length - length($name) ) );
2043 3         8 my $char = $datum->get_char;
2044 3         7 $string .= $char . "\n";
2045             }
2046 1         2 $string .= "\t;\nEND;\n";
2047 1         6 return $string;
2048             }
2049              
2050             =item to_dom()
2051              
2052             Analog to to_xml.
2053              
2054             Type : Serializer
2055             Title : to_dom
2056             Usage : $matrix->to_dom
2057             Function: Generates a DOM subtree from the invocant
2058             and its contained objects
2059             Returns : an Element object
2060             Args : Optional:
2061             -compact => 1 : renders characters as sequences,
2062             not individual cells
2063              
2064             =cut
2065              
2066             sub to_dom {
2067 0     0 1 0 my $self = shift;
2068 0         0 my $dom = $_[0];
2069 0         0 my @args = @_;
2070              
2071             # handle dom factory object...
2072 0 0 0     0 if ( looks_like_instance( $dom, 'SCALAR' )
2073             && $dom->_type == _DOMCREATOR_ )
2074             {
2075 0         0 splice( @args, 0, 1 );
2076             }
2077             else {
2078 0         0 $dom = $Bio::Phylo::NeXML::DOM::DOM;
2079 0 0       0 unless ($dom) {
2080 0         0 throw 'BadArgs' => 'DOM factory object not provided';
2081             }
2082             }
2083             #### make sure argument handling works here...
2084 0         0 my ( %args, $ids_for_states );
2085 0 0       0 %args = @args if @args;
2086 0         0 my $type = $self->get_type;
2087 0 0       0 my $verbosity = $args{'-compact'} ? 'Seqs' : 'Cells';
2088 0         0 my $xsi_type = 'nex:' . ucfirst($type) . $verbosity;
2089 0         0 $self->set_attributes( 'xsi:type' => $xsi_type );
2090 0         0 my $elt = $self->get_dom_elt($dom);
2091 0         0 my $normalized = $self->_normalize_symbols;
2092              
2093             # the format block
2094 0         0 my $format_elt = $dom->create_element( '-tag' => 'format' );
2095 0         0 my $to = $self->get_type_object;
2096 0         0 $ids_for_states = $to->get_ids_for_states(1);
2097              
2098             # write state definitions
2099 0         0 $format_elt->set_child(
2100             $to->to_dom( $dom, $normalized, $self->get_polymorphism ) );
2101              
2102             # write column definitions
2103             $format_elt->set_child($_)
2104 0 0       0 for $self->_package_char_labels( $dom,
2105 0         0 %{$ids_for_states} ? $to->get_xml_id : undef );
2106 0         0 $elt->set_child($format_elt);
2107              
2108             # the matrix block
2109 0         0 my $mx_elt = $dom->create_element( '-tag' => 'matrix' );
2110 0         0 my @char_ids;
2111 0         0 for ( 0 .. $self->get_nchar ) {
2112 0         0 push @char_ids, 'c' . ( $_ + 1 );
2113             }
2114              
2115             # write rows
2116 0         0 my $special = $self->get_type_object->get_ids_for_special_symbols(1);
2117 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
2118              
2119             # $row->to_dom is calling ...::Datum::to_dom...
2120 0         0 $mx_elt->set_child(
2121             $row->to_dom(
2122             $dom,
2123             '-states' => $ids_for_states,
2124             '-chars' => \@char_ids,
2125             '-symbols' => $normalized,
2126             '-special' => $special,
2127             %args,
2128             )
2129             );
2130             }
2131 0         0 $elt->set_child($mx_elt);
2132 0         0 return $elt;
2133             }
2134              
2135             # returns an array of elements
2136             sub _package_char_labels {
2137 0     0   0 my ( $self, $dom, $states_id ) = @_;
2138 0         0 my @elts;
2139 0         0 my $labels = $self->get_charlabels;
2140 0         0 for my $i ( 1 .. $self->get_nchar ) {
2141 0         0 my $char_id = 'c' . $i;
2142 0         0 my $label = $labels->[ $i - 1 ];
2143 0         0 my $elt = $dom->create_element( '-tag' => 'char' );
2144 0         0 $elt->set_attributes( 'id' => $char_id );
2145 0 0       0 $elt->set_attributes( 'label' => $label ) if $label;
2146 0 0       0 $elt->set_attributes( 'states' => $states_id ) if $states_id;
2147 0         0 push @elts, $elt;
2148             }
2149 0         0 return @elts;
2150             }
2151 6     6   17 sub _tag { 'characters' }
2152 634     634   1463 sub _type { $CONSTANT_TYPE }
2153 18     18   33 sub _container { $CONSTANT_CONTAINER }
2154              
2155             sub _update_characters {
2156 137     137   236 my $self = shift;
2157 137         337 my $nchar = $self->get_nchar;
2158 137         499 my $characters = $self->get_characters;
2159 137         369 my $type_object = $self->get_type_object;
2160 137         245 my @chars = @{ $characters->get_entities };
  137         348  
2161 137         304 my @defined = grep { defined $_ } @chars;
  607         991  
2162 137 100       342 if ( scalar @defined != $nchar ) {
2163 26         95 for my $i ( 0 .. ( $nchar - 1 ) ) {
2164 157 50       365 if ( not $chars[$i] ) {
2165 157         776 $characters->insert_at_index(
2166             $factory->create_character(
2167             '-type_object' => $type_object
2168             ),
2169             $i,
2170             );
2171             }
2172             }
2173             }
2174 137         229 @chars = @{ $characters->get_entities };
  137         309  
2175 137 50       529 if ( scalar(@chars) > $nchar ) {
2176 0           $characters->prune_entities( [ $nchar .. $#chars ] );
2177             }
2178             }
2179              
2180             =back
2181              
2182             =cut
2183              
2184             # podinherit_insert_token
2185              
2186             =head1 SEE ALSO
2187              
2188             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
2189             for any user or developer questions and discussions.
2190              
2191             =over
2192              
2193             =item L<Bio::Phylo::Taxa::TaxaLinker>
2194              
2195             This object inherits from L<Bio::Phylo::Taxa::TaxaLinker>, so the
2196             methods defined therein are also applicable to L<Bio::Phylo::Matrices::Matrix>
2197             objects.
2198              
2199             =item L<Bio::Phylo::Matrices::TypeSafeData>
2200              
2201             This object inherits from L<Bio::Phylo::Matrices::TypeSafeData>, so the
2202             methods defined therein are also applicable to L<Bio::Phylo::Matrices::Matrix>
2203             objects.
2204              
2205             =item L<Bio::Phylo::Manual>
2206              
2207             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
2208              
2209             =back
2210              
2211             =head1 CITATION
2212              
2213             If you use Bio::Phylo in published research, please cite it:
2214              
2215             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
2216             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
2217             I<BMC Bioinformatics> B<12>:63.
2218             L<http://dx.doi.org/10.1186/1471-2105-12-63>
2219              
2220             =cut
2221              
2222             }
2223             1;
2224             __DATA__
2225              
2226             my %CONSERVATION_GROUPS = (
2227             'strong' => [ qw(
2228             STA
2229             NEQK
2230             NHQK
2231             NDEQ
2232             QHRK
2233             MILV
2234             MILF
2235             HY
2236             FYW
2237             )],
2238             'weak' => [ qw(
2239             CSA
2240             ATV
2241             SAG
2242             STNK
2243             STPA
2244             SGND
2245             SNDEQK
2246             NDEQHK
2247             NEQHRK
2248             FVLIM
2249             HFY
2250             )],
2251             );
2252              
2253             sub description {
2254             my ( $self, $desc ) = @_;
2255             if ( defined $desc ) {
2256             $self->set_desc( $desc );
2257             }
2258             return $self->get_desc;
2259             }
2260              
2261             sub num_sequences {
2262             my ( $self, $num ) = @_;
2263             # setter?
2264             return scalar @{ $self->get_entities };
2265             }
2266              
2267             sub datatype {
2268             my ( $self, $type ) = @_;
2269             # setter?
2270             return uc $self->get_type;
2271             }
2272              
2273             sub score {
2274             my ( $self, $score ) = @_;
2275             if ( defined $score ) {
2276             $self->set_score( $score );
2277             }
2278             return $self->get_score;
2279             }
2280              
2281             sub add_seq {
2282             my ( $self, $seq, $order ) = @_;
2283             $self->insert( $seq );
2284             }
2285              
2286             sub remove_seq {
2287             my ( $self, $seq ) = @_;
2288             $self->delete( $seq );
2289             }
2290              
2291             sub purge {
2292             $logger->warn
2293             }
2294              
2295             sub sort_alphabetically {
2296             my $self = shift;
2297             my @sorted = map { $_->[0] }
2298             sort { $a->[1] cmp $b->[1] }
2299             map { [ $_, $_->get_name ] }
2300             @{ $self->get_entities };
2301             $self->clear;
2302             $self->insert(@sorted);
2303             return @sorted;
2304             }
2305              
2306             sub each_seq {
2307             my $self = shift;
2308             return @{ $self->get_entities };
2309             }
2310              
2311             sub each_alphabetically {
2312             my $self = shift;
2313             return map { $_->[0] }
2314             sort { $a->[1] cmp $b->[1] }
2315             map { [ $_, $_->get_name ] } @{ $self->get_entities };
2316             }
2317              
2318             sub each_seq_with_id {
2319             my ( $self, $name ) = @_;
2320             return @{
2321             $self->get_by_regular_expression(
2322             '-value' => 'get_name',
2323             '-match' => qr/^\Q$name\E$/
2324             )
2325             }
2326             }
2327              
2328             sub get_seq_by_pos {
2329             my ( $self, $pos ) = @_;
2330             return $self->get_by_index( $pos - 1 );
2331             }
2332              
2333             sub select {
2334             my ( $self, $start, $end ) = @_;
2335             my $clone = $self->clone;
2336             my @contents = @{ $clone->get_entities };
2337             my @deleteme;
2338             for my $i ( 0 .. $#contents ) {
2339             if ( $i < $start - 1 or $i > $end - 1 ) {
2340             push @deleteme, $contents[$i];
2341             }
2342             }
2343             $clone->delete( $_ ) for @deleteme;
2344             return $clone;
2345             }
2346              
2347             sub select_noncont {
2348             my ( $self, @indices ) = @_;
2349             my $clone = $self->clone;
2350             my @contents = @{ $clone->get_entities };
2351             my ( @deleteme, %keep );
2352             %keep = map { ( $_ - 1 ) => 1 } @indices;
2353             for my $i ( 0 .. $#contents ) {
2354             if ( not exists $keep{$i} ) {
2355             push @deleteme, $contents[$i];
2356             }
2357             }
2358             $clone->delete( $_ ) for @deleteme;
2359             return $clone;
2360             }
2361              
2362             sub slice {
2363             my ( $self, $start, $end, $include_gapped ) = @_;
2364             my $clone = $self->clone;
2365             my $gap = $self->get_gap;
2366             SEQ: for my $seq ( @{ $clone->get_entities } ) {
2367             my @char = $self->get_char;
2368             my @slice = splice @char, ( $start - 1 ), ( $end - $start - 1 );
2369             if ( not $include_gapped ) {
2370             if ( not grep { $_ !~ /^\Q$gap\E$/ } @slice ) {
2371             next SEQ;
2372             }
2373             }
2374             $seq->set_char(@slice);
2375             }
2376             }
2377              
2378             sub map_chars {
2379             my ( $self, $from, $to ) = @_;
2380             for my $seq ( @{ $self->get_entities } ) {
2381             my @char = $seq->get_char;
2382             for my $c ( @char ) {
2383             $c =~ s/$from/$to/;
2384             }
2385             $seq->set_char( @char );
2386             }
2387             }
2388              
2389             sub uppercase {
2390             my $self = shift;
2391             for my $seq ( @{ $self->get_entities } ) {
2392             my @char = $seq->get_char;
2393             my @uc = map { uc $_ } @char;
2394             $seq->set_char(@uc);
2395             }
2396             }
2397              
2398             # from simplealign
2399             sub match_line {
2400             my ($self,$matchlinechar, $strong, $weak) = @_;
2401             my %matchchars = ('match' => $matchlinechar || '*',
2402             'weak' => $weak || '.',
2403             'strong' => $strong || ':',
2404             'mismatch' => ' ',
2405             );
2406              
2407             my @seqchars;
2408             my $alphabet;
2409             foreach my $seq ( $self->each_seq ) {
2410             push @seqchars, [ split(//, uc ($seq->seq)) ];
2411             $alphabet = $seq->alphabet unless defined $alphabet;
2412             }
2413             my $refseq = shift @seqchars;
2414             # let's just march down the columns
2415             my $matchline;
2416             POS:
2417             foreach my $pos ( 0..$self->length ) {
2418             my $refchar = $refseq->[$pos];
2419             my $char = $matchchars{'mismatch'};
2420             unless( defined $refchar ) {
2421             last if $pos == $self->length; # short circuit on last residue
2422             # this in place to handle jason's soon-to-be-committed
2423             # intron mapping code
2424             goto bottom;
2425             }
2426             my %col = ($refchar => 1);
2427             my $dash = ($refchar eq '-' || $refchar eq '.' || $refchar eq ' ');
2428             foreach my $seq ( @seqchars ) {
2429             next if $pos >= scalar @$seq;
2430             $dash = 1 if( $seq->[$pos] eq '-' || $seq->[$pos] eq '.' ||
2431             $seq->[$pos] eq ' ' );
2432             $col{$seq->[$pos]}++ if defined $seq->[$pos];
2433             }
2434             my @colresidues = sort keys %col;
2435              
2436             # if all the values are the same
2437             if( $dash ) { $char = $matchchars{'mismatch'} }
2438             elsif( @colresidues == 1 ) { $char = $matchchars{'match'} }
2439             elsif( $alphabet eq 'protein' ) { # only try to do weak/strong
2440             # matches for protein seqs
2441             TYPE:
2442             foreach my $type ( qw(strong weak) ) {
2443             # iterate through categories
2444             my %groups;
2445             # iterate through each of the aa in the col
2446             # look to see which groups it is in
2447             foreach my $c ( @colresidues ) {
2448             foreach my $f ( grep { index($_,$c) >= 0 } @{$CONSERVATION_GROUPS{$type}} ) {
2449             push @{$groups{$f}},$c;
2450             }
2451             }
2452             GRP:
2453             foreach my $cols ( values %groups ) {
2454             @$cols = sort @$cols;
2455             # now we are just testing to see if two arrays
2456             # are identical w/o changing either one
2457             # have to be same len
2458             next if( scalar @$cols != scalar @colresidues );
2459             # walk down the length and check each slot
2460             for($_=0;$_ < (scalar @$cols);$_++ ) {
2461             next GRP if( $cols->[$_] ne $colresidues[$_] );
2462             }
2463             $char = $matchchars{$type};
2464             last TYPE;
2465             }
2466             }
2467             }
2468             bottom:
2469             $matchline .= $char;
2470             }
2471             return $matchline;
2472             }
2473              
2474             sub match {
2475             my ( $self, $match ) = @_;
2476             if ( defined $match ) {
2477             $self->set_matchchar($match);
2478             }
2479             else {
2480             $self->set_matchchar('.');
2481             }
2482             $match = $self->get_matchchar;
2483             my $lookup = $self->get_type_object->get_lookup->{$match} = [ $match ];
2484             my @seqs = @{ $self->get_entities };
2485             my @firstseq = $seqs[0]->get_char;
2486             for my $i ( 1 .. $#seqs ) {
2487             my @char = $seqs[$i]->get_char;
2488             for my $j ( 0 .. $#char ) {
2489             if ( $char[$j] eq $firstseq[$j] ) {
2490             $char[$j] = $match;
2491             }
2492             }
2493             $seqs[$i]->set_char(@char);
2494             }
2495             1;
2496             }
2497              
2498             sub unmatch {
2499             my ( $self, $match ) = @_;
2500             if ( defined $match ) {
2501             $self->set_matchchar($match);
2502             }
2503             else {
2504             $self->set_matchchar('.');
2505             }
2506             $match = $self->get_matchchar;
2507             my @seqs = @{ $self->get_entities };
2508             my @firstseq = $seqs[0]->get_char;
2509             for my $i ( 1 .. $#seqs ) {
2510             my @char = $seqs[$i]->get_char;
2511             for my $j ( 0 .. $#char ) {
2512             if ( $char[$j] eq $match ) {
2513             $char[$j] = $firstseq[$j];
2514             }
2515             }
2516             $seqs[$i]->set_char(@char);
2517             }
2518             1;
2519             }
2520              
2521             sub id {
2522             my ( $self, $name ) = @_;
2523             if ( defined $name ) {
2524             $self->set_name( $name );
2525             }
2526             return $self->get_name;
2527             }
2528              
2529             sub missing_char {
2530             my ( $self, $missing ) = @_;
2531             if ( defined $missing ) {
2532             $self->set_missing( $missing );
2533             }
2534             return $self->get_missing;
2535             }
2536              
2537             sub match_char {
2538             my ( $self, $match ) = @_;
2539             if ( defined $match ) {
2540             $self->set_matchchar( $match );
2541             }
2542             return $self->get_matchchar;
2543             }
2544              
2545             sub gap_char {
2546             my ( $self, $gap ) = @_;
2547             if ( defined $gap ) {
2548             $self->set_gap( $gap );
2549             }
2550             return $self->get_gap;
2551             }
2552              
2553             sub symbol_chars {
2554             my ( $self, $includeextra ) = @_;
2555             my %seen;
2556             for my $row ( @{ $self->get_entities } ) {
2557             my @char = $row->get_char;
2558             $seen{$_} = 1 for @char;
2559             }
2560             return keys %seen if $includeextra;
2561             my $special_values = $self->get_special_symbols;
2562             my %special_keys = map { $_ => 1 } values %{ $special_values };
2563             return grep { ! $special_keys{$_} } keys %seen;
2564             }
2565              
2566             sub consensus_string {
2567             my $self = shift;
2568             my $to = $self->get_type_object;
2569             my $ntax = $self->get_ntax;
2570             my $nchar = $self->get_nchar;
2571             my @consensus;
2572             for my $i ( 0 .. $ntax - 1 ) {
2573             my ( @column, %column );
2574             for my $j ( 0 .. $nchar - 1 ) {
2575             $column{ $self->get_by_index($i)->get_by_index($j) } = 1;
2576             }
2577             @column = keys %column;
2578             push @consensus, $to->get_symbol_for_states(@column);
2579             }
2580             return join '', @consensus;
2581             }
2582              
2583             sub consensus_iupac {
2584             $logger->warn
2585             }
2586              
2587             sub is_flush { 1 }
2588              
2589             sub length { shift->get_nchar }
2590              
2591             sub maxname_length { $logger->warn }
2592              
2593             sub no_residues { $logger->warn }
2594              
2595             sub no_sequences {
2596             my $self = shift;
2597             return scalar @{ $self->get_entities };
2598             }
2599              
2600             sub percentage_identity { $logger->warn }
2601              
2602             # from simplealign
2603             sub average_percentage_identity{
2604             my ($self,@args) = @_;
2605              
2606             my @alphabet = ('A','B','C','D','E','F','G','H','I','J','K','L','M',
2607             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
2608              
2609             my ($len, $total, $subtotal, $divisor, $subdivisor, @seqs, @countHashes);
2610              
2611             if (! $self->is_flush()) {
2612             throw 'Generic' => "All sequences in the alignment must be the same length";
2613             }
2614              
2615             @seqs = $self->each_seq();
2616             $len = $self->length();
2617              
2618             # load the each hash with correct keys for existence checks
2619              
2620             for( my $index=0; $index < $len; $index++) {
2621             foreach my $letter (@alphabet) {
2622             $countHashes[$index] = {} if not $countHashes[$index];
2623             $countHashes[$index]->{$letter} = 0;
2624             }
2625             }
2626             foreach my $seq (@seqs) {
2627             my @seqChars = split //, $seq->seq();
2628             for( my $column=0; $column < @seqChars; $column++ ) {
2629             my $char = uc($seqChars[$column]);
2630             if (exists $countHashes[$column]->{$char}) {
2631             $countHashes[$column]->{$char}++;
2632             }
2633             }
2634             }
2635              
2636             $total = 0;
2637             $divisor = 0;
2638             for(my $column =0; $column < $len; $column++) {
2639             my %hash = %{$countHashes[$column]};
2640             $subdivisor = 0;
2641             foreach my $res (keys %hash) {
2642             $total += $hash{$res}*($hash{$res} - 1);
2643             $subdivisor += $hash{$res};
2644             }
2645             $divisor += $subdivisor * ($subdivisor - 1);
2646             }
2647             return $divisor > 0 ? ($total / $divisor )*100.0 : 0;
2648             }
2649              
2650             # from simplealign
2651             sub overall_percentage_identity{
2652             my ($self, $length_measure) = @_;
2653              
2654             my @alphabet = ('A','B','C','D','E','F','G','H','I','J','K','L','M',
2655             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
2656              
2657             my ($len, $total, @seqs, @countHashes);
2658              
2659             my %enum = map {$_ => 1} qw (align short long);
2660              
2661             throw 'Generic' => "Unknown argument [$length_measure]"
2662             if $length_measure and not $enum{$length_measure};
2663             $length_measure ||= 'align';
2664              
2665             if (! $self->is_flush()) {
2666             throw 'Generic' => "All sequences in the alignment must be the same length";
2667             }
2668              
2669             @seqs = $self->each_seq();
2670             $len = $self->length();
2671              
2672             # load the each hash with correct keys for existence checks
2673             for( my $index=0; $index < $len; $index++) {
2674             foreach my $letter (@alphabet) {
2675             $countHashes[$index] = {} if not $countHashes[$index];
2676             $countHashes[$index]->{$letter} = 0;
2677             }
2678             }
2679             foreach my $seq (@seqs) {
2680             my @seqChars = split //, $seq->seq();
2681             for( my $column=0; $column < @seqChars; $column++ ) {
2682             my $char = uc($seqChars[$column]);
2683             if (exists $countHashes[$column]->{$char}) {
2684             $countHashes[$column]->{$char}++;
2685             }
2686             }
2687             }
2688              
2689             $total = 0;
2690             for(my $column =0; $column < $len; $column++) {
2691             my %hash = %{$countHashes[$column]};
2692             foreach ( values %hash ) {
2693             next if( $_ == 0 );
2694             $total++ if( $_ == scalar @seqs );
2695             last;
2696             }
2697             }
2698              
2699             if ($length_measure eq 'short') {
2700             ## find the shortest length
2701             $len = 0;
2702             foreach my $seq ($self->each_seq) {
2703             my $count = $seq->seq =~ tr/[A-Za-z]//;
2704             if ($len) {
2705             $len = $count if $count < $len;
2706             } else {
2707             $len = $count;
2708             }
2709             }
2710             }
2711             elsif ($length_measure eq 'long') {
2712             ## find the longest length
2713             $len = 0;
2714             foreach my $seq ($self->each_seq) {
2715             my $count = $seq->seq =~ tr/[A-Za-z]//;
2716             if ($len) {
2717             $len = $count if $count > $len;
2718             } else {
2719             $len = $count;
2720             }
2721             }
2722             }
2723              
2724             return ($total / $len ) * 100.0;
2725             }
2726              
2727             sub column_from_residue_number {
2728             my ( $self, $seqname, $resnumber ) = @_;
2729             my $col;
2730             if ( my $seq = $self->get_by_name($seqname) ) {
2731             my $gap = $seq->get_gap;
2732             my @char = $seq->get_char;
2733             for my $i ( 0 .. $#char ) {
2734             $col++ if $char[$i] ne $gap;
2735             if ( $col + 1 == $resnumber ) {
2736             return $i + 1;
2737             }
2738             }
2739             }
2740             }
2741              
2742             sub displayname {
2743             my ( $self, $name, $disname ) = @_;
2744             my $seq;
2745             $self->visit( sub{ $seq = $_[0] if $_[0]->get_nse eq $name } );
2746             $self->throw("No sequence with name [$name]") unless $seq;
2747             my $disnames = $self->get_generic( 'displaynames' ) || {};
2748             if ( $disname and $name ) {
2749             $disnames->{$name} = $disname;
2750             return $disname;
2751             }
2752             elsif( defined $disnames->{$name} ) {
2753             return $disnames->{$name};
2754             }
2755             else {
2756             return $name;
2757             }
2758             }
2759              
2760             # from SimpleAlign
2761             sub maxdisplayname_length {
2762             my $self = shift;
2763             my $maxname = (-1);
2764             my ($seq,$len);
2765             foreach $seq ( $self->each_seq() ) {
2766             $len = CORE::length $self->displayname($seq->get_nse());
2767             if( $len > $maxname ) {
2768             $maxname = $len;
2769             }
2770             }
2771             return $maxname;
2772             }
2773              
2774             # from SimpleAlign
2775             sub set_displayname_flat {
2776             my $self = shift;
2777             my ($nse,$seq);
2778              
2779             foreach $seq ( $self->each_seq() ) {
2780             $nse = $seq->get_nse();
2781             $self->displayname($nse,$seq->id());
2782             }
2783             return 1;
2784             }
2785              
2786             sub set_displayname_count { $logger->warn }
2787              
2788             sub set_displayname_normal { $logger->warn }
2789              
2790             sub accession {
2791             my ( $self, $acc ) = @_;
2792             if ( defined $acc ) {
2793             $self->set_generic( 'accession' => $acc );
2794             }
2795             return $self->get_generic( 'accession' );
2796             }
2797              
2798             sub source {
2799             my ( $self, $source ) = @_;
2800             if ( defined $source ) {
2801             $self->set_generic( 'source' => $source );
2802             }
2803             return $self->get_generic( 'source' );
2804             }
2805              
2806             sub annotation {
2807             my ( $self, $anno ) = @_;
2808             if ( defined $anno ) {
2809             $self->set_generic( 'annotation' => $anno );
2810             }
2811             return $self->get_generic( 'annotation' );
2812             }
2813              
2814             sub consensus_meta {
2815             my ( $self, $meta ) = @_;
2816             if ( defined $meta ) {
2817             $self->set_generic( 'consensus_meta' => $meta );
2818             }
2819             return $self->get_generic( 'consensus_meta' );
2820             }
2821              
2822             # XXX this might be removed, and instead inherit from SimpleAlign
2823             sub max_metaname_length {
2824             my $self = shift;
2825             my $maxname = (-1);
2826             my ($seq,$len);
2827              
2828             # check seq meta first
2829             for $seq ( $self->each_seq() ) {
2830             next if !$seq->isa('Bio::Seq::MetaI' || !$seq->meta_names);
2831             for my $mtag ($seq->meta_names) {
2832             $len = CORE::length $mtag;
2833             if( $len > $maxname ) {
2834             $maxname = $len;
2835             }
2836             }
2837             }
2838              
2839             # alignment meta
2840             for my $meta ($self->consensus_meta) {
2841             next unless $meta;
2842             for my $name ($meta->meta_names) {
2843             $len = CORE::length $name;
2844             if( $len > $maxname ) {
2845             $maxname = $len;
2846             }
2847             }
2848             }
2849              
2850             return $maxname;
2851             }
2852              
2853              
2854             sub get_SeqFeatures { return }
2855              
2856