    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 ･･･ 小数第３位以下を、四捨五入する)
    039 *  }
    040 *
    041 * @og.formSample
    042 * ●形式：
    043 *      ① &lt;og:tableFilter classId="NORMALIZE" selectedAll="true"
    044 *                       keys="VAL_CLMS"
    045 *                       vals='"USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY"' /&gt;
    046 *
    047 *      ② &lt;og:tableFilter classId="NORMALIZE"  selectedAll="true" &gt;
    048 *               {
    049 *                   VAL_CLMS  : USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY ;
    050 *               }
    051 *         &lt;/og:tableFilter&gt;
    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 ･･･ 小数代３位以下を、四捨五入する)"    );
    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                // １回目は最小値、最大値を求める処理
    097                for( int row=0; row<ROW_CNT; row++ ) {
    098                        final String[] vals = table.getValues( row );
    099                        normal.check( vals );
    100                }
    101
    102                // ２回目は(値 - 最小値) / (最大値 - 最小値) を計算します。
    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}