読者です 読者をやめる 読者になる 読者になる

たまにゃんのメモ帳

情報系関連のメモ書きを主に載せていきます。あわよくば他の人の参考になれば...

読売巨人軍の坂本勇人はなぜ併殺が少ないのか?

元ネタは坂本勇人3129打席24併殺打wwwwwwwwww

読売巨人軍坂本勇人は右打者であるにも関わらずなぜ併殺が少ないのだろうか?このまとめは2013年に更新されたもので、2017年開幕前のデータでは5499打席49併殺打という結果になっている。

ゲッツーが多いと名高い広島東洋カープ新井貴浩は8379打席231併殺打という結果であり、坂本と比べると明らかに併殺打の割合が大きい。新井は坂本に比べておおよそ3倍のペースで併殺を生み出している。 ちなみに俊足で左打者で内野安打が多い中日ドラゴンズ大島洋平は3833打席25併殺で0.7倍ほど坂本より少ない。

坂本がなぜ俊足左打者の大島と比べても遜色がないくらいに併殺が少ないのかをデータを集めて検証したいと思う。

データセットを集める

NPBは公式に打席の結果の詳細まで公開していないので、ヌルデータさん拝借する。データの収集はScrapyを利用した。幸い坂本は2007年から一軍のキャリアをスタートしているので2016年までの全ての打席の成績を手にすることができた。

2007~2016までの坂本勇人全打席(5499打席)のデータを収集する。HTMLのパースは個人的にBeautifulSoupの方が扱いやすいのでこちらを利用する。

# -*- coding: utf-8 -*-
import scrapy
import re
from bs4 import BeautifulSoup
from atbat.items import AtbatItem
import mojimoji

RBI_ELIMINATION = re.compile("\(.*\)")
SHIRT_NUMBER_ELIMINATION = re.compile("[0-9]+")
YEAR_EXTRACTION = re.compile("([0-9]{4,4})")

class NulldataSpider(scrapy.Spider):
    name = "nulldata"
    allowed_domains = ["lcom.sakura.ne.jp"]
    start_urls = [
        'http://lcom.sakura.ne.jp/NulData/2007/Central/C/f/25_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2015/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2014/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2013/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2012/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2011/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2010/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2009/Central/G/f/6_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2008/Central/G/f/61_stat_all.htm',
        'http://lcom.sakura.ne.jp/NulData/2007/Central/G/f/61_stat_all.htm',
    ]

    def parse(self, response):
        soup = BeautifulSoup(response.body)
        # Get Year
        m = YEAR_EXTRACTION.search(response.url)
        year = "2016"
        if m is not None:
            year = m.group(0)
        # Get player_name
        spans = soup.find_all("span", attrs={"style": "font-size:14pt"})
        if len(spans) == 0:
            raise Exception("player name is not found!! Fix matching algorithm")
        player_name = SHIRT_NUMBER_ELIMINATION.sub('', spans[0].text).strip()

        # Get at bat results
        trs = soup.find_all("tr", attrs={"onmouseover": "M_over(this)"})
        for tr in trs:
            divs = tr.find_all("div", attrs={"align": "left"})
            tds = tr.find_all("td")
            if len(divs) == 1 and len(tds) >= 1:
                days = tds[0].text.replace('\r\n', '').replace('\n', '').split('/')
                month = days[0]
                day = days[1]
                results = divs[0].text.replace('\r\n', '').replace('\n', '')
                results = [RBI_ELIMINATION.sub('', i) for i in results.split(',')]

                for i, v in enumerate(results):
                    item = AtbatItem()
                    item['player_name'] = player_name
                    item['day'] = day
                    item['month'] = month
                    item['year'] = year
                    item['atBat'] = "{}".format(i + 1)
                    item['result'] = mojimoji.han_to_zen(v)
                    yield item
# -*- coding: utf-8 -*-
import scrapy


class AtbatItem(scrapy.Item):
    player_name = scrapy.Field() # 選手名
    year = scrapy.Field() # 年
    month = scrapy.Field() # 月
    day = scrapy.Field() # 日
    atBat = scrapy.Field() # 第何打席
    result = scrapy.Field() # 結果

データを取得し終えるとこのような csv ができる。

player_name atBat day month result year
坂本勇人 1 29 3 右飛 2013
坂本勇人 2 29 3 中飛 2013
坂本勇人 3 29 3 遊併打 2013
坂本勇人 1 30 3 右2 2013
坂本勇人 2 30 3 中安 2013
坂本勇人 3 30 3 遊併打 2013
坂本勇人 4 30 3 三ゴロ 2013
坂本勇人 5 30 3 空三振 2013
坂本勇人 6 30 3 四球 2013
坂本勇人 1 31 3 遊ゴロ 2013

iPython Notebookで集計

打席結果をグラフで見る

まずはそれぞれの打席結果の累計を見てみる。

%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('ggplot')
font = {'family': 'Ricty Diminished'}
plt.rc('font', **font)

df = pd.read_csv("csv/sakamoto_atbat.csv")
resultDf = pd.DataFrame(dataset['result'].value_counts())
resultDf[resultDf['result'] > 100].plot(kind="bar") # 結果が100回以上のものを出力

最も空三振の数が多いのは大抵の選手に共通するが、中飛、右飛の多さに目が行く。「ぷぷぷしゃかもとまた打ち上げてる」、「ポップフライ 坂本」と言われる要因はここにあるのだろう。

sakamoto_result.png

打席結果を分類してみる

多少強引な方法だが以下のように打席結果を分類する

# ダブルプレー
def is_double_play(x):
    return '併' in x

# ゴロアウト ※併殺は除く
def is_ground_ball_out(x):
    return 'ゴロ' in x \
    and '併' not in x \
    and '安' not in x

# フライアウト ※併殺は除く
def is_fly_ball_out(x):
    return '飛' in x \
    and '犠' not in x \
    and '併' not in x \
    and '失' not in x \
    and '安' not in x \
    and '本' not in x

# ライナーアウト ※併殺は除く
def is_line_drive_ball_out(x):
    return '直' in x \
    and '併' not in x \
    and '失' not in x \
    and '安' not in x \
    and '左直2' not in x and '左直3' not in x \
    and '中直2' not in x and '中直3' not in x \
    and '右直2' not in x and '右直3' not in x


# 三振
def is_strike_out(x):
    return '三振' in x or '振逃' in x

# 犠飛 or 犠打
def is_sacrifice(x):
    return '犠' in x

# 四死球
def is_BB_or_HBP_out(x):
    return '四球' in x \
    or '敬遠' in x \
    or '死球' in x

# 野選
def is_fielders_choice(x):
    return '野選' in x

# エラー
def is_error(x):
    return '失' in x and '犠' not in x

# Hit
def is_hit(x):
    return '本' in x or '安' in x \
    or '中2' in x or '中3' in x \
    or '左2' in x or '左3' in x \
    or '右2' in x or '右3' in x \
    or '左直2' in x or '左直3' in x \
    or '中直2' in x or '中直3' in x \
    or '右直2' in x or '右直3' in x

df['is_double_play'] = df.result.apply(is_double_play)
df['is_ground_ball_out'] = df.result.apply(is_ground_ball_out)
df['is_fly_ball_out'] = df.result.apply(is_fly_ball_out)
df['is_line_drive_ball_out'] = df.result.apply(is_line_drive_ball_out)
df['is_strike_out'] = df.result.apply(is_strike_out)
df['is_sacrifice'] = df.result.apply(is_sacrifice)
df['is_BB_or_HBP_out'] = df.result.apply(is_BB_or_HBP_out)
df['is_fielders_choice'] = df.result.apply(is_fielders_choice)
df['is_error'] = df.result.apply(is_error)
df['is_hit'] = df.result.apply(is_hit)

次に三振以外でアウトになった場合の打球傾向について調べる。

三振以外のアウトカウント = 打数 - 三振 - ヒット - エラー - 野選

安打を考慮しない理由は、中安と書かれていても、ゴロかフライかライナーかどうかが分からないためである。{

double_play_count = df['is_double_play'].value_counts()[True]
ground_ball_out_count = df['is_ground_ball_out'].value_counts()[True]
fly_ball_out_count = df['is_fly_ball_out'].value_counts()[True]
line_drive_ball_out_count = df['is_line_drive_ball_out'].value_counts()[True]
strike_out_count = df['is_strike_out'].value_counts()[True]
sacrifice_count = df['is_sacrifice'].value_counts()[True]
BB_or_HBP_out_count = df['is_BB_or_HBP_out'].value_counts()[True]
fielders_choice_count =  df['is_fielders_choice'].value_counts()[True]
error_count = df['is_error'].value_counts()[True]
hit_count = df['is_hit'].value_counts()[True]

atbat = len(df) # 打席数
atbats = atbat - (sacrifice_count + BB_or_HBP_out_count) # 打数
out_count_without_strike_out = atbats - is_hit_count \
                                - strike_out_count \
                                - is_error_count \
                                - fielders_choice_count # 三振以外のアウト数

三振以外のアウトの打球傾向の割合を出す。

print("Double Play Count / Out Count without Strike Out = {0:.1f}%".format(100 * (double_play_count / (out_count_without_strike_out))))
print("Fly Ball Out count / Out Count without Strike Out = {0:.1f}%".format(100 * (fly_ball_out_count / (out_count_without_strike_out))))
print("Ground Ball Out count / Out Count without Strike Out = {0:.1f}%".format(100 * (ground_ball_out_count / (out_count_without_strike_out))))
print("The Others count / Out Count without Strike Out = {0:.1f}%".format(100 * (1 - ((double_play_count + fly_ball_out_count + ground_ball_out_count) / (out_count_without_strike_out)))))
Double Play Count / Out Count without Strike Out = 1.2%
Fly Ball Out count / Out Count without Strike Out = 40.9%
Ground Ball Out count / Out Count without Strike Out = 24.9%
The Others count / Out Count without Strike Out = 32.9%

坂本は三振以外のアウトの42.5%がフライアウトという事が分かった。これだけフライの傾向が多ければ、右打者であろうと併殺チャンスを回避することができると思われる。

新井貴浩大島洋平との比較

同じように新井貴浩大島洋平のデータを集めてみると、「各プレイヤーの三振以外でアウトになった打席結果の割合」は以下のような結果になった。なお併殺、ゴロアウト、フライアウト以外はThe Othersとなる。これを見ると他の選手と比べて、坂本のゴロアウトの少なさが分かるだろう。大島の一塁到達速度は坂本より(おそらく)速いがゴロアウトの割合が坂本に比べて多いので、ボールインプレー時には坂本より併殺になる可能性が高い。

out_result.png

結論

坂本勇人の併殺数が少ない理由はフライボールヒッター(とそこそこ速いであろう一塁到達タイム1)のおかげだと推測される。

参考

中日の大島は本当にセカンドゴロ製造機なのか (2015年の打席結果データを作りました)


  1. 定かではないので明言はさける

プロ野球12球団の中でホームの投手力が強いのはどこか?

ここ近年のプロ野球12球団の中でホームゲームに強い球団を調べようと、 ホームとビジターの投手成績を比較してみました。

データはここから拝借しております。

baseballdata.jp

ホームとビジターの投手成績をどのように比較するのか?

ホームとビジタ−は当然異なる球場を使うので、得点PFが異なります。 ですので、純粋に防御率による比較では不十分であると考えられます。

防御率の他に簡単に計算できる投手能力指標といえばK/BBがあります。

K/BBとは

K/BB (SO/BB) (Strikeout to Walk ratio) とは、奪三振 (K:strikeout)と与四球 (BB:Base on Balls)の比率で、投手の制球力を示す指標の1つ。3.5を超えると優秀と言われる。 https://ja.wikipedia.org/wiki/K/BB

要は奪三振と与四球のみを投手能力として採用するという事です。

各球団のホームとビジターのK/BBを2011~2015まですべて算出して平均を出します。

{ \displaystyle
HomePitchingPower = \frac{\sum_{m=2011}^{2015} \frac{HomeKBB_m} {VisitorKBB_m}}{5}
}

これを全球団分計算します。

スクレイピングしたデータは

2011年~2015年の各チームの投手成績 · GitHub

においておきます。

結論

ホーム投手力一位と最下位を述べます。

ホーム投手力

ホーム投手力が最も高いのは福岡ソフトバンクホークス

ホーム投手力が最も低いのは北海道日本ハムファイターズ

となりました。

実験結果

各チームの実験結果のデータになっています。

1位のソフトバンクはビジターに比べてホームで1.3倍程度投手指標が向上しています。 最下位の日本ハムは逆にビジターのほうが成績が上になっています。

面白い事に11/12球団においてホームの方が成績がよいことがわかりました。

Team HOME_K/BB VISITOR_K/BB HOME_K/BB / VISITOR_K/BB
Hawks 2.942 2.214 1.332
Tigers 2.912 2.406 1.221
Buffaloes 2.707 2.232 1.22
Giants 2.845 2.549 1.121
Eagles 2.611 2.337 1.118
Dragons 2.484 2.232 1.11
Swallows 2.193 2.047 1.076
Marines 2.256 2.179 1.046
Carp 2.369 2.315 1.037
Baystars 2.066 1.994 1.034
Lions 2.137 2.121 1.014
Fighters 2.14 2.27 0.94

考察

実験結果から分かった事は2つ

なぜソフトバンクのホーム投手力が高く日本ハムのホーム投手力が低いのか? 仮設としては球場の大きさやマウンドの高さなどが原因だろうと考えられますが、詳しくは分かりません。

ほぼ全ての球団の投手成績が向上している理由としては、審判による判定の甘さという事が考えられると思います。 ストライクゾーンが広がれば当然三振を取る確率が増え、与四球になる確率が減ります。 その結果K/BBが上昇し、逆にビジターではK/BBが減少します。

下記の記事にもホームゲームの勝率が高い本当の理由について触れられています。

www.insightnow.jp

おまけ

各年とチームごとにHOME K/BBの順位を出してみましたが、面白い結果が見えてきました。 10位中8つチームが2011~2012の飛ばないボール問題の時に残したデータとなっています。 この二年は歴史的投高の時代で、防御率1点台や2点台の投手がたくさん現れた年になっています。

この年はストライクゾーンの変化と飛ばないボールのおかげで投手がストライクゾーンの中で勝負することができ、 どのチームも軒並み投手成績が向上した年になっています。

Rank Year Team HOME_K/BB
1 2011 Hawks 4.168
2 2011 Tigers 3.354
3 2011 Buffaloes 3.248
4 2011 Dragons 3.209
5 2011 Eagles 3.182
6 2012 Dragons 3.134
7 2015 Hawks 3.069
8 2012 Tigers 3.049
9 2011 Fighters 2.968
10 2014 Buffaloes 2.911

Node.js Full-Stack Web Application Framework

Node Web Framework

昔はNodeのWebアプリを作る時Expressを使っていたが、Expressは汎用性はあるが基本的にはSinatraとかFlaskみたいなMicro Web Application FrameworkなのでRailsのようなFull-Stack Web Application Frameworkがないのかを調べてみた

CompoundJS (旧RailwayJS)

Tower.js

  • その名の通り、たくさんのnpmに公開されているモジュール(Component)を合わせたWeb Application Framework
  • URL : https://github.com/tower/tower

どっちがいいのかわからないが少なくともとっつきやすいのはExpressとRailsを触ったことがあるのでCompoundJSかなぁ?

CoffeeScriptのMixinのやり方が気持ち悪い

先日CoffeeScriptのCookbookを見つけたのだが、CoffeeScriptでは用意されていないMixinを無理やりやっているのが面白かったので紹介する。

参考文献

URL : http://coffeescriptcookbook.com/chapters/classes_and_objects/mixins

bootstrap-coffeedoc

CoffeeDoc With Bootstrapテーマ

CoffeeScriptAPI Documentを作るのにどのAPI Document Generatorもあまりデザインが微妙だったのでCoffeeDocを改良してBootstrapのテーマでAPI Documentを作成できるようにしました。 テーマはBootswatchのFlatlyでnpmにbootstrap-coffeedocという名前で公開しました。

  • 使い方
$ npm install -g bootstrap-coffeedoc
$ coffeedoc 
Usage: coffeedoc [options] [targets]

Options:
  --output        Set output directory                                              [default: "docs"]
  --parser        Parser to use. Available parsers: commonjs, requirejs             [default: "commonjs"]
  --renderer      Renderer to use. Available renderers: html, bootstrap, gfm, json  [default: "html"]
  --stdout        Direct all output to stdout instead of files                    
  --ignore        Files or directories to ignore                                  
  --help          Show this help                                                  
  --hide-private  Do not document methods beginning with an underscore 
$ coffeedoc --renderer bootstrap test.coffee

Autotools ( automake, autoconf, libtool ) 使い方まとめ

Autotoolsの使い方をまとめてみた

Autotoolsとは、主にUNIX系OSにおいてソフトウェアパッケージ開発を行うための、ツール及びフレ ームワークの一種である。このツールを使用することにより、多種多様なUNIX互換環境にパッケージを対応させることが容易になる。 Autotoolsは主に autoconf/automake/libtools の3つから成り立っている。

静的ライブラリをリンクして扱う単純なプログラムのプロジェクトを作る。 libtoolは今回使っていない。

1. 事前準備

いくつか自分でファイルを作成する必要がある。

project
├── Makefile.am
└── src
       ├── Makefile.am
       ├── main.c
       └── lib
              ├── Makefile.am
              ├── lib_test.h
              └── lib_test.c

ソースコード

  • src/main.c
#include <stdio.h>
#include "lib/lib_test.h"

int main(int argc, char **argv){
  lib_print("autotools test\n");
  return 0;
}
  • src/lib/lib_test.c
#include <stdio.h>
#include "lib_test.h"

void lib_print(const char* str) {
   printf(str);
}
  • src/lib/lib_test.h
#ifndef LIB_TEST_H
#  define LIB_TEST_H

void lib_print(const char*);

#endif /* ifndef LIB_TEST_H */

Makefile.am

SUBDIRS = src #サブソースディレクトリ
DIST_SUBDIRS = $(SUBDIRS)
SUBDIRS = lib #サブソースディレクトリ

bin_PROGRAMS = program #実行ファイル名

# 静的ライブラリのリンク
program_LDFLAGS = -L./lib 
program_LDADD = -llib_test

program_CFLAGS =  -I ./lib 

program_DEPENDENCIES = ./lib/liblib_test.a

program_SOURCES = main.c
noinst_LIBRARIES = liblib_test.a
liblib_test_a_CFLAGS = \
                      -I../    \
                      -I../lib \
                      $(CFLAGS)

liblib_test_a_SOURCES = \
                       lib_test.c lib_test.h

2. configure作成

autoscanコマンドでconfigure.acを作成する。ついでにREADMEなども作成しておく。

$ cd project
$ touch NEWS README AUTHORS ChangeLog
$ autoscan

するとconfigure.scanファイルが作成されるのでconfigure.acにリネームして編集する

 $ mv configure.scan configure.ac
  • 編集前 configure.scan
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.68])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT
  • 編集後 configure.ac
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.68])
AC_INIT([project], [1.0.0], [test@domain])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE

# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
AM_PROG_CC_C_O
AC_PROG_RANLIB

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT(Makefile \
          src/lib/Makefile \
          src/Makefile)

autoreconfでconfigureを作成する。

$ autoreconf -i
configure.ac:11: installing `./compile'
configure.ac:6: installing `./install-sh'
configure.ac:6: installing `./missing'
src/Makefile.am: installing `./depcomp'
Makefile.am: installing `./INSTALL'
Makefile.am: required file `./NEWS' not found
Makefile.am: required file `./README' not found
Makefile.am: required file `./AUTHORS' not found
Makefile.am: required file `./ChangeLog' not found
Makefile.am: installing `./COPYING' using GNU General Public License v3 file
Makefile.am:     Consider adding the COPYING file to the version control system
Makefile.am:     for your code, to avoid questions about which license your project uses.

configureでmakefileファイルを作成しmakeでビルドする

$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
...
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/lib/Makefile
config.status: creating src/Makefile
config.status: creating config.h
config.status: executing depfiles commands
$make

3. 実行

最後は実行して動作確認する

$ cd src
$ ./program
autotools test

まだまだ使い慣れていないところはあるがlinuxでC,C++のプログラムを書く上で非常に重要だ。

参考URL

高速なハッシュテーブルの実装

高速でメモリ効率のよい Concurrent Hash Table を研究しているのだが、 Concurrentではないがいいベンチマークがあったので紹介する。 Google Dense Hashtableより速いようでこれを参考にする。

TommyDS

高速なハッシュテーブルとトライ木のライブラリ

C++のmapがクソすぎる。Google Dense Hashtableよりいいパフォーマンスを出しているのでこの実装を参考にしようかなぁ。