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 java.util.List; 019import java.util.ArrayList; 020import java.math.BigDecimal; // 6.9.2.0 (2018/03/05) 021import java.math.RoundingMode; // 6.9.2.0 (2018/03/05) 022 023/** 024 * StandardDeviation は、登録されたデータから、標準偏差等の値を求めます。 025 * 026 * このプログラムは、0データを無視する特殊な計算をしています。 027 * これは、成形条件ミドルウエアが、0をデータなしとして扱っているためです。 028 * よって、一般的な標準偏差等の値を求めることは出来ません。 029 * 030 * ここではデータを追加していき、取り出すときに、計算した値を文字列配列で返します。 031 * 作成するカラムは、CNT,SUM,AVG,(STDEVS or STDEVP),COEFF,M3S,M2S,M1S,M0S,P0S,P1S,P2S,P3S です。 032 * 033 * CNT(個数),SUM(合計),AVG(平均), 034 * STDEVS(標本標準偏差:n-1) または、STDEVP(母標準偏差:n) を、useDEVP(trueで、母標準偏差) で選択します。 035 * COEFF(変動係数) は、標準偏差(σ)を算術平均で、割ったものの百分率 036 * M3S(~-3σ),M2S(-3σ~-2σ),M1S(-2σ~-σ),M0S(-σ~0),P0S(0~σ),P1S(σ~2σ),P2S(2σ~3σ),P3S(3σ~) 037 * FILTERは、1:(-2σ~-σ or σ~2σ) , 2:(-3σ~-2σ or 2σ~3σ) , 3:(~-3σ or 3σ~) のみピックアップします。 038 * 初期値の 0 は、フィルターなしです。 039 * 040 * 6.9.9.2 (2018/09/18) 041 * COEFF(変動係数)の最小値でフィルターするためのキーワード MIN_CV を追加します。 042 * これは、単位(%)で、指定の値以下の変動係数のレコードを出力しません。 043 * 044 * @og.rev 6.7.7.0 (2017/03/31) 新規追加 045 * @og.rev 6.9.3.0 (2018/03/26) 標本標準偏差と母標準偏差は、一つだけにし、変動係数を追加します。 046 * 047 * @version 6.7.7 2017/03/31 048 * @author Kazuhiko Hasegawa 049 * @since JDK1.8, 050 */ 051class StandardDeviation { 052 // * このプログラムのVERSION文字列を設定します。 {@value} */ 053 private static final String VERSION = "6.9.9.2 (2018/09/18)" ; 054 055// public static final String[] ADD_CLMS = new String[] { "CNT","SUM","AVG","STDEVS","STDEVP","M3S","M2S","M1S","M0S","P0S","P1S","P2S","P3S" }; 056 public static final String[] ADD_CLMS = new String[] { "CNT","SUM","AVG","STDEV","COEFF","M3S","M2S","M1S","M0S","P0S","P1S","P2S","P3S" }; // 6.9.3.0 (2018/03/26) 057 private static final int HIST_SU = 8; // "M3S","M2S","M1S","M0S","P0S","P1S","P2S","P3S" の個数 058 059 private final List<Double> data = new ArrayList<>(); 060 061 private final int ftype ; // フィルタータイプ(0,1,2,3) 062 private final boolean useDEVP ; // 初期値が、"P" (母標準偏差) 063 private final String format ; // 初期値が、"%.3f" 064 private final double minCV ; // 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターする 065 066 private double sum ; 067 private double pow ; // 6.9.2.0 (2018/03/05) 分散の計算方法を変更 068 069 /** 070 * 各種条件を指定した標準偏差計算用のインスタンスを作成します。 071 * 072 * @og.rev 6.7.7.0 (2017/03/31) 新規追加。 073 * @og.rev 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターするためのキーワード MIN_CV を追加。 074 * 075 * @param ftype フィルタータイプ(0,1,2,3) 076 * @param useDEVP 初期値が、"P" (母標準偏差) 077 * @param format 初期値が、"%.3f" 078 * @param minCV 変動係数の最小値(%) 079 */ 080// public StandardDeviation( final int ftype , final boolean useDEVP , final String format ) { 081 public StandardDeviation( final int ftype , final boolean useDEVP , final String format , final String minCV ) { 082 this.ftype = ftype; 083 this.useDEVP= useDEVP; 084 this.format = format; 085 this.minCV = parseDouble( minCV ); // 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターする 086 } 087 088 /** 089 * 内部情報を、初期化します。 090 * 091 * @og.rev 6.7.7.0 (2017/03/31) 新規追加。 092 * 093 */ 094 public void clear() { 095 data.clear(); 096 sum = 0d; 097 pow = 0d; 098 } 099 100 /** 101 * データを追加します。 102 * 103 * 引数の文字列を、double に変換して使用します。 104 * 変換できない場合は、エラーにはなりませんが、警告を出します。 105 * ただし、値が、0.0 の場合は、対象外にします。 106 * 107 * @og.rev 6.7.7.0 (2017/03/31) 新規追加。 108 * @og.rev 6.9.2.0 (2018/03/05) 分散の計算方法を変更 109 * 110 * @param strVal データ 111 */ 112 public void addData( final String strVal ) { 113 final double val = parseDouble( strVal ); 114 if( val != 0d ) { 115 data.add( val ); 116 sum += val; 117 pow += val * val ; // 6.9.2.0 (2018/03/05) 118 } 119 } 120 121 /** 122 * データから計算した結果を、文字列に変換して、返します。 123 * 124 * 標準偏差の式を 125 * σ=sqrt(Σ(Xi - Xave)^2 / n) 126 * から 127 * σ=sqrt(Σ(Xi^2) / n - Xave^2)) 128 * に変形します。 129 * 参考:http://imagingsolution.blog107.fc2.com/blog-entry-62.html 130 * 131 * @og.rev 6.7.7.0 (2017/03/31) 新規追加。 132 * @og.rev 6.9.2.0 (2018/03/05) 分散の計算方法を変更 133 * @og.rev 6.9.3.0 (2018/03/26) 標本標準偏差と母標準偏差は、一つだけにし、変動係数を追加します。 134 * @og.rev 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターするためのキーワード MIN_CV を追加。 135 * 136 * @return データから計算した結果 137 */ 138 public String[] getData() { 139 final int cnt = data.size(); 140 if( cnt == 0 ) { return null; } 141 final double avg = sum/cnt; // 平均 142 // double sa1 = 0d; 143 144 // // 標準偏差の計算のために一度回す 145 // for( final double val : data ) { 146 // sa1 += Math.pow( val - avg , 2 ) ; 147 // } 148 149 // final double stdevs = cnt==1 ? 0d : Math.sqrt( sa1/(cnt-1) ); // 母集団の標本の標準偏差(標本標準偏差) 150 // final double stdevp = Math.sqrt( sa1/cnt ); // 母集団全ての標準偏差(母標準偏差) 151 152 // 6.9.2.0 (2018/03/05) 分散の計算方法を変更 153 final double vari = Math.abs( pow/cnt - avg * avg ); // マイナスはありえない(計算誤差) 154 // 6.9.3.0 (2018/03/26) 標本標準偏差と母標準偏差は、一つだけにし、変動係数を追加します。 155// final double stdevp = Math.sqrt( vari ); // 母集団全ての標準偏差(母標準偏差) 156// final double stdevs = cnt==1 ? 0d : Math.sqrt( vari * cnt / (cnt-1) ); // 誤差があるので、掛け算してから、SQRTします。 157 final double stdev = useDEVP ? Math.sqrt( vari ) 158 : cnt==1 ? 0d : Math.sqrt( vari * cnt / (cnt-1) ); 159 160 // 6.9.3.0 (2018/03/26) 変動係数(標準偏差/平均 の百分率) 161 final double coeff = stdev / avg * 100 ; 162 163 // 6.9.9.2 (2018/09/18) COEFF(変動係数)の最小値でフィルターするためのキーワード MIN_CV を追加。 164 if( coeff < minCV ) { return null; } // minCV より小さい場合は、null(レコードを追加しない) 165 166 // 6.9.2.0 (2018/03/05) 毎回計算ではなく固定値を使用します。 167// final double sa2 = useDEVP ? stdevp : stdevs ; // useDEVP == true の場合、母標準偏差 を使用します。 168// final double SA1 = halfUp( useDEVP ? stdevp : stdevs ) ; // useDEVP == true の場合、母標準偏差 を使用します。 169 final double SA1 = halfUp( stdev ) ; // useDEVP == true の場合、母標準偏差 を使用します。 170 final double SA2 = SA1 * 2 ; // 2σ 171 final double SA3 = SA1 * 3 ; // 3σ 172 173 // 確率分布の合計グラフを作成するためにもう一度回す 174 final int[] dtCnt = new int[HIST_SU]; 175 for( final double val : data ) { 176 final double val2 = halfUp( val - avg ); 177 178 // 6.9.2.0 (2018/03/05) 毎回計算ではなく固定値を使用します。 179// if( 0.0d == val2 || cnt == 1 ) { dtCnt[4]++ ; } // 0 ・・・データが1件の場合 180// else if( val2 < -sa2*3 ) { dtCnt[0]++ ; } // -3σ< 181// else if( -sa2*3 <= val2 && val2 < -sa2*2 ) { dtCnt[1]++ ; } // -2σ< 182// else if( -sa2*2 <= val2 && val2 < -sa2*1 ) { dtCnt[2]++ ; } // -1σ< 183// else if( -sa2*1 <= val2 && val2 < 0.0d ) { dtCnt[3]++ ; } // 0< 184// else if( 0.0d <= val2 && val2 < sa2*1 ) { dtCnt[4]++ ; } // 0≦ 185// else if( sa2*1 <= val2 && val2 < sa2*2 ) { dtCnt[5]++ ; } // 1σ≦ 186// else if( sa2*2 <= val2 && val2 < sa2*3 ) { dtCnt[6]++ ; } // 2σ≦ 187// else if( sa2*3 <= val2 ) { dtCnt[7]++ ; } // 3σ≦ 188 189 // 標準偏差等が0に近い場合の誤差を考慮して、比較順を変更します。 190 if( cnt == 1 || 0d == val2 || 0d == SA1 ) { dtCnt[4]++ ; } // 0 ・・・データが1件、平均との差がゼロ、標準偏差がゼロ 191 else if( 0d <= val2 && val2 < SA1 ) { dtCnt[4]++ ; } // 0≦ 192 else if( -0d == val2 ) { dtCnt[3]++ ; } // 0< 平均との差がマイナスゼロの場合 193 else if( -SA1 <= val2 && val2 < 0d ) { dtCnt[3]++ ; } // 0< 194 else if( SA1 <= val2 && val2 < SA2 ) { dtCnt[5]++ ; } // 1σ≦ 195 else if( -SA2 <= val2 && val2 < -SA1 ) { dtCnt[2]++ ; } // -1σ< 196 else if( SA2 <= val2 && val2 < SA3 ) { dtCnt[6]++ ; } // 2σ≦ 197 else if( -SA3 <= val2 && val2 < -SA2 ) { dtCnt[1]++ ; } // -2σ< 198 else if( SA3 <= val2 ) { dtCnt[7]++ ; } // 3σ≦ 199 else if( val2 < -SA3 ) { dtCnt[0]++ ; } // -3σ< 200 } 201 202 // 6.7.2.0 (2017/01/16) FILTERパラメータ追加。 203 // ここで、フィルター処理を行います。 204 final boolean useValue ; 205 switch( ftype ) { 206 case 1 : useValue = ( dtCnt[0] + dtCnt[1] + dtCnt[2] + dtCnt[5] + dtCnt[6] + dtCnt[7] ) > 0 ; break ; 207 case 2 : useValue = ( dtCnt[0] + dtCnt[1] + dtCnt[6] + dtCnt[7] ) > 0 ; break ; 208 case 3 : useValue = ( dtCnt[0] + dtCnt[7] ) > 0 ; break ; 209 default : useValue = true ; break; 210 } 211 212 if( useValue ) { 213 final String[] vals = new String[ADD_CLMS.length]; // CNT,SUM,AVG,STDEVS,STDEVP,M3S,M2S,M1S,M0S,P0S,P1S,P2S,P3S の個数 214 215 vals[0] = String.valueOf( cnt ); // CNT 216 vals[1] = String.format( format , sum ); // SUM 217 vals[2] = String.format( format , avg ); // AVG 218 // 6.9.3.0 (2018/03/26) 標本標準偏差と母標準偏差は、一つだけにし、変動係数を追加します。 219// vals[3] = String.format( format , stdevs ); // STDEVS(標本標準偏差) 220// vals[4] = String.format( format , stdevp ); // STDEVP(母標準偏差) 221 vals[3] = String.format( format , stdev ); // useDEVP=true で、STDEVP(母標準偏差) , false で、STDEVS(標本標準偏差) 222 vals[4] = String.format( "%.2f" , coeff ); // 6.9.3.0 (2018/03/26) 変動係数は、小数第二位で四捨五入します。 223 vals[5] = String.valueOf( dtCnt[0] ); // M3S 224 vals[6] = String.valueOf( dtCnt[1] ); // M2S 225 vals[7] = String.valueOf( dtCnt[2] ); // M1S 226 vals[8] = String.valueOf( dtCnt[3] ); // M0S 227 vals[9] = String.valueOf( dtCnt[4] ); // P0S 228 vals[10] = String.valueOf( dtCnt[5] ); // P1S 229 vals[11] = String.valueOf( dtCnt[6] ); // P2S 230 vals[12] = String.valueOf( dtCnt[7] ); // P3S 231 232 return vals; 233 } 234 return null; 235 } 236 237 /** 238 * 引数の文字列を、double に変換して返します。 239 * 240 * 処理が止まらないように、null や、変換ミスの場合は、ゼロを返します。 241 * 242 * @param val 変換する元の文字列 243 * 244 * @return 変換後のdouble 245 * @og.rtnNotNull 246 */ 247 private double parseDouble( final String val ) { 248 double rtn = 0.0d; 249 if( val != null && !val.trim().isEmpty() ) { 250 try { 251 rtn = Double.parseDouble( val.trim() ); 252 } 253 catch( final NumberFormatException ex ) { 254 final String errMsg = "文字列を数値に変換できません。val=[" + val + "]" + ex.getMessage(); ; 255 System.out.println( errMsg ); 256 } 257 } 258 259 return rtn ; 260 } 261 262 /** 263 * 引数のdoubleを、少数点3桁で、四捨五入(HALF_UP)します。 264 * 265 * 長い処理式を、短くすることが目的のメソッドです。 266 * 267 * @param val 変換する元のdouble 268 * 269 * @return 変換後のdouble 270 * @og.rtnNotNull 271 */ 272 private double halfUp( final double val ) { 273 return BigDecimal.valueOf( val ).setScale( 3 , RoundingMode.HALF_UP ).doubleValue(); 274 } 275}