生物屋さんのためのゼロからのプログラミング

―忘れないための覚書 (たま~に更新)―

極大値と極小値を検出する

今回は、極大値と極小値を検出するプログラミングを示す。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class PeakFind extends JFrame implements ActionListener {
	Random rand = new Random();
	int no = 30;  //データ数
	int range = 3; //最大値の探索範囲
	int thr = 5; //閾値

	//解析例用のデータを入れる配列
	int data [] = new int [no];

	//極大値を取る番号を入れる配列の作成
	int maxRNo [] = new int [no];

	//極小値を取る番号を入れる配列の作成
	int minRNo [] = new int [no];

	//閾値以上の値を取る極大値の番号を入れる配列の作成
	int subRNo [] = new int [no];

	//解析用データの作成
	public void makeData (int [] data) {
		System.out.println("解析用のデータ:");
		for (int i = 0; i < data.length; i++) {
			data [i] = rand.nextInt(100) + 1;
			System.out.print(data [i] + " ");
		}
		System.out.println();
	}

	//a ~ a + bの範囲での極大値を取る番号の取得
	public int maxNo (int a, int b, int [] c) {
		int maxNum = a;
		int max =  c[a];
		for (int i = a; i < a + b && i < c.length; i++) {
			if (c[i] > max) {
				max = c[i];
				maxNum = i;
			}
		}
		return maxNum;
	}

	//a ~ bの間で極小値を取る番号の取得
	public int minNum (int a, int b, int [] c) {
		int minNum = a;
		int min = c[a];
		for (int i = a; i < b; i++) {
			if (min > c[i]) {
			min = c[i];
			minNum = i;
			}
		}
		return minNum;
	}

	//データの解析
	public void AnalyData () {

		makeData(data);

		//極大値の探索
		int count = 0;
		for (int i = 0; i < no - range; i++) {
			if (maxNo(i, range, data) == maxNo (i + 1, range, data)) {
				int check = 0;
				for (int k = 1; k < range; k++) {
					if (maxNo(i, range, data) == maxNo(i+k, range, data)) {
						check++;
					}
				}
				if (check == range - 1) {
					maxRNo[count] = maxNo(i, range, data);
					count++;
				}
			}
		}

		//極小値の探索
		minRNo [0] = minNum (0, maxRNo[0], data);
		for (int i = 0; i < no; i++ ) {
			if (maxRNo[i+1] == 0) break;
			minRNo [i+1] = minNum(maxRNo[i], maxRNo[i+1], data);
		}

		//差分の計算
		int subCount = 0;
		for (int i = 0; i < no; i++) {
			if (data [maxRNo[i]] - data [minRNo[i]] >= thr) {
				subRNo[subCount] = maxRNo[i];
				subCount++;
			}
		}

		//極大値の出力
		System.out.println("極大値:");
		for (int i = 0; i < no; i++) {
			if (maxRNo [i] == 0) break;
			System.out.print(data[maxRNo[i]] + " ");
		}
		System.out.println();

		//極小値の出力
		System.out.println("極小値:");
		for (int i = 0; i < no; i++) {
			if (i > 0 && minRNo [i] == 0) break;
			System.out.print(data[minRNo[i]] + " ");
		}
		System.out.println();

		//Peakの出力
		System.out.println("Peak : ");
		for (int i = 0; i < no; i++) {
			if (subRNo[i] == 0) break;
			System.out.print(data[subRNo[i]] + " ");
		}
		System.out.println();
	}

	public void actionPerformed (ActionEvent e) {
		String cmd = e.getActionCommand();
		if (cmd.equals("button")) {
			AnalyData();
		}
	}

	PeakFind (String title) {
		setTitle(title);
		setSize(180, 80);
		setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

		JButton button = new JButton("Push");
		button.addActionListener(this);
		button.setActionCommand("button");

		JPanel pane = new JPanel();
		pane.add(button);
		getContentPane().add(pane, BorderLayout.CENTER);
	}

	public static void main(String[] args) {
		PeakFind frame = new PeakFind("Max");
		frame.setVisible(true);
	}
}

極大値の定義は、
f:id:Aki-Miya:20150204075707p:plain
上図のように、前後“Range - 1”フレーム内で最大となる点を極大値としている。例えば、図に示した「Rang = 3」の場合は、前後2フレームで最大となる点が極大値であり、下記のコードのようにした。

	//a ~ a + bの範囲での極大値を取る番号の取得
	public int maxNo (int a, int b, int [] c) {
		int maxNum = a;
		int max =  c[a];
		for (int i = a; i < a + b && i < c.length; i++) {
			if (c[i] > max) {
				max = c[i];
				maxNum = i;
			}
		}
		return maxNum;
	}

そして、極小値の定義は「極大値間の最小値」としている。ただし、最初の極小値は「最初のデータから最初の極大値の間の最小値」とし、下記のコードのようにした。

	//a ~ bの間で極小値を取る番号の取得
	public int minNum (int a, int b, int [] c) {
		int minNum = a;
		int min = c[a];
		for (int i = a; i < b; i++) {
			if (min > c[i]) {
			min = c[i];
			minNum = i;
			}
		}
		return minNum;
	}

このプログラミングでは、簡潔にするために解析用データはRandomで作成し、「Range = 3, 閾値 = 5」とした。

入力したデータ全体で、極大値を探索するために、

		//極大値の探索
		int count = 0;
		for (int i = 0; i < no - range; i++) {
			if (maxNo(i, range, data) == maxNo (i + 1, range, data)) {
				int check = 0;
				for (int k = 1; k < range; k++) {
					if (maxNo(i, range, data) == maxNo(i+k, range, data)) {
						check++;
					}
				}
				if (check == range - 1) {
					maxRNo[count] = maxNo(i, range, data);
					count++;
				}
			}
		}

とした。この中の“k”の部分で“Range - 1”の範囲で最大値となるかのチェックを行っている。

また、下記の部分で、「極大値 ― 極小値」が閾値を超えたものをPeakとした。

		//差分の計算
		int subCount = 0;
		for (int i = 0; i < no; i++) {
			if (data [maxRNo[i]] - data [minRNo[i]] >= thr) {
				subRNo[subCount] = maxRNo[i];
				subCount++;
			}
		}

このプログラミングの実行した結果を下記に示す。

解析用のデータ:
30 38 43 9 5 22 63 64 50 12 8 92 95 98 64 46 2 87 57 65 51 27 15 12 90 90 20 31 16 96 
極大値:
43 64 98 87 90 
極小値:
30 5 8 2 12 
Peak : 
43 64 98 87 90 

前回までのプログラミングと、今回の極大値検出ツールを組み合わせることで、エクセルファイルからデータを読み込み、Peakを検出し、解析結果をエクセルファイルに出力できるはず。

次回は、解析用データをグラフに描き、その中で極大値と極小値をプロットする方法を書く。(JFreeChartを用いて2種類のグラフを重ねて表記する)