2015-03-31

【備忘録】 Java で Excel ファイルを読み込む

Apache POI (Poor Obfuscation Implementation) は Apache ソフトウェア財団のプロジェクトで Word や Excel といった Microsoft Office 形式のファイルを読み書きできる 100% Java ライブラリとして提供されています。配布ライセンスは Apache License に従っています。

Apache POI プロジェクトは、次のようなサブコンポーネントからなっています(ウィキペディア Apache POI から抜粋)。

POIFS (Poor Obfuscation Implementation File System)
Microsoft OLE 2 複合ドキュメント形式を読み書きするコンポーネント。すべての Microsoft Office ファイルは OLE 2 ファイルであるため、POIFS は他の POI 構成要素の基礎となっている。
HSSF (Horrible SpreadSheet Format)
Microsoft Excel (XLS) 形式のファイルを扱う。Excel 97以降のファイルを読み書きできる。
XSSF (XML SpreadSheet Format)
Office Open XML Workbook形式のファイルを扱う。Excel 2007で採用されたOOXML形式のファイルを読み書きできる。
HWPF (Horrible Word Processor Format)
Microsoft Word (DOC) 形式のファイルを扱う。Word 97以降のファイルを読み書きできる。Word 95以前の形式も限定的に読むことができる。
XWPF (XML Word Processor Format)
Office Open XML Document形式のファイルを扱う。Word 2007で採用されたOOXML形式のファイルを読み書きできる。
HSLF (Horrible Slide Layout Format)
Microsoft PowerPoint (PPT) 形式のファイルを扱う。PowerPoint 97以降のファイルを読み書きできる。
XSLF (XML Slide Layout Format)
Office Open XML Presentation形式のファイルを扱う。PowerPoint 2007で採用されたOOXML形式のファイルを読み書きできる。
HPSF (Horrible Property Set Format)
Microsoft Officeのドキュメントサマリーを読むコンポーネント。
HDGF (Horrible DiaGram Format)
Microsoft Visio形式のファイルを扱う。現在は読み取りのみ可能。
HPBF (Horrible PuBlisher Format)
Microsoft Publisher形式のファイルを扱う。ファイル内の一部の読み取りに限られている。
HSMF (Horrible Stupid Mail Format)
Microsoft Outlook (MSG) 形式のファイルを扱う。現在はファイルの読み取りのみ可能。

多機能なライブラリですが、今回は Excel の XLSX 形式のファイルを読み込むサンプルを紹介します。

動作環境は次の通りです。

  • OS: WIndows 8.1 (64bit)
  • Java: Java SE 1.8.0_40-b25
  • IDE: NetBeans IDE 8.0.2
  • poi-bin-3.11-20141221
リスト:XLSXReadSample.java 
package xlsxreadsample;

import java.io.FileInputStream;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class XLSXReadSample {
    public static void main(String[] args) {
        String fileName = "F:/sample_commonality.xlsx";

        try {
            XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream(fileName));

            System.out.println("Data dump:\n");

            for (int k = 0; k < wb.getNumberOfSheets(); k++) {
                XSSFSheet sheet = wb.getSheetAt(k);
                int rows = sheet.getPhysicalNumberOfRows();
                System.out.println("Sheet " + k + " \"" + wb.getSheetName(k) + "\" has " + rows + " row(s).");

                for (int r = 0; r < rows; r++) {
                    XSSFRow row = sheet.getRow(r);
                    if (row == null) {
                        continue;
                    }

                    int cells = row.getPhysicalNumberOfCells();
                    System.out.println("\nROW " + row.getRowNum() + " has " + cells + " cell(s).");

                    for (int c = 0; c < cells; c++) {
                        XSSFCell cell = row.getCell(c);
                        String value = null;

                        switch (cell.getCellType()) {
                            case XSSFCell.CELL_TYPE_FORMULA:
                                value = "FORMULA value=" + cell.getCellFormula();
                                break;
                            case XSSFCell.CELL_TYPE_NUMERIC:
                                value = "NUMERIC value=" + cell.getNumericCellValue();
                                break;
                            case XSSFCell.CELL_TYPE_STRING:
                                value = "STRING value=" + cell.getStringCellValue();
                                break;
                            default:
                        }
                        System.out.println("CELL col=" + cell.getColumnIndex() + " VALUE=" + value);
                    }

                }
            }
        } catch (Exception e) {
            System.err.println("Caught Exception: " + e.getMessage());
        }
    }
    
}

実行例を以下に示します。

run:
Data dump:

Sheet 0 "sample_commonality" has 21 row(s).

ROW 0 has 8 cell(s).
CELL col=0 VALUE=STRING value=LOTID
CELL col=1 VALUE=STRING value=STAGE_A
CELL col=2 VALUE=STRING value=STAGE_B
CELL col=3 VALUE=STRING value=STAGE_C
CELL col=4 VALUE=STRING value=STAGE_D
CELL col=5 VALUE=STRING value=STAGE_E
CELL col=6 VALUE=STRING value=STAGE_F
CELL col=7 VALUE=STRING value=YIELD
...
(省略)
...
ROW 20 has 8 cell(s).
CELL col=0 VALUE=STRING value=P20
CELL col=1 VALUE=STRING value=A03
CELL col=2 VALUE=STRING value=B02
CELL col=3 VALUE=STRING value=C01
CELL col=4 VALUE=STRING value=D03
CELL col=5 VALUE=STRING value=E02
CELL col=6 VALUE=STRING value=F02
CELL col=7 VALUE=NUMERIC value=91.78
ビルド成功(合計時間: 1秒)

参考サイト

  1. Read / Write Excel file in Java using Apache POI
  2. https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HSSFReadWrite.java
  3. Apache POIでExcelを操作

 

2015-03-18

Dialog とウィンドウを閉じるときのイベント処理

Java SE 8 Update 40 より、JavaFX に DIalog が導入されたということですので、遅ればせながら Dialog のサンプルを紹介します。ウィンドウの右上の X をクリックしてウィンドウを閉じようとしたときに、確認用のダイアログを表示させるサンプルです。

動作環境は次の通りです。

  • OS: WIndows 8.1 (64bit)
  • Java: Java SE 1.8.0_40-b25
  • IDE: NetBeans IDE 8.0.2
リスト:DialogSample.java 
package dialogsample;

import java.util.Optional;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

public class DialogSample extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setOnCloseRequest((WindowEvent t) -> {
            abortAction(t);
        });

        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction((ActionEvent event) -> {
            System.out.println("Hello World!");
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void abortAction(WindowEvent t) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("確認");
        alert.setHeaderText(null);
        alert.setContentText("本当に終了しますか?");

        Optional<ButtonType> result = alert.showAndWait();
        if (result.get() == ButtonType.OK) {
            System.exit(0);
        } else {
            t.consume();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }

}

実行例を以下に示します。

参考サイト

  1. JavaFX 8u40 のダイアログを探る - Programming Studio
  2. JavaFX Dialogs (official) | code.makery.ch
  3. Dialog (JavaFX 8)

 

2015-03-15

ProgressBar と Thread

bitWalk's: ProgressIndicator と Thread を読み直していたら、自分で書いた記事にも関わらずコードが実に判りにくい。これは SWT のせいではなく、自分のプログラミングセンスの無さに違いないとガッカリします。

今回は、気を取り直して、JavaFX で同じようなことを確認するプログラムを紹介します。プログラムの判りやすさが少しでも改善していることを祈って…。なお、今回は二つのクラスに分けました。

動作環境は次の通りです。

  • OS: WIndows 8.1 (64bit)
  • Java: Java SE 1.8.0_40-b25
  • IDE: NetBeans IDE 8.0.2
リスト:ProgressBarTest.java 
package progressbartest;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ProgressBarTest extends Application {

    ProgressBar pb = new ProgressBar(0);

    @Override
    public void start(Stage stage) {
        Group root = new Group();
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("ProgressBarTest");

        Button bt = new Button("処理開始");
        bt.setOnAction((ActionEvent e) -> {
            startWork();
        });

        VBox vb = new VBox();
        vb.setSpacing(0);
        vb.setAlignment(Pos.CENTER);
        vb.getChildren().addAll(bt, pb);

        bt.prefWidthProperty().bind(vb.widthProperty());
        bt.prefHeightProperty().bind(vb.heightProperty());
        pb.prefWidthProperty().bind(vb.widthProperty());

        scene.setRoot(vb);
        stage.show();
    }

    void startWork() {
        DummyTask task = new DummyTask();
        Thread th = new Thread(task);
        pb.progressProperty().bind(task.progressProperty());

        th.setDaemon(true);
        th.start();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

スレッドを処理するダミーの処理を以下に示します。

リスト:DummyTask.java 
package progressbartest;

import javafx.concurrent.Task;

class DummyTask extends Task<Void> {

    @Override
    protected Void call() throws Exception {
        updateProgress(-1, 1);

        // dummy task
        long sleepTime = 5000; // milliseconds
        long startTime = System.currentTimeMillis();
        long endTime = startTime + sleepTime;

        while (System.currentTimeMillis() < endTime) {
            if (isCancelled()) {
                break;
            }
            updateProgress(System.currentTimeMillis() - startTime, sleepTime);
        }

        updateProgress(1, 1);
        return null;
    }
}

実行例を以下に示します。

DummyTask.java の 20 行目をコメントアウトすると、処理の進捗度を示さない表示になります。

参考サイト

  1. Using JavaFX UI Controls: Progress Bar and Progress Indicator | JavaFX 2 Tutorials and Documentation
  2. ProgressBar (JavaFX 8)

 

2015-03-08

CSV ファイルを読み込んで JavaFX の TableView に表示する(改)

CSV ファイルを読み込んで TableView に表示させるサンプルを前回紹介しましたが、サンプルの完成度が低かったので、手直しをしたものを掲載しました。

動作環境は次の通りです。

  • OS: WIndows 8.1 (64bit)
  • Java: Java SE 1.8.0_40-b25
  • IDE: NetBeans IDE 8.0.2
リスト:TableViewSample.java 
package tableviewsample;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableViewSample extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    ObservableList<String> headers = FXCollections.observableArrayList();
    ObservableList<ObservableList> data = FXCollections.observableArrayList();

    TableView<ObservableList> table;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group(), 600, 400);
        stage.setTitle("CSV Read Sample");

        MenuBar menuBar = new MenuBar();
        generateMenu(menuBar, stage);

        table = new TableView();

        VBox vbox = new VBox();
        vbox.getChildren().addAll(menuBar, table);
        vbox.prefWidthProperty().bind(scene.widthProperty());
        vbox.prefHeightProperty().bind(scene.heightProperty());
        table.prefHeightProperty().bind(vbox.heightProperty());
        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    private void generateMenu(MenuBar menuBar, Stage stage) {
        // Menu File
        Menu menuFile = new Menu("File");
        menuBar.getMenus().addAll(menuFile);

        // MenuItem Exit
        MenuItem open = new MenuItem("Open");
        open.setOnAction((ActionEvent t) -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Open CSV File");
            fileChooser.getExtensionFilters().addAll(
                    new FileChooser.ExtensionFilter("CSV", "*.csv"),
                    new FileChooser.ExtensionFilter("All", "*.*")
            );
            
            File file = fileChooser.showOpenDialog(stage);
            if (file != null) {
                readCSV(file);
                open.setDisable(true);
            }
        });

        // MenuItem Exit
        MenuItem exit = new MenuItem("Exit");
        exit.setOnAction((ActionEvent t) -> {
            System.exit(0);
        });

        menuFile.getItems().addAll(open, new SeparatorMenuItem(), exit);
    }

    void readCSV(File file) {
        try {
            if (checkBeforeReadfile(file)) {
                FileInputStream in = new FileInputStream(file);
//                InputStreamReader sr = new InputStreamReader(in, "UTF-8");
                InputStreamReader sr = new InputStreamReader(in, "Shift_JIS");
                BufferedReader br = new BufferedReader(sr);

                String line;
                boolean isHeader = true;
                while ((line = br.readLine()) != null) {
                    StringTokenizer token = new StringTokenizer(line, ",");
                    ObservableList<String> lineList = FXCollections.observableArrayList();

                    if (isHeader) {
                        while (token.hasMoreTokens()) {
                            headers.add(token.nextToken());
                        }
                        isHeader = false;
                    } else {
                        while (token.hasMoreTokens()) {
                            lineList.add(token.nextToken());
                        }
                        data.add(lineList);
                    }
                }
            } else {
                System.out.println("No file exists or can't open.");
            }
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFoundException : " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IOException : " + e.getMessage());
        }

        generateTable();

    }

    private void generateTable() {
        int idx = 0;
        TableColumn[] column = new TableColumn[headers.size()];
        for (String colName : headers) {
            column[idx] = new TableColumn(colName);
            column[idx].setCellValueFactory(new SetCellProperty(idx));
            idx++;
        }
        table.getColumns().addAll(column);

        table.setItems(data); // finally add data to tableview
    }

    boolean checkBeforeReadfile(File file) {
        if (file.exists()) {
            if (file.isFile() && file.canRead()) {
                return true;
            }
        }
        return false;
    }

    private static class SetCellProperty implements Callback<CellDataFeatures<ObservableList, String>, ObservableValue<String>> {

        private final int i;

        public SetCellProperty(int i) {
            this.i = i;
        }

        @Override
        public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
            return new SimpleStringProperty(param.getValue().get(i).toString());
        }
    }
}

実行例を以下に示しました。郵便番号データダウンロード 住所の郵便番号(ローマ字) zip形式 - 日本郵便 からダウンロードした CSV ファイルにヘッダー情報をつけるなどの加工をしておいて読み込んでみました。

参考サイト

  1. Updated: Dynamic TableView Data From Database « Java and FX

 

2015-03-05

CSV ファイルを読み込んで JavaFX の TableView に表示する

JavaFX に、一時期は大変注目していたのですが、TableView に読み込んだ CSV ファイルの内容を読み込んで表示することに挫折して以来、しばらく遠ざかってしまっていました。

Java 8 のリリースから、JavaFX 2 もバージョン番号を合わせ、JavaFX 8 になりました。あらためて JavaFX のプログラミングに取り組もうと、いろいろチュートリアルを見ましたが、TableView については、相変わらず列のサイズ(仕様)が決まっているデータを扱うサンプルばかりで、予め大きさが判らない、任意のファイルを読み込むような場合にどうするかについて答えてくれるようなサンプルに出会えませんでした。

それでは、自分でやり方を見つけ出せば良いのでしょうが、インターネットでなんでも情報を探し出せるようになると、人間、楽をしたがるものです。自分と同じことをしようとしている人が必ずいるはずだと信じて、丁寧に探し続けたところ、ついに見つけました【参考サイト 1.】

仕組みを判りやすく人様に説明できるほど咀嚼できていません。でも、とにかく動作した、というサンプルを紹介してしまいます。自分が GUI のアプリケーションを開発する場合、このようなスプレッドシート状のウィジェットを使いこなすことは避けては通れませんので、十分に理解できた上で改めて TableView の使い方を紹介します。今回は、まだ中途半端ですが、CSV ファイルを読み込んで TableView に表示する、動作確認用のサンプルを下記に示しました。

なお、動作環境は次の通りです。

  • OS: WIndows 8.1 (64bit)
  • Java: Java SE 1.8.0_31-b13
  • IDE: NetBeans IDE 8.0.2
リスト:TableViewSample.java 
package tableviewsample;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TableViewSample extends Application {

    ObservableList<String> headers = FXCollections.observableArrayList();
    ObservableList<ObservableList> data = FXCollections.observableArrayList();

    FileChooser fileChooser = new FileChooser();
    TableView<ObservableList> table;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("CSV Read Sample");

        MenuBar menuBar = new MenuBar();
        generateMenu(menuBar, stage);

        table = new TableView();

        VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(menuBar, table);
        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    private void generateMenu(MenuBar menuBar, Stage stage) {
        // Menu File
        Menu menuFile = new Menu("File");
        menuBar.getMenus().addAll(menuFile);

        // MenuItem Exit
        MenuItem open = new MenuItem("Open");
        open.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent t) {
                fileChooser.setTitle("Open CSV File");
                fileChooser.getExtensionFilters().addAll(
                        new FileChooser.ExtensionFilter("CSV", "*.csv"),
                        new FileChooser.ExtensionFilter("All", "*.*")
                );
                File file = fileChooser.showOpenDialog(stage);
                if (file != null) {
                    readCSV(file);
                    open.setDisable(true);
                }
            }
        });

        // MenuItem Exit
        MenuItem exit = new MenuItem("Exit");
        exit.setOnAction((ActionEvent t) -> {
            System.exit(0);
        });

        menuFile.getItems().addAll(open, new SeparatorMenuItem(), exit);
    }

    void readCSV(File file) {
        try {
            if (checkBeforeReadfile(file)) {
                FileInputStream in = new FileInputStream(file);
                InputStreamReader sr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(sr);

                String line;
                boolean header = true;
                while ((line = br.readLine()) != null) {
                    StringTokenizer token = new StringTokenizer(line, ",");
                    ObservableList<String> lineList = FXCollections.observableArrayList();

                    if (header) {
                        while (token.hasMoreTokens()) {
                            headers.add(token.nextToken());
                        }
                        header = false;
                    } else {
                        while (token.hasMoreTokens()) {
                            lineList.add(token.nextToken());
                        }
                        data.add(lineList);
                    }
                }
            } else {
                System.out.println("No file exists or can't open.");
            }
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFoundException : " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IOException : " + e.getMessage());
        }

        generateTable();

    }

    private void generateTable() {
        int colN = 0;
        TableColumn[] column = new TableColumn[headers.size()];
        for (String colName : headers) {
            final int idx = colN;
            column[colN] = new TableColumn(colName);
            column[colN].setCellValueFactory(
                    new Callback<CellDataFeatures<ObservableList, String>, ObservableValue<String>>() {
                        @Override
                        public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
                            return new SimpleStringProperty(param.getValue().get(idx).toString());
                        }
                    });
            colN++;
        }
        table.getColumns().addAll(column);

        table.setItems(data); // finally add data to tableview
    }

    boolean checkBeforeReadfile(File file) {
        if (file.exists()) {
            if (file.isFile() && file.canRead()) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

実行例を以下に示しました。

今後、大きなファイルを読み込むなどベンチマークを重ねて、その結果を紹介するようにします。

参考サイト

  1. Updated: Dynamic TableView Data From Database « Java and FX

 

2015-03-01

Commonality Analysis - Java 編

R で、一元配置分散分析を利用した Commonality Analysis の例を紹介しましたが、今回は同じことを Java で実施して、結果を検証したいと思います。

R による Commonality Analysis の記事を下記に示しました。

前回のおさらい

STAGE_AYIELD
A0194.08
A0192.84
A0296.52
A0296.34
A0294.11
A0194.36
A0393.14
A0294.94
A0396.33
A0198.81
A0396.13
A0294.26
A0192.06
A0395.94
A0398.62
A0293.24
A0194.23
A0194.32
A0296.16
A0391.78

Commonality Analysis は、複数の工程 (STAGE) において、工程内の複数の装置と、アウトプットである収率 (YIELD) との関係で、使用した装置との共通性(依存性)を見つけ出すことが目的でした。YIELD に対する装置の依存性を調べるために、工程(STAGE_A など)それぞれについて、一元配置分散分析をして、その p-Value から、装置の違いが YIELD の違いに対して有意かどうかを検定しました(右表は STAGE_A の例)。

R 上では、右の表を tbl というデータフレームにした場合、次のようにして一元配置分散分析をして p-Value を確認できます。

result <- oneway.test(YIELD ~ STAGE, data = tbl, var.equal = TRUE)
print(result$p.value)
[1] 0.6785116

この方法は、複数の工程における複数の装置の組み合わせによって、検定の結果が正しいかどうかが左右されるという不確かさがあります 1.。しかし現実的な対応としては、まず分散分析をして有意なシグナルが見つかれば、そこからドリルダウンして、確かに正しい検定結果かどうかを検証することによって不確かさを補うことができます。

例えば、半導体デバイスの製造工程のように長大な工程の中から、問題解決のとっかかりとなるシグナルを見つけ出すような場合に、強力な手法となります。

そういうわけで、R ばかりでなく、他のプログラミング言語でも同じ処理ができるようにしておいた方が良いだろうと考え、計算結果の検証も兼ねて、Java でも同等の処理をするサンプルを作成しました。

Java による計算

Java では、分散分析の計算の部分に Apache Commons Math の一元配置分散分析 (One-Way ANOVA tests) を利用することにします。

Math - The Commons Math User Guide - Statistics にある一元配置分散分析の部分を下記に引用しました。

One-Way ANOVA tests
double[] classA =
   {93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0 };
double[] classB =
   {99.0, 92.0, 102.0, 100.0, 102.0, 89.0 };
double[] classC =
   {110.0, 115.0, 111.0, 117.0, 128.0, 117.0 };
List classes = new ArrayList();
classes.add(classA);
classes.add(classB);
classes.add(classC);

Then you can compute ANOVA F- or p-values associated with the null hypothesis that the class means are all the same using a OneWayAnova instance or TestUtils methods:

double fStatistic = TestUtils.oneWayAnovaFValue(classes); // F-value
double pValue = TestUtils.oneWayAnovaPValue(classes);     // P-value

To test perform a One-Way ANOVA test with significance level set at 0.01 (so the test will, assuming assumptions are met, reject the null hypothesis incorrectly only about one in 100 times), use

TestUtils.oneWayAnovaTest(classes, 0.01); // returns a boolean
                                          // true means reject null hypothesis
STAGE_A
A01A02A03
94.0896.5293.14
92.8496.3496.33
94.3694.1196.13
98.8194.9495.94
92.0694.2698.62
94.2393.2491.78
94.3296.16

一元配置分散分析における p-Value の算出は、TestUtils.oneWayAnovaPValue メソッドを利用すればできそうです。

public double anovaPValue(Collection<double[]> categoryData)
                   throws NullArgumentException,
                          DimensionMismatchException,
                          ConvergenceException,
                          MaxCountExceededException

しかし、データ (YIELD) は、左の表のようなグループになるように整理しなければなりません。

R のデータフレームの使い方に慣れてしまうと、Java のような汎用的なプログラミング言語でこういう処理を記述しようとするとなにかと面倒ですが、なるべくシンプルになるように配慮しながら記述してみました。しかし、データの並び替えやら型変換などに手間取り、コーディングが結構長くなってしまいました。

なお、このサンプルで使用している CSV ファイルは、前述の R の記事で使用したファイルと同じです shared sample から sample_commonality.csv.zip をダウンロードできるようにしてあります。

サンプルファイルの格納に使っていたストレージサービス 4shared が、あまりに使いにくいので Google Drive へ変更しました。

リスト:ToolCommonalityAnalysis.java 
package onewayanova;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.math3.stat.inference.TestUtils;

public class ToolCommonalityAnalysis {
    // CSV ファイルを格納するリスト
    List<List<String>> content = new ArrayList<List<String>>();
    // STAGE 毎に分散分析をした p-Value を格納するリスト
    List<ResultData> result = new ArrayList<ResultData>();
    // 有意レベル
    double significant = 0.02;

    ToolCommonalityAnalysis(String fileName) {
        readCSV(fileName);
        calcANOVA();
    }

    /**
     * readCSV - CSV ファイルの読み込み処理
     * 
     * @param fileName
     *            name of CSV file to read
     */
    void readCSV(String fileName) {
        try {
            if (checkBeforeReadfile(fileName)) {
                FileInputStream in = new FileInputStream(fileName);
                InputStreamReader sr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(sr);

                String line;
                boolean header = true;
                while ((line = br.readLine()) != null) {
                    StringTokenizer tokenizer = new StringTokenizer(line, ",");
                    // 後の処理の都合で、行単位ではなく、列単位で読み込んでいる
                    if (header) {
                        readColHeader(tokenizer);
                        header = false;
                    } else {
                        readColValues(tokenizer);
                    }
                }
                br.close();
            } else {
                System.out.println("ファイルが無いか、開くことができません。");
                return;
            }
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFoundException : " + e.getMessage());
            return;
        } catch (IOException e) {
            System.out.println("IOException : " + e.getMessage());
            return;
        }
    }

    /**
     * checkBeforeReadfile - CSV ファイルの読み込み可チェック
     * 
     * @param file
     *            file instance to read
     * @return true if OK otherwide false
     */
    boolean checkBeforeReadfile(String fileName) {
        File file = new File(fileName);
        if (file.exists()) {
            if (file.isFile() && file.canRead()) {
                return true;
            }
        }
        return false;
    }

    /**
     * readColHeader - CSV ファイルのヘッダーを読み込む処理
     * 
     * @param token
     *            token of row
     */
    void readColHeader(StringTokenizer token) {
        while (token.hasMoreTokens()) {
            String value = token.nextToken();
            List<String> col = new ArrayList<String>();
            col.add(value);
            content.add(col);
        }
    }

    /**
     * readColValues - CSV ファイルの行順で列毎に読み込む処理
     * 
     * @param token
     *            token of row
     */
    void readColValues(StringTokenizer token) {
        int i = 0;
        while (token.hasMoreTokens()) {
            String value = token.nextToken();
            List<String> colArr = content.get(i);
            colArr.add(value);
            i++;
        }
    }

    /**
     * calcANOVA - 一元配置分析
     */
    void calcANOVA() {
        int cols = content.size();

        for (int i = 1; i < cols - 1; i++) {
            // 水準列 (STAGE)
            List<String> colCond = new ArrayList<String>(content.get(i));
            // YIELD
            List<String> colValue = new ArrayList<String>(content.get(cols - 1));

            // 水準列のヘッダー名の取得
            String condHeader = colCond.get(0);
            // リストからヘッダー名を削除
            colCond.remove(0);
            colValue.remove(0);
            // Commons Math で処理できるようデータグループを整理
            List<double[]> dataGroup = arrangeDataGroup(colCond, colValue);

            // Commons Math で 一元配置分散分析を実施して p-Value を算出
            double pValue = TestUtils.oneWayAnovaPValue(dataGroup); // P-value
            ResultData resultStage = new ResultData(condHeader, pValue);
            result.add(resultStage);
        }
    }

    /**
     * arrangeDataGroup
     *
     * @param levelList 水準のリスト
     * @param valueList 水準に対応する値 (String) のリスト
     * @return Commons Math/oneWayAnovaPValue に渡すデータ
     */
    List<double[]> arrangeDataGroup(List<String> levelList, List<String> valueList) {
        // ユニークな水準数を TreeSet で調査
        List<String> levelSet = new ArrayList<String>(new TreeSet<String>(levelList));
        int levelNum = levelSet.size();

        // 水準名と整数値を連想配列にして、それぞれの条件群にYIELDを振り分ける
        TreeMap<String, Integer> levelKey = new TreeMap<String, Integer>();

        // Double 型の水準別リストのインスタンス
        List<List<Double>> groupDList = new ArrayList<List<Double>>();

        // 水準数でループ
        for (int i = 0; i < levelNum; i++) {
            // 水準名と整数値のペアを定義
            levelKey.put((String) levelSet.get(i), (Integer) i);
            // 水準別に Double 型の Yield を格納する空のインスタンスを準備
            List<Double> groupDouble = new ArrayList<Double>();
            groupDList.add(groupDouble);
        }

        // String を Double の配列リストへ変換して準備したリストに格納
        for (int i = 0; i < levelList.size(); i++) {
            String level = levelList.get(i);
            Double valueDouble = Double.parseDouble(valueList.get(i));
            List<Double> groupDouble = groupDList.get(levelKey.get(level));
            groupDouble.add(valueDouble);
        }

        // Common Math に渡せる double 型の配列/リストへ変換
        List<double[]> groupList = new ArrayList<double[]>();
        for (int i = 0; i < levelNum; i++) {
            List<Double> group = new ArrayList<Double>(groupDList.get(i));
            // 水準iの要素の数
            int groupSize = group.size();
            // 水準iの配列を確保
            double[] groupArr = new double[groupSize];
            // Double から double 型に変換して配列に格納するループ
            for (int j = 0; j < groupSize; j++) {
                groupArr[j] = (group.get(j)).doubleValue();
            }
            groupList.add(groupArr);
        }
        return groupList;
    }

    /**
     * showTable - 読み込んだファイルを表示
     */
    void showTable() {
        for (int i = 0; i < content.size(); i++) {
            System.out.println(content.get(i));
        }
    }

    /**
     * showResult - 分散分析の結果 (p-Value) を表示
     */
    void showResult() {
        DecimalFormat df = new DecimalFormat("0.000000000");
        System.out.println("\nSTAGE    P-VALUE     SIG.");
        for (ResultData data : result) {
            String stage = data.getStage();
            double pval = data.getPvalue();
            String sigStr;
            if (pval < significant) {
                sigStr = new String("*");
            } else {
                sigStr = new String("");
            }
            System.out.println(stage + "  " + df.format(pval) + "  " + sigStr);

        }
    }

    // 結果を格納するリストの要素の構造を定義
    class ResultData {
        String stage;
        Double pvalue;

        ResultData(String stage, Double pvalue) {
            this.stage = stage;
            this.pvalue = pvalue;
        }

        String getStage() {
            return stage;
        }

        Double getPvalue() {
            return pvalue;
        }
    }

    public static void main(String[] args) {
        String fileName = "D:/Users/bitwalk/Documents/data/sample_commonality.csv";
        ToolCommonalityAnalysis tca = new ToolCommonalityAnalysis(fileName);
        tca.showTable();
        tca.showResult();
    }
}

実行結果は以下のようになります。R による算出結果と同じになることが確認できました。

[LOTID, P01, P02, P03, P04, P05, P06, P07, P08, P09, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20]
[STAGE_A, A01, A01, A02, A02, A02, A01, A03, A02, A03, A01, A03, A02, A01, A03, A03, A02, A01, A01, A02, A03]
[STAGE_B, B01, B02, B03, B01, B03, B01, B02, B02, B03, B03, B01, B02, B02, B01, B03, B02, B01, B03, B03, B02]
[STAGE_C, C01, C04, C01, C03, C02, C03, C02, C01, C02, C03, C01, C01, C04, C03, C02, C03, C04, C03, C02, C01]
[STAGE_D, D01, D02, D01, D03, D02, D01, D02, D01, D03, D03, D01, D01, D02, D02, D02, D01, D03, D03, D02, D03]
[STAGE_E, E01, E03, E01, E02, E03, E03, E01, E02, E03, E02, E01, E01, E01, E02, E03, E01, E01, E02, E03, E02]
[STAGE_F, F01, F01, F03, F04, F01, F03, F04, F02, F04, F03, F02, F02, F02, F01, F04, F03, F03, F04, F01, F02]
[YIELD, 94.08, 92.84, 96.52, 96.34, 94.11, 94.36, 93.14, 94.94, 96.33, 98.81, 96.13, 94.26, 92.06, 95.94, 98.62, 93.24, 94.23, 94.32, 96.16, 91.78]

STAGE    P-VALUE     SIG.
STAGE_A  0.678511601  
STAGE_B  0.001784216  *
STAGE_C  0.239092024  
STAGE_D  0.849674707  
STAGE_E  0.435107778  
STAGE_F  0.422098879  

長期の出張中に記事を投稿しています。先日、この出張のために買った廉価な Windows のノート PC 上で動作確認をしていますので、Linux 固有の話題からはしばらく遠ざかります。

参考サイト

  1. 交絡 - Wikipedia