1#--------------------------------------------------------------------------- 2 3package Algorithm::Cluster; 4 5#--------------------------------------------------------------------------- 6# Copyright (c) 2003 John Nolan. All rights reserved. 7# This program is free software. You may modify and/or 8# distribute it under the same terms as Perl itself. 9# This copyright notice must remain attached to the file. 10# 11# Algorithm::Cluster is a set of Perl wrappers around the 12# C Clustering library. 13# 14#--------------------------------------------------------------------------- 15# The C clustering library for cDNA microarray data. 16# Copyright (C) 2002 Michiel Jan Laurens de Hoon. 17# 18# This library was written at the Laboratory of DNA Information Analysis, 19# Human Genome Center, Institute of Medical Science, University of Tokyo, 20# 4-6-1 Shirokanedai, Minato-ku, Tokyo 108-8639, Japan. 21# Contact: michiel.dehoon 'AT' riken.jp 22# 23# The Algorithm::Cluster module for Perl was released under the same terms 24# as the Perl Artistic license. See the file artistic.txt for details. 25#--------------------------------------------------------------------------- 26 27 28use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @EXPORT); 29use vars qw($DEBUG); 30use strict; 31use DynaLoader; 32 33require Exporter; 34 35$VERSION = '1.59'; 36$DEBUG = 1; 37@ISA = qw(DynaLoader Exporter); 38 39@EXPORT_OK = qw( 40 mean 41 median 42 kcluster 43 kmedoids 44 somcluster 45 treecluster 46 clusterdistance 47 clustercentroids 48 distancematrix 49 pca 50); 51 52use warnings::register; 53 54bootstrap Algorithm::Cluster $VERSION; 55 56 57#------------------------------------------------------------- 58# Debugging functions 59# 60sub version { 61 return _version(); 62} 63 64 65#------------------------------------------------------------- 66# Wrapper for printing warnings 67# 68sub module_warn { 69 return unless warnings::enabled(); 70 warnings::warn("Algorithm::Cluster", join '', @_); 71} 72 73#------------------------------------------------------------- 74# Make sure that the first parameter is a reference-to-array, 75# whose first member is itself a reference-to-array, 76# and that that array has at least one member. 77# 78sub data_is_valid_matrix { 79 unless (ref($_[0]) eq 'ARRAY') { 80 module_warn("Wanted array reference, but got a reference to ", 81 ref($_[0]), ". Cannot parse matrix"); 82 return; 83 } 84 my $nrows = scalar @{ $_[0] }; 85 unless ($nrows > 0) { 86 module_warn("Matrix has zero rows. Cannot parse matrix"); 87 return; 88 } 89 my $firstrow = $_[0]->[0]; 90 unless (defined $firstrow) { 91 module_warn("First row in matrix is undef scalar (?). Cannot parse matrix",); 92 return; 93 } 94 unless (ref($firstrow) eq 'ARRAY') { 95 module_warn("Wanted array reference, but got a reference to ", 96 ref($firstrow), ". Cannot parse matrix"); 97 return; 98 } 99 my $ncols = scalar @{ $_[0]->[0] }; 100 unless ($ncols > 0) { 101 module_warn("Row has zero columns. Cannot parse matrix"); 102 return; 103 } 104 unless (defined($_[0]->[0]->[0])) { 105 module_warn("Cell [0,0] is undefined. Cannot parse matrix"); 106 return; 107 } 108 return 1; 109} 110 111 112#------------------------------------------------------------- 113# Wrapper for the mean() function 114# 115sub mean { 116 if(ref $_[0] eq 'ARRAY') { 117 return _mean($_[0]); 118 } else { 119 return _mean([@_]); 120 } 121} 122 123#------------------------------------------------------------- 124# Wrapper for the median() function 125# 126sub median { 127 if(ref $_[0] eq 'ARRAY') { 128 return _median($_[0]); 129 } else { 130 return _median([@_]); 131 } 132} 133 134 135#------------------------------------------------------ 136# This function is called by the wrappers for library functions. 137# It checks the dimensions of the data, mask and weight parameters. 138# 139# Return false if any errors are found in the data matrix. 140# 141# Detect the dimension (nrows x ncols) of the data matrix, 142# and set values in the parameter hash. 143# 144# Also check the mask matrix and weight arrays, and set 145# the parameters to default values if we find any errors, 146# however, we still return true if we find errors. 147# 148sub check_matrix_dimensions { 149 my ($param, $default) = @_; 150 #---------------------------------- 151 # Check the data matrix 152 # 153 return unless data_is_valid_matrix($param->{data}); 154 #---------------------------------- 155 # Remember the dimensions of the weight array 156 # 157 $param->{nrows} = scalar @{ $param->{data} }; 158 $param->{ncols} = scalar @{ $param->{data}->[0] }; 159 #---------------------------------- 160 # Check the mask matrix 161 # 162 unless (data_is_valid_matrix($param->{mask})) { 163 module_warn("Parameter 'mask' is not a valid matrix, ignoring it."); 164 $param->{mask} = $default->{mask} 165 } else { 166 my $mask_nrows = scalar @{ $param->{mask} }; 167 my $mask_ncols = scalar @{ $param->{mask}->[0] }; 168 unless ($param->{nrows} == $mask_nrows and $param->{ncols} == $mask_ncols) { 169 module_warn("Data matrix is $param->{nrows}x$param->{ncols}, but mask matrix is ${mask_nrows}x${mask_ncols}.\nIgnoring the mask."); 170 $param->{mask} = $default->{mask}; 171 } 172 } 173 #---------------------------------- 174 # Check the weight array 175 # 176 unless(ref $param->{weight} eq 'ARRAY') { 177 module_warn("Parameter 'weight' does not point to an array, ignoring it."); 178 $param->{weight} = $default->{weight}; 179 } else { 180 my $weight_length = scalar @{ $param->{weight} }; 181 if ($param->{transpose} eq 0) { 182 unless ($param->{ncols} == $weight_length) { 183 module_warn("Data matrix has $param->{ncols} columns, but weight array has $weight_length items.\nIgnoring the weight array."); 184 $param->{weight} = $default->{weight} 185 } 186 } 187 else { 188 unless ($param->{nrows} == $weight_length) { 189 module_warn("Data matrix has $param->{nrows} rows, but weight array has $weight_length items.\nIgnoring the weight array."); 190 $param->{weight} = $default->{weight} 191 } 192 } 193 } 194 return 1; 195} 196 197 198sub check_distance_matrix { 199 my $distances = $_[0]; 200 my $i; 201 my $row; 202 my $column; 203 #---------------------------------- 204 # Check the data matrix 205 # 206 my $reference = ref($distances); 207 if (!$reference) { 208 return "Wanted array reference but did not receive a reference"; 209 } 210 elsif ($reference ne 'ARRAY') { 211 return "Wanted array reference, but got a $reference"; 212 } 213 my $nobjects = scalar @{ $distances }; 214 unless ($nobjects > 0) { 215 return "Distance matrix has zero rows"; 216 } 217 $i = 0; 218 foreach $row (@{ $distances}) { 219 unless (defined $row) { 220 return "Row $i is undefined"; 221 } 222 unless (ref($row) eq 'ARRAY') { 223 return "Row $i is not an array"; 224 } 225 unless (@{$row} == $i) { 226 return "Row $i has incorrect columns"; 227 } 228 foreach $column (@{$row}) { 229 unless (defined($column)) { 230 return "Row $i contains undefined columns"; 231 } 232 } 233 $i++; 234 } 235 return "OK"; 236} 237 238sub check_initialid { 239 my ($param, $default, $nobjects) = @_; 240 my $i; 241 my @counter = {}; 242 #---------------------------------- 243 # Check the initial clustering solution, if specified 244 # 245 if(ref $param->{initialid} ne 'ARRAY') { 246 module_warn("Optional parameter 'initialid' should be an array"); 247 return; 248 } 249 if (@{ $param->{initialid}} == 0) { 250 # no initial clustering solution specified 251 if ($param->{nclusters}==-1) { 252 $param->{nclusters} = 2; # default value 253 } 254 if ($param->{nclusters} > $nobjects) { 255 module_warn("More clusters requested than elements available"); 256 return; 257 } 258 unless($param->{npass} =~ /^\d+$/ and $param->{npass} > 0) { 259 module_warn("Parameter 'npass' must be a positive integer (got '$param->{npass}')"); 260 return; 261 } 262 return 1; 263 } 264 if (@{ $param->{initialid}} != $nobjects) { 265 module_warn("Optional parameter 'initialid' should contain $nobjects elements"); 266 return; 267 } 268 foreach $i (@{ $param->{initialid}}) { 269 unless($i =~ /^\d+$/ and $i >= 0) { 270 module_warn("Optional parameter 'initialid' should only contain non-negative integers"); 271 return; 272 } 273 } 274 if ($param->{nclusters} == -1) { 275 # number of clusters was not specified. Infer it from initialid 276 foreach $i (@{ $param->{initialid}}) { 277 if ($i > $param->{nclusters}) { 278 $param->{nclusters} = $i; 279 } 280 } 281 $param->{nclusters}++; 282 } else { 283 # check if initialid is consistent with number of clusters 284 foreach $i (@{ $param->{initialid}}) { 285 if ($i >= $param->{nclusters}) { 286 module_warn("Optional parameter 'initialid' inconsistent with nclusters"); 287 return; 288 } 289 } 290 } 291 # Check that none of the clusters are empty 292 for ($i = 0; $i < $param->{nclusters}; $i++) { 293 push(@counter, 0); 294 } 295 foreach $i (@{ $param->{initialid}}) { 296 $counter[$i]++; 297 } 298 for ($i = 0; $i < $param->{nclusters}; $i++) { 299 if ($counter[$i]==0) { 300 module_warn("Optional parameter 'initialid' contains empty clusters"); 301 return; 302 } 303 } 304 # No errors detected 305 $param->{npass} = 0; 306 return 1; 307} 308 309#------------------------------------------------------------- 310# Wrapper for the kcluster() function 311# 312sub kcluster { 313 #---------------------------------- 314 # Define default parameters 315 # 316 my %default = ( 317 nclusters => -1, 318 data => [[]], 319 mask => '', 320 weight => '', 321 transpose => 0, 322 npass => 1, 323 method => 'a', 324 dist => 'e', 325 initialid => [], 326 ); 327 #---------------------------------- 328 # Local variable 329 # 330 my $nobjects = 0; 331 #---------------------------------- 332 # Accept parameters from caller 333 # 334 my %param = (%default, @_); 335 my @data = @{$param{data}}; 336 #---------------------------------- 337 # Check the data, matrix and weight parameters 338 # 339 return unless check_matrix_dimensions(\%param, \%default); 340 #---------------------------------- 341 # Check the transpose parameter 342 # 343 if ($param{transpose} == 0) { 344 $nobjects = $param{nrows}; 345 } elsif ($param{transpose} == 1) { 346 $nobjects = $param{ncols}; 347 } else { 348 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 349 return; 350 } 351 #---------------------------------- 352 # Check the initial clustering, if specified, and npass 353 # 354 return unless check_initialid(\%param, \%default, $nobjects); 355 #---------------------------------- 356 # Check the other parameters 357 # 358 unless($param{method} =~ /^[am]$/) { 359 module_warn("Parameter 'method' must be either 'a' or 'm' (got '$param{method}')"); 360 return; 361 } 362 unless($param{dist} =~ /^[cauxskeb]$/) { 363 module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')"); 364 return; 365 } 366 #---------------------------------- 367 # Invoke the library function 368 # 369 return _kcluster(@param{ 370 qw/nclusters nrows ncols data mask weight transpose npass method dist initialid/ 371 }); 372} 373 374#------------------------------------------------------------- 375# Wrapper for the kmedoids() function 376# 377 378sub kmedoids { 379 #---------------------------------- 380 # Define default parameters 381 # 382 my %default = ( 383 384 nclusters => 2, 385 distances => [[]], 386 npass => 1, 387 initialid => [], 388 ); 389 #---------------------------------- 390 # Accept parameters from caller 391 # 392 my %param = (%default, @_); 393 #---------------------------------- 394 # Check the distance matrix 395 # 396 my $message = check_distance_matrix($param{distances}); 397 unless ($message eq "OK") { 398 module_warn($message); 399 return; 400 } 401 $param{nobjects} = scalar @{ $param{distances} }; 402 #---------------------------------- 403 # Check the initial clustering, if specified, and npass 404 # 405 return unless check_initialid(\%param, \%default, $param{nobjects}); 406 #---------------------------------- 407 # Invoke the library function 408 # 409 return _kmedoids(@param{ 410 qw/nclusters nobjects distances npass initialid/ 411 }); 412} 413 414#------------------------------------------------------------- 415# treecluster(): Wrapper for the treecluster function 416# 417sub treecluster { 418 #---------------------------------- 419 # Define default parameters 420 # 421 my %default = ( 422 data => [[]], 423 mask => '', 424 weight => '', 425 transpose => 0, 426 dist => 'e', 427 method => 's', 428 ); 429 #---------------------------------- 430 # Accept parameters from caller 431 # 432 my %param = (%default, @_); 433 #---------------------------------- 434 # Check the data, matrix and weight parameters 435 # 436 my $message = check_distance_matrix($param{data}); 437 if ($message eq "OK") { 438 $param{nrows} = scalar @{ $param{data} }; 439 $param{ncols} = scalar @{ $param{data} }; 440 $param{mask} = $default{mask}; 441 $param{weight} = $default{weight}; 442 $param{transpose} = $default{transpose}; 443 $param{dist} = $default{dist}; 444 #---------------------------------- 445 # Check the clustering method 446 # 447 unless($param{method} =~ /^[sma]$/) { 448 module_warn("Parameter 'method' must be one of [sma] (got '$param{method}')"); 449 return; 450 } 451 } else { 452 return unless check_matrix_dimensions(\%param, \%default); 453 unless($param{transpose} =~ /^[01]$/) { 454 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 455 return; 456 } 457 unless($param{dist} =~ /^[cauxskeb]$/) { 458 module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')"); 459 return; 460 } 461 unless($param{method} =~ /^[smca]$/) { 462 module_warn("Parameter 'method' must be one of [smca] (got '$param{method}')"); 463 return; 464 } 465 } 466 #---------------------------------- 467 # Invoke the library function 468 # 469 return _treecluster(@param{ 470 qw/nrows ncols data mask weight transpose dist method/ 471 }); 472} 473 474 475 476#------------------------------------------------------------- 477# Wrapper for the clusterdistance() function 478# 479sub clusterdistance { 480 #---------------------------------- 481 # Define default parameters 482 # 483 my %default = ( 484 data => [[]], 485 mask => '', 486 weight => '', 487 cluster1 => [], 488 cluster2 => [], 489 dist => 'e', 490 method => 'a', 491 transpose => 0, 492 ); 493 #---------------------------------- 494 # Accept parameters from caller 495 # 496 my %param = (%default, @_); 497 #---------------------------------- 498 # Check the cluster1 and cluster2 arrays 499 # 500 if($param{cluster1} =~ /^\d+$/) { 501 $param{cluster1} = [int($param{cluster1})]; 502 } elsif(ref $param{cluster1} ne 'ARRAY') { 503 module_warn("Parameter 'cluster1' does not point to an array. Cannot compute distance."); 504 return; 505 } elsif(@{ $param{cluster1}} <= 0) { 506 module_warn("Parameter 'cluster1' points to an empty array. Cannot compute distance."); 507 return; 508 } 509 if($param{cluster2} =~ /^\d+$/) { 510 $param{cluster2} = [int($param{cluster2})]; 511 } elsif(ref $param{cluster2} ne 'ARRAY') { 512 module_warn("Parameter 'cluster2' does not point to an array. Cannot compute distance."); 513 return; 514 } elsif(@{ $param{cluster2}} <= 0) { 515 module_warn("Parameter 'cluster2' points to an empty array. Cannot compute distance."); 516 return; 517 } 518 $param{cluster1_len} = @{ $param{cluster1}}; 519 $param{cluster2_len} = @{ $param{cluster2}}; 520 #---------------------------------- 521 # Check the data, matrix and weight parameters 522 # 523 return unless check_matrix_dimensions(\%param, \%default); 524 #---------------------------------- 525 # Check the other parameters 526 # 527 unless($param{transpose} =~ /^[01]$/) { 528 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 529 return; 530 } 531 unless($param{method} =~ /^[amsxv]$/) { 532 module_warn("Parameter 'method' must be 'a', 'm', 's', 'x', or 'v' (got '$param{method}')"); 533 return; 534 } 535 unless($param{dist} =~ /^[cauxskeb]$/) { 536 module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')"); 537 return; 538 } 539 #---------------------------------- 540 # Invoke the library function 541 # 542 return _clusterdistance(@param{ 543 qw/nrows ncols data mask weight cluster1_len cluster2_len 544 cluster1 cluster2 dist method transpose/ 545 }); 546} 547 548 549#------------------------------------------------------------- 550# Wrapper for the clustercentroids() function 551# 552sub clustercentroids { 553 #---------------------------------- 554 # Define default parameters 555 # 556 my %default = ( 557 data => [[]], 558 mask => '', 559 clusterid => [], 560 method => 'a', 561 transpose => 0, 562 ); 563 #---------------------------------- 564 # Accept parameters from caller 565 # 566 my %param = (%default, @_); 567 #---------------------------------- 568 # Check the data, matrix and weight parameters 569 # 570 return unless check_matrix_dimensions(\%param, \%default); 571 #---------------------------------- 572 # Check the other parameters 573 # 574 unless($param{transpose} =~ /^[01]$/) { 575 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 576 return; 577 } 578 unless($param{method} =~ /^[am]$/) { 579 module_warn("Parameter 'method' must be 'a' or 'm' (got '$param{method}')"); 580 return; 581 } 582 #---------------------------------- 583 # Check the clusterid arrays 584 # 585 if($param{clusterid} =~ /^\d+$/) { 586 $param{clusterid} = [int($param{clusterid})]; 587 } elsif(ref $param{clusterid} ne 'ARRAY') { 588 module_warn("Parameter 'clusterid' does not point to an array. Cannot compute distance."); 589 return; 590 } elsif(@{ $param{clusterid}} <= 0) { 591 module_warn("Parameter 'clusterid' points to an empty array. Cannot compute distance."); 592 return; 593 } 594 my $clusterid_len = @{ $param{clusterid}}; 595 my $nrows = $param{nrows}; 596 my $ncols = $param{ncols}; 597 if ($param{transpose}==0 and $clusterid_len != $nrows) { 598 die "Parameter 'clusterid' should have a size of $nrows; found $clusterid_len"; 599 } 600 elsif ($param{transpose}==1 and $clusterid_len != $ncols) { 601 die "Parameter 'clusterid' should have a size of $ncols; found $clusterid_len"; 602 } 603 my $nclusters = -1; 604 foreach (@{$param{clusterid}}) { 605 if ($_ > $nclusters) { 606 $nclusters = $_; 607 } 608 } 609 $param{nclusters} = $nclusters + 1; 610 #---------------------------------- 611 # Invoke the library function 612 # 613 return _clustercentroids(@param{ 614 qw/nclusters nrows ncols data mask clusterid transpose method/ 615 }); 616} 617 618 619#------------------------------------------------------------- 620# Wrapper for the distancematrix() function 621# 622sub distancematrix { 623 #---------------------------------- 624 # Define default parameters 625 # 626 my %default = ( 627 628 data => [[]], 629 mask => '', 630 weight => '', 631 dist => 'e', 632 transpose => 0, 633 ); 634 #---------------------------------- 635 # Accept parameters from caller 636 # 637 my %param = (%default, @_); 638 #---------------------------------- 639 # Check the data, matrix and weight parameters 640 # 641 return unless check_matrix_dimensions(\%param, \%default); 642 #---------------------------------- 643 # Check the transpose parameter 644 # 645 unless($param{transpose} =~ /^[01]$/) { 646 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 647 return; 648 } 649 #---------------------------------- 650 # Check the other parameters 651 # 652 unless($param{dist} =~ /^[cauxskeb]$/) { 653 module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')"); 654 return; 655 } 656 #---------------------------------- 657 # Invoke the library function 658 # 659 return _distancematrix(@param{ 660 qw/nrows ncols data mask weight transpose dist/ 661 }); 662} 663 664 665#------------------------------------------------------------- 666# Wrapper for the somcluster() function 667# 668sub somcluster { 669 #---------------------------------- 670 # Define default parameters 671 # 672 my %default = ( 673 data => [[]], 674 mask => '', 675 weight => '', 676 transpose => 0, 677 nxgrid => 10, 678 nygrid => 10, 679 inittau => 0.02, 680 niter => 100, 681 dist => 'e', 682 ); 683 #---------------------------------- 684 # Accept parameters from caller 685 # 686 my %param = (%default, @_); 687 #---------------------------------- 688 # Check the data, matrix and weight parameters 689 # 690 return unless check_matrix_dimensions(\%param, \%default); 691 #---------------------------------- 692 # Check the other parameters 693 # 694 unless($param{transpose} =~ /^[01]$/) { 695 module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')"); 696 return; 697 } 698 unless($param{nxgrid} =~ /^\d+$/ and $param{nxgrid} > 0) { 699 module_warn("Parameter 'nxgrid' must be a positive integer (got '$param{nxgrid}')"); 700 return; 701 } 702 703 unless($param{nygrid} =~ /^\d+$/ and $param{nygrid} > 0) { 704 module_warn("Parameter 'nygrid' must be a positive integer (got '$param{nygrid}')"); 705 return; 706 } 707 unless($param{inittau} =~ /^\d+.\d+$/ and $param{inittau} >= 0.0) { 708 module_warn("Parameter 'inittau' must be a non-negative number (got '$param{inittau}')"); 709 return; 710 } 711 unless($param{niter} =~ /^\d+$/ and $param{niter} > 0) { 712 module_warn("Parameter 'niter' must be a positive integer (got '$param{niter}')"); 713 return; 714 } 715 unless($param{dist} =~ /^[cauxskeb]$/) { 716 module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')"); 717 return; 718 } 719 #---------------------------------- 720 # Invoke the library function 721 # 722 return _somcluster(@param{ 723 qw/nrows ncols data mask weight transpose nxgrid nygrid inittau niter dist/ 724 }); 725} 726 727 728#------------------------------------------------------------- 729# Wrapper for the pca() function 730# 731sub pca { 732 #---------------------------------- 733 # Accept parameters from caller 734 # 735 my $data = shift; 736 #---------------------------------- 737 # Check the data matrix 738 # 739 return unless data_is_valid_matrix($data); 740 #---------------------------------- 741 # Remember the dimensions of the data array 742 # 743 my $nrows = scalar @{$data}; 744 my $ncols = scalar @{$data->[0]}; 745 746 #---------------------------------- 747 # Invoke the library function 748 return _pca($nrows, $ncols, $data); 749} 750 751 7521; 753 754__END__ 755 756 757=head1 NAME 758 759Algorithm::Cluster - Perl interface to the C Clustering Library. 760 761 762=head1 DESCRIPTION 763 764This module is an interface to the C Clustering Library, 765a general purpose library implementing functions for hierarchical 766clustering (pairwise simple, complete, average, and centroid linkage), 767along with k-means and k-medians clustering, and 2D self-organizing 768maps. This library was developed at the Human Genome Center of the 769University of Tokyo. The C Clustering Library is distributed along 770with Cluster 3.0, an enhanced version of the famous 771Cluster program originally written by Michael Eisen 772while at Stanford University. 773 774=head1 EXAMPLES 775 776See the scripts in the examples subdirectory of the package. 777 778=head1 CHANGES 779 780=over 4 781 782=item * C Clustering Library version 1.59 783 784=head1 TO DO 785 786=over 787 788=head1 THANKS 789 790Thanks to Michael Eisen, for creating the software packages 791Cluster and TreeView. 792 793=head1 AUTHOR 794 795John Nolan jpnolan@sonic.net 2003. 796Michiel de Hoon michiel.dehoon "AT" riken.jp 2003-2017. 797Seiya Imoto imoto "AT" ims.u-tokyo.ac.jp 2003-2010. 798Satoru Miyano 2003-2010. 799A copyright statement is contained in the source code itself. 800 801This module is a Perl wrapper for the C clustering library for 802cDNA microarray data, Copyright (C) 2002 Michiel Jan Laurens de Hoon. 803 804See the source of Cluster.pm for a full copyright statement. 805 806=cut 807 8081; 809