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

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

- https://simplestatistics.org/
- GitHub: https://github.com/simple-statistics/simple-statistics
- Documents: https://simplestatistics.org/docs/

分散や標準偏差といった記述統計、ベイズ分類器、線形回帰などが使えるようです。
`.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`とします。

```bash
$ 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の出力結果に混ざって欲しくない場合は、バイナリ扱いにしておきます。

```bash
$ echo 'package-lock.json binary' >> .gitattributes
```
必要なnpmパッケージをインストールします。

```bash
$ 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の初期設定
まずデフォルトの初期設定をして、それから変更を加えていきます。

```bash
$ 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を、下記のように変更します。

```diff: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`を、下記のように修正します。

```diff: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」と表示されれば成功です。

## 線形回帰

回帰分析については[拙稿](https://qiita.com/suzuki_sh/items/857f5986633e0b6c68ce#%E3%81%84%E3%81%84%E3%81%AD%E3%81%8C%E3%82%82%E3%82%89%E3%81%88%E3%82%8B%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0%E3%82%92%E5%9B%9E%E5%B8%B0%E5%88%86%E6%9E%90%E3%81%A7%E6%8E%A2%E3%81%99)等をご参照下さい。

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

```typescript
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](https://qiita-image-store.s3.amazonaws.com/0/50391/43268907-f888-c4f7-908d-97d8f8f99369.png)

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

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

## 参考
- [Fix the React & Webpack tutorial by mbertram · Pull Request \#765 · Microsoft/TypeScript\-Handbook](https://github.com/Microsoft/TypeScript-Handbook/pull/765)
    - React + TypeScriptのセットアップは、公式ドキュメントに送られている上記PullRequestを参考に作成(TypeScript公式が古いままのため)
- [Datadogを使ってサーバのリソーストレンドを分析する](https://qiita.com/tacumai/items/5e3f5bb12e8d528da50f)