File Coverage

blib/lib/Bio/Phylo/Matrices/MatrixRole.pm
Criterion Covered Total %
statement 375 1244 30.1
branch 79 398 19.8
condition 13 120 10.8
subroutine 40 109 36.7
pod 33 80 41.2
total 540 1951 27.6


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