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