[Pandas]代表値を求める(平均値、中央値、最頻値)

一般的によく知られている代表値といえば、平均値(mean)、中央値(median)、最頻値(mode)です。

Pandasを使ってこれらの値を求めてみます。

下準備

今回はTitanicデータセットから年齢(Age)を使用します。

import pandas as pd
import math

df = pd.read_csv('./titanic/train.csv')

平均値を求める

平均値を求めるにはmean()を使用します。

# 平均値(mean)を求める
df['Age'].mean()
# -> 29.69911764705882

中央値を求める

中央値を求めるにはmedian()を使用します。

# 中央値(median)求める
df['Age'].median()

最頻値を求める

最頻値を求めるには一工夫が必要です。最頻値は最も大きい度数の階級値になるため、一旦度数分布表を作成します。

ヒストグラムを確認する

度数分布表を作成する前にヒストグラムを確認しておきましょう。

df['Age'].hist(bins=10)

ビンの数を10に設定してヒストグラムを作成すると、20代が最頻値のようだということがわかります。

最頻値を求める

視覚的に確認できたところで、最頻値を求めます。度数分布表の作成方法はいくつかありますが、ここでは簡易的にcut()value_counts()を組み合わせて、先ほど確認したグラフと同じ区間で度数分布表を作成します。

# 度数分布表を作成する
pd.cut(df['Age'], 10).value_counts()
---
(16.336, 24.294]    177
(24.294, 32.252]    169
(32.252, 40.21]     118
(40.21, 48.168]      70
(0.34, 8.378]        54
(8.378, 16.336]      46
(48.168, 56.126]     45
(56.126, 64.084]     24
(64.084, 72.042]      9
(72.042, 80.0]        2
Name: Age, dtype: int64

作成した結果を確認しても20代が最頻値のようです。最頻値は区間の最小値と最大値の中間になりますので、$24.294-(24.294-16.336)\div2=20.315$となります。

[Oracle]WITH句で階層問い合わせを行う

OracleではWITH句を使って階層問い合わせを行うことができます。

サンプルデータ

説明として、階層を持つフォルダを管理するFOLDER_TBLとフォルダに格納するファイルを管理するFILE_TBLを用意します。

CREATE TABLE FOLDER_TBL (
  FOLDER_ID NUMBER(10) NOT NULL,
  PARENT_ID NUMBER(10),
  FOLDER_NAME VARCHAR2(255) NOT NULL,
  PRIMARY KEY (FOLDER_ID),
  FOREIGN KEY (FOLDER_ID) REFERENCES FOLDER_TBL(FOLDER_ID) ON DELETE CASCADE
);

FOLDER_TBLはフォルダを識別するためのFOLDER_IDとあるフォルダとその上位フォルダを紐づけるためのPARENT_IDを持っています。PARENT_IDは最上位フォルダの場合はNULLになります。

CREATE TABLE FILE_TBL (
  FILE_ID NUMBER(10) NOT NULL,
  FOLDER_ID NUMBER(10) NOT NULL,
  FILE_NAME VARCHAR2(255) NOT NULL,
  FILE_SIZE NUMBER(10),
  PRIMARY KEY (FILE_ID),
  FOREIGN KEY (FOLDER_ID) REFERENCES FOLDER_TBL(FOLDER_ID) ON DELETE CASCADE
);

ファイルはFILE_IDによって識別され、格納先のフォルダに紐づけるためのFOLDER_IDを持っています。FOLDER_IDNULLになることはなく、必ずどこかのフォルダに紐づきます。

-- FOLDER_TBLテーブルにデータを登録するSQL
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (1, NULL, 'Folder 1');
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (2, 1, 'Folder 1-1');
INSERT INTO FOLDER_TBL (FOLDER_ID, parent_id, FOLDER_NAME) VALUES (3, NULL, 'Folder 2');

-- FILE_TBLテーブルにデータを登録するSQL
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (1, 1, 'file1.txt', 100);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (2, 1, 'file2.txt', 200);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (3, 2, 'file3.txt', 150);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (4, 2, 'file4.txt', 300);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (5, 3, 'file5.txt', 50);
INSERT INTO FILE_TBL (FILE_ID, FOLDER_ID, FILE_NAME, FILE_SIZE) VALUES (6, 3, 'file6.txt', 250);

サンプルデータを用意しました。

FOLDER_TBLは以下のようなデータとなっています。最上位階層は二つ、うち一つにはサブフォルダがあります。

FOLDER_IDPARENT_IDFOLDER_NAME
1NULLFolder 1
21Folder 1-1
3NULLFolder 2

FILE_TBLは以下のようなデータになっています。各フォルダに2ファイルずつファイルが格納されています。

FILE_IDFOLDER_IDFILE_NAMEFILE_SIZE
11file1.txt100
21file2.txt200
32file3.txt150
42file4.txt300
53file5.txt50
63file6.txt250

下層から上層に向かって階層問い合わせをする

あるファイルを指定し、その格納フォルダから最上位フォルダまでの階層を取得してみます。ここではサブフォルダにあるfile3.txtが格納されているフォルダから上位フォルダへ向かって階層問い合わせを行います。

WITH FOLDER_HIERARCY (
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_PATH
) AS (
  -- 最下層のフォルダ
  SELECT
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_NAME FOLDER_PATH
  FROM
    FOLDER_TBL FD1
  WHERE
    -- フォルダに格納されているファイルのファイル名が'file3.txt'
    EXISTS(SELECT 1 FROM FILE_TBL FL1 WHERE FD1.FOLDER_ID = FL1.FOLDER_ID AND FL1.FILE_NAME = 'file3.txt')
  UNION ALL
  SELECT
    FD2.FOLDER_ID,
    FD2.PARENT_ID,
    FD2.FOLDER_NAME,
    FD2.FOLDER_NAME || ' / ' || BASE.FOLDER_PATH
  FROM
    FOLDER_HIERARCY BASE -- このテーブルに対して
    INNER JOIN FOLDER_TBL FD2 -- このテーブルを結合していく
    ON
      BASE.PARENT_ID = FD2.FOLDER_ID -- 下位フォルダのPARENT_IDに一致するFOLDER_IDのフォルダは1つ上位のフォルダとなる
)
SELECT
  FOLDER_PATH || ' / file3.txt'
FROM
  FOLDER_HIERARCY
WHERE
  PARENT_ID IS NULL -- PARENT_IDがNULLのレコードに絞り込むと最上位フォルダまでのパスが得られる
;

ポイントは、

  • 階層問い合わせをするときは項目を明示する(しないとエラーになる)
  • 基準となるレコードに対して上位のレコードを結合していく

です。下層から上層に向かって階層問い合わせする場合は、基準となるテーブル(BASE)の親IDと一つ上位となるテーブル(FD2)のIDを結合する点に気をつけましょう。

上層から下層に向かって階層問い合わせをする

同じことを逆からやってみましょう。

WITH FOLDER_HIERARCY (
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_PATH
) AS (
  -- 最上層のフォルダ
  SELECT
    FOLDER_ID,
    PARENT_ID,
    FOLDER_NAME,
    FOLDER_NAME FOLDER_PATH
  FROM
    FOLDER_TBL FD1
  WHERE
    FD1.PARENT_ID IS NULL
  UNION ALL
  SELECT
    FD2.FOLDER_ID,
    FD2.PARENT_ID,
    FD2.FOLDER_NAME,
    BASE.FOLDER_PATH || ' / ' || FD2.FOLDER_NAME
  FROM
    FOLDER_HIERARCY BASE -- このテーブルに対して
    INNER JOIN FOLDER_TBL FD2 -- このテーブルを結合していく
    ON
      BASE.FOLDER_ID = FD2.PARENT_ID -- 上位フォルダのFOLDER_IDに一致するPARENT_IDのフォルダは1つ下位のフォルダとなる
)
SELECT
  FOLDER_PATH || ' / ' || FILE_NAME
FROM
  FOLDER_HIERARCY T1
  INNER JOIN FILE_TBL T2
  ON
    T1.FOLDER_ID = T2.FOLDER_ID
WHERE
  FILE_NAME = 'file3.txt'
;

上層から下層に向かって階層問い合わせする場合は、基準となるテーブル(BASE)のIDと一つ下位となるテーブル(FD2)の親IDを結合する点に気をつけましょう。

いずれの検索結果もFolder 1 / Folder 1-1 / file3.txtとなります。

[Spring Boot]IntelliJ IDEAでLombokを使っていてエラーになる場合の対処方法

以下の記事に解決方法が載っていました。

https://stackoverflow.com/questions/72583645/compile-error-with-lombok-in-intellij-only-when-running-build

遭遇した事象

Spring Initializrからダウンロードした時点でLombokは依存関係に追加されています。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

IntelliJ IDEAでLombokを使用可能にするためには、設定のビルド、実行、デプロイ>コンパイラー>アノテーションプロセッサーで、「アノテーション処理を有効にする」にチェックをつけ、「プロジェクトクラスパスからプロセッサーを取得する」にチェックがついている状態にします。

ここまでがよく知られているLombokの導入方法になります。

lombok.Dataアノテーションを定義したクラスを作成し、

package com.t0k0sh1.tutorial.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private String email;
    private String password;
}

自動生成されているであろうgetterを使ってみます。

package com.t0k0sh1.tutorial.controller;

import com.t0k0sh1.tutorial.entity.User;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {
    private final List<User> users = new ArrayList<>();

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
    }

}

すると、getterが見つからずにコンパイルエラーとなります。

/Users/t0k0sh1/Workspace/tutorial/src/main/java/com/t0k0sh1/tutorial/controller/UserController.java:15: エラー: シンボルを見つけられません
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
                                           ^
  シンボル:   メソッド getId()
  場所: タイプUserの変数 a

対処方法

build.gradleを以下のように書き換えることで問題を解消することができます。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	implementation 'org.modelmapper:modelmapper:3.1.1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// 以下の1行を追加する
	annotationProcessor 'org.projectlombok:lombok:1.18.22'
}

tasks.named('test') {
	useJUnitPlatform()
}

追加でannotationProcessor 'org.projectlombok:lombok:1.18.22'を追記します。(バージョン部分は元の記述に合わせてください)

Gradleプロジェクトの再ロード(右端のGradleタブを開いて更新ボタンをクリックする)し、プロジェクトのビルドを行なってください。

すると、先ほどまでエラーとなっていましたが、今度はビルドに成功します。

[Spring Boot]プロジェクトを作成する(Spring Initializr)

Spring Bootのプロジェクトの作成方法はいくつかありますが、本記事ではSpring Initializrを使って作成します。

Projectを選択する

執筆時点では「Gradle – Groovy」「Gradle – Kotolin」「Maven」から選択できます。使いたいものを選択してください。

ここでは「Gradle – Groovy」を選択しています。

Languageを選択する

執筆時点では「Java」「Kotolin」「Groovy」から選択できます。使いたいものを選択してください。

ここでは「Java」を選択しています。

Spring Bootのバージョンを選択する

Spring Bootのバージョンを選択してください。

ここでは「3.0.4」を選択しています。

Project Metadataを入力する

プロジェクトのメタデータを入力します。

内容はプロジェクトに合わせて設定してください。

Dependenciesを選択する

Dependenciesの選択は任意ですが、Webアプリケーションのプロジェクトを作成するので、Spring Webだけ追加しておきます。すでに使用するものが決まっている場合はそれも追加しておいてください。

プロジェクトをダウンロードする

GENERATEボタンをクリックしてプロジェクトをダウンロードしてください。

プロジェクトを展開・配置してIDEで開く

ではダウンロードしたプロジェクトを展開・配置して、IDEで開いてみましょう。ここではIntelliJ IDEA Ultimateを使用しています。

まずはダウンロードしたZIPファイルを展開し任意のフォルダに配置します。

$ unzip ~/Downloads/tutorial.zip -d ~/Workspace/

次にIDEでプロジェクトを開きます。

動作確認のためにアプリケーションを実行してみましょう。

アプリケーションが起動したらhttp://localhost:8080/にアクセスします。

Spring BootではMainメソッド持ったクラス以外何もないため、表示するページがなく、エラー画面が表示されますが、この画面が表示しているのであれば問題ありません。

[Oracle]ユーザー(スキーマ)を作成する

ローカル環境で使用する開発用・テスト用のユーザーを作成します。

今回使用した環境は以下のとおりです。

  • Oracle Database XE 21c

本記事で実行しているSQLは、すべてSYSTEMユーザーで実行しています。

テーブルスペースを確認する

まずは使用するテーブルスペースを確認します。

-- テーブルスペースを一覧表示する
SELECT TABLESPACE_NAME, INITIAL_EXTENT FROM DBA_TABLESPACES;
TABLESPACE_NAMEINITIAL_EXTENT
SYSTEM65536
SYSAUX65536
UNDOTBS165536
TEMP1048576
USERS65536

ここではDEFAULT TABLESPACEUSERSを使用し、TEMPORARY TABLESPACETEMPを使用します。

ユーザーを作成する

どのテーブルスペースを使用するかが決まったら、ユーザーを作成します。

-- ユーザーを作成する
CREATE USER EXAMPLE IDENTIFIED BY PASSWORD DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP PROFILE DEFAULT;

ここではEXAMPLEというユーザー(スキーマ)をPASSWORDというパスワードで作成しています。

このままではログインすらできないユーザーですので、作成したユーザーに権限を与えます。ここではローカルでの開発用・テスト用のユーザーですので、作成しそうなオブジェクトを作成する権限は一通り与えておきます。

-- ユーザーに権限を与える
GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE TRIGGER, CREATE SYNONYM, UNLIMITED TABLESPACE TO EXAMPLE;

作成したユーザー名とパスワードでログインすれば、ユーザー名と同じスキーマが使用可能になっています。

パスワードの有効期限を確認・設定する(オプション)

この手順は必須ではありませんが、念のためパスワードの有効期限を確認しておきます。プロジェクトや職場のポリシーにもよりますが、開発用・テスト用のユーザーですので、パスワードの有効期限切れにはならない方が運用が楽だと思います。

今回使用したプロファイルはDEFAULTですので、DEFAULTプロファイルの有効期限を確認します。

-- DEFAULTプロファイルの有効期限を確認する
SELECT PROFILE, RESOURCE_NAME, RESOURCE_TYPE, LIMIT FROM DBA_PROFILES WHERE RESOURCE_NAME = 'PASSWORD_LIFE_TIME' AND PROFILE = 'DEFAULT';
PROFILERESOURCE_NAMERESOURCE_TYPELIMIT
DEFAULTPASSWORD_LIFE_TIMEPASSWORDUNLIMITED

今回はOracle XEを使用していますが、特に設定変更していない状態でUNLIMITEDになっています。

ここがUNLIMITED以外になっていて、無期限に変更したい場合は以下のSQLを実行します。

-- DEFAULTプロファイルの有効期限を無期限に変更する
ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;

SQL実行後に再度確認して、UNLIMITEDになっていることを確認してください。

Apple Silicon MacのDockerでOracle Databaseを動かす

執筆時点(2023/03/01)での暫定的な対応となる点にご注意ください。将来的にはOracle DatabaseがARMに対応する可能性があります。
Oracle DatabaseのARM対応については、以下の動画の26:40 – 27:22をご覧ください。

Oracle Database: What’s new, what’s next

Apple Silicon MacでOracle Databaseを動かす選択肢はいくつかありますが、よく知られている方法は私の環境ではうまく生きませんでした。

ここでは私の環境で成功した方法を共有します。

この方法は、

で紹介されている方法になります。

手順を実行するためには、

  • Homebrew
  • Docker

が必要となります。

Colimaをインストールする

まずはColimaをインストールします。Dockerの--platform linux/x86_64ではうまくいきませんでしたので、ご注意ください。

$ brew install colima

Homebrewでインストールしますので、インストールしていない場合は先にインストールしてください。

次にColimaを起動します。起動するとDockerのコンテキストが変更されます。

$ colima start --arch x86_64 --memory 4

ここでは参考にした記事と同じく4(GB)のメモリを割り当てていますが、必要に応じて増やしてください。

$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                                     KUBERNETES ENDPOINT   ORCHESTRATOR
colima *            moby                colima                                    unix:///Users/user1/.colima/default/docker.sock
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                                               swarm
desktop-linux       moby                                                          unix:///Users/user1/.docker/run/docker.sock

現在のコンテキストがcolimaになっていることを確認してください。変更されていない場合は、

$ docker context use colima

で変更してください。

Oracle Databaseを起動する

Oracle DatabaseをDockerで起動します。

$ docker run -d -p 1521:1521 -e ORACLE_PASSWORD=<パスワード> -v oracle-volume:/opt/oracle/oradata gvenzl/oracle-xe

<パスワード>部分は任意のパスワードを設定してください。このパスワードはSYSユーザーとSYSTEMユーザーのパスワードになります。

例えば、パスワードをpasswordにした場合は以下のようになります。

$ docker run -d -p 1521:1521 -e ORACLE_PASSWORD=password -v oracle-volume:/opt/oracle/oradata gvenzl/oracle-xe

すぐに制御が返ってきますので、少し経ったら接続してみましょう。ローカルにSQL*Plusはインストールされていないと思いますので、何かしらのデータベース接続ツールを使用してください。

私はDataGripを使って接続しています。ユーザーはsystem、パスワードは先ほど起動時に設定したパスワードを入力してください。

まとめ

恐らくこれが現状では最も簡単にApple Silicon MacでOracle Databaseを動作させる方法だと思います。将来的にはVirtualBoxなどでもっと簡単にOracle Databaseを動作させることができるようになる可能性がありますので、暫定的な対応とお考えください。

Nx-WorkspaceでAngular+NestJSのプロジェクトを構築する

Nx Workspaceを使ってAngular+NestJSのプロジェクトを作成します。

Nx Workspaceとは?

Nx Workspaceとは、Angular CLIを使用して開発する際に、複数のアプリケーションやライブラリを一元管理するツールです。Nx WorkspaceはAngular CLIだけでなく、ReactやNestJSなども管理することができます。

今回はフロントエンドとしてAngularを使用し、バックエンドとしてNestJSを使用してみます。

Angular CLIをインストールする

まずはAngular CLIをインストールします。

Node.jsは執筆時点のLTS版を使用しています。

$ node -v
v18.14.0
$ npm -v
9.4.2

通常の手順どおりにAngular CLIをインストールします。すでにインストール済みの場合はこの手順はスキップしてください。

$ npm install -g @angular/cli
$ ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 15.1.5
Node: 18.14.0
Package Manager: npm 9.4.2
OS: darwin arm64

Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.1501.5 (cli-only)
@angular-devkit/core         15.1.5 (cli-only)
@angular-devkit/schematics   15.1.5 (cli-only)
@schematics/angular          15.1.5 (cli-only)

次に@nwrl/schematicsをインストールします。@nwrl/schematicsは新しいアプリケーションやライブラリを生成するためのツールです。

$ npm install -g @nrwl/schematics

警告がかなり表示されるかもしれませんが、気にせずインストールしてください。

プロジェクトを作成する

create-nx-workspaceを使ってプロジェクトを作成します。後からアプリケーションを追加するので、--preset=emptyを指定して空のプロジェクトを作成します。

執筆時点では1回だけ質問されますが、ここではNoで回答します。

$ npx create-nx-workspace@latest demo-app --preset=empty
$ cd demo-app

以降の操作は作成したプロジェクト内で行います。

フロントエンドの作成

Angularをインストールし、フロントエンドアプリケーションを作成します。

まずは、@nrwl/angularパッケージをインストールします。

$ npm install -D @nrwl/angular

次にAngularのアプリケーションを作成します。nx generate @nrwl/angular:appコマンドを使うことでAngularのアプリケーションを作成できます。

$ npx nx generate @nrwl/angular:app frontend --routing

インストールが完了したら、サーバーを起動して動作確認してみましょう。

$ npx nx serve frontend

Angularの標準的な画面とは異なりますが、http://localhost:4200/にアクセスし、下記のような画面が表示されればサーバーが問題なく起動しています。

バックエンドの作成

次にバックエンドを作成します。基本的な流れは先ほどと同じです。

まずは@nrwl/nestパッケージをインストールします。

$ npm install -D @nrwl/nest

nx generate @nrwl/nest:appコマンドでバックエンドアプリケーションを作成します。

$ npx nx generate @nrwl/nest:app backend --frontendProject frontend

>  NX  Generating @nrwl/nest:application

UPDATE package.json
CREATE apps/backend/src/app/.gitkeep
CREATE apps/backend/src/assets/.gitkeep
CREATE apps/backend/src/main.ts
CREATE apps/backend/tsconfig.app.json
CREATE apps/backend/tsconfig.json
CREATE apps/backend/webpack.config.js
CREATE apps/backend/project.json
CREATE apps/backend/.eslintrc.json
CREATE apps/backend/jest.config.ts
CREATE apps/backend/tsconfig.spec.json
CREATE apps/backend-e2e/project.json
CREATE apps/backend-e2e/jest.config.ts
CREATE apps/backend-e2e/src/backend/backend.spec.ts
CREATE apps/backend-e2e/src/support/global-setup.ts
CREATE apps/backend-e2e/src/support/global-teardown.ts
CREATE apps/backend-e2e/src/support/test-setup.ts
CREATE apps/backend-e2e/tsconfig.json
CREATE apps/backend-e2e/tsconfig.spec.json
CREATE apps/backend-e2e/.eslintrc.json
CREATE apps/frontend/proxy.conf.json
UPDATE apps/frontend/project.json
CREATE apps/backend/src/app/app.controller.spec.ts
CREATE apps/backend/src/app/app.controller.ts
CREATE apps/backend/src/app/app.module.ts
CREATE apps/backend/src/app/app.service.spec.ts
CREATE apps/backend/src/app/app.service.ts

added 27 packages, changed 1 package, and audited 1462 packages in 13s

180 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

こちらもサーバーを起動し、動作確認をしてみます。

$ npx nx serve backend

http://localhost:3333/apiにアクセスし、以下のようにメッセージが返ってくれば正常にサーバーが起動しています。

データバージョン管理ツール「DVC」を導入する

データバージョン管理ツールのDVCを導入し、データセットをDVCで管理する方法について説明します。

DVCのインストールと初期化

DVCをインストールして初期化を行います。

DVCはpipコマンドまたはcondaコマンドでインストールします。

$ pip install dvc
または
$ conda install -c conda-forge dvc

次にDVCの初期化を行いますが、Gitの初期化を行なっていない場合は先にgit initを実行してください。

プロジェクトルートで以下のコマンドを実行します。

$ dvc init
Initialized DVC repository.

You can now commit the changes to git.

+---------------------------------------------------------------------+
|                                                                     |
|        DVC has enabled anonymous aggregate usage analytics.         |
|     Read the analytics documentation (and how to opt-out) here:     |
|             <https://dvc.org/doc/user-guide/analytics>              |
|                                                                     |
+---------------------------------------------------------------------+

What's next?
------------
- Check out the documentation: <https://dvc.org/doc>
- Get help and share ideas: <https://dvc.org/chat>
- Star us on GitHub: <https://github.com/iterative/dvc>

コマンドを実行すると、.dvcと.dvcignoreが作成されます。

一旦変更をコミットしましょう。

データセットの管理先を設定する(リモートストレージ)

機械学習やディープラーニングで使用するデータセットはサイズが非常に大きく、Gitリポジトリで管理するのに適していません。

ここではAmazon S3にデータセットを保存する手順で説明します。S3が使用できなくて、NASやファイルサーバーがある場合はそちらを使っていただいても構いませんし、大容量ディスクがPCに搭載されていればそちらも選択しても構いません。

$ aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

AmazonS3FullAccessポリシーをもったユーザーをAWS CLIに設定します。

またアップロードするS3バケットを用意してください(アクセス範囲には十分ご注意ください)。

$ dvc remote add -d main s3://datascience-dvc-remote-storage
Setting 'main' as a default remote.

リモートストレージを設定すると、.dvc/configに設定が書き込まれます。

[core]
    remote = main
['remote "main"']
    url = s3://datascience-dvc-remote-storage

リモートストレージを追加したら変更をコミットしておきましょう。

データセットを追加する

設定できたのでデータセットを追加してみます。ここではKaggleのTitanicのデータセットを登録します。

$ mkdir -p datasets/titanic
$ cd datasets/titanic
$ kaggle competitions download -c titanic
$ unzip titanic.zip
$ rm titanic.zip
$ ls
gender_submission.csv   test.csv                train.csv

この時点では、追加したファイルは未追跡のファイルとして認識されています。

次に、DVCで管理するように設定します。

$ dvc add datasets/titanic
100% Adding...|█████████████████████████████████████████████████|1/1 [00:00, 43.52file/s]
                                                                                                                                                                                  
To track the changes with git, run:

        git add datasets/titanic.dvc datasets/.gitignore

To enable auto staging, run:

        dvc config core.autostage true

新たにdatasets/titanic.dvcdatasets/.gitignoreが作成されました。

datasets/titanic.dvctitanicディレクトリのMD5ハッシュ等の情報を保持しており、

outs:
- md5: bf1a6a19059092be578a3807608e236c.dir
  size: 93081
  nfiles: 3
  path: titanic

datasets/.gitignoreはDVCで管理するように指定したtitanicディレクトリが設定されています。

/titanic

ここまでの変更をコミットしておきましょう。

リモートストレージに保存する

今の状態は追加したデータセットをGitリポジトリの管理下から除外し、管理情報だけをGitリポジトリで管理するようにしただけです。実際のデータセットはローカルにしか存在しません。

これをさきほど設定したS3バケットに保存しましょう。

dvc pushコマンドで保存できますが、これまでの手順通りに進めた場合、以下のようにエラーが発生します。

$ dvc push
ERROR: unexpected error - s3 is supported, but requires 'dvc-s3' to be installed: No module named 'dvc_s3'

Having any troubles? Hit us up at https://dvc.org/support, we are always happy to help!

DVCではS3をサポートしていますが、追加のモジュールが必要となるようです。では、モジュールを追加します。

$ pip install dvc-s3
または
$ conda install -c conda-forge dvc-s3

DVCをインストールしたときと同じ方法でインストールしてください。インストールができたら、再度dvc pushコマンドを実行します。

$ dvc push
4 files pushed

コンソールに出力された内容をみると、4ファイル保存したようですが、datasets/titanicディレクトリには3ファイルしかないはずです。リモートストレージに保存されている内容を確認してみましょう。

$ aws s3 ls s3://datascience-dvc-remote-storage
                           PRE 02/
                           PRE 61/
                           PRE b5/
                           PRE bf/
$ aws s3 ls s3://datascience-dvc-remote-storage/02/
2023-01-07 22:25:47      28629 9c9cd22461f6dbe8d9ab01def965c6
$ aws s3 ls s3://datascience-dvc-remote-storage/61/
2023-01-07 22:25:47      61194 fdd54abdbf6a85b778e937122e1194
$ aws s3 ls s3://datascience-dvc-remote-storage/b5/
2023-01-07 22:25:47       3258 6943b6ee3d9af0913ee5dd83b3d729
$ aws s3 ls s3://datascience-dvc-remote-storage/bf/
2023-01-07 22:25:47        218 1a6a19059092be578a3807608e236c.dir

最後のファイルだけ.dirとなっており、上3ファイルが各CSVファイルで、一番最後がtitanicディレクトリになります。

リモートストレージからデータセットを取得する

前述のとおり、データセット自体はGitリポジトリで管理されていません。そのため、新たにリポジトリをcloneした場合、データセットが存在状態となります。ですので、リモートストレージからデータセットをベット取得してくる必要があります。

ここでは、titanicディレクトリを削除してからデータセットを取得してみます。

$ rm -rf datasets/titanic
$ dvc pull
A       datasets/titanic/                                                                                                                                                         
1 file added
$ ls datasets/titanic
gender_submission.csv   test.csv                train.csv

データセットがdvc pullコマンドを実行することで取得できました。

また、引数でdvcファイルを指定することで、明示的に特定のファイル/ディレクトリだけを取得することができます。

$ dvc pull datasets/titanic.dvc

必要なデータセットだけ取得できるため、実務ではこちらの方がよく使うことになると思います。ただし、DVCの管理下に追加する単位が取得できる最小単位となるため、データセット単位で管理下に加えるのか、ファイル単位で管理下に加えるのかはプロジェクトによって検討する必要があります。

最後に

DVCでは、単にデータセットを管理するツールではなく、他にも機能があります。本記事では、その中のデータセットの管理について説明しました。

TypeScriptでJestを使ったテスト環境を構築する

TypeScriptでJestを使ったテスト環境を構築します。

パッケージのインストール

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

$ npm install -D jest ts-jest @types/jest ts-node

ts-nodeはTypeScriptの環境構築の過程ですでインストールしている場合があります。インストールしていない場合はts-nodeもインストールしてください。

設定ファイルを作成する

設定ファイルを作成します。本手順でインストールしたバージョンは29.3.1ですが、バージョンによって挙動や質問の内容が変更になっている場合があるのでご注意ください。

$ npx jest --init

The following questions will help Jest to create a suitable configuration for your project

✔ Would you like to use Jest when running "test" script in "package.json"? … yes
✔ Would you like to use Typescript for the configuration file? … yes
✔ Choose the test environment that will be used for testing › node
✔ Do you want Jest to add coverage reports? … yes
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls, instances, contexts and results before every test? … yes

✏️  Modified /Users/t0k0sh1/Workspace/honeycomb/packages/cli/package.json

📝  Configuration file created at /Users/t0k0sh1/Workspace/honeycomb/packages/cli/jest.config.ts

ここでは、TypeScriptで設定ファイルを書く設定(2つ目の質問)にしています。ただ、実際に設定ファイルを出力してみると、JavaScriptで書かれた設定ファイルjest.config.jsも出力されました。これが正しい挙動かは不明ですが、複数の設定ファイルがあるとjestコマンドが実行できないため、以下のいずれかの対応を行なってください。

  • TypeScriptで設定ファイルを定義したい場合はjest.config.jsjest.config.js.mapを削除する
  • JavaScriptで設定ファイルを定義したい場合は2つ目の質問でnoを選択する

設定ファイルの出力後、jest.config.tsに1箇所修正を加えます。

export default {
  // A map from regular expressions to paths to transformers
- // transform: undefined,
+ transform: {
+   "^.+\\.(ts|tsx)$": "ts-jest",
+ },
}

この設定はテストコードを書いた時にimport/exportが使用できるようにするための設定です。

テストコードを書く

実際にテストコードを書いてみましょう。

まずはテスト対象のコードです。

export function greet(name: string): string {
  return `Hello ${name}`;
}

次にテストコードです。前述で設定を行なっているため、テストコード内でimportが使用可能です。

import { greet } from "./greeting";

test("Greeting is", () => {
  expect(greet("John")).toBe("Hello John");
});

テストを実行すると以下のようになります。

$ npm run test

> example@.1.0 test
> jest

 PASS  src/greeting.spec.ts
 PASS  dist/greeting.spec.js
-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |                   
 greeting.ts |     100 |      100 |     100 |     100 |                   
-------------|---------|----------|---------|---------|-------------------

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.866 s, estimated 1 s
Ran all test suites.

Node.jsでTypeScriptの環境を構築する

Node.jsでTypeScriptの環境を構築します。

構築する環境について

本記事では、以下のバージョンで構築しています。

$ node -v
v18.12.1
$ npm -v
9.1.2

以降の手順で、 Node.jsのバージョンが必要となりますので、必ず確認してください。

TypeScriptをインストールする

TypeScriptをインストールしていきます。

$ npm install -D typescript @types/node@18

使用しているバージョンがv18.12.1ですので、@typesパッケージも同じバージョンのものを指定します。

インストールできたらインストールされているバージョンを確認します。

$ npx tsc --version
Version 4.9.4

上記のようにtscコマンドを使ってバージョンが表示できればインストールは完了しています。

tsconfig.jsonを作成する

次にTypeScriptの設定ファイルのtsconfig.jsonを作成します。まずは、以下のコマンドを実行してファイルを作成します。

$ npx tsc --init

このままでも動作しますが、一部設定を変更しておきます。

{
  "compilerOptions": {
-   // "outDir": "./",               /* Specify an output folder for all emitted files. */
+   "outDir": "./dist",              /* Specify an output folder for all emitted files. */
- }
+ },
+ "include": [
+   "src/**/*"
+ ]
}

ここでは、TypeScriptファイルの格納先(include)とコンパイルして作成されるJavaScriptファイルの出力先(outDir)を追加・変更しています。

環境の構築時期やTypeScriptのバージョン、プロジェクトの都合によっては他の設定も変更することがありますが、ここでは省略します。

動作確認

実際に動作確認をしてみましょう。まずはsrc/index.tsを作成し、ファイルの中身は以下のようにします。

function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("John"));

以下のコマンドを実行し、src/index.tsをコンパイルします。

$ npx tsc

ファイルはdist/index.jsにされ、設定の違いで多少異なるかもしれませんが、以下のように出力されます。

"use strict";
function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet("John"));

実行にはJavaScriptファイルを使用します。

$ node dist/index.js
Hello, John!

エラーなくメッセージが表示されれば正しくコンパイルされています。

package.jsonにスクリプトを追加する

package.jsonにスクリプトを追加してnpmコマンドでビルドできるようにします。

{
  "scripts": {
-   "test": "echo \"Error: no test specified\" && exit 1"
+   "build": "tsc",
+   "watch": "tsc --watch"
  },
}

ここでは2つのスクリプトを追加しています。ひとつはビルドを行うスクリプト(build)で、もう一つは変更を監視して変更があればビルドを行うスクリプトです(watch)。

追加したコマンドは以下のように実行します。

$ npm run build
$ npm run watch

M1/M2 Macで機械学習の環境を構築する

TensorflowとPytorchがApple Siliconに対応したため、Pythonの機械学習・ディープラーニング環境を構築します。

仮想環境の作成

仮想環境を作成します。

使用するPythonのバージョンですが、このあとインストールするTensorflowが執筆時点では3.7から3.10をサポートしており、Pytorchが3.7以上をサポートしているため、3.10を使用することにします。

conda create -n datascience python=3.10
conda activate datascience

ここでは仮想環境の名前をdatascienceにしていますが、任意の名前で構いません。

機械学習関連

機械学習関連のパッケージをインストールします。

機械学習でよく使われるパッケージをインストールします。

conda install numpy scipy pandas scikit-learn

代表的な可視化パッケージをインストールします。

conda install matplotlib seaborn plotly

EDAパッケージをインストールします。

conda install -c conda-forge pandas-profiling autoviz sweetviz

ローコード機械学習をインストールします。

conda install -c conda-forge pycaret

Jupyter notebookをインストールします。

conda install notebook

スクレイピング関連

スクレイピングでよく使用されるパッケージをインストールします。

conda install -c conda-forge requests scrapy beautifulsoup4

画像処理関連

画像処理でよく使用されるパッケージをインストールします。

conda install -c conda-forge pillow opencv

ディープラーニング関連

TensorflowとPytorchをインストールします。

Tensorflow

Xcode Command Line Toolをインストールしていない場合は以下のコマンドでインストールします。 

xcode-select --install

Tensorflow dependenciesをインストールします。

conda install -c apple tensorflow-deps

Tensorflowをインストールします。

pip install tensorflow-macos

Metalプラグインをインストールします。

pip install tensorflow-metal

インストール後にGPUが使用可能か確認しましょう。

import sys
import tensorflow.keras
import pandas as pd
import sklearn as sk
import scipy as sp
import tensorflow as tf
import platform

gpu = len(tf.config.list_physical_devices('GPU'))>0
print("GPU is", "available" if gpu else "not available")

以下のように表示されればGPUが使用可能です。

GPU is available

Pytorch

PytorchはTensorflowに比べるとインストールは簡単です。以下のコマンドでインストールします。

pip install --pre torch torchvision --extra-index-url https://download.pytorch.org/whl/nightly/cpu

インストール後にGPUが使用可能か確認しましょう。

import torch

gpu = torch.backends.mps.is_available()
print("GPU is", "available" if gpu else "not available")

以下のように表示されればGPUが使用可能です。

GPU is available

今後の課題

現時点で形態素解析パッケージであるMecabのインストールに失敗しています。手順もいくつか示されていますが、実際に試したところいずれもうまくいっていません(インストール自体に失敗する、インストール後に動作しない)。

こちらは引き続きインストール方法を調査し、判明次第本記事を更新したいと思います。

[Ansible]SSHがUNREACHABLEになるときの対処方法

Ansibleを使っていて、SSH接続がUNREACHABLEになる場合の対処方法について説明します。

事象について

ここで説明する事象は以下のようなものです。

$ ansible all -m ping
server1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: Warning: Permanently added 'server1,192.168.1.1' (ECDSA) to the list of known hosts.\r\nansible@server1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
    "unreachable": true
}

疎通確認のためにpingを実行してみると、対象サーバーに到達できない(UNREACHABLE)というエラーが返ってきます。

ちなみに、sshコマンドを使って接続してみると、エラーなく接続できますし、

$ ssh ansible@server1
ansible@server1's password: 
[ansible@server1 ~]$

pingコマンドも通ります。

$ ping server1
PING server1 (192.168.1.1) 56(84) bytes of data.
64 bytes from server1 (192.168.1.1): icmp_seq=1 ttl=64 time=0.141 ms
64 bytes from server1 (192.168.1.1): icmp_seq=2 ttl=64 time=0.617 ms
64 bytes from server1 (192.168.1.1): icmp_seq=3 ttl=64 time=0.429 ms
^C
--- server1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2039ms
rtt min/avg/max/mdev = 0.141/0.395/0.617/0.195 ms

原因と対処方法

Ansibleでは、pingを実行する前にSSH接続しているのですが、このとき特にオプション指定をしなければ、公開鍵認証になります。

前述でSSH接続にはパスワード認証を行っていたので、これが接続できない原因となります。対処方法は単純で、パスワード認証でSSH接続するようにします。

具体的な手順

まずは、sshpassパッケージをインストールします。Ubuntuの場合、以下のようにしてパッケージをインストールしてください。

$ apt-get install sshpass
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  sshpass
0 upgraded, 1 newly installed, 0 to remove and 122 not upgraded.
Need to get 10.3 kB of archives.
After this operation, 29.7 kB of additional disk space will be used.
Get:1 http://ports.ubuntu.com/ubuntu-ports focal/universe arm64 sshpass arm64 1.06-1 [10.3 kB]
Fetched 10.3 kB in 1s (12.5 kB/s) 
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package sshpass.
(Reading database ... 25842 files and directories currently installed.)
Preparing to unpack .../sshpass_1.06-1_arm64.deb ...
Unpacking sshpass (1.06-1) ...
Setting up sshpass (1.06-1) ...

もし、以下のようなエラーが発生する場合は、先にapt-get updateを実行してください。

Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package sshpass

sshpassがインストールできたら、再度コマンドを実行しますが、実行の際に--ask-passオプションを指定します。

$ ansible --ask-pass all -m ping
SSH password: 
server1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

pingが成功したことを確認できました。

毎回オプションを指定するのが面倒な場合は、ansible.cfgask_pass = Trueを書いておくことで省略できます。

[defaults]
ask_pass = True
モバイルバージョンを終了