Tak's Notebook

Kaggle, Machine Learning, Engineering

bleaguer を使って2020-2021シーズン前半のBリーグをデータで見る

はじめに

B.LEAGUEデータ分析用Rパッケージ「bleaguer」を使って今シーズン前半のBリーグを振り返りました。

Rでの分析を前提として便利関数が用意されてるようですが、Python をメインに使ってるので アップロード済みの生データをダウンロードして利用しました。(頻繁に最新のデータが更新されてるようなので助かります)

github.com

詳しい説明は bleaguer の作成者のブログを参考にすると良さそうです。

bleaguebydata.hatenablog.jp

準備

データをロードする関数を用意した。

URL は何度も叩くのはアレなので 一度リクエスト叩いてロードしたものはローカルに保存しておく。

import io
import os

import pandas as pd
import requests


def load(url: str, overwrite: bool = False) -> pd.DataFrame:
    data_prefix = "./data"
    # notebooks 以下での実行時に data_prefix を変更する
    if os.path.basename(os.getcwd()) == "notebooks":
        data_prefix = f".{data_prefix}"
    os.makedirs(data_prefix, exist_ok=True)
    data_path = f"{data_prefix}/{os.path.basename(url)}"
    # ローカルにデータがないときだけgithubからデータをロードする
    if not os.path.exists(data_path) or overwrite:
        print(f"Load from {url}")
        r = requests.get(url)
        df = pd.read_csv(io.BytesIO(r.content), sep=",")
        df.to_csv(data_path, index=False)
    else:
        df = pd.read_csv(data_path)
    return df

生データを集計する関数も用意した。

from typing import Dict, List, Union

import pandas as pd


def aggregate(
    df: pd.DataFrame, key: Union[str, List[str]], agg_dict: Dict[str, List[str]]
) -> pd.DataFrame:
    for v in agg_dict.values():
        assert type(v) == list
    df_agg = df.groupby(key).agg(agg_dict).round(2)
    df_agg.columns = [f"{c[0]}_{c[1]}" for c in df_agg.columns]
    df_agg = df_agg.reset_index()
    return df_agg

可視化には Plotly を使う。

使用バージョンは 4.13.0 で、画像を保存する際は kaleido がインストールされている必要がある。(Plotly 4.9.0 以降では Orca ではなく Kaleido がデフォルトのエクスポート操作に使われる。pip install もできて楽)

from abc import ABCMeta, abstractmethod
from typing import Dict, Optional

import numpy as np
import pandas as pd
import plotly.graph_objs as go
import plotly.offline as py


class BasePlotly(metaclass=ABCMeta):
    colors = ["#3D0553", "#4D798C", "#7DC170", "#F7E642"]

    def _convert_to_str(self, arr: np.array) -> np.array:
        return np.array(arr, dtype=str)

    def _plotly_layout(
        self, title: str = None, xtitle: str = None, ytitle: str = None
    ) -> go.Layout:
        return go.Layout(
            title=title,
            xaxis=dict(title=xtitle, ticklen=5, zeroline=False, gridwidth=2),
            yaxis=dict(title=ytitle, ticklen=5, gridwidth=2),
        )

    def _save(self, fig: go.Figure, path: str):
        fig.write_image(path, width=1200, height=600)

    @abstractmethod
    def show(self):
        pass


class BarPlotly(BasePlotly):
    def show(
        self,
        data: pd.DataFrame,
        xcol: str,
        ycol: str,
        is_horizontal: bool = False,
        title: Optional[str] = None,
        xtitle: Optional[str] = None,
        ytitle: Optional[str] = None,
        save_path: Optional[str] = None,
    ):
        orientation = "v"
        color_col = ycol
        text_col = ycol
        if is_horizontal:
            orientation = "h"
            color_col = xcol
            text_col = xcol
        trace = [
            go.Bar(
                x=self._convert_to_str(data[xcol].values),
                y=data[ycol].values,
                text=data[text_col].values,
                textposition="auto",
                marker=dict(
                    color=data[color_col].values,
                    colorscale="Viridis",
                    showscale=True,
                    reversescale=True,
                ),
                orientation=orientation,
            )
        ]
        layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle)
        fig = go.Figure(data=trace, layout=layout)
        if save_path is not None:
            self._save(fig, save_path)
        return py.iplot(fig, show_link=False)

第9節(11/14・11/15)までの順位表

f:id:takaishikawa42:20201205235554p:plain
第10節(12/2)までの順位表

Bリーグ公式サイトからスクリーンショットで転載。

各種のチームスタッツは基本的に順位に比例するので 最初に順位表を貼っておく。

各地区上位3チームずつと、残りチームの中で勝率上位の2チームがチャンピオンシップトーナメントに出場する。

以下では2020-2021年シーズンの第9節(11/14・11/15)以前のデータを集計したものだが、上記順位表は第10節(12/2)を含む。

グラフ化

以下にグラフだけ表示するが 実験に使った Jupyter notebook は以下に nbviewer を使って確認することが出来る。

https://nbviewer.jupyter.org/github/takaiyuk/hatena-blog/blob/master/bleague-analysis/notebooks/20201203.ipynb

得点・被得点

f:id:takaishikawa42:20201205232848p:plain
チーム平均得点

f:id:takaishikawa42:20201206102453p:plain
チーム平均被得点

f:id:takaishikawa42:20201205232855p:plain
選手平均得点

チーム得点は千葉・富山が抜けていることが分かる。両チームともオフェンスをプッシュできるPG(富樫・宇都)と、得点力の高いインサイドエドワーズ・マブンガ)を擁しているのが寄与していそう。

チーム被得点(ディフェンス力を表す)は宇都宮・琉球が上位。両チームともシーズン中に10連勝して、両地区で首位になっている。ディフェンス力が高いチームが安定して強いことが多いが、今シーズンのBリーグでも高いチームディフェンス力は勝利に重要な要素であるよう。

選手別平均得点を見るとインサイドの外国籍選手が独占している。特に下位チームの選手が目立っているように見える。下位チームほど得点源を外国籍選手に頼ってしまっているのだろうか。

ベンチスコア

f:id:takaishikawa42:20201205233218p:plain
ベンチ出場時間

f:id:takaishikawa42:20201205233222p:plain
ベンチ得点

ベンチ選手の出場時間および得点を見ると、渋谷・宇都宮・東京・川崎といったチームはベンチの出場時間も得点も高いことが分かり、ベンチ層が厚いことが分かる、一方で、千葉や富山は上位チームの割にベンチ選手の出場時間や得点は低く、スターターの出場時間が長くなっている。シーズンを減るごとに 試合の間隔が短いときに 選手に疲労がたまり、怪我やパフォーマンスの低下に繋がらないかが懸念されそう。

リバウンド

f:id:takaishikawa42:20201205233427p:plain
チーム平均オフェンスリバウンド

f:id:takaishikawa42:20201205233430p:plain
チーム平均ディフェンスリバウンド

f:id:takaishikawa42:20201205233433p:plain
チーム平均トータルリバウンド

f:id:takaishikawa42:20201205233436p:plain
選手平均オフェンスリバウンド

f:id:takaishikawa42:20201205233441p:plain
選手平均ディフェンスリバウンド

f:id:takaishikawa42:20201205233445p:plain
選手平均トータルリバウンド

チームのトータルリバウンド数は選手のリバウンド力、特にオフェンスリバウンド力に依存しそう。特に琉球のクーリーの平均オフェンスリバウンド数は突出している。それを追う千葉・秋田の選手のリバウンド数がチームのリバウンド数に影響している。また、宇都宮はリバウンド数が突出している選手が少ないのにもかかわらずチーム平均リウバンド数が3位になってる。これはディフェンスが良いので、打たせて落とさせるディフェンスをしているので チーム全体でリバウンドを共有している結果なのだろうか。

アシスト

f:id:takaishikawa42:20201205233631p:plain
チーム平均アシスト

f:id:takaishikawa42:20201205233645p:plain
選手平均アシスト

チーム全体のアシスト数が多いほど パスでつないで点数を決めているので 相手のディフェンスに的を絞らせず守りづらいと言われている。上位チームに混じって大阪が上位にランクインしているので 順位自体は高くないが 良いオフェンスを展開しているのかもしれない(試合を見てないのであまり知らない)。

選手別のアシスト数を見ると、PGを差し置いて滋賀のハミルトンと富山のマブンガが突出している。おそらくボール保持時間が長く、アウトサイドからドライブしてヘルプが来なければ決めるし、寄ってきたらキックアウトするチームの中心的な選手なことがスタッツに現れている。日本バスケの特徴的なところのように感じた。(知らんけど)

ティー

f:id:takaishikawa42:20201205233704p:plain
チーム平均スティー

f:id:takaishikawa42:20201205233726p:plain
選手平均スティー

チームスティールが多いのはテンポの早い攻撃をするチームが多そう。

選手別スティール数は三遠の川嶋が突出している(あまりプレーを見たことがないので特徴はわからないが クレバーな選手なのか)。また、外国籍選手が多いのは気になるところ。通常 ガードなどボール保持時間が長い選手についたり、インサイドのヘルプで下のボールを叩く選手がスティールが多くなる傾向があると思ったので。これも日本バスケの特徴なのかもしれない。(知らんけど)

ブロック

f:id:takaishikawa42:20201205233754p:plain
チーム平均ブロック

f:id:takaishikawa42:20201205233818p:plain
選手平均ブロック

チームブロックは秋田が多い。デイビスが突出して多いのが影響していそう。秋田はスティールも多い割に順位が振るわないのは 激戦の東地区に属しているからか得点力が低いからか。

スリーポイント

f:id:takaishikawa42:20201205233849p:plain
チームスリーポイント成功率

f:id:takaishikawa42:20201205233955p:plain
チーム平均スリーポイント成功数

f:id:takaishikawa42:20201205234021p:plain
チーム平均スリーポイント試投数

f:id:takaishikawa42:20201205234042p:plain
選手スリーポイント成功率

f:id:takaishikawa42:20201205234113p:plain
選手平均スリーポイント成功数

f:id:takaishikawa42:20201205234128p:plain
選手平均スリーポイント試投数

チームとしてはスリーポイント成功率・成功数は三河が一番になっている。

選手としては三河の金丸が成功数1位にもかかわらず成功率も上位10人に入ってるのは特筆すべき点のように思われる。また、アシスト数の多かった滋賀のハミルトンや富山のマブンガのスリーポイント試投数が多いことから、外でも中でもプレイできるオールラウンダーである印象を受ける。

フリースロー

f:id:takaishikawa42:20201205234151p:plain
チームフリースロー成功率

f:id:takaishikawa42:20201205234439p:plain
選手フリースロー成功率(上位)

f:id:takaishikawa42:20201205234522p:plain
選手フリースロー成功率(下位)

ポゼッション

簡単に言うと攻撃回数を表すポゼッションをチームごとに見てみる。

def calc_posession(FGA: int, TO: int, FTA: int, OR: int) -> float:
    """
    ref. https://www.nbastuffer.com/analytics101/possession/
    - FGA: Field Goal Attempts
    - TO: Turnovers
    - FTA: Free Throw Attempts
    - OR: Offensive Rebounds
    """
    return 0.96 * (FGA + TO + 0.44 * FTA - OR)

f:id:takaishikawa42:20201205234712p:plain
チーム平均ポゼッション数

富山・大阪は試合のテンポが早いことが分かる。

ターンオーバー

f:id:takaishikawa42:20201205234747p:plain
チーム平均ターンオーバー数(ポゼッションあたり)

f:id:takaishikawa42:20201205234909p:plain
選手平均ターンオーバー数(上位)

f:id:takaishikawa42:20201205234932p:plain
選手平均ターンオーバー数(下位)

f:id:takaishikawa42:20201205234944p:plain
選手平均ターンオーバー数(上位・PG)

f:id:takaishikawa42:20201205234959p:plain
選手平均ターンオーバー数(下位・PG)

チームのポゼッションあたりのターンオーバー数を見ると東京が少ない。ミスの少ない安定感のある試合運びをしていそう。

ターンオーバー数はボール保持時間の長い選手ほど多くなる傾向もあるので、富山のマブンガが多いのはチームのスタイルの影響だろう(その割に滋賀のハミルトンは少なく見える)。また、ターンオーバー数をPGに絞ると、信州の西山が多いのが気になる。得点力も低いのはボール運びに苦戦しているのも一因かもしれない(見てないのでわからない)。また、宇都宮のガード陣のターンオーバー数が少ないのも目立つ。単に宇都宮の場合はボール運びにフォワードの比江島やロシターが参加することが多いことが影響しているかもしれないが。

オフェンス効率

f:id:takaishikawa42:20201205235021p:plain
オフェンス効率(ポゼッションあたりの得点)

千葉のオフェンス効率は突出していることが分かる。

ディフェンス効率

f:id:takaishikawa42:20201206173650p:plain
ディフェンス効率(ポゼッションあたりの被得点)

宇都宮のディフェンス効率は突出して良いことが分かる。

クォーターごとの平均得点

f:id:takaishikawa42:20201205235219p:plain
チーム第1クォーター平均得点

f:id:takaishikawa42:20201205235246p:plain
チーム第1クォーター平均得点の平均得点に占める割合

f:id:takaishikawa42:20201205235319p:plain
チーム第4クォーター平均得点

f:id:takaishikawa42:20201205235336p:plain
チーム第4クォーター平均得点の平均得点に占める割合

終わりに

最初はデータがあるので軽く見てみるかといろいろ眺めているうちに、かなりのボリュームになってしまいました。

過去データを見ながら試合を見ると楽しさが倍増です。(この選手はフリースロー成功率が悪いから このフリースローも1本は外すなと思って見ていると実際に外したり)

最後になりますが bleaguer を公開・保守してくださっているメンテナーの方に感謝します。

ソースコードは以下に置いてあります。

github.com

2020年買ってよかったもの

はじめに

今年買ってよかったものをまとめた。(去年の買ってよかったものはこちら

2020年はコロナの年だったので、だいぶそれに影響を受けたものになった。

ユニクロ ウルトラライトダウンベスト

www.uniqlo.com

薄いし温かい。分厚いダウンジャケットを着なくても、軽いジャケットのインナーとして着れば十分。小さくたためて脱ぎ着もしやすいので 暑くなったら簡単にしまえるのも良い。

WFHで買ったもの

ニトリ 組み合わせフリーデスク プレフェ

www.nitori-net.jp

ロジクール トラックボールマウス SW-M570

モニターアーム

TP-Link 5ポート スイッチングハブ

自宅勤務用に色々買い揃えたけど、特にデスク・ロジクールのマウス・モニターアーム・有線LAN環境を揃えて快適になった。

PlayStation 4

自粛期間中にフォートナイトにハマったので。十数年ぶりにゲーム機を買った。

Yogibo Mate Dog (Dippo)

Yogibo Mate Dog (ディッポー)

Yogibo Mate Dog (ディッポー)

  • メディア: おもちゃ&ホビー

ヨギボーが家庭内で大流行して、その流れペットみたいに可愛いヨギボーメイトが我が家に来た。癒やし。

アノーバ

anovaculinary.com

低温調理器。低温調理している。牛・豚・鶏、どれも美味しい。

キャンプグッズ

SOTO レギュレーターストーブ ST-310

ソト(SOTO) レギュレーターストーブ ST-310

ソト(SOTO) レギュレーターストーブ ST-310

  • 発売日: 2012/04/26
  • メディア: スポーツ用品

スノーピーク チタンマグカップ

スノーピーク チタンシェラカップ

スノーピーク(snow peak) チタン シェラカップ E-104

スノーピーク(snow peak) チタン シェラカップ E-104

  • 発売日: 2012/03/12
  • メディア: スポーツ用品

今年の流行語にもなったけど、こういうご時世なのでキャンプ(主にコテージ泊・デイキャンプ)によく行って、それに合わせてキャンプグッズを買い集めている。

特にガスストーブは気楽に外で調理できるようになってよくて、テーブルウェアもキャンプ感がだいぶ出て良い。これだけで 朝キャンプ上でコーヒーを沸かして飲む体験ができる。

ヒロシのキャンプ本

ふたりソロキャンプ

キャンプにハマるきっかけになった本と漫画。

本を読むとグッズを集めたくなり、漫画を読むとキャンプ飯を食べたくなる。

おわりに

終わりです。

Basketball Behavior Challenge BBC2020 で4チーム中2位に

TOC

コンペ概要

選手とボールの座標情報の軌跡からバスケのあるプレイがスクリーンプレイかどうかを予測するコンペ。

https://competitions.codalab.org/competitions/23905

データ概要

スクリーンのユーザーとスクリーナー、ユーザーのディフェンス、ボールの座標情報が25fpsで提供されている。座標情報は SportVU というトラッキングシステムを利用して計測されている。

特徴量

自分は以下のような特徴量を考えて、プレイごとに平均値や最小値、前フレームとの差分等を集計した。

  • 各選手間の距離
    • スクリーンプレイが発生すると選手間の距離は小さくなる傾向があるため。
  • 各選手とバスケットゴールの距離
    • スクリーンプレイが発生するとバスケットゴールに向かう傾向があるため(と思ったけどそうでもない気がする)。
  • 3選手の面積
    • スクリーンプレイが発生すると選手間の距離は小さくなる傾向があるため。
  • 各選手間のコサイン類似度
    • スクリーンプレイが発生するとスクリナーとディフェンスは逆方向に動く傾向があるため。

モデル

可視化

BasketballAnalyzeR を参考にNBAコートを matplotlib で描画するコードを自作した(DrawNBACourt)。matplotlib animation を利用して gif 化すると選手の軌跡がよく分かった。以下に正例と負例を書いてみる。

f:id:takaishikawa42:20200903231123g:plain
positive example

f:id:takaishikawa42:20200903231239g:plain
nagative example

- red circle: screen user
- orange circle: screener
- blue cross: screen user's defender
- black star: ball

上の正例を見てみると おそらくスクリナーがユーザーがボールを受けやすいようにオフボールスクリーンをしていることが確認できる。
また、下の負例を見ると スクリナー(とされている選手)のシュートフェイクでディフェンスを飛ばせ、ユーザー(とされている選手)にパスを出しシュートを打たせているように見える。

1位解法

「Basketball Behavior Challenge BBC2020」で4チーム中1位に - u++の備忘録

自分との差分だなと思ったのは以下。

  • tsfresh
  • 1dcnn
  • ensemble

atma-cup #5 での経験を上手く利用したとのことでした。シングルモデルでの Public における性能はほぼ同等だったように思われましたが、アンサンブルで大きく底上げしたようです。自分はうまくアンサンブルを上手く機能させることができず、シングルモデルのままだったところで大きく差がついたのかなと考えています。その他、細かいけど重要な部分で差がついていそうな気がするのでコードを追ってみようと思います。

Ref.

github.com

Python で Multi-stage Builds する

TOC


github のレポジトリです。

github.com

きっかけ

Python でマルチステージビルドをどうするかという話。特にデータ分析用で Jupyter やLightgbm 等のパッケージを含んだものに関する例が多くないように感じたので、ハマったポイントも含めて備忘録的にまとめました。

最近 NLP 周りのイメージを作ってる時に信じられないくらいにイメージサイズが大きくなってきたので(これはまた別の問題を含んでいるかもしれないが)、イメージサイズの縮小を考えないと漠然と思ってる時に Twitter で見かけたのが以下のブログ記事です。

future-architect.github.io

この記事を参考にして自分のイメージでもマルチステージビルドを試してみることにしました。

Python で Multi-stage Build する

  • Dockerfile

 FROM python:3.7.7-buster as builder
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 COPY requirements.txt $APP_HOME
 RUN apt-get -y update \
   && apt-get -y install clang cmake nodejs npm \
   && git clone --recursive https://github.com/microsoft/LightGBM && cd LightGBM && mkdir build && cd build && cmake .. && make -j4 && cd ../python-package && python setup.py install && cd ../.. && rm -r LightGBM \
   && git clone --recursive https://github.com/dmlc/xgboost && cd xgboost && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../python-package && python setup.py install && cd ../.. && rm -r xgboost \
   && pip install -r requirements.txt \
   && export NODE_OPTIONS=--max-old-space-size=4096 \
   && jupyter labextension install @jupyterlab/toc \
   && jupyter labextension install jupyterlab-plotly \
   && jupyter nbextension enable --py widgetsnbextension
 
 FROM python:3.7.7-slim-buster as runner
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
 COPY --from=builder /usr/local/bin /usr/local/bin
 COPY --from=builder /usr/local/share/jupyter /usr/local/share/jupyter
 COPY --from=builder /usr/lib/x86_64-linux-gnu/libgomp.so.1 /usr/lib/x86_64-linux-gnu/libgomp.so.1
 RUN apt-get -y update \
   && apt-get -y install git vim \
   && apt-get clean \
   && rm -rf /var/lib/apt/lists/* \
   && mkdir /root/.kaggle
 CMD jupyter lab \
   --no-browser \
   --port=8888 \
   --ip=0.0.0.0 \
   --allow-root
  • requirements.txt
 catboost==0.22
 category-encoders==2.1.0
 dask==2.9.2
 hydra-core==0.11.3
 ipywidgets==7.5.1
 joblib==0.14.1
 jupyterlab==2.0.1
 kaggle==1.5.6
 matplotlib==3.2.1
 mlflow==1.7.2
 numba==0.48.0
 numpy==1.18.2
 pandas==1.0.3
 plotly==4.7.1
 scikit-learn==0.21.3
 seaborn==0.10.0
 tqdm==4.42.0

テーブルデータのデータ分析・機械学習モデルの構築には大体こんな感じでイメージを作っています。Lightgbm, Xgboost は pip で入れるとエラーが出た記憶があるのでソースコードがビルドしています。また EDA などデータ可視化する際には Plotly を使うことが時たまあるので nodejs, npm を入れたり、jupyter lab の拡張で jupyterlab-plotlyを追加したりしています。

今は別個で作ってますが、そのうちこれをベースイメージにしてNN・Image・NLPNLP-ja等の別イメージを作って簡単に使い回せるようにしたいと考えています。

ハマったところ

基本的にビルドステージでビルドしたものを、実行ステージに COPY する際に実行に必要なものを渡せないとエラーが出ます。

パッケージ類は以下でコピーして実行用コンテナに渡します。

COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages

Jupyter

最初はこれだけ動くかなと思ったら、これだとそもそも Docker が起動しなかった。エラーを読むと jupyter: command not found と出ていた。調べると Jupyter は /usr/local/bin 以下にあるらしい。なので

COPY  --from=builder /usr/local/bin /usr/local/bin

を追加することでコンテナが起動した。

Jupyter Lab

その後 Jupyter Lab を起動して localhost:8888 を叩くと以下のエラーがブラウザに表示された。

 JupyterLab Error
 
 JupyterLab application assets not found in "/usr/local/share/jupyter/lab"
 Please run `jupyter lab build` or use a different app directory

サーバは動いてるけど assets がないみたいなので、これはエラーメッセージ通りに

COPY --from=builder /usr/local/share/jupyter /usr/local/share/Jupiter

以上を追加することで Jupyter lab がブラウザに表示された。

Lightgbm

Lightgbm を Import する際に以下のエラーが出た

 >>> import lightgbm as lgb
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "/usr/local/lib/python3.7/site-packages/lightgbm/__init__.py", line 8, in <module>
     from .basic import Booster, Dataset
   File "/usr/local/lib/python3.7/site-packages/lightgbm/basic.py", line 34, in <module>
     _LIB = _load_lib()
   File "/usr/local/lib/python3.7/site-packages/lightgbm/basic.py", line 29, in _load_lib
     lib = ctypes.cdll.LoadLibrary(lib_path[0])
   File "/usr/local/lib/python3.7/ctypes/__init__.py", line 442, in LoadLibrary
     return self._dlltype(name)
   File "/usr/local/lib/python3.7/ctypes/__init__.py", line 364, in __init__
     self._handle = _dlopen(self._name, mode)
 OSError: libgomp.so.1: cannot open shared object file: No such file or directory

libgomp.so が必要だったみたい。このファイルの在処が分かりにくかったけど、以下を追加することで解消した。

COPY --from=builder /usr/lib/x86_64-linux-gnu/libgomp.so.1 /usr/lib/x86_64-linux-gnu/libgomp.so.1

Docker Image のサイズを比較する

シンプルにシングルステージでビルドする場合


 FROM python:3.7.7-slim
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 ADD requirements.txt $APP_HOME
 RUN mkdir /root/.kaggle \
   && apt-get -y update \
   && apt-get -y install clang cmake git nodejs npm vim \
   && git clone --recursive https://github.com/microsoft/LightGBM && cd LightGBM && mkdir build && cd build && cmake .. && make -j4 && cd ../python-package && python setup.py install && cd ../.. && rm -r LightGBM \
   && git clone --recursive https://github.com/dmlc/xgboost && cd xgboost && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../python-package && python setup.py install && cd ../.. && rm -r xgboost \
   && pip install -r requirements.txt \
   && export NODE_OPTIONS=--max-old-space-size=4096 \
   && jupyter labextension install @jupyterlab/toc \
   && jupyter labextension install jupyterlab-plotly@4.7.1 \
   && jupyter nbextension enable --py widgetsnbextension
 CMD jupyter lab \
   --no-browser \
   --port=8888 \
   --ip=0.0.0.0 \
   --allow-root 


実行に不要なパッケージを削除する場合

上記の Dockerfile の RUN の最後に以下の3行を追加する

 && apt-get -y remove clang cmake nodejs npm \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*


マルチステージビルド

上記の通り


結果

Size
Single 2.94GB
Single-rm 2.89GB
Multi 1.58GB

まだまだ全然大きいですが、当初の半分くらいになりました。ML周りのパッケージやJupyter周りのパッケージが大きいのかもしれません。/usr/local/bin 以下を全部渡す必要もないのかもしれません。(分からない)

余談

jupyter labextension install の前に export NODE_OPTIONS=--max-old-space-size=4096 をしているのは JavaScript heap out of memory のエラーが出たためです。node.js に割り当てられるメモリのデフォルトが決まっているらしく、それの上限を上げることで回避できるようです。

github.com


Qiita の記事をはてなブログに移行するコードを書いた

TOC

概要

Qiita にある記事をマークダウン形式で保存し、それらをはてなブログに投稿します。

はてな側へは XML で扱わなければならないので慣れず、Qiita の記事を参考に投稿する機能を実装しました。

Github にコードをアップしました。

github.com

使い方

README に書いている通り config.yml.exampleconfig.ymlとしてコピーして、適宜書き換えればすぐに実行できます。

API KEY は詳細設定の「AtomPub」という項目に書かれています。

TODO

  • 画像はQiita上のものを参照しているので、これをはてなブログ側に持ってきた方が移行した感がもうちょっと出る。
  • 投稿される本文がHTML形式なのでちょっとした手直しがツラそう
    • html -> markdown に変更する方法ってあるのかしら

ドキュメント

Plotly を Seaborn ライクに使えるようにする

https://qiita.com/takaiyuk/items/e68c493642adfb04310e

2019-01-20T22:00:28+09:00

2019-11-25T22:44:23+09:00

TL;DR

Plotly でグラフ作成する際に役立つと思って自作したヘルパー関数(?)を紹介しています。

Plotly とはインタラクティブで良い感じのグラフをお手軽に作成できるライブラリです。
ただ、matplotlib や seaborn とも異なる独特な書き方があるので、seaborn っぽくデータフレームとX軸・Y軸に当たる列名を与えるだけで良い感じに表示してくれるものがあると嬉しいと思ったので、作成した次第です。

https://plot.ly/python/

下図は画像ですが、Plotly の強みはインタラクティブに動作することです。具体的には、ホバーするとプロットされた値が表示されたり、グラフの一部を拡大縮小できることです。

これを本記事では確認できませんが、以下で動作を確認できます。

https://nbviewer.jupyter.org/github/takaiyuk/notebooks/blob/master/PlotlyWrapper.ipynb

データ準備

plorly を使用するとき、plotly.offlineplotly.graph_objs を主に使います。

plotly.offlineは与えられたグラフ情報とレイアウト情報を表示するときに、
plotly.graph_objsは表示するグラフ情報やレイアウト情報の中身を作るのに使います。
つまり、後者を使ってヒストグラムやら棒グラフやらの中身を記述したり、グラフのタイトルなどレイアウトの仕方を指定したりして、それらの情報を前者によって統合して表示するといったイメージだと思います。(あやふや)

plotly.offline.init_notebook_mode(connected=True) は jupyter notebook 上で表示するために記入するようです。

import numpy as np
import plotly
import plotly.offline as py
import plotly.graph_objs as go
import seaborn as sns

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
py.init_notebook_mode(connected=True)  # You can plot your graphs offline inside a Jupyter Notebook Environment.
print(f"Plotly version: {plotly.__version__}")  # output: 4.3.0

"""
Colors of Viridis: 
https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html
"""
C = ['#3D0553', '#4D798C', '#7DC170', '#F7E642']  

データは seaborn に標準装備されているものをいくつか使うことにします。
事前に適当な処理も書いておきます。

"""
Load dataset with load_dataset function of seaborn: 
https://github.com/mwaskom/seaborn-data/blob/master/titanic.csv
"""
class DataLoader:
    def __init__(self):
        pass

    def load_titanic(self, group=None):
        df = sns.load_dataset("titanic")
        if not group==None:
            df = df.groupby(group)["survived"].mean().to_frame().reset_index()
        return df

    def load_iris(self):
        return sns.load_dataset("iris")

    def load_tips(self, group=None):
        df = sns.load_dataset("tips")
        if not group==None:
            df = df.groupby(group)["tip"].mean().to_frame().reset_index()
        return df

    def load_flights(self, group=None):
        df = sns.load_dataset("flights")
        if not group==None:
            df = df.groupby([group])["passengers"].sum().to_frame().reset_index()
        return df
class PlotlyWrapper:
    def __init__(self):
        self.colors = ["#3D0553", "#4D798C", "#7DC170", "#F7E642"]

    def _convert_to_str(self, arr):
        return np.array(arr, dtype=str)

    def _plotly_layout(self, title=None, xtitle=None, ytitle=None):
        return go.Layout(
            title=title,
            xaxis=dict(title=xtitle, ticklen=5, zeroline=False, gridwidth=2),
            yaxis=dict(title=ytitle, ticklen=5, gridwidth=2),
        )

    def distplot(self, data, col, bin_dict=None, title=None, xtitle=None, ytitle=None):
        trace = [
            go.Histogram(
                x=data[col].values,
                histfunc="count",
                marker=dict(color=self.colors[0]),
                xbins=bin_dict,
            )
        ]
        layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle)
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)

    def boxplot(self, data, col, title=None, xtitle=None, ytitle=None):
        trace = [go.Box(y=data[col].values, marker=dict(color=self.colors[0]))]
        layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle)
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)

    def barplot(self, data, xcol, ycol, title=None, xtitle=None, ytitle=None):
        trace = [
            go.Bar(
                x=self._convert_to_str(data[xcol].values),
                y=data[ycol].values,
                text=data[ycol].values,
                textposition="auto",
                marker=dict(
                    color=data[ycol].values,
                    colorscale="Viridis",
                    showscale=True,
                    reversescale=True,
                ),
            )
        ]
        layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle)
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)

    def countplot(self, data, col, title=None, xtitle=None, ytitle=None):
        trace = [
            go.Histogram(
                x=data[col].values, histfunc="count", marker=dict(color=self.colors[0])
            )
        ]
        layout = self._plotly_layout(title=title, xtitle=xtitle, ytitle=ytitle)
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)

    def scatterplot(
        self, data, xcol, ycol, size=1, title=None, xtitle=None, ytitle=None
    ):
        trace = [
            go.Scatter(
                x=self._convert_to_str(data[xcol].values),
                y=data[ycol].values,
                mode="markers",
                marker=dict(
                    sizemode="diameter",
                    sizeref=1,
                    size=data[ycol].values ** size,
                    color=data[ycol].values,
                    colorscale="Viridis",
                    reversescale=True,
                    showscale=True,
                ),
                text=self._convert_to_str(data[xcol].values),
            )
        ]
        layout = go.Layout(
            autosize=True,
            title=title,
            hovermode="closest",
            xaxis=dict(title=xtitle, ticklen=5, zeroline=False, gridwidth=2),
            yaxis=dict(title=ytitle, ticklen=5, gridwidth=2),
            showlegend=False,
        )
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)

    def lineplot(
        self,
        data,
        xcol,
        ycol,
        title=None,
        xtitle=None,
        ytitle=None,
        linewidth=2,
        rangeslider=False,
        slider_type="date",
    ):
        if rangeslider is True:
            xaxis = dict(
                title=xtitle,
                ticklen=5,
                zeroline=False,
                gridwidth=2,
                rangeslider=dict(visible=True),
                type=slider_type,
            )
        else:
            xaxis = dict(title=xtitle, ticklen=5, zeroline=False, gridwidth=2)

        if type(ycol) == list:
            trace = []
            for i in range(len(ycol)):
                t = go.Scatter(
                    x=data[xcol].values,
                    y=data[ycol[i]].values,
                    mode="lines",
                    name=data[ycol[i]].name,
                    line=dict(width=linewidth, color=self.colors[i]),
                )
                trace.append(t)
        else:
            trace = [
                go.Scatter(
                    x=data[xcol].values,
                    y=data[ycol].values,
                    mode="lines",
                    name=data[ycol].name,
                    line=dict(width=linewidth, color=self.colors[0]),
                )
            ]
        layout = go.Layout(
            title=title, xaxis=xaxis, yaxis=dict(title=ytitle, ticklen=5, gridwidth=2)
        )
        fig = go.Figure(data=trace, layout=layout)
        return py.iplot(fig, show_link=False)
dataloader = DataLoader()
plty = PlotlyWrapper()


以下本題です。実際にコードと出力されるグラフを並べて羅列していきます。

Histogram

df = dataloader.load_iris()
plty.distplot(df, col="sepal_length")

hist.png

Boxplot

df = dataloader.load_iris()
plty.boxplot(df, col="sepal_length")

boxplot.png

Barplot

df = dataloader.load_flights(group="month")
plty.barplot(df, xcol="month", ycol="passengers")

barplot.png

Countplot

df = dataloader.load_titanic()
plty.countplot(df, col="alive")

countplot.png

Scatterplot

df = dataloader.load_tips(group="day")
plty.scatterplot(df, xcol="day", ycol="tip", size=3)

scatterplot.png

Lineplot

df = dataloader.load_flights("year")
plty.lineplot(df, xcol="year", ycol="passengers", rangeslider=True)

lineplot.png

Jupyter Lab を使ってる場合の注意

Plotly をレンダリングするための extension をインストールしておく必要があります。

詳しくは下記URLをご覧ください。

SQL 初心者のためのクエリ集(更新・構造操作系)

https://qiita.com/takaiyuk/items/38c303c50fdc132bb378

2018-11-11T17:54:33+09:00

2018-11-11T17:54:33+09:00

トピック

  • データの操作ではなく、DBやテーブル自体を更新したり、操作したりする系の基本的なクエリ集です。(自分用メモなので怪しい所が多々あるかもしれない)

  • 以下の記事に基本操作をまとめています(Python コードと並べて)

  • Udemy のこちらのコースで勉強していました。

データの更新

新規データの追加

  • 新規データを1行追加
    • 列リストと、values句の値リストは、数が一致している必要がある。
insert into
    TABLE (COL1, COL2, ...)
values
    (VALUE1, VALUE2, ...)
  • 列リストを省略して新規データを追加
    • テーブルの前列に対して、値を指定する
insert into
    TABLE  -- 3列のテーブルとする
values
    (VALUE1, VALUE2, VALUE3)
  • 複数行を追加
insert into
    TABLE (COL1, COL2, ...)
values
    (VALUE1, VALUE2, ...)
    (VALUE3, VALUE4, ...)
    (VALUE5, VALUE6, ...)

データの更新

  • ある列すべての値を更新
set sql_safe_updates = 0;  -- safe モードの解除。実務ではあまり使わない

update
    TABLE
set
    COL = COL * 0.9;
  • 特定の行のデータだけを更新
update
    TABLE
set
    COL1 = COL1 * 0.9
where
    col2 > 1000;

行の削除

大量のデータ(10万件以上とか)を削除する際には予想以上に時間がかかるので注意

delete from
    TABLE
[where
    条件式]  -- 削除する行の条件を指定できる。指定なしだとテーブルの全行を削除。

DB構造の操作

DBの追加・削除

  • DB確認
show databases;
  • DB追加
    • 半角の英数字とアンダースコアで書く。
    • マジックナンバー命名者しか意味が分からない数字)を使った名前は避ける。
create database 
    DB_NAME;
  • DBの削除
drop database
    DB_NAME;

テーブルの追加・削除・構造変更

  • テーブルの確認
-- use DB_NAME;
show tables;
  • 列の確認
show columns from TABLE;
  • テーブルの新規作成
create table 
    TABLE_NAME(COLNAME1 DATATYPE not null auto_increment primary key,
               COLNAME2 DATATYPE not null);
/*
- not null: null を許可しない
- auto_increment: idを自動的に振る
- primary key: 主キーの設定
*/
  • テーブルの構造変更

テーブルに列の追加

alter table TABLE  -- 変更するテーブル名
add NEW_COL DATATYPE  -- 新しい列の列名とデータ型
after COL;  -- どの列の後ろに置くか

テーブルの列の変更

alter table TABLE 
change OLD_COL NEW_COL DATATYPE;

テーブルの列の削除

alter table TABLE
drop COL;
  • テーブルの削除
drop table 
    TABLE_NAME