Engineering Bear

野球とエンジニアリングを楽しむブログ

Jupyter上でPyOCRを使える環境を整えて、実際にOCRしてみた

どうも。

最近画像関連でOCRに手を出したので年末年始のこのタイミングでまとめようかなと。(もう成人式も終わってるけど)
まあ、目的は「アプリのプロ野球速報をOCRしたら面白そう」って思いつきで始めたのですが…*1
もともと大学時代に画像処理は少し齧っていたので、改めて触れたいなという気持ちもあったり。

基本的なことについて

OCRとは

OCRとは、「光学文字認識」の英語の頭文字をとったもので、画像の中の文字を文字コードの列(機械が認識できる文字)に変換するソフトウェアのことを指す。(Wikipediaより)

要するに、画像の中から文字列を抽出するソフトってことです。

PyOCRについて

そのOCRPythonで利用しやすくするために作られたライブラリが、PyOCRです。
下記のTesseract-OCRというOCRエンジンを、Pythonでラップしたものです。

Tesseract-OCRとは

縮めて"Tesseract"とも。

OCRのエンジンで、多様なオペレーティングシステム上で動作するオープンソースソフトウェアであり、Apache License 2.0 の下で配布されている。文字認識を行うライブラリと、それを用いたコマンドラインインターフェイスを持つ。(これもWikipediaより)

JupyterでPyOCR

Jupyter NotebookであればPyOCRも同様にわかりやすい形で出力してくれます。
下記画像みたいな感じで(結果はさておき)

f:id:kumappp:20200117173117p:plain
実行例

ちなみに元画像はこちら。野球好きならよく見たことある画像ですね。

f:id:kumappp:20200117173145j:plain
速報画像

リポジトリ情報

ここにプッシュしております。
github.com

環境情報

バージョン情報は下記。

  • Python: 3.6.7
  • PyOCR: 0.7.2
  • tesseract-OCR: 4.0.0-beta.1
  • Jupyter: 4.4.0

作成したDockerfile

作成したDockerfileは下記です。今回はJupyter用イメージを引っ張ってきて、そこにtesseract-OCRを追加するような感じ。

FROM jupyter/datascience-notebook

USER root
RUN sudo apt update && sudo apt -y install tesseract-ocr && \
    apt install tesseract-ocr-jpn && \
    apt install tesseract-ocr-script-jpan
WORKDIR /home/jovyan/work
COPY requirements.txt ./
RUN pip install -r requirements.txt

今回は日本語を抽出したいので、日本語エンジンも併せてインストールします。(tesseract-ocr-jpntesseract-ocr-script-jpan

Jupyterの起動

コンテナを起動すればJupyterも同時に起動するので、下記コマンドでビルドや起動を行えばOK。

ビルド

$ docker build -t sokuho/jupyter .

サーバ起動

$ docker run -v <current_directory>:/home/jovyan/work --name sokuho -p 8888:8888 sokuho/jupyter

コンテナ起動

$ docker start -a sokuho

簡単なPyOCRの実践

とりあえず簡単に作ったコードがこちら

from PIL import Image
import sys
sys.path.append('/path/to/dir')

import pyocr
import pyocr.builders

def print_ocr(img_path,tesseract_layout=3):
    tools = pyocr.get_available_tools()
    if len(tools) == 0:
        print("No OCR tool found")
        sys.exit(1)
    tool = tools[0]

    txt = tool.image_to_string(
        Image.open(img_path),
        lang='jpn',
        builder=pyocr.builders.TextBuilder(tesseract_layout=tesseract_layout)
    )
    print(txt)

PILは、画像の読み込みに必要です。

OCRツールの取得

9~13行目でOCRの実行に必要なツールを取得します。
このツールの中で、下記メソッドを使うことで、利用可能言語データも確認できます。

import pyocr

tools = pyocr.get_available_tools()

tool = tools[0]
langs = tool.get_available_languages()

print(langs)

実行例

['Japanese', 'osd', 'eng', 'jpn']

OCRの実行

ツールのimage_to_string()を利用して、OCRを実行します。
引数については下記。

  1. 画像データ(サンプルだとファイル取得までやってる)
  2. 利用言語(上記利用可能言語参照)
  3. レイアウト解析のオプション(詳細は下記、デフォルトは3)

ちなみに実行結果のサンプルは上の方にある、Jupyterによる出力がそれです。

レイアウト解析のオプション

エンジン内で画像をレイアウト解析する際の挙動をオプションを指定することによってコントロール出来ます。
各オプションの挙動は下記。

  • 0: 方向とスクリプト検出(OSD)のみ。
  • 1: OSDによる自動ページセグメンテーション。
  • 2: 自動ページセグメンテーション(OSDなし)。
  • 3: 完全自動ページセグメンテーション(OSDなし)。(デフォルト)
  • 4: 様々なサイズのテキストの単一列を想定。
  • 5: 垂直に配置されたテキストの単一の均一なブロックを想定。
  • 6: 単一の均一なテキストブロックを想定。
  • 7: 画像を単一のテキスト行として扱う。
  • 8: 画像を単一の単語として扱う。
  • 9: 画像を円の中の1つの単語として扱う。
  • 10: 画像を単一の文字として扱う。
  • 11: スパーステキスト。順不同でできるだけ多くのテキストを検索する。
  • 12: OSDによるスパーステキスト。
  • 13: 生のライン。内部の処理をバイパスしつつ画像内にテキストが1行だけあるものとして扱う。

参考
blog.machine-powers.net
github.com

所感

なんとなく始めたOCRですが、思った以上に壁はでかかったようです。
上の実行例の通り、デフォルトの使用だけだと目的の実現は難しそうですが…
調べたところ、訓練データの精度をあげるツールがあるそうなので、(jTessBoxEditorというらしい)
sourceforge.net
こういうのを触ってみるのもありかもです。*2
あと訓練データ自体も日本語の各フォントに対応したものがあるみたいなので、
その点で調査してみてもいいかもです。
qiita.com

今回は勉強がてらOCRを使ってみたかったのでPyOCRを使いましたが、
単純にOCRを利用したいだけなら各企業が提供しているサービスを利用するのが現実的かな、と。
有名なのはGoogleの「Vision API
cloud.google.com

LINEの「LINE BRAIN OCR」(API公開はまだだそう)
www.linebrain.ai

AmazonAWSサービスの一つ)の「Amazon Textract」(日本語未対応)
aws.amazon.com

速報のスクショをとってOCRエンジンに投げてデータ連携を行う、みたいなことをしたければ
上記APIを使うことになるのかな〜と思います。*3
速報以外にもスクショして画像投げて〜って流れを作れば色々なことが出来そうですね。

以上、お読み頂きありがとうございました〜

*1:データ欲しいだけならスクレイピングとかしたほうがいいと言われました、そりゃそうだ

*2:ただJavaの実行ファイルなのでJavaの環境が必要なんだよなぁ…

*3:単純に速報データがほしけりゃスクレイピング(ry

PyCon JP 2019に参加・登壇しました(感想文)

こんにちは、お久しぶりですくまぴです。

また期間が空いてしまい、継続の難しさを感じております…
今日は、9/14~17に開催された(カンファレンスDayは16,17)PyCon JP 2019のふりかえりを書きたいと思います。
一参加者として関わった2018と比べて、スタッフ及び登壇者として参加した今年は、
PyConというものの楽しさ、難しさをより一層感じました。

今回は登壇した時のことを書きたいと思います。

TL;DR

  • 色々な方のサポートを頂き登壇にまで至れたので、改めて感謝を述べたい
  • 事前の準備をしっかりしたので、堂々と発表できた
  • 一方で反応がイマイチだったので、内容の精査の必要性を痛感した
  • もっと自分の推し技術に触れる時間を増やして、「価値観」を洗練すべき

トークが採択されるまで

今回、PyCon JP 2019に提出したCfPが通りました。
…いや、通ってはいないんですが、待機リストに残ることが出来ました。

その後、待機リストから採用に至ったとの連絡がありました。*1

そもそもCfPを出したきっかけとして、5,6月頃DjangoとJupyterを組み合わせる開発を各もくもく会でやっていたところ、
#pyhack にて「CfP出してみれば?」と背中を押されたので、乗っかる形で出してみた次第です。
出す分には損ないですからね。
Beginner向けかつ新設の15分枠なので、「こういうものもあるんだよ!」っていう紹介が出来ればいいかな、くらいの気持ちで送りました。
(これが上述の反応がイマイチに繋がったと私は思ってるんですが…)

PyCon JP 2019のタイムスケジュールにCfPの内容がそのまま出ているので、こちらからどうぞ。
pycon.jp

発表までの準備

繰り上げ採択の連絡を頂いたのが #pyhack 合宿中だったので、この話をした所、
#stapy スタッフのnikkieさんよりちょうど登壇者を探しているので、壁打ちの意味も込めてどうですか?と提案されたので
先んじて登壇しました。

startpython.connpass.com

↓資料はこちら
speakerdeck.com

発表した感想としては、会場の人の多さや発表練習不足によって緊張してしどろもどろになってしまったのが残念だということと、
DjangoとJupyterを触ったことある人でないと伝わりづらい内容だったかな、と聞いていた方の反応を見て思ったことです。

この経験を生かし、

  • 「Beginner向けの15分枠」ということを強く意識すること
  • 発表の原稿をしっかり作って練習すること

の2点をふまえて登壇用の資料を作り、練習を行いました。
#rettypy や #pyhack でスライドを見て頂いてアドバイスを頂きました、ありがとうございました。

いざ、発表

↓ギリギリまで調整して出来上がったスライドはこちら
speakerdeck.com

いざ壇上に上がると人がたくさんいてより緊張感が増しましたが…
なんとか発表を終えることが出来ました。
↑のタイムテーブルから発表の様子をアーカイブしたものがYouTubeにあがっているので、こちらも併せてどうぞ。(自分は怖くて見れないw)
カンペとしてiPadを手元に用意しながら発表したんですが、
途中からPCのスライドとiPadのスライド両方操作するのが煩わしくなり
結局カンペなしで喋ってました。(やはり原稿は頭の中に入れておくのが一番…!)

個人的には、今回はしどろもどろにならず喋れたんでよかったかな、と思っています。(発表については)*2

発表を終えて

やはり聴いた方の反応は気になるので、#pyconjp タグや会場の #pyconjp_2 のタグを終わった後に眺めていました。
他の登壇者と比べると「こんなものがあるのか」や、「こういうことか、覚えた」みたいな
「気づき」や「学び」を得た、みたいなツイート数に違いがあるなーと。
練習の段階で「この発表を聴くとこういうことができるようになる、みたいなイメージが欲しい」
とはよく言われていて個人的には意識していましたが、まだまだだな、と。

例えば、「Django-pandas」というライブラリ、私はDjango×Jupyter使う上ですごく便利だと思っていて
そこに反応した方も1人いらっしゃったのですが、資料としてはちょこんと紹介しているだけなので、
そりゃ気づきづらいよね、という…

反省・感想

内容について

DjangoとJupyterというホットワードを組み合わせる、といった点に目をつけたのはよかったかなと思います。
ただそれらを組み合わせることにより生み出す「価値」のアピールが足りなかった気がします。
その理由としては、単純にまだまだ自分自身が「Django×Jupyter」の真の価値に気づけてないからじゃないかな、と。
要するにまだまだ勉強不足というか実用経験が足りないな、と思いました。
利用はあくまで個人、かつサービスとしてリリースしてる訳でもないので…

Beginner向けということについて

上述したように、Beginner向けというのはむちゃくちゃ意識しました。*3
ただ、来てくださった方達を見ると、あまり自分の想定よりは遥かにできる人達が来ていました。*4
その意識から誤っていた感は否めないと思います。

15分という枠について

今年からできた枠ですが、15分というと「LTよりは長いがセッションのトークとしては短い」って感じだと思います。
その分、「何を残し、何を捨てるか」はよりシビアになるので、その物差しになる自分の価値観が
上述した限りだとちょっとずれていたのかな、と…

総じて

満足感より悔いが残る結果となりました。
改めて感じたのは、
「発表するほどの技術を、私はどれだけ使いこなせていたか?」
という点。
知らないものは当然発表出来ないし、
「知る」には知ろうする「機会」が重要。
その「機会」を捻出するほど、推したい技術に向き合う時間を作れなかった*5、と思いました。

最後に

苦い思い出とはなりましたが、そもそもの登壇に至れたのは
普段私も参加している #pyhack , #rettypy, #mokupy 各もくもく会でアドバイスやレビューをしてくださった皆様のおかげです。
またスタッフ業も行っていましたが、その際も発表のお手伝い等してもらいました。
この場を借りてお礼を申し上げたいと思います。

去年も感じましたが、やはりPyConは色々な方がいて、その方々に支えられて行われているのだと。
そんなところで登壇したのだから、もっと悔いなく終えられたらよかったのに、と強く思いました。
来年以降も発表然りスタッフ然り関わることができたら、悔いがないように普段から技術ともっと触れ合いたいな、と意識した次第です。

以上、読んで頂きありがとうございました。

*1:Twitterでそのことツイートしたっけなと思ったらしていなかったw

*2:なお発表の数時間前に突発的に入れた334ネタは盛大に空振りした模様

*3:DjangoとJupyterの紹介なんてあるし

*4:知っている方結構いましたが、大体実力者の方

*5:決して仕事やプライベートが忙しいわけではなく、単純に技術と触れ合う時間が少なかった

コミュニティに貢献する〜DjangoCongress JP 2019に参加して来ました〜

お久しぶりです、くまぴです。

前回ブログを更新してから公私共に色々あって更新が滞ってしまいました…
(野球のLT大会について記事を書いてはいたのですが、まとまらずそのままに…)
とりあえず今回は5/18(土)と5/19(日)に開催されたDjangoCongress JP 2019に参加して来た感想を述べたいと思います。

TL;DR

  • Djangoコミュニティのイベントに初参加しました
  • セッションを通して、Djangoを使いこなすイメージがついて来ました
  • 2日のイベントを通して「コミュニティへの貢献」を考えさせられました

参加にあたって

今までのPythonの記事にあるようにどちらかというとデータに興味がある勢の私ですが
業務ではWebエンジニアということもあって、裏でひっそりDjangoのことも学んでました。*1

作ってる途中のやつ↓
github.com

データのWebAPI化に興味があったので、モデル設計の話だとかRESTAPIに関する話*2を聞けたらなーと思い、
参加した次第です。

参加した感想

Day1 カンファレンスデー

イベントURLはこちら↓
django.connpass.com

とりあえず興味があるセッションにいくつか行ってみました。

一番興味ある分野に近いのはNakajima Yuukiさんの「Make Query Great Again!」でした。

スライド↓

www.slideshare.net

モデル設計とは違いますが、自分が今後データを抽出する処理を考えたときに重要になってくる部分で
かつまだDjangoのお作法をあまり知らなかったので、非常にためになりました。

特にM2Mのお話は業務で色々苦労した経験があるので、Djangoだとこういうやり方があるのか〜というのを知れたのはデカイと思ってます(苦笑)


あとRESTAPIでなく、GraphQLのお話も聞いて来ました。

nasumさんの「DjangoではじめるGraphQLとフロントエンド開発の協業」↓
speakerdeck.com

GraphQLについては名前だけ知ってる程度で全然中身は知らなかったので、
今後自分のプロダクトへの導入を検討してもいいかも…

また日頃もくもく会でお世話になっているnikkieさんの「Django Girls Blogのネクストステップ 〜実務レベルへ橋渡し〜」も聞いて来ました。

スライド↓
gitpitch.com

私も以前試した「Django Girls Tutorial」を発展させたお話。
以前自分も触っただけあってベースが明確になっていたので、
完成に到るまでのプロセスがはっきりとしたイメージで伝わって来ました。
あとチュートリアルレベルで作ったものに色々な機能を1個ずつ実装していくのいいですね。
自分も参考にしようと思います。

セッション後のパーティでもどなたかが言ってましたが、
今回DjangoRESTFramework(以下DRF)に関するセッションがありませんでした。
Webフレームワークと組み合わせるお話のセッションでDRFについて言及はありましたが、
こちらはそんな流行してる訳ではないのかな?

Day2 スプリントデー

イベントURLはこちら↓
django.connpass.com

こちらはDjangoのコントリビューターや英語ドキュメントの翻訳者など、Djangoコミュニティに貢献したい人が集まって、
各々の作業をするイベント。

勘違いしてて申し訳ないんですが、Djangoもくもく会のようなノリで最初は申し込んでいました。
あとでそれとはちょっと違うと知って、、、
ただDjangoの強者と関わり合える貴重な機会だと思ったので、そのまま参加しました。

その中で、私がやったのは2つ。

  1. 今後OSSのコントリビュータになれたらの思いも込めて、DjangoプロジェクトのContributing Tutorialを行う
  2. このブログを書く(DjangoConのレポート)

2つ目はまさに今やっているのでおいといて…
1つ目はやったこと自体は大きなことではないですが、自分にとっては貴重な体験となりました。
主に自分のものじゃないリポジトリをフォークして、開発するという点において。

今までOSSのコントリビュータにはなったことがないものの
今後自分が使いこなした上でさらなる発展に貢献したい、というようなOSSに会えた時*3のために
お作法というか、やり方を知っときたいとは思っていたので、いい機会になりました。

チュートリアルだけやるのもアレなので、実際に挙がってるチケットをみて何か出来ないかな〜と探してもみました。
まあ初心者が着手できるような簡単なチケットは全部誰かしらアサインされてましたが…
今後Djangoを触っていく上で着手できるような状態に自分がなったら、対応してみたいな、と思いました。

まとめ

今回2日間イベントに参加して感じたのは、スタッフさんやコントリビュータさんのコミュニティに対する熱意。
Djangoだけでなく、Python自体に貢献したい、という人の話を色々聞いて自分もそのような意識が湧いて来ました。

思えば去年PyConに参加した時に「その内スタッフとかやってみたいなあ」と漠然と思っていましたが、
改めてDjangoConのスタッフさんや今年のPyConのスタッフさんの話を聞くと貢献活動を通して
自分自身の成長やキャリアに繋げている人が多かったので、
自分もコミュニティに「詳しくなる」だけでなく「貢献する」といった点も意識して活動していければ、と思いました。

私的な話ですが来月業務で開発しているアプリがリリースされるので、
リリースして安定活動すれば恐らく平日にも外部活動できる時間が取れる…はず(苦笑)

以上です、ここまで読んでくださりありがとうございました。

*1:とはいえデータをあれこれするのに必要だと判断したためですが

*2:というかDjangoRESTFramework

*3:もちろん今回のDjangoも含めて

あけましておめでとうございます&BaseballPlayStudyでLTしてきました

あけましておめでとうございます。
新年一発目のブログです。
…とはいってもネタは先月、去年内の出来事ですけど…

TL:DR

  • 野球好きが集う一大イベント、BaseballPlayStudyにてLTを行ってきました
  • お題は誰でもみれる打席結果を集計して分析を行った、というものです
  • あまり大きくは目立てませんでしたが、資料作りからLTまでの流れを楽しんで実施できました
  • 周りのレベルが皆高い…自分も負けないよう頑張ります

BaseballPlayStudyでLTしてきました

今回は、先月12/12(水)に行われた、下記勉強会で登壇したことを書きたいと思います。
bpstudy.connpass.com

この勉強会、名のとおり「野球」についての勉強会で、
4年以上毎年春と冬に開催されている野球好きが賑わう一大イベントなのです。
Twitterでも今回、前回は開催中ハッシュタグがトレンド入りするなど名の知れた勉強会です。*1
本来このようなHOTなイベントの感想ブログは発表したらすぐ書くべきなのですが…
色々あって*2新年最初のブログとさせて頂きます。

LTのお題について

LTに使ったスライドをあげておきます。
speakerdeck.com

お題は、
「気軽に手に入る成績データで野球分析をしてみた〜山田哲人の復活とフライボール革命を添えて〜」
です。

お題の理由

理由はスライドにあげている「同じような打席結果が多くなった時って好調or不調なのかも?」と疑問に思ったことが第一なのですが、
前回、前々回のエントリーにあげているようなスクレイピング&データ加工の勉強をちょうどしていたのと、そのタイミングでいつかはLTしたいと思っていたBaseballPlayStudyのLT募集のお知らせを見て、「じゃあこの勉強の成果を発表しよう!」と思ったのもきっかけの一つです。

裏話

LTを応募した時点でpandasやrequestsはある程度触っていましたが、ちゃんとスライドにあげているような資料まで結果を持っていったのは応募してからです。
正直、副題の一つである「フライボール革命」については起きているだろう、という目論見でLTに応募し、分析をしていたので、結果を出した時は焦りました(笑)
スライドにある「マッスルボール革命」はそんな結果から無理矢理導いた結果ですw 最初応募した時はそんな結論に至るなんて思いもしませんでしたw
あと、山田選手もある程度「こんな感じだろうなあ」と思って分析していましたが、予想と違ったら違ったで興味深い結果になったので、その時は楽しかったです(小並感)。

分析&スライド作りについて

LTに応募して採択されたところで、分析とスライド作りを始めました。
もともと分析用プログラムはある程度作っていたので焦りはありませんでしたが…
やはりスライド作りはまだ慣れないです(笑)

分析

細かいプログラムの話は前回、前々回のエントリーで述べたので割愛します。
詳しい話はこちら↓(宣伝)
kumappp.hatenablog.com

苦労したのがグラフとして可視化するところで、打席結果を集計した結果の数値は色々出てくるのですが、
それをうまい具合にグラフとして表現するにはどうしたら良いか、といった点であまり知見がなく試行錯誤していました。
そんなところ、普段から参加していたもくもく会で分析に詳しい方から
「こう表現したら良いのでは?」
とアドバイスを頂き、結果グラフとしてわかりやすさが向上し、発表するに足りるくらいのものとなりました。

具体的には、今回のスライドのグラフでは、打席結果を「打球角度」や「打球方向」別にグルーピングして集計した結果を出していますが、
アドバイスを頂く前は打席結果をそのまま集計していたため、対象データ数が多くグラフとして視認性に著しく欠けた形となっていました。
冷静に考えれば当たり前のことのような気がしますが、発表時期が迫っていて盲目的になっていたので、とても助かりました。*3

お礼も兼ねてもくもく会を宣伝します。
強者も初心者もバランスよくいて、それでいて質問とかもしやすく過ごしやすいもくもく会です。
※下記リンクは前回開催された時のページです
retty.connpass.com

スライド作り

前回のLTもそうでしたが、普段業務でスライド作りをほとんどしない故、探り探りスライドは作ってます。
今回のスライドも、前回のBaseballPlayStudyで登壇した方のスライドを参考に作りました。*4

一番困ったのは、発表時間が6分しかない(LTだしね…)ので、スライドを最小限に減らす必要があることです。
多すぎると発表できないのはもちろん、伝えたいことも早口になり伝わりづらくなってしまいます。(おまけに私は滑舌も悪いので余計…)
てな訳で一度ネタ部分、伝えたい部分全部載っけたスライドを作りました。*5
そしてそこから、スライドを最小限にするようカットする場所を選択します。
当然ながら、その場合カットされる場所はネタの部分になるので、涙ながらネタを減らし最小限にスライドを減らしていきました(笑)

f:id:kumappp:20190114222621p:plain
振り返りのこのスライドは挨拶代わりのネタとしてちょうどいいと思ったのですが(笑)

まあ、そもそもLT応募した時の概要に

分析テーマは、「山田哲人の復活の理由」と、「日本でもフライボール革命は起きているのか?」です。

ってうっかりテーマを二つ入れてしまったことが最大の原因なのですが…
何はともあれ発表練習してもギリギリ話切れるレベルまで削ぎ落としました。
発表時の資料は、完全版と比べてスライド枚数はもちろん文章量も減ってます*6

いざ、当日

当日、早めに業務を切り上げ、カフェで最終確認をし、いざ発表の地へ

自分の順番は、主催者である佐藤さん(@haru860)(ちなみに大トリ)の一つ前という、ハードルの高そうな*7順番になってしまいました。

発表

自分の発表を待つ間、他の登壇者の方の発表をきくわけですが、
おお〜っとなるものからクスッとなるものまでまあ色々あるんですけども、
とにかく、やっていることのレベルが高い!
技術的な意味ではもちろん、資料のレベルが高い…
初登壇の方でも資料がかなり作り込まれていて、スライド作りは慣れてそうだな〜と感じました。

そんなこんなで発表の時間がやってまいりました。

発表中は資料の多さに自分の滑舌の悪さも相まって最初の方はかなり早口になっていました(汗)
ただ、後半からは慣れて自分のペースで喋れた…と思います。
とりあえず最終的に「マッスルボール革命が起きている」を前面に出して〆れたのは良かったです。(笑)

発表中はもちろんTwitterで自分の発表の感想の様子は見れませんが、終わった後見てみると
皆さん色々感想をつぶやいてくださって助かりました。
いくつか抜粋をば。

後いくつか感想ブログも見させて頂きました、感想くださった方本当にありがとうございました!

打ち上げ

懇親会にも参加しました。
bpstudy.connpass.com

登壇した方と色々話が出来、とても楽しかったです。
上述したような、分析してみたら予想外の結果になって方針を変えたことに共感してもらえたり、技術的な裏話を聞けたりなど。
やはり野球好き勉強会の懇親会と言うだけあって、終始野球談義で盛り上がりました。
私は野球好きならどのチームのファンでも楽しく話せる自身はあったのですが、
そんな私でも聞いたことがないようなネタ(主に現地ネタが多い)がたくさん聞けて、
一野球ファンとしてもとても充実した時間を過ごせたと思います。

まとめ&これから

まとめ

振り返ると、自分の興味ある分野でのLTをすることは、
単純にモチベーションあげられるのはもちろん、技術的にも「発表」という一大イベントをゴールとして
逆算的に必要な手段を洗い出して集め、実施に移れるので
エンジニアとしてとても有効なのでは、と感じました。
LT駆動開発とか締切駆動開発、といった話はたまに聞きますが、
今回それの良さを身を以て感じた次第です。*8
半ば勢いで申し込んだLTでしたが、本当に有意義な時間を過ごせたと思います。

これから

野球の勉強会こそBaseballPlayStudyしか今はありませんが、
別に普通の勉強会でも、野球を通して身についた技術力を発信するのは良いかと思うので、
今年はそういった勉強会での登壇も目指してこれから勉強頑張っていこうと思います。
また、BaseballPlayStudyは次回おそらく開幕前の3月になると思われますが、
そこでもLT(採択されれば)します!
内容は、まだ考えていませんが(笑)

遅くなった感想は、以上です。
今回のBaseballPlayStudyを通じて知り合えた方、3月また会いましょう!

*1:書いててそんな勉強会で登壇…もとい登板したのか…という気になってきた

*2:多忙とか他にやることがあったとか理由はあれど、一番は発表して気が抜けてしまった…というところです(反省)

*3:あとスクレイピングについての注意事項も頂きました、それがなかったらすごく短いペースでデータサイトにリクエストを飛ばしてサーバをダウンさせていたかもしれない

*4:今回のスライドで使ったKeynoteテーマも参考にさせてもらいました(笑)

*5:speakerdeckにあげたのはそのカットする前のものです

*6:なお「山田のトリプルスリーはノルマ」ネタは発表直前にハッと思いついて発表版にのみ入れた

*7:印象に残らなそう的な意味で

*8:良くも悪くも、自分のやる気を出すにはこの手段が最適ってことがわかりました(笑)

[Python] requestsとpandasで野球の打席結果を取得する②

こんにちは!

気づいたら年末も年末になってしまいました。
このネタは年が変わる前に書いて、気持ちよく2018年を〆ようと思ったくまぴです。

今回は前回の
kumappp.hatenablog.com
この記事の続きを書いていこうと思います。

先日登壇した
Baseball Play Study 2018 冬 (BPStudy#136) - connpass
の元ネタです。

今回の目的

今回は、pandasで出来ることをつらつらと書いていきます。
私の備忘録も兼ねて、野球のデータを解析できるまでに利用したメソッドを羅列するような感じで。

はじめに

これからの例に出てくる変数名は以下のように、前回のエントリーのものを引き継いでます。

import pandas as pd
df = pd.read_json(response.text)

※response.textは野球データを指してます

pandasのDataFrameの要素を修正する

文字列データを修正する

df['RE'] = df['RE'].str.replace('<td>','')

strアクセサを用いて、str型の組み込み関数を呼び出します。そして、Pythonの場合と同様に文字列操作を行います。
上記例だと、の文字列を空文字に変換しています。文字列データから不要な要素を取り除くのが目的です。
dfのインデックスに’RE’を指定していますが、これはpandasのDataFrameの、列名「RE」の要素に対して文字列操作を行い、それを直接元の列に代入しています。

不要なデータを含む行を取り除く

df = df[df['MD'] != '月日']

この操作で、「MD」列に「月日」というデータを持つ行を取り除く操作を行っています。
dfの変数名が何度も出てきてややこしいですが、中をみていくと

df['MD'] != '月日'

この操作で、Boolean型の列を持つSeriesが生成されます。
これを元のdfのindexに代入すると、Trueの行(データが「月日」でない)のみが抽出される=「月日」のデータ行が削除される、になります。
SQLのwhere句でデータを絞るイメージです。

空白のデータ部分に対して、1つ上の行のデータをコピーする

なんのことやらと思う人がいるかもしれないですが、下記のようなイメージです。

ヘッダ1 ヘッダ2
aaa bbb

ヘッダ1 ヘッダ2
aaa bbb
aaa bbb

ExcelでいうCtrl+D(上の行をコピー)操作を同じです。今回のお題でいうと、日付が大体空白になっていますが、そこに対して上の行と同じ日付を入れよう、といった目的で行います。

df = df.apply(lambda x: x.str.strip()).replace('', np.nan)
df = df.fillna(method='ffill')

2回操作を行っていますが、理由として、pandasに空白を上の行データで代替するメソッドはありませんが、NAN状態のデータならあるので、
一旦空白データをNANデータに変換する処理を行っています。(npはNumpyをimportした時の別名として定義しています)
df.apply(lambda x: x.str.strip())でdfの全行に対しstr型の組み込み関数であるstripを適用した上で空白をNANにreplaceしています。
そして、fillnaメソッドでNAN値を除外します。method='ffill'と指定することで、上の行のデータをNAN値の代わりの値として代入してくれます。

時系列データに変換

pd.to_datetime(df['MD'])

pandas.to_datetime()で指定したカラムのデータ型をdatetime64[ns]型に変更します。
これによりmatplotlibをはじめとした可視化ツールなどで、時系列分析ができるようになります。
上記例だと、「MD」列のデータ型を日付型に変えています。
こちらの列は「10/01」といったデータが入っているので、このデータがそのまま日付型となります。

データの型変更

df = df.astype({'AVG': 'float64', 'DASU' : 'int64', 'HIT' : 'int64'})

DataFrame.astype()で、データを任意の型に変えます。
上記例だと「AVG」の列を「float64」型に、「DASU」列と「HIT」列を「int64」型に変更しています。
AVG」は打率で、小数点以下の数値を含むため「float64」型に変えています。
「DASU」と「HIT」はそれぞれ打数と安打数で、集計のため整数型に変えています。

データを集計・分析する

集計する

grouped = df.groupby('RE')

DataFrame.groupby()で指定の列を対象にグルーピングします。
SQLの「group by」と同じような感覚で使えます。
この場合は、groupedという変数にDataFrameを「RE」列を対象にグルーピングした結果を代入しています。

数をカウントする

df_new = grouped.count()

上記のグルーピング結果から、各データの出現回数を集計するメソッドが、上記DataFrame.count()です。
これで、「RE」列に出てくる打席結果を集計した結果が、df_new変数に入りました。

データをソートする

df_new.sort_values('count', ascending=False, inplace=True)

集計した結果等を、ソートしたい時に用います。
この場合、「count」という列(df_newのカウント数が入った列に便宜的に列名をつけてます)を対象に、ソートしています。
各引数ですが、以下のようになっています。

  • ascending:ソート順。Trueなら昇順、Falseなら降順
  • inplace:このメソッドだけでなくDataFrameのメソッドに多くありますが、Trueを指定することで破壊的にデータを変更できます。(元の変数、この場合df_newを直接ソート後のデータで上書きできます)

ファイルのやりとり

CSV形式で出力する

df_new.to_csv('result.csv', encoding='utf-8')

DataFrame.to_csv()で、DataFrameの中身をCSV形式で出力します。
この場合df_newのデータを、「result.csv」というファイル名で出力しています。
encodingで、エンコード方式を指定できます。
前回のエントリーでやったようなどこかのサーバにリクエストを飛ばしてデータを入手した時は、
このようにファイル出力すると、再度またアクセスする必要がなくなるので、利便性及びデータ提供側のサーバへの負担を減らす、といった意味で良いかと思われます。

まとめ

まだまだ色々なメソッドがありますが、使ったメソッドを抜粋してみました。
pandasはデータ分析には欠かせないライブラリだと思うので、とりあえず色々触ってみると利用意図を実感できながら覚えられるかと。
今後も、便利そうなメソッドや利用方法があればブログにのっけていきたいと思います。

[Python] requestsとpandasで野球の打席結果を取得する①

こんにちは。

今シーズンもあっという間に終わってしまいました。
我がヤクルトは前年の雪辱を果たし、見事CS出場を成し遂げました!
若手も少しずつ頭角を現し始め、先日のドラフトも含めて早くも来シーズンが待ち遠しいです…
 
本日は、シーズンが終わって野球のデータも揃ったということで、Pythonを通して野球のデータをワイワイする(雑)話をしたいと思います。
 

今日の目的

PythonでHTTPリクエストを簡単に作成できるライブラリ「requests」と、同じくPythonでデータを取り扱う際に様々な機能を提供してくれるライブラリ「pandas」を使って、野球のデータをpython上で取得、加工するまでを行います。
本エントリーではデータを「pandas」の「DataFrame」*1に落とし込むところまで進めます。

プログラミング環境

「Jupyter Notebook」を使います。pandasを使う際にいい感じで可視化してくれます。

データ提供してくださるサイト様

「データで楽しむプロ野球」様です。(リンクフリーなので引用させて頂きました。)
baseballdata.jp

細かなデータが充実していて、見ていて飽きないサイトです。
選んだ決め手は、選手ごとの全打席結果(今シーズンだけでなく、2011〜2017シーズンまでのものも)が載っているからです。

バージョン情報

Python及びモジュールのバージョンは下記です。仮想環境を作成して、pipでインストールします。

Python: 3.6
pandas: 0.19.2
requests: 2.19.1
jupyter: 1.0.0
notebook: 5.0.0

データを探す手段について

データを得るための手段はいくつかあって

  • 公開されているAPIから取得する(今回はこれ)
  • CSVExcel形式等のファイルをダウンロードする
  • WEBページからスクレイピングする

これらが主に該当するかと思われます。
その中で一つ目の「公開されているAPIから取得する」という方法を今回はとります。
この手段はサイトによって異なるので、ページを巡ってデータファイルがダウンロードできる場所を探したり、
ブラウザの開発者ツール(Chromeなら、WindowsであればF12キー、macであればcommand + option + Iキーで表示出来ます)を用いて
ソースやレスポンス内容を確認する必要があります。

※注 リクエストを送る行為は、過度にやるとデータ提供者の環境に迷惑をかけることになるので、for文の中に入れるなどして短期間に繰り返し行うのは、避けましょう。

実際に取得してみる

では、実際にデータのを「データで楽しむプロ野球」様から取得したいと思います。

まずはインポート

import pandas as pd
import requests
import json

そしてサイトの調べたい選手の全打席ページで、開発者用ツールを開くと、テキストファイルから全打席データを取ってきていることがわかると思います。*2
なので、こんな感じでリクエストを送ります*3

response = requests.get('http://baseballdata.jp/playerB/1000132S.txt')

ざっくり解説すると、「response」という変数に上記URLに対してGETメソッドのHTTPリクエストを送った時の、返却値を代入しています。
GETメソッドも、requestsなら「.get」という見ただけでわかるようなメソッドで簡単に実行出来ます。

また、日本語が混ざっていてかつ、文字コードを指定していないページなので、エンコードします。*4

response.encoding = response.apparent_encoding

レスポンスヘッダに文字コード情報がないので、apparent_encodingを実行することで、文字コード推定のモジュール(chardet)が働いて
正しくエンコードしてくれます。

この時点で、データが取得出来ています。pandas用に加工はされていませんが…
中身は下のコードをJupyter上で実行すると表示されます。(下記画像参照)

response.text

f:id:kumappp:20181108014809p:plain

みたことのある様な単語がチラチラありますが、このままじゃよくわかりませんね…笑
よくみると、一応JSONの形式にはなっています。
あと、HTMLタグが散見されますが、こちらのサイト様の仕様なので、一旦置いときます。

あとは、下記の様にpandasのメソッドでデータを読み込むだけで、上記のデータがpandasのDataFrameに入ってきます。
こちらは、JSON形式のデータを読み込むメソッドです。

df = pd.read_json(response.text)

f:id:kumappp:20181108020657p:plain

これだけでpandasのDataFrameにサイト上のデータを取り込むことが出来ました!
…と言いたいところですが、これでは何のデータか一見ほとんど分かりませんね…
何はともあれ、これでデータの取得処理自体は終わりです。
これから、使いやすいよう、みやすいようデータを加工する必要が出てきます。
pandasのメソッドを駆使しますが、少し長いので次回で…

データを取得する・まとめ

requests及びpandasどちらもシンプルな書き方でデータを取得することが出来ました。
実際に何かしらのデータを取得する時は、対象データがAPIとして公開されていないものも多く、その場合はrequestsの結果として返される(であろう)
HTMLやXML構文からデータをスクレイピングする必要があったりするので、*5
もうちょっと複雑にはなるかと思います。
とはいえ、まずデータを取得する学習の触りには、こういったシンプルなAPIをまず扱ってみるとよいのかな、と思います。*6

これだけだと野球要素が薄いので、次回、データを加工し実際にサイトで出ているような形まで持っていきたいと思います!

では一旦、また。

*1:pandasの強力な武器で、2次元データを整形して表示してくれる

*2:テキスト形式なのはわりと特殊な例だと思います

*3:これはヤクルトの山田選手の全打席ページです、他の選手をみたい方は別途URLをはっつけてください

*4:参考: https://kanji.hatenablog.jp/entry/python-requests-beautifulsoup-encoding

*5:有名なスクレイピングライブラリにBeautifulSoup等がある

*6:データの中身はシンプルとは言い難いですが笑

PyconJPに行って来ました

こんにちは。

徐々に涼しくなり、過ごしやすい季節になって来ました。

野球のシーズンももう大詰めですね…

 

さて、本日は9/17(月)と9/18(火)に行われた#pyconjpの感想を書こうと思います!

f:id:kumappp:20181008023502j:plain

※これは会場で配られてたお菓子ですw

 

TL;DR

  • 初めての参加でしたが有意義な時間になりました!
  • Djangoの話が色々聞けたのは良かった
  • 会場で話せた方からも色々な話を聞けてモチベーションが上がりました!

 

 セッションについて

セッションは下記のものを聴いてきました!

1日目

  • Keynote)Argentina in Python:community, dreams, travels and learning
  • (招待講演)東大松尾研流実践的AI人材育成法
  • DjangoではじめるPyCharm実践入門
  • Django REST FrameworkにおけるAPI実装プラクティス
  • Pythonで時系列のデータを分析してみよう。
  • Pythonistaの選球眼(せんきゅうがん) -エンジニアリングと野球の目利きになる技術

2日目

 

みごとにWeb系が多いですね(笑)

実際、今年のPyConのセッションではDjangoに関するセッションが一番多かったようです。

私は今年が初めての参加なので、今までの傾向であったり最近のPython事情について詳しいわけではないですが、最近周りでもPythonでWeb、特にDjangoの話題はよく耳にする気がします。私も今回これ目当てでセッション巡ってたわけですし。

 

 感想ですが、上述したようにWeb系のセッション目当てだったので、いい収穫を得られたと思います。なぜWeb系というと、最近よくjupyter notebookやpandasを使ってデータをいじくっているのですが(下図参照)、将来的にいい感じのUI実装して外部に公開できたらなーと思ってたいので、後学の為に聞きたいと思っていました。

Web系の中でも、REST API実装については特に興味があったので、1日目の「Django REST FrameworkにおけるAPI実装プラクティス」、「Pythonistaの選球眼(せんきゅうがん) -エンジニアリングと野球の目利きになる技術」、2日目の「Djangoだってカンバンつくれるもん(Django Channels + Vue)」、「Notebook as Web API: Turn your notebook into Web API」の4つのセッションは特に興味深く聞かせて頂きました。

ちなみに、現在勉強がてらやっているデータの可視化例がこちら↓

f:id:kumappp:20181008025639p:plain

 

これは、山田選手の打席結果を今年(10/7時点)と2017年それぞれ集約し、棒グラフに出したものです。*1

今でこそただデータを表示しているだけですが、ここにインタラクティブにデータを操作できるような機能の実装だとか、*2利用する側の需要というか、目的を考えたときに、JavaScriptだとかそのフレームワークだとかでいい感じのUI実装できるといいなと。

そういう意味では、「Notebook as Web API: Turn your notebook into Web API」では今まさにjupyter notebookでやってることをそのままAPI技術の話を聞くことが出来ましたし、「Pythonistaの選球眼(せんきゅうがん) -エンジニアリングと野球の目利きになる技術」では上述した自分のやりたいことを実際やっているお話を聞けたので、大変有意義な時間になりました。

 

総合的な感想

何度か言いましたが、初めての参加だったので、自分の目的に沿えるか不安でしたが、(カンファレンス自体は、大学生の時に何回か行ったことありますけど、それっきり)今でははっきりと、行ってよかったなと思えます。

収穫があったのもそうですが、今まで何度かもくもく会や勉強会でお会いした方とも話せて、そこからさらに知り合う人も増えて、色々な方からPythonに関する熱いお話を聞けたので、自分の刺激にも、モチベーションにもなりました。

来年も開催されると思うので、絶対参加したいと思います。

 

この経験をふまえて

とりあえずはDjangoの勉強!

そしてDjango REST Framework周りを学んでAPIの作成!

そこからフロントサイドに手をつけようかと思います。(経験のあるVue.jsかな)

並行して、まだまだデータ分析も学ぶことが多いので、野球のデータを交えながら学んでいきたいと思います。*3

 

ここまで読んでくださり、ありがとうございました!

もうちょい更新頻度上げられるよう頑張ります!*4

*1:この実装についても、ブログにまとめようかと

*2:このグラフ可視化ライブラリはBokehを使ってるので、この時点でもインタラクティブな操作ができているのはナイショ

*3:もうシーズン終了なのでデータも出揃います、早いなあ…

*4:アウトプット大事!