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