Wekaが生成する二分木をEXCEL/LibreOffice Calcの式に直すスクリプト


データマイニングツールWekaを使って簡単な分類をさせていろいろ試行錯誤しているのですが、Wekaがテキストで吐き出す二分木を読みながらEXCEL/LibreOffice Calcの式(多段IF())に頭の中で変換しつつタイプする作業がかなり苦痛になってきた(たまに木のサイズが80とか90とか…)ので、面倒臭がって先送りしてきた変換スクリプトをえいやっと作ってしまいました。

スクリプト名を Weka2EXCEL.pl とすると、こんな感じで使用します。

$ cat | ./Weka2EXCEL.p > output
(実行すると入力待ちになるので、Wekaから二分木をコピーして標準入力に貼りつけてやってください)

こうするとインデント付きの多段IF()な式が出力されます。
このままEXCEL/LibreOffice Calcに貼りつけてもいいのですが、式が長いと拒否されることがあるので、その時はちょっとコンパクトにしてあげてください。

$ tr -d ‘\n ‘ < output ; echo スクリプトをそのまま使うとIF()の第1引数の条件式がおかしくなるはずですので、お使いのデータに合わせて、%VARSのattr01, attr02...を修正してください。 Wekaに与えるデータによっては、二分木にならないことがありますが、その場合はこのスクリプトでは変換できません。 また、このスクリプトはFreeBSD 8.2-RELEASEで動作確認しています。
Linux等でも動くと思いますが、場合によっては一行目のPerlのPathを変更する必要があるかもしれません。

[Perl]
#!/usr/local/bin/perl

my $spacer = ” “;
my $G_pointer = 0;

%VARS = ( ‘attr01’=>’B3’,
‘attr02’=>’C3’,
‘attr03’=>’D3’,
‘attr04’=>’E3’,
‘attr05’=>’F3’,
‘attr06’=>’G3’,
‘attr07’=>’H3’,
);

@WEKATREE = <>;

#先頭行を見つける
my $i;
for($i =0; $i < $#WEKATREE; $i++) { if( $WEKATREE[$i] =~ /^(\w+)\s*(=|\<|\<=|\>|\>=)\s*(\S+)/) {
# print “find first line[#” . $i . “]>” . $WEKATREE[$i] . “\n”; #debug
last;
}
}
my $G_pointer = $i;

&recur(“left”, 0);

exit;

#再帰でTreeをトレースし、IF(A3>1.3, X3, Y3) 形式に変換する
sub recur {
my ($mode, $depth) = @_;

$line = $WEKATREE[$G_pointer];
chomp($line);

if( $mode =~ /left/i ) {
if( $line =~ /^(\|\s+)*(\w+)\s*(=|\<|\>|\<=|\>=)\s*(\S+)\s*:\s*(\w+)\s*\(\d+\/\d+\)\s+\[\d+\/\d+\]\s*$/ ) {
# | agehaba < 0.78 : sell (15/4) [8/4] # print "debug>[$line]\n”;
my $var = $VARS{$2};
my $op = $3;
my $val = $4;
my $sig = $5;
$ret = &mk_indent($spacer, $depth);
$ret .= “if( $var $op $val,\n”;
$ret .= &mk_indent($spacer, $depth + 1) . “\”$sig\”,\n”;
print $ret;
$G_pointer ++;
&recur(“right”, $depth+1);
if( &get_depth($line) <= &get_depth($WEKATREE[$G_pointer+1]) ) { #次が更に深かったら $G_pointer ++; &recur("right", $depth+1); } else { $ret = &mk_indent($spacer, $depth); } $ret = &mk_indent($spacer, $depth); if( $depth == &get_depth($WEKATREE[$G_pointer+1]) +1 ) { #","の直前は改行しない $ret .= ")"; } else { $ret .= ")\n"; } print $ret; return; } elsif( $line =~ /^(\|\s+)*(\w+)\s*(=|<|>|<=|>=)\s*(\S+)$/ ) {
# | agehaba < 0.89 my $var = $VARS{$2}; my $op = $3; my $val = $4; $ret = &mk_indent($spacer, $depth); $ret .= "if( $var $op $val,\n"; print $ret; $G_pointer ++; &recur("left", $depth+1); $ret = ",\n"; print $ret; $G_pointer ++; &recur("right", $depth+1); $ret = &mk_indent($spacer, $depth); if( $depth == &get_depth($WEKATREE[$G_pointer+1]) +1 && !($WEKATREE[$G_pointer+1] =~ /^\s*$/) ) { #","の直前は改行しない $ret .= ")"; }else{ $ret .= ")\n"; } print $ret; return; } else { print "debug(recur(left))>想定外のパターンに遭遇[” . $line . “]\n”;
}
$ret = &mk_indent($spacer, $depth);
$ret .= “)\n”;
print $ret;
return;
}
elsif( $mode =~ /right/i ) {
#
if( $line =~ /^(\|\s+)*(\w+)\s*(=|\<|\>|\<=|\>=)\s*(\S+)\s*:\s*(\w+)\s*\(\d+\/\d+\)\s+\[\d+\/\d+\]\s*$/ ) {
# | agehaba < 0.78 : sell (15/4) [8/4] # print "debug>[$line]\n”;
my $var = $VARS{$2};
my $op = $3;
my $val = $4;
my $sig = $5;
$ret = &mk_indent($spacer, $depth);
$ret .= “\”$sig\”\n”;
print $ret;
if( $WEKATREE[$G_pointer+1] =~ /^\s*$/ ) { #次が空行だったら
return;
}
elsif( &get_depth($line) <= &get_depth($WEKATREE[$G_pointer+1]) ) { #次がさらに深かったら $G_pointer ++; &recur("right", $depth+1); $ret = &mk_indent($spacer, $depth); $ret .= ")-z\n"; print $ret; return; } else { return; } return; } elsif( $line =~ /^(\|\s+)*(\w+)\s*(=|<|>|<=|>=)\s*(\S+)$/ ) {
# | agehaba < 0.89 my $var = $VARS{$2}; my $op = $3; my $val = $4; if( &get_depth($line) <= &get_depth($WEKATREE[$G_pointer+1]) ) { #次がさらに深かったら # print &mk_indent($spacer, $depth) . "right部分がif()\n"; #debug $G_pointer ++; &recur("left", $depth); } return; } elsif( $line =~ /^\s*$/ ) { #空行だったら右手探索を中断 return; } elsif( $line =~ /^Size of the tree/ ) { #print STDERR "debug(recur(right))> 最終行に到着\n”;
return;
}
else {
print “debug(recur(right))>想定外のパターンに遭遇($G_pointer)[” . $line . “]\n”;
}
}
return;

}

sub mk_indent {
my ($spacer, $multi) = @_;
# print “debug(mk_indent)> \$spacer=[$spacer] \$multi=[$multi]\n”;

my $ret = “”;
for(my $i; $i<$multi; $i++) { $ret .= $spacer; } return $ret; } sub get_depth { my ($p) = @_; my $str = $p; my $count = $str =~ s/\|/\|/g; return $count; } [/Perl]