    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.plugin.table;
    017
    018// import java.util.HashMap;
    019// import java.util.Map;
    020
    021import org.opengion.fukurou.util.StringUtil;
    022import org.opengion.hayabusa.db.AbstractTableFilter;
    023import org.opengion.hayabusa.db.DBColumn;
    024// import org.opengion.hayabusa.db.DBColumnConfig;
    025import org.opengion.hayabusa.db.DBTableModel;
    026import org.opengion.hayabusa.db.DBTableModelUtil;
    027import org.opengion.hayabusa.resource.ResourceManager;
    028
    029/**
    030 * TableFilter_STDDEV は、TableFilter インターフェースを継承した、DBTableModel 処理用の
    031 * 実装クラスです。
    032 *
    033 * ここではグループ単位に、平均、標準偏差等を求め、データの分布を示すデータを作成します。
    034 * グループキーとなるカラムは、あらかじめソーティングしておく必要があります。(キーブレイク判断するため)
    035 * グループキー以外の値は、参考情報として残し、カラムの最後に、
    036 * CNT,SUM,AVG,STDEVS,STDEVP,M3S,M2S,M1S,M0S,P0S,P1S,P2S,P3S カラムを追加します。
    037 *
    038 * CNT(個数),SUM(合計),AVG(平均),STDEVS(標本標準偏差:n-1),STDEVP(母標準偏差:n)
    039 * M3S(～-3σ),M2S(-3σ～-2σ),M1S(-2σ～-σ),M0S(-σ～0),P0S(0～σ),P1S(σ～2σ),P2S(2σ～3σ),P3S(3σ～)
    040 * FILTERは、1:(-2σ～-σ or σ～2σ) , 2:(-3σ～-2σ or 2σ～3σ) , 3:(～-3σ or 3σ～) のみピックアップします。
    041 * 初期値の 0 は、フィルターなしです。
    042 *
    043 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
    044 * 【パラメータ】
    045 *  {
    046 *       GROUP_KEY  : グループカラム     (複数指定可)
    047 *       VAL_KEY    : 値のカラム         (必須)
    048 *       USE_TYPE   : P(母) or S(標本)   (初期値:P(母標準偏差))
    049 *       FORMAT     : 数値のフォーマット (初期値:%.3f ･･･ 小数第３位以下を、四捨五入する)
    050 *       FILTER     : 1 , 2 , 3          (初期値:0)
    051 *  }
    052 *
    053 * @og.formSample
    054 * ●形式：
    055 *      ① &lt;og:tableFilter classId="STDDEV" selectedAll="true"
    056 *                   keys="GROUP_KEY,VAL_KEY" vals='"GOKI,SID",ACT_VAL' /&gt;
    057 *
    058 *      ② &lt;og:tableFilter classId="STDDEV"  selectedAll="true" &gt;
    059 *               {
    060 *                   GROUP_KEY : GOKI,SID ;
    061 *                   VAL_KEY   : ACT_VAL ;
    062 *               }
    063 *         &lt;/og:tableFilter&gt;
    064 *
    065 * @og.rev 6.7.1.0 (2017/01/05) 新規追加
    066 *
    067 * @version  0.9.0  2000/10/17
    068 * @author   Hiroki Nakamura
    069 * @since    JDK1.1,
    070 */
    071public class TableFilter_STDDEV extends AbstractTableFilter {
    072        // * このプログラムのVERSION文字列を設定します。 {@value} */
    073        private static final String VERSION = "6.7.2.0 (2017/01/16)" ;
    074
    075        private static final String[] ADD_CLMS = new String[] { "CNT","SUM","AVG","STDEVS","STDEVP","M3S","M2S","M1S","M0S","P0S","P1S","P2S","P3S" };
    076        private static final int      HIST_SU  = 8;             // "M3S","M2S","M1S","M0S","P0S","P1S","P2S","P3S" の個数
    077
    078        private DBTableModel    table   ;
    079
    080        /**
    081         * デフォルトコンストラクター
    082         */
    083        public TableFilter_STDDEV() {
    084                super();
    085                initSet( "GROUP_KEY"    , "グループカラム     (複数指定可)"                 );
    086                initSet( "VAL_KEY"              , "値のカラム         (必須)"                          );
    087                initSet( "USE_TYPE"             , "P(母) or S(標本)   (初期値:P)"                     );
    088                initSet( "FORMAT"               , "数値のフォーマット (初期値:%.3f ･･･ 小数代３位以下を、四捨五入する)"    );
    089                initSet( "FILTER"               , "1 , 2 , 3          (初期値:0)"                  );
    090        }
    091
    092        /**
    093         * DBTableModel処理を実行します。
    094         *
    095         * @og.rev 6.7.2.0 (2017/01/16) FILTERパラメータ追加。
    096         *
    097         * @return 処理結果のDBTableModel
    098         */
    099        public DBTableModel execute() {
    100                table   = getDBTableModel();
    101                final ResourceManager   resource = getResource();
    102
    103                final String[]  grpClm  = StringUtil.csv2Array( getValue( "GROUP_KEY" ) );
    104                final int               valNo   = table.getColumnNo(    getValue( "VAL_KEY"   ) );              // 必須なので、無ければエラー
    105                final String    devType = getValue( "USE_TYPE" );
    106                final String    fmt             = getValue( "FORMAT" );
    107                final int               ftype   = StringUtil.nval( getValue( "FILTER" ) , 0 );                  // 6.7.2.0 (2017/01/16)
    108
    109                final boolean   useDEVP = devType == null || devType.isEmpty() || "P".equals( devType ) ;       // 初期値が、"P" (母標準偏差)
    110                final String    format  = fmt == null || fmt.isEmpty() ? "%.3f" : fmt ;                                         // 初期値が、"%.3f"
    111
    112                // グループカラムのカラム番号を求めます。
    113                final int[] grpNos = new int[grpClm.length];
    114                for( int i=0; i<grpNos.length; i++ ) {
    115                        grpNos[i] = table.getColumnNo( grpClm[i] );             // 無ければ、エラーにします。
    116                }
    117
    118                final DBColumn[] orgClms = table.getDBColumns() ;
    119                final String names[] = new String[orgClms.length + ADD_CLMS.length - 1];        // 元のカラムに、追加カラムを加えます。-1は、値カラムを削除するためです。
    120
    121                final DBTableModel nTable = DBTableModelUtil.newDBTable();
    122                nTable.init( names.length );
    123
    124                int orgNo = 0;
    125                for( int i=0; i<names.length; i++ ) {
    126                        if( i == valNo ) {                      // 値のカラム列に、追加カラムを、挿入します。
    127                                for( int j=0; j<ADD_CLMS.length; j++ ) {
    128                                        nTable.setDBColumn( i++, resource.makeDBColumn( ADD_CLMS[j] ) );
    129                                }
    130                                i -= 1;         // for文で、++するので、ひとつ戻しておく。
    131                                orgNo++;
    132                        }
    133                        else {
    134                                nTable.setDBColumn( i, orgClms[orgNo++] );
    135                        }
    136                }
    137
    138                final int ROW_CNT = table.getRowCount();
    139                String bkKey = getSeparatedValue( 0, grpNos );          // ブレイクキー
    140                String[] old = table.getValues( 0 );
    141                int    cnt = 1;                                                                         // 件数
    142                double sum = parseDouble( old[valNo] );                         // 合計
    143                int    stPos = 0;                                                                       // ｸﾞﾙｰﾌﾟの開始行番号
    144                // １回目は初期設定しておく(row=1)。最後はキーブレイクしないので、１回余分に回す(row<=ROW_CNT)。
    145                for( int row=1; row<=ROW_CNT; row++ ) {
    146                        final String rowKey = row==ROW_CNT ? "" : getSeparatedValue( row, grpNos );             // 余分なループ時にブレイクさせる。
    147                        final int edPos = row;                                                          // ｸﾞﾙｰﾌﾟの終了行番号
    148                        if( bkKey.equals( rowKey ) ) {                                  // 前と同じ(継続)
    149                                old = table.getValues( row );
    150                                cnt++ ;
    151                                sum += parseDouble( old[valNo] );
    152                        }
    153                        else {                                                                                  // キーブレイク
    154                                final double avg = sum/cnt;
    155
    156                                double sa1 = 0d;
    157                                for( int j=stPos; j<edPos; j++ ) {                   // 標準偏差の計算のためにもう一度回す
    158                                        final double val = parseDouble( table.getValue( j,valNo ) );
    159                                        sa1 += Math.pow( val - avg , 2 ) ;
    160                                }
    161                                final double stdevs = cnt==1 ? 0d : Math.sqrt( sa1/(cnt-1) );           // 母集団の標本の標準偏差(標本標準偏差)
    162                                final double stdevp = Math.sqrt( sa1/cnt );                                                     // 母集団全ての標準偏差(母標準偏差)
    163
    164                                final int[] dtCnt = new int[HIST_SU];
    165                                final double sa2 = useDEVP ? stdevp : stdevs ;                  // useDEVP == true の場合、母標準偏差 を使用します。
    166                                for( int j=stPos; j<edPos; j++ ) {                   // 確率分布の合計グラフを作成するためにもう一度回す
    167                                        final double val2 = parseDouble( table.getValue( j,valNo ) ) - avg;
    168
    169                                        if(        0.0d == val2 || cnt == 1      ) { dtCnt[4]++ ; }             //   0  ･･･データが１件の場合
    170                                        else if(                   val2 < -sa2*3 ) { dtCnt[0]++ ; }          // -3σ＜
    171                                        else if( -sa2*3 <= val2 && val2 < -sa2*2 ) { dtCnt[1]++ ; }               // -2σ＜
    172                                        else if( -sa2*2 <= val2 && val2 < -sa2*1 ) { dtCnt[2]++ ; }               // -1σ＜
    173                                        else if( -sa2*1 <= val2 && val2 <  0.0d  ) { dtCnt[3]++ ; }               //   0＜
    174                                        else if(   0.0d <= val2 && val2 <  sa2*1 ) { dtCnt[4]++ ; }               //   0≦
    175                                        else if(  sa2*1 <= val2 && val2 <  sa2*2 ) { dtCnt[5]++ ; }               //  1σ≦
    176                                        else if(  sa2*2 <= val2 && val2 <  sa2*3 ) { dtCnt[6]++ ; }               //  2σ≦
    177                                        else if(  sa2*3 <= val2                  ) { dtCnt[7]++ ; }          //  3σ≦
    178                                }
    179
    180                                // 6.7.2.0 (2017/01/16) FILTERパラメータ追加。
    181                                // ここで、フィルター処理を行います。
    182                                final boolean useValue ;
    183                                switch( ftype ) {
    184                                        case 1  : useValue = ( dtCnt[0] + dtCnt[1] + dtCnt[2] + dtCnt[5] + dtCnt[6] + dtCnt[7] ) > 0 ; break ;
    185                                        case 2  : useValue = ( dtCnt[0] + dtCnt[1] +                       dtCnt[6] + dtCnt[7] ) > 0 ; break ;
    186                                        case 3  : useValue = ( dtCnt[0] +                                             dtCnt[7] ) > 0 ; break ;
    187                                        default : useValue = true ;
    188                                }
    189
    190                                if( useValue ) {
    191                                        final String[] vals = new String[names.length];
    192                                        orgNo = 0;
    193                                        for( int k=0; k<names.length; k++ ) {
    194                                                if( k == valNo ) {                              // 値のカラム列に、追加カラムを挿入している。
    195                                                        vals[k++] = String.valueOf( cnt );                              // CNT
    196                                                        vals[k++] = String.format( format , sum );              // SUM
    197                                                        vals[k++] = String.format( format , avg );              // AVG
    198                                                        vals[k++] = String.format( format , stdevs );   // STDEVS(標本標準偏差)
    199                                                        vals[k++] = String.format( format , stdevp );   // STDEVP(母標準偏差)
    200                                                        vals[k++] = String.valueOf( dtCnt[0] );                 // M3S
    201                                                        vals[k++] = String.valueOf( dtCnt[1] );                 // M2S
    202                                                        vals[k++] = String.valueOf( dtCnt[2] );                 // M1S
    203                                                        vals[k++] = String.valueOf( dtCnt[3] );                 // M0S
    204                                                        vals[k++] = String.valueOf( dtCnt[4] );                 // P0S
    205                                                        vals[k++] = String.valueOf( dtCnt[5] );                 // P1S
    206                                                        vals[k++] = String.valueOf( dtCnt[6] );                 // P2S
    207                                                        vals[k  ] = String.valueOf( dtCnt[7] );                 // P3S                  // for文で、++するので、最後は、++ しない。
    208                                                        orgNo++;
    209                                                }
    210                                                else {
    211                                                        vals[k] = old[orgNo++];         // ひとつ前の行の値
    212                                                }
    213                                        }
    214
    215                                        nTable.addColumnValues( vals );
    216                                }
    217
    218                                if( row==ROW_CNT ) { break; }                                   // 最後のデータは強制終了
    219                                cnt = 1;
    220                                old = table.getValues( row );
    221                                sum = parseDouble( old[valNo] );
    222                                stPos = row;
    223                                bkKey = rowKey;
    224                        }
    225                }
    226
    227                return nTable;
    228        }
    229
    230        /**
    231         * 各行のキーとなるキーカラムの値を連結した値を返します。
    232         *
    233         * @param       row             行番号
    234         * @param       clmNo   カラム番号配列
    235         *
    236         * @return      各行のキーとなるキーカラムの値を連結した値
    237         * @og.rtnNotNull
    238         */
    239        private String getSeparatedValue( final int row, final int[] clmNo ) {
    240                final StringBuilder buf = new StringBuilder();
    241                for( int i=0; i<clmNo.length; i++ ) {
    242                        if( clmNo[i] >= 0 ) {
    243                                final String val = table.getValue( row, clmNo[i] );
    244                                if( val != null && val.length() > 0 ) {
    245                                        buf.append( val ).append( '_' );
    246                                }
    247                        }
    248                }
    249                return buf.toString();
    250        }
    251
    252        /**
    253         * 引数の文字列を、double に変換して返します。
    254         *
    255         * 処理が止まらないように、null や、変換ミスのの場合は、ゼロを返します。
    256         *
    257         * @param       val     変換する元の文字列
    258         *
    259         * @return      変換後のdouble
    260         * @og.rtnNotNull
    261         */
    262        private double parseDouble( final String val ) {
    263                double rtn = 0.0d;
    264                if( val != null && !val.trim().isEmpty() ) {
    265                        try {
    266                                rtn = Double.parseDouble( val.trim() );
    267                        }
    268                        catch( final NumberFormatException ex ) {
    269                                final String errMsg = "文字列を数値に変換できません。val=[" + val + "]" + ex.getMessage(); ;
    270                                System.out.println( errMsg );
    271                        }
    272                }
    273
    274                return rtn ;
    275        }
    276}