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 ・・・ 小数第3位以下を、四捨五入する) 050 * FILTER : 1 , 2 , 3 (初期値:0) 051 * } 052 * 053 * @og.formSample 054 * ●形式: 055 * @ <og:tableFilter classId="STDDEV" selectedAll="true" 056 * keys="GROUP_KEY,VAL_KEY" vals='"GOKI,SID",ACT_VAL' /> 057 * 058 * A <og:tableFilter classId="STDDEV" selectedAll="true" > 059 * { 060 * GROUP_KEY : GOKI,SID ; 061 * VAL_KEY : ACT_VAL ; 062 * } 063 * </og:tableFilter> 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 ・・・ 小数代3位以下を、四捨五入する)" ); 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 // 1回目は初期設定しておく(row=1)。最後はキーブレイクしないので、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 ・・・データが1件の場合 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}