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