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 ・・・ 小数第3位以下を、四捨五入する) 061 * FILTER : 1 , 2 , 3 (初期値:0) 062 * MIN_CV : 変動係数の最小除外値(%指定) 例:2.0 063 * } 064 * 065 * @og.formSample 066 * ●形式: 067 * ① <og:tableFilter classId="STDDEV2" selectedAll="true" 068 * keys="GROUP_KEY,CLMNO" vals='"GOKI,SID",7' /> 069 * 070 * ② <og:tableFilter classId="STDDEV2" selectedAll="true" > 071 * { 072 * GROUP_KEY : GOKI,SID ; 073 * CLMNO : 7 ; 074 * } 075 * </og:tableFilter> 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 ・・・ 小数代3位以下を、四捨五入する)" ); 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 // 1回目は初期設定しておく(row=1)。最後はキーブレイクしないので、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}