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.Arrays; 019 020import org.opengion.fukurou.util.StringUtil; 021import org.opengion.hayabusa.db.AbstractTableFilter; 022import org.opengion.hayabusa.db.DBTableModel; 023 024/** 025 * TableFilter_NORMALIZE は、TableFilter インターフェースを継承した、DBTableModel 処理用の 026 * 実装クラスです。 027 * 指定の数値データ(横並び)を、最小値「0」~最大値「1」に正規化(スケーリング)します。 028 * 値 = (値 - 最小値) / (最大値 - 最小値) を計算します。 029 * 030 * 正規化するカラムは、VAL_CLMS で指定します。これは、数値カラムで、CSV形式で指定します。 031 * 032 * カラムの値が null または、ゼロ文字列の場合は、計算から除外します。 033 * 034 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。 035 * 【パラメータ】 036 * { 037 * VAL_CLMS : 正規化(スケーリング)を行うカラム列 038 * FORMAT : 数値のフォーマット (初期値:%.3f ・・・ 小数第3位以下を、四捨五入する) 039 * } 040 * 041 * @og.formSample 042 * ●形式: 043 * ① <og:tableFilter classId="NORMALIZE" selectedAll="true" 044 * keys="VAL_CLMS" 045 * vals='"USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY"' /> 046 * 047 * ② <og:tableFilter classId="NORMALIZE" selectedAll="true" > 048 * { 049 * VAL_CLMS : USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY ; 050 * } 051 * </og:tableFilter> 052 * 053 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 054 * 055 * @version 8.4 (2023/03/10) 056 * @author Kazuhiko Hasegawa 057 * @since JDK1.11, 058 */ 059public class TableFilter_NORMALIZE extends AbstractTableFilter { 060 /** このプログラムのVERSION文字列を設定します。 {@value} */ 061 private static final String VERSION = "8.4.1.0 (2023/02/10)" ; 062 063 private DBTableModel table ; 064 065 /** 066 * デフォルトコンストラクター 067 */ 068 public TableFilter_NORMALIZE() { 069 super(); 070 initSet( "VAL_CLMS" , "正規化(スケーリング)を行うカラム列" ); 071 initSet( "FORMAT" , "数値のフォーマット (初期値:%.3f ・・・ 小数代3位以下を、四捨五入する)" ); 072 } 073 074 /** 075 * DBTableModel処理を実行します。 076 * 077 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 078 * 079 * @return 処理結果のDBTableModel 080 */ 081 public DBTableModel execute() { 082 table = getDBTableModel(); 083 084 final int ROW_CNT = table.getRowCount(); 085 if( ROW_CNT == 0 ) { return table; } // 0 は、処理なし 086 087 final String[] valClms = StringUtil.csv2Array( getValue( "VAL_CLMS" ) ); 088 // 最大、最小判定を行うカラム番号を求めます。 089 final int[] clmNos = getTableColumnNo( valClms ); 090 091 final String fmt = getValue( "FORMAT" ); 092 final String format = fmt == null || fmt.isEmpty() ? "%.3f" : fmt ; // 初期値が、"%.3f" 093 094 final Normalization normal = new Normalization( clmNos , format ); 095 096 // 1回目は最小値、最大値を求める処理 097 for( int row=0; row<ROW_CNT; row++ ) { 098 final String[] vals = table.getValues( row ); 099 normal.check( vals ); 100 } 101 102 // 2回目は(値 - 最小値) / (最大値 - 最小値) を計算します。 103 for( int row=0; row<ROW_CNT; row++ ) { 104 final String[] vals = table.getValues( row ); 105 normal.chenge( vals ); // vals の配列の中身を直接書き換えています。 106 } 107 108 return table; 109 } 110 111 /** 112 * 実際の間引き処理を行うクラス。 113 * 114 * 最大、最少を求めて、正規化処理部分だけを分離しました。 115 * 116 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 117 */ 118 private static final class Normalization { 119 private final int[] clmNos ; // 数値カラムのカラム番号 120 private final int clmSize; // 数値カラムの個数 121 private final double[] minVals; // 最小値 122 private final double[] maxVals; // 最大値 123 private final String format; // 文字列化する時の数値フォーマット 124 125 /** 126 * 正規化処理を行うクラスのコンストラクター 127 * 128 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 129 * 130 * @param clmNos 最大最小処理を行うカラム番号配列 131 * @param format 初期値が、"%.3f" 132 */ 133 public Normalization( final int[] clmNos , final String format ) { 134 this.clmNos = clmNos; 135 this.clmSize = clmNos.length; // 値部分のみの配列番号 136 this.format = format; 137 138 minVals = new double[clmSize]; 139 maxVals = new double[clmSize]; 140 141 Arrays.fill( minVals,Double.POSITIVE_INFINITY ); // 最小値の初期値は、正の無限大 142 Arrays.fill( maxVals,Double.NEGATIVE_INFINITY ); // 最大値の初期値は、負の無限大 143 } 144 145 /** 146 * 正規化処理のための最大最小のデータを求めます。 147 * 148 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 149 * 150 * @param vals チェックする行配列データ 151 */ 152 public void check( final String[] vals ) { 153 for( int i=0; i<clmSize; i++ ) { 154 final String val = vals[clmNos[i]]; 155 if( !StringUtil.isNull( val ) ) { // データが null の場合は、置換が発生しません。 156 final double dval = Double.parseDouble( val ); 157 if( minVals[i] > dval ) { minVals[i] = dval; } // 最小値の入れ替え 158 if( maxVals[i] < dval ) { maxVals[i] = dval; } // 最大値の入れ替え 159 } 160 } 161 } 162 163 /** 164 * 正規化処理のための最大最小のデータ変換を行います。 165 * 166 * @og.rev 8.4.1.0 (2023/02/10) 新規作成 167 * 168 * @param vals 変換する行配列データ 169 */ 170 public void chenge( final String[] vals ) { 171 for( int i=0; i<clmSize; i++ ) { 172 final String val = vals[clmNos[i]]; 173 if( !StringUtil.isNull( val ) ) { // データが null の場合は、置換が発生しません。 174 final double dval = Double.parseDouble( val ); 175 vals[clmNos[i]] = String.format( format,( dval - minVals[i] ) / (maxVals[i] - minVals[i]) ); // 正規化(Normalization) 176 } 177 } 178 } 179 } 180}