ビジネスパーソン・ガジェット置場 empty lot for business

営業や仕事、それに伴う生活を便利に楽にするツール、ガジェットを作ります。既にあるツールも自分用にカスタマイズ。

pythonで大谷さんの今シーズンの成績を記録する

今回のガジェットはエンゼルスの大谷さんの活躍が毎日楽しみでしょうがない僕のためのガジェットです。今シーズンはぐっと我慢の試合も多い感じなのですが、データを見ると実は安定していたり、やっぱ記録を見るとすごいなと思います。実際このプログラムを作ったのはシーズン開始してすぐくらいだったけど、毎日毎日、自動で動いてくれておりまして、しっかりデータを取ってきてくれております。

python&baseball

例えばこんな状況で

この記事を書いている今日は、今シーズン3度目の1試合2ホーマー!いや!最高です!!そんな大谷さんの情報はヤフーさんが毎日更新してくれているのですが、直近6試合とかの表示なので、日々の変化という点で焦点を当てるとやはり毎日毎日、せっせとその情報をとってきておかないといけないなということで、毎日定期的に1度だけスクレイピングでデータをいただいてきています。そしてそれをまとめて、可視化しています。今シーズンの大谷さん追い込まれちゃうとちょっと厳しい、とか、調子悪いのかなと思いつつも実際データはそこまで悪くないとか色々見えます。この前スタメンを外れて九回に代打で出てきた時のMVPコール!痺れました!!!いずれは骨格なんかを画像で調べてデータを出していきたいですね!! 

 

www.youtube.com

 

このガジェットでできることはこちら

・毎日定期的にyahooスポーツさんの大谷さんのページの情報を取得しにいきます。

・取得した情報をそれぞれ処理してcsvにまとめていきます。

・まとめたデータはstreamlitを使用して可視化することができます。

 

評価(自己)

役立ち度  ★★★(平均)
効率化   ★★★(平均)
ミス防止  ★★★(平均)
楽しさ   ★★★★★(最高)
操作    ★★★★(良し)

※ファンとしては見ていて最高なのですがかなり自己満足です。。csvファイルいっぱいなので若干効率化にはなっていないかもですが、一旦セットすれば後はもう!

 

実行環境

使用環境    streamlit 
使用言語    python
使用ライブラリ  numpy、pandas、BeautifulSoup、altair

 

 

コード

 

※こっちがデータを取得してまとめるコードです。(automaterで毎日動かしてます)

import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup

def main():
    url = 'https://baseball.yahoo.co.jp/mlb/player/2100825/top'
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'html.parser')
    
    # ①今シーズンの投手、打者、得点圏の成績用のデータフレーム
    base_names = ['pitch_df', 'hit_df', 'scorer_df']
    # ②今シーズンの、投手「最近6試合・対チーム別成績・月別成績・球場別成績」、打者「最近6試合・対チーム別成績・月別成績・球場別成績・カウント別・塁上別」
    names = ['recent_p_result', 'byteam_p_result', 'monthly_p_result', 'byfield_p_result', 'recent_h_result', 'byteam_h_result', 'monthly_h_result', 'byfield_h_result', 'bycount_h_result', 'bybase_h_result']
    # ③通算の投手成績、打者成績
    years_title_names = ['pitch_title', 'hit_title']
    idxs = [4, 5]
    nums = [30, 26]

    # ①を一度に取得
    results = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[:3]
    for base_name, result in zip(base_names, results):
        elems_th = result.find_all('th')
        keys = [elem_th.text for elem_th in elems_th]
        elems_td = result.find_all('td')
        values = [elem_td.text for elem_td in elems_td]
        _df = {}
        for key, value in zip(keys, values):
            _df[key] = value
        # 変数名を動的に決定
        exec(f'{base_name} = pd.DataFrame([_df])')


    conditional_results = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[3]
    tables = conditional_results.find_all('table')
    extract_data = tables[7]
    del tables[7]

    # ②を一度に取得
    for name, table in zip(names, tables):
        elems_th = table.find_all('th')
        cols = [elem_th.text for elem_th in elems_th]
        elems_td = table.find_all('td')
        vals = [elem_td.text for elem_td in elems_td]
        new_vals = []
        for val in vals:
            new_vals.append(val.replace('\n', ''))
        chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
        # 変数名を動的に取得
        exec(f'{name} = pd.DataFrame(chuncked_vals, columns=cols)')
        
    # 右投手・左投手ごとの成績
    elems_th = extract_data.find_all('th')
    cols = [elem_th.text for elem_th in elems_th]
    elems_td = extract_data.find_all('td')
    vals = [elem_td.text for elem_td in elems_td]
    vals.insert(12, vals[0])
    vals.insert(36, vals[24])
    new_vals = []
    for val in vals:
        new_vals.append(val.replace('\n', ''))
    chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
    bydominant_arms_result = pd.DataFrame(chuncked_vals, columns=cols)

    
    # ③を一度に取得
    for year_title_name, idx, num in zip(years_title_names, idxs, nums):
        years_title = soup.find_all('section', attrs={'class': 'bb-modCommon01'})[idx]
        elems_th = years_title.find_all('th')[:num]
        cols = [elem_th.text for elem_th in elems_th]
        elems_td = years_title.find_all('td')
        vals = [elem_td.text for elem_td in elems_td]
        new_vals = [val.replace('\n', '') for val in vals]
        chuncked_vals = np.array_split(new_vals, len(new_vals)/len(cols))
        totals = years_title.find_all('th')[num:]
        total_vals = [total.text for total in totals]
        new_total_vals = [total_val.replace('\n', '') for total_val in total_vals]
        new_total_vals.insert(1, '-')
        total_title = np.array(new_total_vals)
        chuncked_vals.append(total_title)
        exec(f'{year_title_name} = pd.DataFrame(chuncked_vals, columns=cols)')

    # 各データフレームの名前を変数名用にリスト化
    df_lists = base_names + names + years_title_names
    df_lists.append('bydominant_arms_result')

    # リスト化した名前から動的に変数名を呼び出しcsvファイルとして書き出し
    for df_list in df_lists:
        exec(f'{df_list}.to_csv(df_list+".csv", index=False)')
        
    # 日々の積み上げ打撃成績をデータフレーム化
    df_dailyh = pd.read_csv('recent_h_result.csv')
    stacked_h_result = pd.read_csv('h_result.csv')
    df_hit = pd.read_csv('hit_df.csv')
    df_today = pd.DataFrame(df_dailyh.iloc[0, :])
    df_today = df_today.T
    df_today['打率'] = df_hit['打率']
    stacked_h_result = pd.concat([stacked_h_result, df_today])
    stacked_h_result = stacked_h_result.drop_duplicates('日付')
    stacked_h_result.reset_index(drop=True)
    stacked_h_result.to_csv('h_result.csv', index=False)
    
    # 日々の積み上げ投手成績をデータフレーム化
    df_dailyp = pd.read_csv('recent_p_result.csv')
    stacked_p_result = pd.read_csv('p_result.csv')
    df_todayp = pd.DataFrame(df_dailyp.iloc[0, :])
    stacked_p_result = pd.concat([stacked_p_result, df_todayp.T])
    stacked_p_result = stacked_p_result.drop_duplicates('日付')
    stacked_p_result.reset_index(drop=True)
    stacked_p_result.to_csv('p_result.csv', index=False)
        

if __name__ == '__main__':
    main()

 

こっちがstreamlitで可視化する方のコードです。

import pandas as pd
import streamlit as st
from glob import glob
import altair as alt

st.title("Ohtani's season")

    
st.write('### ■今シーズンの打者成績')
df_hit = pd.read_csv('hit_df.csv')
result_hit = st.multiselect(
    '項目を選択してください。',
    list(df_hit.columns),
    ['打率', '安打', '本塁打', '盗塁', '三振', 'OPS']
)
if not result_hit:
    st.error('少なくとも一つの項目は選んでください')
else:
    data = df_hit.loc[:,result_hit]
    data.columns = '   ,   '.join(data.columns).split(',')
    st.dataframe(data)
    
st.write('##### 月別')
m_hit = pd.read_csv('monthly_h_result.csv')
m_result_hit = st.multiselect(
    '項目を選択してください。',
    list(m_hit.columns),
    ['月', '打率', '安打', '本塁打', '盗塁', '三振', 'OPS']
)
if not m_result_hit:
    st.error('少なくとも一つの項目は選んでください')
else:
    data = m_hit.loc[:,m_result_hit]
    data.columns = '   ,   '.join(data.columns).split(',')
    st.dataframe(data)
 
    
st.write('#### 打者成績積み上げ')
hit_stacked = pd.read_csv('h_result.csv')
hit_stacked.columns = '   日付  ', '     対戦チーム ', '   打数', '   安打', '   本塁打', '   打点', '   得点', '   三振', '   四球', '   死球', '     打席結果    ', '   打率  '
st.dataframe(hit_stacked)


st.write('#### 各打撃結果推移を見る')
ymin1 = 0
ymax1 = 1
ymin2 = 0
ymax2 = 7

hit_graph = pd.read_csv('h_result.csv')
kind = st.selectbox('項目を選んでください', hit_graph.columns[3:], index=0)
base = alt.Chart(hit_graph).encode(
    alt.X('日付:N', axis=alt.Axis(title=None))
).properties(
    width=600, height=400, title='成績推移')

line1 = base.mark_line(opacity=0.3, color='#57A44C').encode(
    alt.Y('打率:Q',
          axis=alt.Axis(title='打率', titleColor='#57A44C'),
          scale=alt.Scale(domain=[ymin1, ymax1])
         )
)
line2 = base.mark_line(stroke='#5276A7', interpolate='monotone').encode(
    alt.Y(f'{kind}:Q',
          axis=alt.Axis(title='種類', titleColor='#5276A7'),
          scale=alt.Scale(domain=[ymin2, ymax2])
         )
)
chart = alt.layer(line1, line2).resolve_scale(
    y = 'independent'
)
st.altair_chart(chart, use_container_width=True)


st.write('#### カウント別打撃成績')
bycount_result = pd.read_csv('bycount_h_result.csv')

chart3 = alt.Chart(bycount_result).mark_bar().encode(
    x=alt.X('カウント:N', title='カウント(ボール-ストライク)'),
    y=alt.Y('打率:Q', title='打率')
).properties(
    width=400, height=300, title='地域別平均馬力')
st.altair_chart(chart3, use_container_width=True)



st.write('### ■今シーズンの投手成績')
st.write('##### シーズン通算')
df_pitch = pd.read_csv('pitch_df.csv')
result_pitch = st.multiselect(
    '項目を選択してください。',
    list(df_pitch.columns),
    ['防御率', '登板', '勝利', '敗戦', '奪三振']
)
if not result_pitch:
    st.error('少なくとも一つの項目は選んでください')
else:
    data = df_pitch.loc[:,result_pitch]
    data.columns = '   ,   '.join(data.columns).split(',')
    st.dataframe(data)

st.write('##### 月別')
m_pitch = pd.read_csv('monthly_p_result.csv')
m_result_pitch = st.multiselect(
    '項目を選択してください。',
    list(m_pitch.columns),
    ['月', '防御率', '登板', '勝利', '敗戦', '奪三振']
)
if not m_result_pitch:
    st.error('少なくとも一つの項目は選んでください')
else:
    data = m_pitch.loc[:,m_result_pitch]
    data.columns = '   ,   '.join(data.columns).split(',')
    st.dataframe(data)



st.write('#### 投手成績積み上げ')
pitch_stacked = pd.read_csv('p_result.csv')
pitch_stacked.columns = '   日付   ', '   対戦チーム    ', '   登板', '   結果', '   投球回', '   投球数', '   打者', '   被安打', '   被本塁打', '   奪三振','   与四球', '   与死球', '   暴投', '   ボーク', '   失点', '   自責点'
st.dataframe(pitch_stacked)

st.write('#### 各投球結果推移を見る')
p_ymin1 = 0
p_ymax1 = 120
p_ymin2 = 0
p_ymax2 = 20

pitch_graph = pd.read_csv('p_result.csv')
p_kind = st.selectbox('項目を選んでください', pitch_graph.columns[6:], index=3)
base = alt.Chart(pitch_graph).encode(
    alt.X('日付:N', axis=alt.Axis(title=None))
).properties(
    width=600, height=400, title='成績推移')

line1 = base.mark_line(opacity=0.3, color='#57A44C').encode(
    alt.Y('投球数:Q',
          axis=alt.Axis(title='打率', titleColor='#57A44C'),
          scale=alt.Scale(domain=[p_ymin1, p_ymax1])
         )
)
line2 = base.mark_line(stroke='#5276A7', interpolate='monotone').encode(
    alt.Y(f'{p_kind}:Q',
          axis=alt.Axis(title='種類', titleColor='#5276A7'),
          scale=alt.Scale(domain=[p_ymin2, p_ymax2])
         )
)
chart2 = alt.layer(line1, line2).resolve_scale(
    y = 'independent'
)
st.altair_chart(chart2, use_container_width=True)


以上です! 頑張れ大谷さん!!今年もMVP!!