    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
    018import org.opengion.fukurou.util.StringUtil;
    019import org.opengion.hayabusa.db.AbstractTableFilter;
    020import org.opengion.hayabusa.db.DBColumn;
    021import org.opengion.hayabusa.db.DBTableModel;
    022import org.opengion.hayabusa.db.DBTableModelUtil;
    023import org.opengion.hayabusa.resource.ResourceManager;
    024
    025import static org.opengion.plugin.table.StandardDeviation.ADD_CLMS;
    026
    027/**
    028 * TableFilter_STDDEV2 は、TableFilter インターフェースを継承した、DBTableModel 処理用の
    029 * 実装クラスです。
    030 * 標準偏差等の対象カラムは、横持で、CLMNO属性で指定したカラム以降に指定します。
    031 * よって、対象カラム以降に、自由にカラムを配置することはできません。
    032 *
    033 * 横持のカラムを、縦に再セットします。その際、キーワードを、CLMNAME で指定のカラムに
    034 * セットします。CLMNAME のカラムは、予め、DBTableModel に用意しておいてください。
    035 * CLMNO が未指定の場合は、CLMNAMEの次からと認識されます。
    036 *
    037 * ここではグループ単位に、平均、標準偏差等を求め、データの分布を示すデータを作成します。
    038 * グループキーとなるカラムは、あらかじめソーティングしておく必要があります。(キーブレイク判断するため)
    039 * グループキー以外の値は、参考情報として残し、CLMS属性に指定したカラムを削除し、カラムの最後に、
    040 * CNT,SUM,AVG,(STDEVS or STDEVP),COEFF,M3S,M2S,M1S,M0S,P0S,P1S,P2S,P3S カラムを追加します。
    041 *
    042 * CNT(個数),SUM(合計),AVG(平均),
    043 * STDEVS(標本標準偏差:n-1) または、STDEVP(母標準偏差:n) を、USE_TYPE で選択します。
    044 * COEFF(変動係数) は、標準偏差(σ)を算術平均で、割ったものの百分率
    045 * M3S(～-3σ),M2S(-3σ～-2σ),M1S(-2σ～-σ),M0S(-σ～0),P0S(0～σ),P1S(σ～2σ),P2S(2σ～3σ),P3S(3σ～)
    046 * FILTERは、1:(-2σ～-σ or σ～2σ) , 2:(-3σ～-2σ or 2σ～3σ) , 3:(～-3σ or 3σ～) のみピックアップします。
    047 * 初期値の 0 は、フィルターなしです。
    048 *
    049 * 6.9.9.2 (2018/09/18)
    050 *   COEFF(変動係数)の最小値でフィルターするためのｷｰﾜｰﾄﾞ MIN_CV を追加します。
    051 *   これは、単位(%)で、指定の値以下の変動係数のﾚｺｰﾄﾞを出力しません。
    052 *
    053 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
    054 * 【パラメータ】
    055 *  {
    056 *       GROUP_KEY  : グループカラム          (複数指定可)
    057 *       CLMNAME    : 縦持キーとなるカラム名  (必須)
    058 *       CLMNO      : 対象カラムの最初の番号
    059 *       USE_TYPE   : P(母) or S(標本)        (初期値:P(母標準偏差))
    060 *       FORMAT     : 数値のフォーマット      (初期値:%.3f ･･･ 小数第３位以下を、四捨五入する)
    061 *       FILTER     : 1 , 2 , 3               (初期値:0)
    062 *       MIN_CV     : 変動係数の最小除外値(%指定)  例:2.0
    063 *  }
    064 *
    065 * @og.formSample
    066 * ●形式：
    067 *      ① &lt;og:tableFilter classId="STDDEV2" selectedAll="true"
    068 *                   keys="GROUP_KEY,CLMNO" vals='"GOKI,SID",7' /&gt;
    069 *
    070 *      ② &lt;og:tableFilter classId="STDDEV2"  selectedAll="true" &gt;
    071 *               {
    072 *                   GROUP_KEY : GOKI,SID ;
    073 *                   CLMNO     : 7 ;
    074 *               }
    075 *         &lt;/og:tableFilter&gt;
    076 *
    077 * @og.rev 6.7.1.0 (2017/01/05) 新規追加
    078 * @og.rev 6.9.3.0 (2018/03/26) 標本標準偏差と母標準偏差は、一つだけにし、変動係数を追加します。
    079 *
    080 * @version  0.9.0  2000/10/17
    081 * @author   Hiroki Nakamura
    082 * @since    JDK1.1,
    083 */
    084public class TableFilter_STDDEV2 extends AbstractTableFilter {
    085        /** このプログラムのVERSION文字列を設定します。 {@value} */
    086        private static final String VERSION = "7.3.0.0 (2021/01/06)" ;
    087
    088        private DBTableModel    table   ;
    089
    090        /**
    091         * デフォルトコンストラクター
    092         *
    093         * @og.rev 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターするためのｷｰﾜｰﾄﾞ MIN_CV を追加。
    094         */
    095        public TableFilter_STDDEV2() {
    096                super();
    097                initSet( "GROUP_KEY"    , "グループカラム           (複数指定可)"           );
    098                initSet( "CLMNAME"              , "縦持キーとなるカラム名   (必須)"                          );
    099                initSet( "CLMNO"                , "対象カラムの最初の番号"                                         );
    100                initSet( "USE_TYPE"             , "P(母) or S(標本)         (初期値:P)"                       );
    101                initSet( "FORMAT"               , "数値のフォーマット       (初期値:%.3f ･･･ 小数代３位以下を、四捨五入する)"      );
    102                initSet( "FILTER"               , "1 , 2 , 3                (初期値:0)"                    );
    103                initSet( "MIN_CV"               , "変動係数の最小除外値(%)"                                               );              // 6.9.9.2 (2018/09/18)
    104        }
    105
    106        /**
    107         * DBTableModel処理を実行します。
    108         *
    109         * @og.rev 6.7.2.0 (2017/01/16) FILTERパラメータ追加。
    110         * @og.rev 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターするためのｷｰﾜｰﾄﾞ MIN_CV を追加。
    111         * @og.rev 7.3.0.0 (2021/01/06) SpotBugs:null ではなく長さが0の配列を返すことを検討する
    112         *
    113         * @return 処理結果のDBTableModel
    114         */
    115        public DBTableModel execute() {
    116                table   = getDBTableModel();
    117                final ResourceManager   resource = getResource();
    118
    119                final String[]  grpClm  = StringUtil.csv2Array( getValue( "GROUP_KEY" ) );
    120                final String    devType = getValue( "USE_TYPE" );
    121                final String    fmt             = getValue( "FORMAT" );
    122                final int               ftype   = StringUtil.nval( getValue( "FILTER" ) , 0 );                  // 6.7.2.0 (2017/01/16)
    123                final String    clmName = getValue( "CLMNAME" );
    124                final int               nameNo  = table.getColumnNo( clmName );                                                 // 必須なので、無ければ、エラーにします。
    125                final int               minNo   = StringUtil.nval( getValue( "CLMNO" ) , nameNo+1 );    // CLMNOが未指定の場合は、CLMNAME の次のカラムになります。
    126                final String    minCV   = getValue( "MIN_CV" );                                                                 // 6.9.9.2 (2018/09/18)
    127
    128                final boolean   useDEVP = devType == null || devType.isEmpty() || "P".equals( devType ) ;       // 初期値が、"P" (母標準偏差)
    129                final String    format  = fmt == null || fmt.isEmpty() ? "%.3f" : fmt ;                                         // 初期値が、"%.3f"
    130
    131                // グループカラムのカラム番号を求めます。
    132                final int[] grpNos = new int[grpClm.length];
    133                for( int i=0; i<grpNos.length; i++ ) {
    134                        grpNos[i] = table.getColumnNo( grpClm[i] );                     // 無ければ、エラーにします。
    135                }
    136
    137                final DBColumn[] orgClms = table.getDBColumns() ;               // 検索時のオリジナルのカラム
    138
    139                // 計算対象のカラムのカラム番号を求めます。
    140                final int nSize = orgClms.length - minNo;                               // 全体カラム数から、対象カラム番号を引けば、残りが対象カラム数
    141                final StandardDeviation[] stdDevs = new StandardDeviation[nSize];               // 追加カラム分
    142                for( int i=0; i<nSize; i++ ) {
    143//                      stdDevs[i] = new StandardDeviation( ftype,useDEVP,format );
    144                        stdDevs[i] = new StandardDeviation( ftype,useDEVP,format,minCV );       // 6.9.9.2 (2018/09/18)
    145                }
    146
    147                // 元のカラムの最小番号以降を、統計カラムに差し替えます。
    148                final int ADD_CLM_LEN = ADD_CLMS.length;
    149                final String names[] = new String[minNo + ADD_CLM_LEN];
    150
    151                final DBTableModel nTable = DBTableModelUtil.newDBTable();
    152                nTable.init( names.length );
    153                int no = 0;
    154                for( ; no<minNo; no++ ) {
    155                        nTable.setDBColumn( no, orgClms[no] );                          // 0 ～ minNo まで、順番にセット
    156                }
    157                for( int j=0; j<ADD_CLM_LEN; j++ ) {
    158                        nTable.setDBColumn( no++, resource.makeDBColumn( ADD_CLMS[j] ) );
    159                }
    160
    161                final int ROW_CNT = table.getRowCount();
    162                String bkKey = getSeparatedValue( 0, grpNos );                  // ブレイクキー
    163                String[] old = table.getValues( 0 );
    164
    165                // 後で、row==0で統合する。
    166                for( int j=0; j<nSize; j++ ) {
    167                        stdDevs[j].addData( old[j+minNo] );                                     // 集計対象カラム
    168                }
    169
    170                // １回目は初期設定しておく(row=1)。最後はキーブレイクしないので、１回余分に回す(row<=ROW_CNT)。
    171                for( int row=1; row<=ROW_CNT; row++ ) {
    172                        final String rowKey = row==ROW_CNT ? "" : getSeparatedValue( row, grpNos );     // 余分なループ時にブレイクさせる。
    173                        if( bkKey.equals( rowKey ) ) {                                  // 前と同じ(継続)
    174                                old = table.getValues( row );
    175                                for( int j=0; j<nSize; j++ ) {
    176                                        stdDevs[j].addData( old[j+minNo] );             // 集計対象カラム
    177                                }
    178                        }
    179                        else {                                                                                  // キーブレイク
    180                                for( int j=0; j<nSize; j++ ) {
    181                                        final String[] rtnVals = stdDevs[j].getData();
    182                                        // 値が戻ってきた場合のみ、テーブルに追加します。
    183//                                      if( rtnVals != null ) {
    184                                        if( rtnVals.length > 0 ) {                              // 7.3.0.0 (2021/01/06) null ではなく長さゼロの配列
    185                                                final String vals[] = new String[names.length];
    186                                                no = 0;
    187                                                for( ; no<minNo; no++ ) {
    188                                                        vals[no] = old[no];
    189                                                }
    190                                                for( int k=0; k<ADD_CLM_LEN; k++ ) {
    191                                                        vals[no++] = rtnVals[k];
    192                                                }
    193
    194                                                vals[nameNo] = orgClms[j+minNo].getName();              // nameNo のカラムを置き換えます。
    195
    196                                                nTable.addColumnValues( vals );
    197                                        }
    198                                        stdDevs[j].clear();                                             // データを取り出した後、初期化します。
    199                                }
    200
    201                                if( row==ROW_CNT ) { break; }                           // 最後のデータは強制終了
    202
    203                                old = table.getValues( row );
    204                                for( int j=0; j<nSize; j++ ) {
    205                                        stdDevs[j].addData( old[j+minNo] );             // 集計対象カラム
    206                                }
    207                                bkKey = rowKey;
    208                        }
    209                }
    210
    211                return nTable;
    212        }
    213
    214        /**
    215         * 各行のキーとなるキーカラムの値を連結した値を返します。
    216         *
    217         * @param       row             行番号
    218         * @param       clmNo   カラム番号配列
    219         *
    220         * @return      各行のキーとなるキーカラムの値を連結した値
    221         * @og.rtnNotNull
    222         */
    223        private String getSeparatedValue( final int row, final int[] clmNo ) {
    224                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    225                // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
    226                for( final int clm : clmNo ) {
    227                        if( clm >= 0 ) {
    228                                final String val = table.getValue( row, clm );
    229//              for( int i=0; i<clmNo.length; i++ ) {
    230//                      if( clmNo[i] >= 0 ) {
    231//                              final String val = table.getValue( row, clmNo[i] );
    232                                if( val != null && val.length() > 0 ) {
    233                                        buf.append( val ).append( '_' );
    234                                }
    235                        }
    236                }
    237                return buf.toString();
    238        }
    239}