2009年2月5日

リファクタリング-4-

リファクタリング-3-で書いた「データ列を1データ列1ファイルに分割する」プログラムをまず、作った。

remove_NaN.pl:
#! /usr/bin/perl
#
# remove_NaN.pl <dataFileName> <template of eachoutput>
# chop dataFile into each row. If each row is clear that
# "Pearson product-moment correlation coefficient" calculation
# will ends in NaN, do not output that entry.
#
# For <template of eachoutput> I mean something like "hogehoge%08d".
# output file will have row number as for %08d part (%08d can be %d
# or anything in integer format ).

# use Math::NumberCruncher;
# $NCref = Math::NumberCruncher->new();

our $at_once = 7000;

$datafilename = $ARGV[0];
$outtemplate = $ARGV[1];

if ( $outtemplate eq '' ) {
$outtemplate = 'temp%08d.TSV';
}

open( DATAFILE, $datafilename ) or die "unable to open $datafilename as datafile\n";


# First, read first line. It's TAG-NAME list.

our @tagname;
our $line;

$line = <DATAFILE>;
@tagname = split /\t/, $line;
for ( $i = 0; $i < scalar( @tagname ); $i++ ) {
$tagname[$i] =~ s/^\s*(".*")\s*$/\1/;
}


# Okey. Now let's read each rows.
# First row is not numeral, but is time of when data was sampled. so skip first row.

our $filecounter = 0;
for ( $row = 0; $row < scalar( @tagname ); $row += $at_once ) {
debug( "$row start\n" );
# bring file pointer to top of line.
seek( DATAFILE, 0, 0 );

# skip tagname line.
$line = <DATAFILE>;

my @output;
my @data;
my @sum;
my @mean;
my @sqsum;
my @subpart;
my @datas;

for ( my $i = 0; $i < $at_once; $i++ ) {
$output[$i] = 1;
}

while (<DATAFILE>) {
my @oneline = split /\t/, $_;
my $val;

for ( my $i = 0; $i < $at_once; $i++ ) {
$oneline[$row + $i] =~ s/^\s*"(.*)"\s*$/\1/;
$val = $oneline[$row + $i];
if ( $val eq '' ) {
$output[$i] = 0;
} else {
push @{$data[$i]}, $val;
$sum[$i] += $val;
}
}
}
debug( "read data\n" );

# calculate $mean and $subpart.

for ( my $i = 0; $i < $at_once; $i++ ) {
if ( $output[$i] ) {
$datas[$i] = scalar( @{$data[$i]} );
$mean[$i] = $sum[$i] / $datas[$i];
if ( $mean[$i] eq 'NaN' ) {
$output[$i] = 0;
next;
}

for ( my $j = 0; $j < $datas[$i]; $j++ ) {
my $val;
$val = $data[$i][$j] - $mean[$i];
$sqsum[$i] += $val * $val;
}
$subpart[$i] = sqrt( $sqsum[$i] );

if (( $subpart[$i] eq 'NaN' )||( $subpart[$i] == 0.0 )) {
$output[$i] = 0;
}
}
}
debug( "calc data\n" );

# dumpout the row.
for ( my $i = 0; $i < $at_once; $i++ ) {
my $filename = sprintf( $outtemplate, $filecounter );
if ( !defined( $tagname[$row+$i] )){
next;
}

open( OUTPUT, "> $filename" ) or die "unable to open $filename as output file. Template was $outtemplate.\n";
print OUTPUT "tagname\t$tagname[$row+$i]\n";
print OUTPUT "Correlation\t" . ( $output[$i] ? 'OK' : 'NG' ) . "\n";
print OUTPUT "sum\t$sum[$i]\n";
print OUTPUT "mean\t$mean[$i]\n";
print OUTPUT "sqsum\t$sqsum[$i]\n";
print OUTPUT "subpart\t$subpart[$i]\n";
print OUTPUT "datas\t$datas[$i]\n";
print OUTPUT "\n";

foreach ( @{$data[$i]} ) {
print OUTPUT "$_\n";
}

close OUTPUT;
debug( "output $filename \n" );

$filecounter++;
}

ROWSTEP_CLEANUP:
undef @output;
undef @data;
undef @sum;
undef @mean;
undef @subpart;
undef @datas;
}

close DATAFILE;

sub debug {
my $time = gmtime();
print STDERR "$time: ";
foreach ( @_ ) {
print STDERR $_;
}
}

このプログラム中、唯一判らないのは「$at_once」だろう。これはようするに、読んだデータの内、$at_once 個のデータ列づつ平均値だのなんだのを計算して、各データ列ごとに別のファイルに出力する、という事だ。

実はこのプログラム、かなりメモリを消費する。データファイル自体は1行づつ読んではパースし、不要な部分は捨てているのだが、元のデータが 27000列とかだと、一日分のデータだけでも 32bit Windows システムではメモリが足りずに途中でハングする。そこで、一度に処理する行数をスクリプト中に書いておくことにした。メモリが足りないならここを調節すればよい。

ただし、可能な限り大きな値を指定しておくことをお勧めする。実験として50を選んであるが、ものすごく遅くて難儀した。27000の場合、540回同じファイルを読み直すことになり、そのたびにファイル全体を文字列処理する必要があるのだから、当たり前と言えば当たり前だが。