【教材紹介】前処理大全

前処理はデータ分析で重要な事項です。なぜなら、有用な特徴情報を上手く抽出できていなければどんなモデルであれ無用の長物です。ただ、前処理は体系だった学問としては存在せず、実務の中で学んでいくことが多いため、気づかずミスを犯していることが多いのです。

名称


前処理大全

データ分析のためのSQL/R/Python実践テクニック

著者

本橋智光

概要

本書は系統立てて前処理を学んでいきます。そこで、SQL/R/PythonのAwesomeなコードとNot Awesomeなコードの実例をもとに、Awesomeとは何かを考えます。

この本が理解でき、実践できるようになれば、データサイエンス初心者とは言えないでしょう。
*ただし、画像や音声、動画などの前処理は行いません。もちろん、発展的な時系列解析などの前処理も扱いません。

必要知識

入門程度のプログラミングの知識(関数等が分かる程度)
入門程度の統計学の知識(統計学3級程度)

得られるスキル

前処理, 抽出, 集約, 結合, 分割, 生成, 展開, Python, R, SQL
数値型の前処理, カテゴリ型の前処理, 日時型の前処理, 文字型の前処理, 位置情報型の前処理

その他の教材紹介ページはこちら → Skill Stacks

【教材紹介】10年戦えるデータ分析入門

何度でも言いたいことですが、データ分析をするための技術はSQLが一番良いです。
PythonやRよりも制限が少なく、ビッグデータを扱えるのはSQLです。
分析に触れる第一歩としてこの本はいかがでしょうか?

名称


10年戦えるデータ分析入門

著者

青木峰郎

概要

データ分析にはPythonやRという論調が多いですが、データサイエンス実務ではSQLの出番の方が多いのではないでしょうか。
テラバイト級データはSQLでは対応可能ですが、Pythonだけで分析をすることは困難です。

SQLでは技術的には様々な分析ができます。
本書では多種多様な分析の方法論を、実際に架空データを使いながら分析することでスキルの定着を図ります。

必要知識

パソコンの操作

得られるスキル

SQL, PostgreSQL, select, where, order_by, group_by, join, window関数, DWH, バッチ処理

その他の教材紹介ページはこちら → Skill Stacks

[Meisyo] Viewは不要!SQL処理高速化方法

今回の問題は、対戦成績をデータ分析用に選手対選手で個別に保存していたことから始まります。

選手個人の総合成績をView処理で出力しまいした。
ただ・・・インデックス等がないのでめっちゃ計算時間が掛かるー!

ユーザーを少なく見積もって100ユーザーでも、1ユーザー最低10選手=1000選手持っています。
最大組み合わせ数は、1000選手×999選手=ほぼ100万・・・多すぎる(^^;

そういったものをSQLでSUMすればどうなるか。
そして、試合ごとに全選手を更新する。

もちろん重かった。
悲しかったです。

というわけで、対応策は下記の通りにしました。
・Viewではなく、総合成績の中間テーブルを作るようにしました。
・更新対象を狭め、自分と対戦相手の選手のみとしました。

改良前

個別成績テーブルスキーマ:
選手ID, 対戦選手ID, 試合数…

↓ 試合ごとにSQLでSUM(アクティブなユーザーの全選手)

総合成績Viewスキーマ:
選手ID, 試合数…

↓

各種データ呼び出し

改良後

個別成績テーブルスキーマ:
選手ID, 対戦選手ID, 試合数…

↓ 試合ごとに自分と対戦相手の選手の成績だけ、SQLでSUM

総合成績テーブルスキーマ:
選手ID, 試合数…

↓

各種データ呼び出し

結果

試合の待ち時間5~10秒(またはエラー)が、2秒以内に収まるようになりました。
SQLをテキトーに設定していたのですが、やっぱりちゃんと考えて書かないとダメですね。
いい教訓になりました。

(VPSでつくる) セキュリティ設定

連載第十四回目です。

セキュリティ設定について何もわからん状態なので色々と漁っていました。
PythonやFlaskなどの個別のモノに対してのセキュリティ設定の書籍は中々多くないので、Linuxなどの基礎的なセキュリティ設定について学習する方がいいと思われます。

その学習には下記のようにシステムの構成図を書いて、どこがセキュリティを設定しきれていないか確認しながら進むといいでしょう。

抜け道で言うと、プロに聞くのが最短ですね。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
その他については上記ページをお読みください。

すべての基本

バージョンアップしよう。
基本的に、脆弱性はバージョンアップすると減る。

CentOS7のセキュリティ設定

参考記事:接続時のセキュリティの設定(CentOS 7)(さくらVPS設定マニュアル)

1. さくらVPSのデフォルトではファイアウォールが起動されていないことがある。

ファイアウォールの起動コマンドは下記の通り。

systemctl start firewalld

さくらVPS独特のパケットフィルタという厄介な設定もあるので注意。

2. ファイアウォール中にサービスの利用を許可するコマンドを使わないと、自作のサービスが動かない

ファイアウォールの許可を追加するコマンドは以下の通り。

firewall-cmd --permanent --zone=public --add-service=許可するサービス名

許可を追加しても、ファイアウォールをリロードしないと設定は反映されません。

参考記事:サーバ作成直後に設定しておくべき初期セキュリティ設定(さくらのVPS)

本来の参考記事はさくらVPSでDjangoアプリを公開する手順の備忘録(note:夕月悠里さん)ですが、公式がベースになっているので記載。

  1. ROOTユーザーは使わない
  2. SSH接続用の公開鍵暗号方式を使う
  3. HTTPS(SSL)使おうぜ
  4. HTTPSに必要な独自ドメインは無料で取れるサービスもあるし、HTTPSでないと安全でないサイトと表示されるのでやりましょう。
    → 記事参照:Let’s EncryptでVPS上のWebサイトをSSL(HTTPS)接続にしよう

あくまで基本やけど、個人開発で全部をカバーできている所は多くない気がする。

Nginxのセキュリティ設定

表示

1. レスポンスにNginxのバージョンを表示させない。

Nginx設定ファイル(/etc/nginx/conf.d/default.conf)に下記を追加。

http {
    server_tokens off;
}

バージョン情報=脆弱性情報ですからねえ・・・。

FTPのセキュリティ設定

ファイル送信にはSFTPかSCPを使おう。

Pythonのセキュリティ設定

Python2はやめてPython3を使おう。
かつ、メジャーバージョンでもできるだけ新しいバージョンを使おう。

そのためには、Nginx Unitさんは早く、公式でPython3.6.8以外の対応をできるようにすべき。Dockerでどうにかなるらしい(未確認)が面倒。
GunicornやuWSGIは、バージョンを気にしなくてOKなのが楽。ただしWindows上では試験的に使えないのは面倒。WSL2等で特別に設定すればできるが面倒。

Flaskのセキュリティ設定

参考記事:Flaskのセキュリティ対策(simon’s noteさん)

特に注意すべきは下記3点。1番が一番抜けやすい気がする。

  1. 自分で変数を埋め込んだHTMLを用意する場合にはescapeを使う
  2. コンテンツセキュリティポリシー(CSP)
  3. X-Content-Type-Options

Flaskバージョンアップ

Flask2が出たのでそちらを使った方がいいかもしれない。

Flaskのセキュリティ設定に関するバグ(?)

1. データベースとの通信に使うapp.SECRET_KEYの乱数生成文字列の設定に要注意

CentOS7+Python3だとデフォルトはascii。
asciiで対応できない文字列(例:日本語等)を入れると、エラーは表示されないが、ログイン時のクッキー操作等に異常が出る。

記事13で行った、文字コードのデフォルト設定をUTF-8にすると解決する。

phpMyAdminのセキュリティ設定

最低限、アクセス可能IPを制限する必要がある。
ディレクトリ名をphpMyAdminにしない。

おわりに

色々調べてみましたが、セキュリティ設定は難しい。。
不足している観点があればURLだけでもコメントいただけると幸いです。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
次回以降は上記記事からご覧ください。

(VPSでつくる) Socket通信を使ったモダンなチャットアプリをFlaskで作ろう

連載第十三回目です。

今回は、チャットアプリを作っていきたいと思います。
チャットの想定としては、会員制、socket通信で滑らかにチャットできる、データベースにログを残すにしましょう。

ただのチャットとは言え、使いやすさも考えて構築を行います。
そのため、HTML、JavaScript(jQuery含む)、CSSなどでの記載も行います。
ただし、理解が簡単になるよう、コメントを多めに書いています。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
その他については上記ページをお読みください。

作業予定

まず、テストアプリプログラムファイルのダウンロードが必要です。
Github(https://github.com/R2002/flask_chat)
テストアプリフォルダ(app/flask_chat)は、VPS上の/usr/share/nginx/app/flask_chatにアップロードします。

作業予定としては以下を想定しています。
・Gunicorn+eventletの設定
・CentOS7日本語対応
・チャットアプリのsystemd上でのサービス化
・Nginxの設定
・公開(デプロイ)

socket通信には、Gunicornやeventletなどのサービスが必要になります。Webサーバー兼リバースプロキシのNginxだけでは対応できません。
そのため、いろいろと設定が必要になります。

続いて、どのような思考でそれぞれのプログラムが書かれているのか確認します。

構成


このアプリは、チャットアプリです。
滑らかなチャットを行うためにsocket通信を使います。

socket通信の担い手はWSGIサーバーであるGunicorn、その一機能のeventletです。
前回のアプリまで使っていたNginx Unitは、eventletに関する資料がなかったので使いませんでした。

ファイル構成


ファイルごとに関係を示します。

前回と比べconfig.pyなどが増えましたが、設計思考は同じです。
viewsとmodelsをディレクトリ化したのは、機能の増加を何度か行い記載量が増えた際に、コーディングの状況が一人開発ですら分かり辛くなるからです。例えば、app.pyにviewsの記載をべた書きしていると、複数人での開発は困難になります。

ディレクトリを分けることによって、機能の一時停止判断や、バージョンアップ・ダウンの切り替え作業が非常に簡単になります。

なお、Flaskアプリの起動にはrun.pyを利用しています。
すべてのファイルは、run.pyから見た絶対的または相対的な記述が必要です。
例えば、importやfromで指定するディレクトリはどこか、パスはどう記載しなければ動かないか等です。

Gunicorn+eventletの設定

flask-socketioの公式を参考にします。

まずは、仮想環境(/usr/share/nginx/venv/flask_chat)を作り、その環境上でgunicornなどをインストールします。
gunicorn以外は、テストアプリに含まれるrequirements.txtをご利用ください。

pip install gunicorn==20.1.0

※バージョンは変更して問題ありません。

最後に、アプリフォルダ(/usr/share/nginx/app/flask_chat)に移動して、gunicornでflask_chatを起動します。

gunicorn -k eventlet -w 1 run:application

ちなみに、Windows上ではgunicornは動きませんのでご注意ください。

CentOS7、Windowsどちらでも動く下記コマンドは優秀ですね!

python run.py

何度かテスト立ち上げをしていると、ポートはもう使われているというエラーが出ます。
その場合はポートを開けるために、プロセスを停止させる必要があります。

プロセスの検索は下記コマンドを利用します。

ps ax

pythonのプロセスを探して、PIDを指定します。

kill -9 [該当のPID]

CentOS7日本語対応

CentOS7はそのままの設定だと言語が英語になっています。
チャットでは日本語を使うのですが、チャット内容に日本語を含む場合エラーが発生します。
エラーが起こらないよう、言語設定を変更していきます。

まずは、環境変数(LANG)を確認します。
LANG=C(英語)となっていると思います。

env

設定ファイルも確認しておきましょう。

less  /etc/locale.conf

次に、使える言語を確認します。
ja_JP.utf8があるか確認してください。

locale -a

最後に、下記コマンドで設定を書き換えます。

localectl set-locale LANG=ja_JP.utf8

これで日本語入力ができるようになりました。

失敗例

下記ファイル群を書き換えても意味がありません。
~/.bash_profile
~/.bashrc
~/.profile

下記コマンドもシェル上だけで有効で、systemd上のサービスにすると日本語にエラーが出ます。

export LANG=ja_JP.utf8

チャットアプリのsystemd上でのサービス化

それでは、デプロイのために色々と設定を行っていきましょう。

Gunicorn

gunicorn.conf.pyを下記の通りに作成します。

workers = 1
worker_class = 'eventlet'
bind = 'unix:/run/flask_chat.socket'

上記のファイルは、以下のようにコマンド上で利用します。

gunicorn run:application --config gunicorn.conf.py

worker_classは、eventletかgeventを指定します。
bindは、どこのポートまたはソケットで立ち上げるかを設定します。今回はソケットを使います。
workerについては、CPU1個あたりworkerを2~4個用意するのがいいようです。1個だとたまにチャットのメッセージが消えます。

CPU数を出力するには下記コードをpythonインタプリンタ上で使用してください。

import multiprocessing
print(multiprocessing.cpu_count())

serviceおよびsocketの準備

flask_chat.serviceとflask_chat.socketを用意します。
※settings_envフォルダに入っているのでアップロードしていただいてもかまいません。

flask_chat.serviceの内容は下記の通りです。

[Unit]
Description=flask_chat daemon
Requires=flask_chat.socket
After=network.target

[Service]
User=root
Group=root
RuntimeDirectory=gunicorn
WorkingDirectory=/usr/share/nginx/app/flask_chat/
ExecStart=/usr/share/nginx/venv/flask_chat/bin/gunicorn --config /usr/share/nginx/app/flask_chat/gunicorn.conf.py run:application
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
KillMode=mixed
PrivateTmp=true
Restart=always

[Install]
WantedBy=multi-user.target

注意すべき点は、ExecStartを仮想環境上のgunicornに指定することです。
venvを直接指定しなくても、勝手に良い感じに読み込みをしてくれます。

flask_chat.socketの内容は下記の通りです。

[Unit]
Description=flask_chat socket

[Socket]
ListenStream=/run/flask_chat.socket

[Install]
WantedBy=sockets.target

2つのファイルをアップロードして、下記コマンドで指定位置にコピーします。

cp flask_chat.service /etc/systemd/system/flask_chat.service
cp flask_chat.socket /etc/systemd/system/flask_chat.socket

サービス起動

下記コマンドでサービスを自動開始設定・開始します。

systemctl enable flask_chat.socket
systemctl start flask_chat.socket

※socketを起動すると、自動的にflask_chat.serviceが起動するのでコマンドは不要です。

最後に、動作確認を行います。

curl --unix-socket /run/flask_chat.socket localhost

エラーが出ずに、HTMLが返ってくれば設定は完了です。

Nginxの設定

Nginx Unit設定ファイル(/usr/share/nginx/conf.json)を下記の通りに変更します。
使わないので空にしましょう。

{
    "listeners": {
    },

    "applications": {
    }
}

設定を反映します。

sudo curl -X PUT --data-binary @/usr/share/nginx/conf.json --unix-socket /var/run/unit/control.sock http://localhost/config
systemctl restart unit

Nginx設定ファイル(/etc/nginx/conf.d/default.conf)のlocationを変更します。

location / {
    proxy_set_header Host $host;
    proxy_pass http://unix:/run/flask_chat.socket;
}

再起動させましょう。

systemctl restart nginx

公開(デプロイ)

上記の手続きが問題なければアプリが起動すると思います。
おつかれさまでした。

あとは、前回と同様にデータベース(flask_chat)を用意しておく必要がありますが、今回は説明を割愛します。

問題がなければ、下記のように別ブラウザおよび別IPからで滑らかなチャットを行えます。

socket通信で問題となる、ブラウザ上では通信確立が上手くできたかわからないについても、通信が確立されるまではチャットを表示しないようにしています。

おわりに

今回は、socket通信を使った滑らかなチャットが可能なアプリをデプロイまで持っていきました。

次回以降は、これまで全く考えられていなかったセキュリティ設定等を行っていきます。
アプリをデプロイしても、頻繁に落ちたり、個人情報が取られたり・・・可能性は十分あり得ます。
そのため、HTTPS(SSL)接続などの設定も行います。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
次回以降は上記記事からご覧ください。

(VPSでつくる) PythonでMariaDB(MySQL)へ接続できるアプリを触ってみよう

連載第十一回目です。

Python3.6.8+FlaskでMariaDBに接続・データベースを編集するアプリを動作させる設定を行い、動作確認します。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
その他については上記ページをお読みください。

作業予定

作業予定としては、下記のアプリを作っていきます。
まず動かしてみて、その後でFlaskの各関数が何をしているか解説していきます。

ディレクトリ構成

/usr/share/nginx 
 ┗ app アプリ用ディレクトリ
 | ┗ flask_db テストアプリ用ディレクトリ
 |   ┗ app.py アプリ本体
 |   ┗ config.py 設定ファイル
 |   ┗ database.py データベース用関数ファイル
 |   ┗ models.py データベースモデル設定ファイル
 |   ┗ run.py 起動ファイル
 ┗ venv 仮想環境用ディレクトリ
   ┗ flask_db テストアプリ用環境

機能

・データベース参照(/)
・1行追加(/insert)
・1行変更(/update)
・1行削除(/delete)

作業内容

まず、テストアプリプログラムファイルのダウンロードが必要です。
Github(https://github.com/R2002/flask_database_test)
アップロードして使用しますので、適宜内容を見てご利用ください。

まずは、ディレクトリを移動します。

cd /usr/share/nginx

次に、アプリ用ディレクトリを作ります。

mkdir app/flask_db

仮想環境を作り、その仮想環境に入ります。

python3 -m venv venv/flask_db
source venv/flask_db/bin/activate

pipをアップグレードしておきます。

pip install --upgrade pip

ダウンロードしたファイルの中にあるrequirements.txtアップロードし、テストアプリに必要なPythonモジュールを下記コマンドでインストールします。仮想環境上でインストールするようにしてください。

pip install -r requirements.txt

ファイルをアップロードできない場合は、パーミッション変更を下記の通り行ってみてください。

chmod 0777 /usr/share/nginx

requirements.txtファイルの内容は以下の通りです。

flask==1.1.2
flask-sqlalchemy==2.5.0
SQLalchemy==1.3.23
pymysql==1.0.2
Flask-Migrate==2.7.0

簡易アプリ(main.py)作成し、複数アプリを同時並行でNginx Unitでデプロイできることを確認します。
まずは、アプリ用ディレクトリに移動します。

cd app/flask_db
[python]

続いて、<strong>main.py</strong>を作成します。
[python]
from flask import Flask

application = Flask(__name__)

@application.route('/')
def index():
    print('test')
    return "Hello Flask"

if __name__ == '__main__':
    application.run()

/usr/share/nginx/conf.jsonを編集し、flask_testに加え新たなアプリ(flask_db)を起動できるようにします。

{
    "listeners": {
        "*:8081": {
            "application": "flask_test"
        },
        "*:8082": {
            "application": "flask_db"
        },
    },

    "applications": {
        "flask_test": {
            "type": "python 3.6.8",
            "path": "/usr/share/nginx/app/flask_test/",
            "home": "/usr/share/nginx/venv/flask_test/",
            "module": "main"
        },
        "flask_db": {
            "type": "python 3.6.8",
            "path": "/usr/share/nginx/app/flask_db/",
            "home": "/usr/share/nginx/venv/flask_db/",
            "module": "main"
        },
    }
}

Nginx Unitの設定を更新します。

sudo curl -X PUT --data-binary @/usr/share/nginx/conf.json --unix-socket /var/run/unit/control.sock http://localhost/config

Nginxの設定ファイル(/etc/nginx/conf.d/default.conf)に下記を追記します。

    location /flask_db/ {
        proxy_pass http://127.0.0.1:8082/;
    }

再起動をしてみましょう。

systemctl restart nginx
systemctl restart unit

下記のURLでアクセスできるようになります。
http://XXX.XXX.XXX.XXX/sample/
→ 前回のテストアプリ
http://XXX.XXX.XXX.XXX/flask_db/
→ 同じようなテスト用アプリ

今後は、flask_dbを改良していきます。

それでは、ファイルアップロードのためのパーミッション変更を行います。

cd /usr/share/nginx
chmod 0777 app/flask_db

flask_db内にファイルをアップロードします。

conf.jsonを編集し、applicationsのflask_db、moduleをmainからrunにしてください。

{
    "listeners": {
       # 略
    },

    "applications": {
        "flask_test": {
           # 略
        },
        "flask_db": {
            # run
            "module": "run" # ここだけ変更
        },
    }
}

Nginx Unitの設定を更新します。

sudo curl -X PUT --data-binary @/usr/share/nginx/conf.json --unix-socket /var/run/unit/control.sock http://localhost/config

phpMyAdminで、新しいデータベースflask_dbを作っておきます。照会順序はutf8_general_ciで問題ありません。
http://XXX.XXX.XXX.XXX/phpMyAdmin/

最後に、アプリ設定ファイル(app/flask_db/config.py)を編集し、phpMyAdminで設定したユーザーのパスワードを記載します。

再起動をしておきましょう。

systemctl restart nginx
systemctl restart unit

ここまでで設定は終わりです!
おつかれさまでした。

あとは実際にアプリを触っていきます。

まず、データベースをcreateします。
そのためにflask_db仮想環境に入っておき、ディレクトリを/usr/share/nginx/app/flask_dbに移動します。

続いて、pythonコマンドでPythonインタプリンタを起動し、下記コマンドを入力します。

from app import create_app
from database import db
db.create_all(app=create_app())

データベースの内容(testテーブル)ができたことを確認してください!

続いて、データベースのMigrate(差分保存)初期設定を行っておきます。
Pythonインタプリンタから出て、下記コマンドを実行します。

set FLASK_APP=run.py
flask db init

※Migrateについての詳細は、Flask-migrateをご覧ください。

それではWebサイトにアクセスします。
まずはトップページ(/flask_db/)を確認します。

まだデータが入っていないため、テーブルは空です。
※このトップページはphpMyAdmin(flask_dbのtestテーブル内)と同じ表示になります。

では、データを入れていきましょう。
データ追加は、/insert/<入れたい値>にアクセスしてください。
テストでは100, 200, 300, 400, 5000の5つの数値をそれぞれ入力しました。

トップページ(/flask_db/)を確認すると、下記のようにデータベースに行が追加されました。

続いて、特定のidの値(data)を更新していきます。
更新は、/update/<id>/<変更したい値>にアクセスしてください。
テストでは/update/3/0, /update/4/100にアクセスしました。

トップページ(/flask_db/)を確認すると、下記のようにデータベースのid3および4の値が変更されました。

最後に、特定のidを削除します。
削除は、/delete/<id>にアクセスしてください。
テストでは/delete/3にアクセスしました。

トップページ(/flask_db/)を確認すると、下記のようにデータベースのid3が削除されました。

これにてテストアプリのテスト起動は終了しました。

締めに、アプリの解説を行います。
アプリの構造は以下の通りです。
HTTP接続からNginx、NginxからNginx Unit、Nginx Unit上でvenvを使ってアプリを起動し、MariaDBにアクセスする方法を取っています。

ファイルの内容については以下の通りです。
app.pyがアプリ本体です。conf.jsonに指定したrun.pyは起動用のアプリであって本体ではありません。

このような構成にすることで、ファイルごとに役割分担を明確にしています。かつ、Nginx Unit以外のAPサーバー(例:uWSGI)などでも動かせるような構成となっています。

おわりに

PythonでMariaDB(MySQL)へ接続できるアプリを触ってみようということで、今回いろいろと設定を行いました。
このアプリをWebサーバー上で起動できれば、その他の応用アプリも起動が可能になります。

ただ、当アプリはapp.pyにルーティングや表示のコードが記載されているため、このまま記載を続けるとapp.pyのコード量が肥大化します。
次回はファイルごとに役割分担を明確にしたFlaskアプリを作成するために、Flaskの機能であるBlueprintをこのアプリに導入していきます。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
次は、12. Python(Flask)でMariaDB(MySQL)へ接続できるアプリをもっと読みやすく改良してみようです。

(VPSでつくる) phpMyAdminインストール

連載第九回目です。

GUI上でMariaDB(MySQL)を触れるようにするために、phpMyAdminをインストールしましょう。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
その他については上記ページをお読みください。

1. phpMyAdminインストール

前回の記事で指定したremiレポジトリを使っていきます。

yum -y --enablerepo=remi-php71 install phpMyAdmin

※もしPHPのバージョンを変更された場合は、php71を編集してください。

環境設定保存用のデータベースを作成しておきます。

vi /usr/share/phpMyAdmin/sql/create_tables.sql

コードを下記から、

-- (activate this statement if necessary)
-- GRANT SELECT, INSERT, DELETE, UPDATE, ALTER ON `phpmyadmin`.* TO
--    'pma'@localhost;

下記の通り変更します。

-- (activate this statement if necessary)
GRANT SELECT, INSERT, DELETE, UPDATE, ALTER ON `phpmyadmin`.* TO
  'root'@localhost;

※今回はrootユーザーをすでに作成しているので、このようにしています。

MariaDBにログインして、クエリを実行します。

mysql -u root -p
source /usr/share/phpMyAdmin/sql/create_tables.sql

権限関係のメモリリフレッシュを行います。

FLUSH PRIVILEGES;

Control+ZコマンドでMariaDBから抜けることができます。

2. Nginxの環境設定

PHP-FPMとNginxを接続するために、TCPとUnix Socketの2通りの方法があります。(参考ページ)
今回はUnix Socketを利用します。

まずは、PHP-FPMファイルの設定(/etc/php-fpm.d/www.conf)を変更します。

listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0666

変更イメージは以下の通りです。

PHPの設定ファイル(/etc/php.ini)を変更します。

session.save_path = "/var/lib/php/session"

このセッションにNginxがアクセスできるようにしておきます。

sudo chown -R root.nginx /var/lib/php/session

最後に、Nginxの設定を変更します。
環境設定ファイル(/etc/nginx/conf.d/default.conf)を2点編集します。
(1)server{}内のlocation ~ \.php$内を変更します。

    location ~ \.php$ {
                fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
    }

(2)server{}内にlocation /phpMyAdminを追記します。

    location /phpMyAdmin {
        alias /usr/share/phpMyAdmin/;
        try_files $uri $uri/ /index.php;

        location ~ ^/phpMyAdmin/(.+\.php)$ {
            alias /usr/share/phpMyAdmin;
            fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
            fastcgi_param SCRIPT_FILENAME /usr/share/phpMyAdmin/$1;
            include fastcgi_params;
            fastcgi_intercept_errors on;
        }
    }

location ~ ^/phpMyAdmin/(.+\.php)$内にallow XXX.XXX.XXX.XXX(IP); deny all;をこの中に追記すると、指定IP以外のアクセスを拒否することができます。

変更イメージとしては以下の通りです。

sudo systemctl restart php-fpm
sudo systemctl reload nginx

3. 動作確認

下記のように表示されればOKです。

rootユーザーでのログインが可能かどうか確認ください。

設定を変更した場合は、php-fpm.sockを削除して、php-fpmなどを再起動します。

rm -f /var/run/php-fpm/php-fpm.sock

おわりに

phpMyAdminがインストールできました。
これにてデータベースの導入は完了です!

次は、FTPでファイルを送受信できるようにします。
これまではviエディタで色々と記載していましたが、この方法だと限界があるので変更します。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
次は、10. WinSCPでFTP接続です。

(VPSでつくる) MariaDB(MySQL)インストール

連載第八回目です。

今回はMariaDBをインストールします。
MariaDBはMySQLの完全互換で、高速性を持っているので採用します。
SQLクエリも全く同じものが使えます。
詳しくはMariaDB vs MySQL: 徹底比較をご覧ください。

MariaDBとphpMyAdminにより、Flaskアプリのデータベースを設定し、GUI上で触れるようになります。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
その他については上記ページをお読みください。

1. MariaDBインストール

PHPと同じように、MariaDBもyumのリポジトリではバージョンが古いので、MariaDBの公式サイトからインストールします。

vi /etc/yum.repos.d/MariaDB.repo

ファイルの内容は下記の通りにします。

[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

インストールします。

yum -y install MariaDB-server MariaDB-client

バージョンを確認します。

mysql -V

下記のように表示されればOKです。

いつものごとく起動と自動起動設定を行っておきます。

systemctl start mariadb
systemctl enable mariadb

2. MariaDB(MySQL)初期設定

下記コマンドで始めます。

mysql_secure_installation

質問が色々と出てきますので、1つずつ丁寧に答えていってください。
1. パスワードを入力してね。(初回はそのままエンターを入力してください)
2. 認証にunix_socketを使うか? >> n
Yにすると別途設定が必要です。
3. MySQLのrootパスワードを変更するか? >> Y
パスワードの入力が必要です。
4. anynomous(無名)ユーザーを除外するか? >> Y
5. リモート接続を不許可にするか? >> Y
6. testデータベースは不要か? >> Y
7. 特権テーブルをリロードするか? >> Y

最後に、環境設定を行います。

vi /etc/my.cnf.d/server.cnf

[mysqld]に下記を追記してください。

character-set-server = utf8

再起動を行います。

systemctl restart mariadb

おわりに

今回はMariaDBをインストールしました。
これでphpMyAdminを導入する準備はできました!

次回はphpMyAdminをインストールします。

この記事は、VPSで作るPythonでのWebアプリ運用環境の連載記事です。
次は、9. phpMyAdminインストールです。

VPSでつくるPythonでのWebアプリ運用環境(連載記事)

今風のWebアプリをリリースするにはレンタルサーバーでは物足りない!

例えば、PythonやRubyに対応しているレンタルサーバーはあまりありません。加えて、データサイエンスや機械学習に強いプログラム言語Pythonは頻繁にアップデートされますので、レンタルサーバーではアップデートに対応して即時に変更反映・・・なんてものはできません。

例えば、私はPython3.Xが入っていないから、変更後のライブラリ〇〇が使えない…ことを何度も体験してきましたorz

そこで、VPS(Virtual Private Server)を使って開発環境を丸ごと借り、自由度の高いサーバーを構築していきます。今回はさくらVPS(https://vps.sakura.ad.jp/)を使っていきます。

VPSにした理由は、クラウドサービスに比べVPSは割安だからです。

よく例に挙がるAWSやGCPは利用料金が高いです。本格的に収益の上がるサイトができた場合は移行するといいかもしれません。

AWSは、1ノードを20日程度放置していただけでも月3万の請求が来ていました。AWSの勉強をしてただけなのに怖いですね。GCPも、仕事で使う分には気にならない程度で、機械学習・データ分析用途でアクセスが多く(1000程度)ても月10万PV行かないくらいで3万以上です。個人開発では高いですね。

なんと、VPSは最小構成で月約650円です。レンタルサーバーに比べて自由度が高いので、ある程度満足できる金額ではないでしょうか。

さくらにした理由は、レンタルサーバーを借りていた際に満足できる経験をしたからです。
①2年以上運用していたが、サーバー停止など大きなトラブルがなかった。
②さくらサーバーからエックスサーバーへの移行時(利用停止時)にさくらのカスタマーサポートに相談したが、その時でさえサポートが充実していた。

まずは、構築環境を下記の通りに示します。
構築する環境はあくまでアプリに合わせて選ぶものなので、あなたのアプリに合わない場合は適宜変更してください。

続いて、具体的に何をするのか目次をリストアップします。
大きな目的である直接の運用コストを減らすためにVPSを採用していますが、VPSはセキュリティ等を含め全てを自分で管理する必要があります。
そのため、手間を減らす必要があるので、自動起動・アップデートの自動化なども忘れずに導入しておきます。

最後に、VPSの総括を行います。
良い点、悪い点、引っかかりやすいポイントなどなど。

補足として、この記事内に作者のバックグラウンド(Webサーバー関連の理解度など)も記載しておきます。ご参考に。

(1)構築環境

基本構成

OS:CentOS7

CentOS8までの中でサポートが一番長い。事例が多い。

Webサーバ:Nginx

Apacheより動的なアクセス処理が得意(らしいので)、今回のWebアプリに最適。

APサーバ:Nginx Unit

より新しく(2018年リリース)、Nginxと併用されやすいGunicornより導入が簡単だった。

その他構成

サーバーへのアクセス:SSH

サーバー管理・設定変更の安全のため。

FTP

サーバーへのファイル転送のため。

SSL接続(HTTPS)のみ

HTTPはNginxの設定で遮断。SSLは独自ドメインの取得が必須。

アプリ

ソーシャルトラッキングアプリ(目標管理+ルームチャット)です。
目標管理に加え、オンラインもくもく会や勉強会ができる感じのアプリです。
→ ある程度小さいデータのやり取りを何度も行う想定。
→ 将来的にレコメンドなどの機械学習システムを導入したい。

Python3.6.8(Flask)+pip3+MySQL

構築がより簡単なため。
Python3.6.8か2.7.5以外の場合はNginx Unitは非推奨。(理由は後述)

Flask+BluePrint

機能拡張を行うために、BluePrintを導入。

MySQL(MariaDB)+PHP+phpMyAdmin

GUIでデータベースを操作したい。(CUIでの操作は面倒…)

(2)利用環境

OS:Windows10

サーバーへのアクセス:TeraTerm

FTP:WinSCP

データベース管理:phpMyAdmin

仮想環境:pyenv+pip

以上が構築環境、利用環境です。
続いて、目次に移ります。

(3)目次

全体構成としては、下記の図の通りです。それを一つずつ構築していきます。

基礎設定

1. VPSへのログイン
2. CentOS7のインストール

PythonでHello World

3. NginxでHello World
4. Pythonのバージョンを2.7.5から3.6.8にする
5. Python仮想環境venvの導入
6. Nginx Unit+venv+FlaskでHello World

データベース+PHPインストール

7. Nginx+PHPでHello World
8. MariaDB(MySQL)インストール
9. phpMyAdminインストール

FTP

10. WinSCPでFTP接続

Pythonアプリ導入

11. PythonでMariaDB(MySQL)へ接続できるアプリを触ってみよう
12. Python(Flask)でMariaDB(MySQL)へ接続できるアプリをもっと読みやすく改良してみよう
13. Socket通信を使ったモダンなチャットアプリをFlaskで作ろう

その他

14. セキュリティ設定
15. Let’s EncryptでVPS上のWebサイトをSSL(HTTPS)接続にしよう

総括

総括・変更履歴

(4)作者のバックグラウンド

具体的には、このWebサーバー構築前はAPサーバーって何やねんってレベルでした…
インフラわからん!
趣味でレンタルサーバーを借りていて、Webサイト(ApacheでCakePHP2(PHP5+MySQL))を10年近く運営しています。
ただ、今回のWebサーバ(Nginx+Nginx Unit(or Gunicorn)でFlask(Python3+MariaDB))を構築するのに1か月近くかかった程度のレベル感です。orz

執筆時の状況は、仕事はエンジニアではなく、コンサルティング(データ・AI系)なので前知識ほぼありません。基本情報技術者レベルの知識はありました。

さいごに

最後までお読みいただき誠にありがとうございました。

試行錯誤の末にどうにかVPSでWebサーバーを動かせるようになりました…。
これまでは、個人ではレンタルサーバー任せ、仕事ではクラウド任せだったので、APサーバーなどを全く意識せずに使っていて今回かなり引っかかりましたorz
このページのように情報を統合して書いておくと、他の人も時間を無駄にしなくて済むといいな~と思って書いています。

分かり辛い点や解釈がおかしい部分があればコメントいただけると幸いです。
よろしくお願いいたします。

ゲームデータで学ぶSQL(初級編)

近年のデータサイエンスブームをきっかけに「SQLを学びたい」と思った方へ。

そう思ったところで、壁になってくるのは実データの入手です。
私は職業としてデータサイエンス関連の業務に従事していますが、キレイに整形されたデータはKaggle等にあるものの、実際のログデータとしてはほど遠いという認識があります。

確かに、「実運用されているサービスのログを入手できないか」と言われれば不可能だと思います。
ただ、私は趣味でゲーム作成をしていることもあり、ログデータを作ってきたので入手が可能でした。
学ぶなら実データの方がいいなと思ったので、今回公開します。

そのため、今回はそのゲームデータを使用してSQLを学びましょう!
演習を3つ用意していますので、取り組んでみてください~(^o^)ノ

データ

名将と呼ばれた者達の実データを使用します。
このデータはユーザーのすべての行動を記録しているactsログ8-9月分(約14万行)です。

meisyo_acts_output_20200930.zip
※ZIPファイルのため解凍が必要です。
※ID等は個人特定が不可能な加工を施しています。

使用データベース

MySQL
※MySQLの最低限のクエリでどうにか集計する方法を学んでほしいため。分析はBigQueryやPostgreSQLなどの方が向いている。
※BigQuery等での読み込みも可能な形式(CSV)のため、そちらでも利用できます。

MySQL環境構築法

XAMPPをインストールし、phpMyAdminを起動してください。
phpMyAdminに実データ(CSV)をインポートしていただくと使用が可能になります。

下記の管理コンソールから、MySQLのAdminをクリックするとphpMyAdminにアクセス可能です。

はじめに

今回行う集計はよくありがちな流れです。

1枚の画像で表しました。

まずはこの図をメモしてください。

集計でミスが起こる場面は下記のどちらかです。
1・1ユーザーごとのログに集約
2・1ユーザーごとのログから図式・文書化

どちらでしょうか?

私の認識では、
2・1ユーザーごとのログから図式・文書化です。

これまでの業務(教育関連)で上手くできない人は、
目的意識がなく、集計ができたら満足してしまうという傾向がありました。
そこさえできていれば問題ないのですが…。そこが難しい。

何のために、何を見たいのかという目的意識をはっきり持ってください。

データベース作成

phpmyAdminを開いて画面左のNewをクリックし、データベースを作成します。

次に、そのデータベースのタブをクリックし、SQLでテーブル構造を設定します。

CREATE TABLE acts (
id INT AUTO_INCREMENT NOT NULL,
mn_id VARCHAR(32),
time VARCHAR(20),
sub VARCHAR(32),
action VARCHAR(1024),
PRIMARY KEY(id)
) ENGINE = INNODB character set utf8mb4;

最後に、テーブル構造を設定したactsにインポートでデータを入れます。
データ量が多いため複数回再送信が必要です。
※id=1を削除しておきましょう。

データ準備を行うことができました。

集計1

目的:日ごとのログインユーザー数を知りたい

まずは、データの構造を理解するところから始めましょう。
設定に使用したSQL構文を確認します。

CREATE TABLE acts (
id INT AUTO_INCREMENT NOT NULL,
mn_id VARCHAR(32),
time VARCHAR(20),
sub VARCHAR(32),
action VARCHAR(1024),
PRIMARY KEY(id)
) ENGINE = INNODB character set utf8mb4;

前提として、各カラムの意味、データの生成要件、用語の定義を知る必要があります。

各カラムの意味は以下の通りです。
id:テーブルログの生成id
mn_id:監督ID(プレイヤーIDと同義)
time:JST(UTC+9)時間での時間表記
sub:機能名
action:各機能での詳細ログ

データ生成要件は以下の通りです。
・何らかの機能を触ると、全ての行動が取得される。
(ログインしなければログは出されない)

用語の定義は以下の通りです。
・ログイン:何らかの機能に触れたことがある(=何らかのログが生成されている)
・日ごと:日本時間での1日ごと

このように、集計の前提を定義することが非常に重要です。
なぜなら、それを定義していないと「思っていたことと違う」分析になってしまいます。
今回は、目的:日ごとのログインユーザー数を知りたいなので、そこから外れないようにしたいですね。

続いて、集計の考え方の手順です。

1・1ユーザーごとのログに集約

まず1からやっていきましょう~。
1ユーザーごと、かつ定義した日ごとのログに集約していきます。

SELECT
  DATE(time) AS date
  ,mn_id
FROM
  `acts`
GROUP BY
  date
  ,mn_id

※DISTINCTでもOKだが、後々のこと(日ごとのプレイ回数を付与等)を考えるとGROUP BYが最適。

2・1ユーザーごとのログから図式・文書化

続いて、日ごとの人数を出力します。

SELECT
  a.date
  ,COUNT(a.mn_id) AS count_uu
FROM
  (
    SELECT
      DATE(time) AS date
      ,mn_id
    FROM
      `acts`
    GROUP BY
      date
      ,mn_id
  ) AS a
GROUP BY
  a.date
ORDER BY
  a.date

※COUNT関数でDISTINCTを使う人は多いです。ただし、今回はすでにa内でGROUP BYしているので不要です。

このようにすると、目的:日ごとのログインユーザー数を知りたいを達成できます。

集計2

目的:日ごとのログインユーザー数とその継続率を知りたい

集計1を終えた方は、継続率って何だろう?決めないとと思っていただけるかと思います。

継続率は、「ログインした当日からN日後の日にログインしているか」という定義にします。
10月10日にログインしていた場合、7日後なら10月17日にログインしているかどうかを判定します。

この指標は、ゲームでユーザーが継続していることはKGI(Key Goal Indicator)(※売上等)の達成に良いことであると仮定しているため、集計を行います。

ここでは、LEFT JOINを使います。

下記のように接続を行います。、
10月10日にログインしたユーザー ← M月J日にログインしたユーザー
プレイヤーIDは同一であるなら結合をOKとする。
SELECTで、N日後を設定する。

INNER JOINだと、
10月10日にログインかつM月J日にログイン、でどちらもログインしていないユーザーは消えます。

LEFT JOINだと、
10月10日にログインしていればよくてM月J日にログインしているかどうかは何も決めないという強くない制約を置きます。
※WITH句を使えないMySQLのバージョンなので、同じ構文を再度書いています。

1・1ユーザーごとのログに集約

まず1からやっていきましょう~。
1ユーザーごと、かつ定義した日ごとのログに集約していきます。

SELECT
  DATE(time) AS date
  ,mn_id
FROM
  `acts`
GROUP BY
  date
  ,mn_id

続いて、1ユーザーごとにN日後にログインしたかどうかのフラグを付けていきます。

SELECT
  a.mn_id
  ,a.date
  ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 7 THEN 1 ELSE 0 END) AS flag_day7 # 差分が7日あるユーザー(Nullの場合の対応をIFNULLで追加)
  ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 14 THEN 1 ELSE 0 END) AS flag_day14 # 差分が14日あるユーザー
  ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 30 THEN 1 ELSE 0 END) AS flag_day30 # 差分が30日あるユーザー
FROM
  (
    SELECT
      DATE(time) AS date
      ,mn_id
    FROM
      `acts`
    GROUP BY
      date
      ,mn_id
  ) AS a
LEFT JOIN
  (
    SELECT
      DATE(time) AS date
      ,mn_id
    FROM
      `acts`
    GROUP BY
      date
      ,mn_id
  ) AS b
ON
  a.mn_id = b.mn_id # プレイヤーIDが同じなら結合
GROUP BY
  a.mn_id
  ,a.date

2・1ユーザーごとのログから図式・文書化

最後に、継続率を出していきましょう。

N日後継続率= 当日ログインしていた人のうち、N日後にもログインしていた人 / 当日ログインしていた人なので、AVGで出せます。
AVGは計算方法が=SUM(case)/COUNT(all)なので、フラグ化した変数を使用すると実施割合を出力できます。

SELECT
  a.date
  ,COUNT(a.mn_id) AS count_uu
  ,AVG(flag_day7) AS per_day7
  ,AVG(flag_day14) AS per_day14
  ,AVG(flag_day30) AS per_day30
FROM
  (
    SELECT
      a.mn_id
      ,a.date
      ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 7 THEN 1 ELSE 0 END) AS flag_day7 # 差分が7日あるユーザー(Nullの場合の対応をIFNULLで追加)
      ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 14 THEN 1 ELSE 0 END) AS flag_day14 # 差分が14日あるユーザー
      ,MAX(CASE WHEN DATEDIFF(IFNULL(b.date, '9999-12-31 23:59:59'), a.date) = 30 THEN 1 ELSE 0 END) AS flag_day30 # 差分が30日あるユーザー
    FROM
      (
        SELECT
          DATE(time) AS date
          ,mn_id
        FROM
          `acts`
        GROUP BY
          date
          ,mn_id
      ) AS a
    LEFT JOIN
      (
        SELECT
          DATE(time) AS date
          ,mn_id
        FROM
          `acts`
        GROUP BY
          date
          ,mn_id
      ) AS b
    ON
      a.mn_id = b.mn_id # プレイヤーIDが同じなら結合
    GROUP BY
      a.mn_id
      ,a.date
  ) AS a
GROUP BY
  a.date
ORDER BY
  a.date
  

このように継続率を出します。
基本的にソーシャルゲームの業界では1・3・7・14・30日後継続率を出すことが多いですが、あくまで一般論であってN日を定義するのはアナリスト次第です。

集計3

目的:2020年9月にログインしたユーザーは何のコンテンツをプレイしているのか知りたい

集計2を終え、何となくですがSQLが分かってきたかと思います。
今回は・・・何のコンテンツをプレイしているかという問題です。

まず用語の定義が必要ですが、ドメイン知識(領域知識、業界知識)がないのでコンテンツとは何かわかりません。

そのため、コンテンツの種類を知るための・・・集計のための集計を行います。
コンテンツの種類が分かったところで、プレイしている人数が分からないと重要度がわかりませんので、併せて集計しておきます。

SELECT
  sub
  ,COUNT(DISTINCT mn_id) AS count_uu # DISTINCT必須
FROM
  `acts`
WHERE
  DATE_FORMAT(DATE(time),'%Y-%m-01') = '2020-09-01' # 9月に限定
GROUP BY
  sub
ORDER BY
  count_uu DESC # 降順

下記表が出力されます。

ここで立ち止まって考えてみましょう。
なぜ何のコンテンツをプレイしているか知りたいのか?を考えてみてください。

私が出せる仮説は、大きくは以下の通りです。
・とりあえず大まかなプレイ傾向を知りたいから、試合や特訓のメインコンテンツが遊ばれているか知りたい
・新規登録ユーザーが想定したプレイ内容(練習試合、特訓)きちんと遊んでくれているのか知りたい
・既存ユーザーが全然プレイしていないコンテンツがないか知りたい

この3点をSQLで集計しようとすると全然違ったモノが必要ですよね?

解決策としては、依頼者に話を聞きましょう。
「何が知りたいのか」「なぜ知りたいのか」をです。
ここをしない人は必ず失敗します。ご注意ください。

今回は講座なので、下記の仮説に従って集計していきます。
とりあえず大まかなプレイ傾向を知りたいから、試合や特訓のメインコンテンツが遊ばれているか知りたい

ドメイン知識を整理します。
試合は3種類あります。
20時間に1回いずれかをプレイ可能です。監督LvUPするともう1度遊べます。
・練習試合:月ごとのリーグ戦(PvP)を行うコンテンツ。
 プレイはsub=game_practice_goで取得可能
・シナリオ:難易度が選べるvsNPCコンテンツ。一度負けると最初からのタワー形式。
 プレイはsub=senario_goで取得可能
・模擬戦:対戦相手が選べるvsNPCコンテンツ。
 プレイはsub=game_test_goで取得可能

特訓は3種類あります。
20時間に1回いずれかをプレイ可能です。監督LvUPするともう1度遊べます。
・挑戦:クリアすることでオリジナル選手を獲得できるコンテンツ
 プレイはsub=practice_chall_fで取得可能
・特訓:特定の選手を強化かつ投打データ取得が行えるコンテンツ
 プレイはsub=practice_training_goで取得可能
・探索:選手強化アイテムを取得できるコンテンツ
 プレイはsub=map_json_userおよびaction=1で取得可能。
 action=0はサーバー接続確認のための確認ログ。

用語の定義としては、以下を決めておきます。
・遊ばれているか
 ログインしている人のうち、遊ばれている割合

ではそれをSQLの考え方を使ってまとめていきましょう。

1・1ユーザーごとのログに集約

GROUP BYを使い、MAXでフラグ化するととても扱いやすいログ形式になります。

SELECT
  DATE(time) AS date
  ,mn_id
  ,MAX(CASE WHEN sub = 'game_practice_go' THEN 1 ELSE 0 END) AS flag_play_gpractice
  ,MAX(CASE WHEN sub = 'senario_go' THEN 1 ELSE 0 END) AS flag_play_gsenario
  ,MAX(CASE WHEN sub = 'game_test_go' THEN 1 ELSE 0 END) AS flag_play_gtest
  ,MAX(CASE WHEN sub = 'practice_chall_f' THEN 1 ELSE 0 END) AS flag_play_pchall
  ,MAX(CASE WHEN sub = 'practice_training_go' THEN 1 ELSE 0 END) AS flag_play_ptraining
  ,MAX(CASE WHEN sub = 'map_json_user' AND action = '1' THEN 1 ELSE 0 END) AS flag_play_pmap
FROM
  `acts`
WHERE
  DATE_FORMAT(DATE(time),'%Y-%m-01') = '2020-09-01' # 9月に限定
GROUP BY
  date
  ,mn_id

2・1ユーザーごとのログから図式・文書化

ここで注意したいのは、「試合・特訓をどちらもプレイしていてほしい」という依頼者が見たいであろう意図を汲むことです。
試合・特訓の種類ごとに役割が異なるので、プレイしているユーザー層は違うことが予想されますが、今回はそもそもプレイしているんだっけ?ということが知りたいのです。

SELECT
  a.date
  ,COUNT(a.mn_id) AS count_uu
  ,AVG(CASE WHEN flag_play_gpractice = 1 OR flag_play_gsenario = 1 OR flag_play_gtest = 1 THEN 1 ELSE 0 END) AS per_play_game # 試合のうちいずれかをプレイ
  ,AVG(CASE WHEN flag_play_pchall = 1 OR flag_play_ptraining = 1 OR flag_play_pmap = 1 THEN 1 ELSE 0 END) AS per_play_practice # 特訓のうちいずれかをプレイ
  ,AVG(flag_play_gpractice) AS per_play_gpractice
  ,AVG(flag_play_gsenario) AS per_play_gsenario
  ,AVG(flag_play_gtest) AS per_play_gtest
  ,AVG(flag_play_pchall) AS per_play_pchall
  ,AVG(flag_play_ptraining) AS per_play_ptraining
  ,AVG(flag_play_pmap) AS per_play_pmap
FROM
  (
    SELECT
      DATE(time) AS date
      ,mn_id
      ,MAX(CASE WHEN sub = 'game_practice_go' THEN 1 ELSE 0 END) AS flag_play_gpractice
      ,MAX(CASE WHEN sub = 'senario_go' THEN 1 ELSE 0 END) AS flag_play_gsenario
      ,MAX(CASE WHEN sub = 'game_test_go' THEN 1 ELSE 0 END) AS flag_play_gtest
      ,MAX(CASE WHEN sub = 'practice_chall_f' THEN 1 ELSE 0 END) AS flag_play_pchall
      ,MAX(CASE WHEN sub = 'practice_training_go' THEN 1 ELSE 0 END) AS flag_play_ptraining
      ,MAX(CASE WHEN sub = 'map_json_user' AND action = '1' THEN 1 ELSE 0 END) AS flag_play_pmap
    FROM
      `acts`
    WHERE
      DATE_FORMAT(DATE(time),'%Y-%m-01') = '2020-09-01' # 9月に限定
    GROUP BY
      date
      ,mn_id
  ) AS a
GROUP BY
  a.date
ORDER BY
  a.date

下記のように出力できました。

データを見ていて気付い方かも居るかもしれませんが、
19日から20日に練習試合からシナリオへユーザーのプレイ傾向が偏りました。
これは、ゲーム内の仕様で練習試合の月次リーグ戦が20日の0時に終了するからです。
それ以降はプレイしてもランキングは変わらないので、シナリオに移行するユーザーも出てくるといった状況が、数値からも見て取れます。

おわりに

今回は行動ログから簡単な集計を行いました。
前提を定義し、なぜそうなるかを考えながら集計を行うという一連の流れを経験できたかと思います。

実際にユーザーが行動した生データですので、いろいろと利用できる方法はあると思います。
例えば、「アイテム課金するユーザーはどのような傾向にあるか。何をどれくらいプレイしていないと課金しないのか。」など、運営目線で言うと実際の売り上げにつながる分析が欲しいですよね。

あとは、「このデータがないと分析できないよ」という気づきもあります。
例えば、月1回の公式戦と呼ばれるトーナメント戦があるのですが、ログは公式戦閲覧しか取っておらず、閲覧ユーザーのチームが勝ったかどうか(または単純に他のチームが気になるから見ているか)はわかりません。
このゲームの中心として公式戦を位置付けているので、「公式戦を勝つために工夫するユーザー像」がプレイを継続するモチベーションではないかと。
それを分析できないのは、公式戦システムの都合もあります。

そのように、SQLを学び、考えるきっかけになれば幸いです。

反響があれば中級編に続くかもしれません。
今回は簡単な行動データのみでしたが、次回はスナップショットや選手データ、アイテム保有など組み合わせた複雑なデータを取り扱えればと思います。

SQLをさらに勉強したい方へのおすすめ図書


SQLをとりあえずガリガリ書けるようになりたいならこちら。技術的、ドメイン知識的な面は弱い。
テスト用のdokoQLの試行回数がもっと増えれば良いなと思います。


まさにオライリー。教科書。基礎はこれでOK。


分析技術寄りの本。入門書の次に買った方が良さそう。
10年戦えるかは置いといて良書。


実務を教えるときに使うならコレ。ドメイン知識寄りの本。
ただ、実務で使わない内容も多いため、こういったものがあるんだなと知るのにも便利。