LoginSignup
9
6

More than 5 years have passed since last update.

SimpleStatisticsを使って、TypeScriptで回帰分析

Last updated at Posted at 2018-10-08

分析といえばRやPythonが有名ですが、JavaScriptにも統計解析に強いライブラリが無いか探していたところ、 SimpleStatisticsというものがありました。

分散や標準偏差といった記述統計、ベイズ分類器、線形回帰などが使えるようです。
.d.ts型定義が同梱されており、TypeScriptでそのまま使うことができます。

こちらを使って回帰分析をしてみましょう。

完成品はこんな感じです。
https://s2terminal.github.io/regression_js_sample/

環境

  • Windows Subsystem for Linux / Ubuntu 16.04.4 LTS
    • たぶんMacでもほぼ同じです
  • node 8.10.0
  • npm 6.4.1
  • webpack 4.20.2
  • TypeScript 3.1.1
  • react 16.5.2
  • simple-statistics 6.1.1

準備

SimpleStatisticsはnodeでもブラウザでも動くそうですが、ここではReact+TypeScriptで実行環境を作ります。
不要な場合は読み飛ばしてください。

適当な名前でリポジトリを作ります。ここではregression_js_sampleとします。

$ mkdir regression_js_sample
$ cd regression_js_sample
$ git init
$ curl 'https://raw.githubusercontent.com/github/gitignore/master/Node.gitignore' > .gitignore
$ echo 'dist/' >> .gitignore

お好みで、package-lock.jsonがgit diffの出力結果に混ざって欲しくない場合は、バイナリ扱いにしておきます。

$ echo 'package-lock.json binary' >> .gitattributes

必要なnpmパッケージをインストールします。

$ npm init --yes
$ npm install --save react react-dom simple-statistics
$ npm install --save-dev webpack webpack-cli @webpack-cli/init\
 typescript ts-loader\
 @types/react @types/react-dom

webpackの初期設定

まずデフォルトの初期設定をして、それから変更を加えていきます。

$ npx webpack init
質問 回答
Will your application have multiple bundles? No
Which module will be the first to enter the application? [default: ./src/index]
Which folder will your generated bundles be in? [default: dist]:
Will you be using ES2015? Yes
Will you use one of the below CSS solutions? No
If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)

できあがったwebpack.config.jsを、下記のように変更します。

webpack.config.js
module.exports = {
   module: {
     rules: [
       {
-        include: [path.resolve(__dirname, 'src')],
-        loader: 'babel-loader',
-
-        options: {
-          plugins: ['syntax-dynamic-import'],
-
-          presets: [
-            [
-              'env',
-              {
-                modules: false
-              }
-            ]
-          ]
-        },
-
-        test: /\.js$/
+        test: /\.ts(x?)$/,
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: "ts-loader"
+          }
+        ]
       }
     ]
   },

-  mode: 'production',
+  mode: 'development',
+  devtool: "source-map",
+  resolve: {
+    extensions: [".ts", ".tsx"]
+  },
+  externals: {
+    "react": "React",
+    "react-dom": "ReactDOM"
   }
 };

TypeScriptの初期設定

こちらもデフォルトの設定に手を入れてい来ます。

$ npx tsc --init

できあがったtsconfig.jsonを、下記のように修正します。

tsconfig.json
     // "lib": [],                             /* Specify library files to be included in the compilation. */
     // "allowJs": true,                       /* Allow javascript files to be compiled. */
     // "checkJs": true,                       /* Report errors in .js files. */
-    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+    "jsx": "react",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
     // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
     // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
-    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
+    "sourceMap": true,                     /* Generates corresponding '.map' file. */
     // "outFile": "./",                       /* Concatenate and emit output to single file. */
-    // "outDir": "./",                        /* Redirect output structure to the directory. */
+    "outDir": "./dist/",                        /* Redirect output structure to the directory. */
     // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
     // "composite": true,                     /* Enable project compilation */
     // "removeComments": true,                /* Do not emit comments to output. */
(中略)

     /* Strict Type-Checking Options */
     "strict": true,                           /* Enable all strict type-checking options. */
-    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
+    "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
     // "strictNullChecks": true,              /* Enable strict null checks. */
     // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
     // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. *

noImplicitAnyなんかはお好みで良いと思います。

Hello world

index.htmlにHTMLを記述していきます。
VSCodeで空のファイルを作成してhtml:5と入力してTabを押すことで、Emmetのテンプレートを呼び出すと便利です。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>回帰分析</title>
</head>
<body>
  <h1>回帰分析</h1>
  <div id="main"></div>

  <!-- 依存関係 -->
  <script src="./node_modules/react/umd/react.development.js"></script>
  <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>

  <!-- メイン処理 -->
  <script src="./dist/main.js"></script>
</body>
</html>

Hello Worldと出力してみます。

src/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";

class Main extends React.Component<any, any> {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById("main"));

実行します

$ npx webpack --watch
$ if which explorer.exe; then explorer.exe index.html; else open index.html; fi

「Hello World」と表示されれば成功です。

線形回帰

回帰分析については拙稿等をご参照下さい。

SimpleStatisticsでの単回帰分析の基本的な使い方はこんな感じです。

import * as SimpleStatistics from "simple-statistics";

const data = [[0,2], [2,3]];

//  linearRegression(data: number[][]): { m: number, b: number }
const mb = SimpleStatistics.linearRegression(data);

// m:勾配, b:y切片
console.log(mb.m) // #=> 1
console.log(mb.b) // #=> 1

(若干わかりにくいキーで返ってくるのが癖がありますが、Rのメソッド名なども似たような感じだと思います。)

Reactで回帰分析

入力フォームで受け取った座標情報について、線形回帰分析できるようにしてみます。

src/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as SimpleStatistics from "simple-statistics";

interface IPointProps {
  index: number;
  onChangeX: any;
  onChangeY: any;
  x: number;
  y: number;
}

class Point extends React.Component<IPointProps, any> {
  public render() {
    return (
      <div>
        <label htmlFor={`x${this.props.index}`}>X{this.props.index}</label>
        <input
          type="text"
          name={`x${this.props.index}`}
          value={this.props.x}
          onChange={(event: React.ChangeEvent<HTMLInputElement>): void =>
            this.props.onChangeX(event)
          }
        />
        <label htmlFor={`y${this.props.index}`}>Y{this.props.index}</label>
        <input
          type="text"
          name={`y${this.props.index}`}
          value={this.props.y}
          onChange={(event: React.ChangeEvent<HTMLInputElement>): void =>
            this.props.onChangeY(event)
          }
        />
      </div>
    );
  }
}

interface IMainProps {
}
interface IMainState {
  data: Array<{
    x: number;
    y: number;
  }>;
  slope: number | null;
  yIntercept: number | null;
}
class Main extends React.Component<IMainProps, IMainState> {
  constructor(props: IMainProps) {
    super(props);
    this.state = {
      data: [
        { x: 0, y: 0 },
        { x: 0, y: 0 }
      ],
      slope: null,
      yIntercept: null
    };
  }

  public handleChange(event: React.ChangeEvent<HTMLInputElement>, key: number) {
    const data = this.state.data;
    const value = +event.target.value;
    if (isNaN(value)) { return; }
    if (event.target.name === 'x') { data[key].x = value; }
    if (event.target.name === 'y') { data[key].y = value; }
    this.setState({ data });
  }

  public handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    const data = this.state.data.map(xy => {
      return [xy.x, xy.y];
    });
    const mb = SimpleStatistics.linearRegression(data); // m:slope, b:y-intercept
    this.setState({ slope: mb.m });
    this.setState({ yIntercept: mb.b });
  }

  public render() {
    const points = this.state.data.map((xy, index) => {
      return (
        <Point
          key={index}
          index={index}
          x={xy.x}
          y={xy.y}
          onChangeX={(e: React.ChangeEvent<HTMLInputElement>) => this.handleChange(e, index)}
          onChangeY={(e: React.ChangeEvent<HTMLInputElement>) => this.handleChange(e, index)}
        />
      );
    });
    return (
      <form action="javascript:void(0)" onSubmit={e => this.handleSubmit(e)}>
        {points}
        <button type="submit">計算</button>
        <p>y={this.state.slope}x+{this.state.yIntercept}</p>
      </form>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById("main"));

実際の動作はこのようになります。

image.png

簡単に回帰分析をする事ができました。
SimpleStatistics、ドキュメントもきれいで読みやすく、良い感じです。
そして、やはりTypeScriptの型定義情報があるおかげで、引数と返り値が静的型付けでヒント出るのはこういうライブラリを扱うときに強力です。

今回使ったプロジェクトの全体はこちらです
https://github.com/s2terminal/regression_js_sample

参考

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6