[Python] 236 MySQL 04 馬名からレース検索

[Python] 235の続きです。

さすがに1万ファイルは多かったようで良く見るとphpMyAdminが白旗をあげていました。

そこでレース結果ファイルを年毎に1つにまとめてインポートしました。

これでレース検索するとraceID検索までで2秒程度でした。しかし結合した年ファイルであればCSV横断検索でも同じ位です。

とは言えSQLによる値の取り出しやすさは捨てがたいものがあり、horseIDの取得からレース検索完了までフルでMySQLを使ったらどんな感じになるかか試してみたいです。

Target frontierはJRA-VANのデータを元に独自データベースとして機能しているわけですから、過度にSQLにこだわる必要もなさそうです。

ただあちらはC言語用のテキストデータなのでより高速なのは間違いないです。

# horseIDはCSV横断検索、receIDはSQL検索
import mysql.connector,glob,csv,re,datetime,sys

config = {
  'user': 'root',
  'password': 'root',
  'host': 'localhost',
  'port': 3306,
  'raise_on_warnings': True
}

print('検索したい馬名を入力してください')
name = input()

<中略>

print(f'検索馬のID {id} 誕生年 {id[0:4]}')

start_year = int(id[0:4]) + 2
print(f'推定デビュー年 {start_year}')

conn = mysql.connector.connect(**config)
cur = conn.cursor()

raceID_l = list()
for y in range(start_year,2017 +1):
	table = f'race_r_{y}'

	try:
		cur.execute(f'SELECT raceID FROM horse_race_result.{table} WHERE horseID = {id}')

	finally:
		result = cur.fetchall()
		if result != []:
			raceID_l.append(result)

# ネストを平滑化する
raceID_l_flatten = [e for l in raceID_l for e in l]

print(f'出走数 {len(raceID_l_flatten)}')
print(raceID_l_flatten)

cur.close()
conn.close()
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
キタサンブラック
[['201202013', 'キタサンブラック']]
検索馬のID 201202013 誕生年 2012
推定デビュー年 2014
出走数 20
[('r201505010105',), ('r201505010807',), ('r201505021210',), ('r201506020811',), ('r201506030811',), ('r201506040511',), ('r201506050810',), ('r201508040711',), ('r201605050811',), ('r201606050910',), ('r201608030411',), ('r201608040311',), ('r201609020411',), ('r201609030811',), ('r201705040911',), ('r201705050811',), ('r201706050811',), ('r201708030411',), ('r201709020411',), ('r201709030811',)]

[Python] 235 MySQL 03 CSVを一括インポートする

1万を超えるレースデータを一括インポートしてみました。

実際は最初のfor文を回さず年ごとに3回に分けてインポートしました。1〜2KB約3400ファイルで2分掛からなかった感じです。

インポート直後にはphpMyAdminがダウンしますが、MAMPを再起動すると正常に動きました。

私が扱うデータ量程度ではMySQLのキャパを超えることはないと思います。

これからせっせとデータを入れていきます。

import csv,mysql.connector,glob
import pandas as pd

config = {
    'user': 'root',
    'password': 'root',
    'host': 'localhost',
    'port': 3306,
    'raise_on_warnings': True
}

for year in range(2015,2017 +1):
    # DB化するCSVファイルのパスとTable名(拡張子なしのファイル名)をリスト化する
    file_l = list()
    table_l = list()
    for file in glob.glob(f'/horse_racing/race_result/{year}/*/*/*.csv'):
        file_l.append(file)
        table_l.append(file[-23:-4])

    # CSVファイルの列タイトルをリストにする
    df = pd.read_csv(file_l[0], encoding='Shift-JIS',header = None)
    column_title = list(df.loc[0])

    column_type = ['varchar(10)','int(2)','int(2)','varchar(200)','varchar(200)','float','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(10)','varchar(10)','varchar(10)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(200)','varchar(13)','int(9)']

    # SQL文に使う列タイトルの文字列を作成する(角括弧を丸括弧に置換えるなど)
    column_l = list()
    for ti,ty in zip(column_title,column_type):
        column = ti + ' ' + str(ty)
        column_l.append(column)

    column_l_str = str(column_l).replace('[','(').replace(']',')').replace("'",'').replace('(万円)','')

    # sqlのリストを作成
    sql_l = list()
    for table in table_l:
        sql = f"create table horse_race_result.{table} {column_l_str}"
        sql_l.append(sql)

    # mysqlに接続
    conn = mysql.connector.connect(**config)
    cur = conn.cursor()

    # データベースhorse_race_resultにtableを作成する
    for file,table,sql in zip(file_l,table_l,sql_l):
        cur.execute(sql)
        cur.execute('begin')

        # CSVファイルを読み込み、各行をtableに挿入する
        with open(file, 'rt', encoding='Shift-JIS') as f:
            reader = csv.reader(f)
            for i,row in enumerate(reader):
                print(f'row {row}')
                if i != 0:
                    row_str = str(row).replace('[','(').replace(']',')')
                    print(row_str)
                    sql = f'insert into horse_race_result.{table} values {row_str}'
                    print(sql)
                    cur.execute(sql)

        cur.execute('commit')

    conn.close()

[Python] 234 複数のCSVファイル内検索 馬名からレース検索 リスト内包表記

一番時間の掛かるraceID検索のところをリスト内包表記で書いてみました。

そもそもopen文を組み込むことができるのか懐疑的だったものの、あっさり書けました。こんなややこしいのを共同開発の現場で見せられたらたまりませんね。

残念ながら時間短縮はならずです。C言語で書いてもそんなに変わらなさそうですが、短いコードなので検討してみても面白そうです。

元コード for文
--------------------------------------------------
raceID_l = list()
for y in range(start_year,2021 +1):
    for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[22] == id:
                    raceID_l.append(row[21])
--------------------------------------------------

リスト内包表記
--------------------------------------------------
raceID_l = [row[21] for y in range(start_year,2021 +1) \
for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv') \
for row in csv.reader(open(file,mode="r", encoding="shift_jis")) \
if row[22]==id]
--------------------------------------------------

[Python] 233 複数のCSVファイル内検索 馬名からレース検索 高速化

前回のコードは見るからに効率の良くなさそうな内容だったので書き直しました。

pandasはやめてcsvモジュールで処理しました。

その結果、80秒が5秒に短縮されました。やはりこの種の処理にpandasを使うのはナンセンスだったようです。

この速さなら本式のデータベースにする程でもないように思いますが、迷うところです。

import glob,csv,re,datetime

print('検索したい馬名を入力してください')
name = input()

horseID_l = list()
for year in range(1986,2019 +1):
    for file in glob.glob(f'/Volumes/DATA_HR/horse_racing/horse_list/horse{year}.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[1] == name:
                    horseID_l.append([row[0],row[1]])

print(f'{horseID_l}')

if len(horseID_l) >= 2:
    print('該当する馬が複数います。番号を入力してください。')
    for i,data in enumerate(horseID_l):
        print(f'{i+1} {data}')
    num = input()
    id = horseID_l[int(num)-1][0]

elif len(horseID_l) == 0:
    print('該当する馬はいません')
    sys.exit()

else:
    id = horseID_l[0][0]

print(f'検索馬のID {id} 誕生年 {id[0:4]}')

raceID_l = list()
start_year = int(id[0:4]) + 2
print(f'推定デビュー年 {start_year}')
for y in range(start_year,2021 +1):
    for file in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv'):
        with open (file, mode="r", encoding="shift_jis") as f:
            reader = csv.reader(f)
            for row in reader:
                if row[22] == id:
                    raceID_l.append(row[21])

print(raceID_l)

race_l = list()
for id in raceID_l:
    for y in range(start_year,2021 +1):
        for file in glob.glob(f'/horse_racing/race_name/{y}/*.csv'):
            with open (file, mode="r", encoding="shift_jis") as f:
                reader = csv.reader(f)
                for row in reader:
                    if row[9] == id:
                        date = row[0]
                        racename = row[3]
                        date_dt = datetime.datetime.strptime(date,'%Y年%m月%d日')
                        date_d = datetime.date(date_dt.year, date_dt.month, date_dt.day)

                        race_l.append([id,date,racename,date_d])

print(f'出走数 {len(race_l)}')
print(sorted(race_l, key=lambda x: x[3]))

[Python] 232 複数のCSVファイル内検索 馬名からレース検索 修正版

[Python] 231のコードを修正しました。

これでキタサンブラックの全20走が抽出できました。

修正前
--------------------------------------------------
b_array = df[df.columns[22]]== id
--------------------------------------------------

修正後
--------------------------------------------------
id_l = df[df.columns[22]].tolist()

if '該当なし' in id_l:
    b_array = df[df.columns[22]]== str(id)
else:
    b_array = df[df.columns[22]]== id
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
キタサンブラック
year 2012 index 3012
キタサンブラック [[2012, 201202013, 'キタサンブラック']]
検索馬のID 201202013 誕生年 2012
推定デビュー年 2014
['r201505010105', 'r201505010807', 'r201505021210', 'r201506020811', 'r201506030811', 'r201506040511', 'r201506050810', 'r201508040711', 'r201605050811', 'r201606050910', 'r201608030411', 'r201608040311', 'r201609020411', 'r201609030811', 'r201705040911', 'r201705050811', 'r201706050811', 'r201708030411', 'r201709020411', 'r201709030811']
出走数 20
[['r201505010105', '2015年1月31日', '3歳新馬', datetime.date(2015, 1, 31)], \
['r201505010807', '2015年2月22日', '3歳500万下', datetime.date(2015, 2, 22)], \
['r201506020811', '2015年3月22日', '第64回フジTVスプリングS(G2)', datetime.date(2015, 3, 22)], \
['r201506030811', '2015年4月19日', '第75回皐月賞(G1)', datetime.date(2015, 4, 19)], \
['r201505021210', '2015年5月31日', '第82回東京優駿(G1)', datetime.date(2015, 5, 31)], \
['r201506040511', '2015年9月21日', '第69回朝日セントライト記念(G2)', datetime.date(2015, 9, 21)], \
['r201508040711', '2015年10月25日', '第76回菊花賞(G1)', datetime.date(2015, 10, 25)], \
['r201506050810', '2015年12月27日', '第60回有馬記念(G1)', datetime.date(2015, 12, 27)], \
['r201609020411', '2016年4月3日', '第60回産経大阪杯(G2)', datetime.date(2016, 4, 3)], \
['r201608030411', '2016年5月1日', '第153回天皇賞(春)(G1)', datetime.date(2016, 5, 1)], \
['r201609030811', '2016年6月26日', '第57回宝塚記念(G1)', datetime.date(2016, 6, 26)], \
['r201608040311', '2016年10月10日', '第51回京都大賞典(G2)', datetime.date(2016, 10, 10)], \
['r201605050811', '2016年11月27日', '第36回ジャパンカップ(G1)', datetime.date(2016, 11, 27)], \
['r201606050910', '2016年12月25日', '第61回有馬記念(G1)', datetime.date(2016, 12, 25)], \
['r201709020411', '2017年4月2日', '第61回大阪杯(G1)', datetime.date(2017, 4, 2)], \
['r201708030411', '2017年4月30日', '第155回天皇賞(春)(G1)', datetime.date(2017, 4, 30)], \
['r201709030811', '2017年6月25日', '第58回宝塚記念(G1)', datetime.date(2017, 6, 25)], \
['r201705040911', '2017年10月29日', '第156回天皇賞(秋)(G1)', datetime.date(2017, 10, 29)], \
['r201705050811', '2017年11月26日', '第37回ジャパンカップ(G1)', datetime.date(2017, 11, 26)], \
['r201706050811', '2017年12月24日', '第62回有馬記念(G1)', datetime.date(2017, 12, 24)]]
--------------------------------------------------

[Python] 231 複数のCSVファイル内検索 馬名からレース検索

馬名から馬名IDを取得し、さらに出走した全てのレースIDを取得して、日付とレース名を出力させました。

下のコードでは登録外の外国馬が出走するジャパンカップ2走分が漏れてしまいます。レースファイルの馬名IDに’該当なし’が含まれているとおかしくなるようです。これを00000など適当な数字に変えればまともに動くと思います。

なお検索時間は1分20秒でした。メモリをふんだんに使ったいわゆる富豪的コードなので仕方ないでしょう。

データベースに移したらどれだけ速くなるのか楽しみです。

目標は1秒ですが3秒位までは許容範囲としたいです。

import glob,csv,re,datetime,sys
import pandas as pd

print('検索したい馬名を入力してください')
name = input()

horseID_l = list()
for year in range(1986,2019 +1):
    namefile = f'/horse_racing/horse_list/horse{year}.csv'

    df = pd.read_csv(namefile,encoding="shift_jis")

    # 馬名ファイルの馬名と検索馬名が完全一致した場合にTrueとする縦向き配列を作成
    b_array = df[df.columns[1]]==name

    # ブール値の横向き配列として取り出しリスト化
    b_array_v = b_array.values.tolist()

    # Trueのインデックス値を算出しhorseIDを取得
    try:
        i = b_array_v.index(True)
    except:
        pass
    else:
        print(f'year {year} index {i}')
        horseID = df.iloc[i,0]
        horsename = df.iloc[i,1]

        horseID_l.append([year,horseID,horsename])

print(f'{name} {horseID_l}')

if len(horseID_l) >= 2:
    print('該当する馬が複数います。番号を入力してください。')
    for i,data in enumerate(horseID_l):
        print(f'{i+1} {data}')
    num = input()
    id = horseID_l[int(num)-1][1]
    year = horseID_l[int(num)-1][0]

elif len(horseID_l) == 0:
    print('該当する馬はいません')
    sys.exit()

else:
    id = horseID_l[0][1]
    year = horseID_l[0][0]

print(f'検索馬のID {id} 誕生年 {year}')

raceID_l = list()
start_year = year + 2
print(f'推定デビュー年 {start_year}')
for y in range(start_year,2021 +1):
    for f in glob.glob(f'/horse_racing/race_result/{y}/*/*/*.csv'):
        df = pd.read_csv(f,encoding="shift_jis")

        b_array = df[df.columns[22]]== id

        # ブール値の横向き配列として取り出しリスト化
        b_array_v = b_array.values.tolist()

        # Trueのインデックス値を算出しraceIDを取得
        try:
            i = b_array_v.index(True)
        except:
            pass
        else:
            raceID = df.iloc[i,21]

            raceID_l.append(raceID)

print(raceID_l)

race_l = list()
for id in raceID_l:
    for y in range(start_year,2021 +1):
        for f in glob.glob(f'/horse_racing/race_name/{y}/*.csv'):
            df = pd.read_csv(f,encoding="shift_jis")

            b_array = df[df.columns[9]]== id

            # ブール値の横向き配列として取り出しリスト化
            b_array_v = b_array.values.tolist()

            # Trueのインデックス値を算出しレース名を取得
            try:
                i = b_array_v.index(True)
            except:
                pass
            else:
                date = df.iloc[i,0]
                racename = df.iloc[i,3]
                date_d = datetime.datetime.strptime(date,'%Y年%m月%d日')

                race_l.append([id,date,racename,date_d])

print(f'出走数 {len(race_l)}')
print(sorted(race_l, key=lambda x: x[3]))
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
キタサンブラック
year 2012 index 3012
キタサンブラック [[2012, 201202013, 'キタサンブラック']]
検索馬のID 201202013 誕生年 2012
推定デビュー年 2014
['r201505010105', 'r201505010807', 'r201505021210', 'r201506020811', 'r201506030811', 'r201506040511', 'r201506050810', 'r201508040711', 'r201606050910', 'r201608030411', 'r201608040311', 'r201609020411', 'r201609030811', 'r201705040911', 'r201706050811', 'r201708030411', 'r201709020411', 'r201709030811']
出走数 18
[['r201505010105', '2015年1月31日', '3歳新馬', datetime.datetime(2015, 1, 31, 0, 0)], ['r201505010807', '2015年2月22日', '3歳500万下', datetime.datetime(2015, 2, 22, 0, 0)], ['r201506020811', '2015年3月22日', '第64回フジTVスプリングS(G2)', datetime.datetime(2015, 3, 22, 0, 0)], ['r201506030811', '2015年4月19日', '第75回皐月賞(G1)', datetime.datetime(2015, 4, 19, 0, 0)], ['r201505021210', '2015年5月31日', '第82回東京優駿(G1)', datetime.datetime(2015, 5, 31, 0, 0)], ['r201506040511', '2015年9月21日', '第69回朝日セントライト記念(G2)', datetime.datetime(2015, 9, 21, 0, 0)], ['r201508040711', '2015年10月25日', '第76回菊花賞(G1)', datetime.datetime(2015, 10, 25, 0, 0)], ['r201506050810', '2015年12月27日', '第60回有馬記念(G1)', datetime.datetime(2015, 12, 27, 0, 0)], ['r201609020411', '2016年4月3日', '第60回産経大阪杯(G2)', datetime.datetime(2016, 4, 3, 0, 0)], ['r201608030411', '2016年5月1日', '第153回天皇賞(春)(G1)', datetime.datetime(2016, 5, 1, 0, 0)], ['r201609030811', '2016年6月26日', '第57回宝塚記念(G1)', datetime.datetime(2016, 6, 26, 0, 0)], ['r201608040311', '2016年10月10日', '第51回京都大賞典(G2)', datetime.datetime(2016, 10, 10, 0, 0)], ['r201606050910', '2016年12月25日', '第61回有馬記念(G1)', datetime.datetime(2016, 12, 25, 0, 0)], ['r201709020411', '2017年4月2日', '第61回大阪杯(G1)', datetime.datetime(2017, 4, 2, 0, 0)], ['r201708030411', '2017年4月30日', '第155回天皇賞(春)(G1)', datetime.datetime(2017, 4, 30, 0, 0)], ['r201709030811', '2017年6月25日', '第58回宝塚記念(G1)', datetime.datetime(2017, 6, 25, 0, 0)], ['r201705040911', '2017年10月29日', '第156回天皇賞(秋)(G1)', datetime.datetime(2017, 10, 29, 0, 0)], ['r201706050811', '2017年12月24日', '第62回有馬記念(G1)', datetime.datetime(2017, 12, 24, 0, 0)]]

[Python] 230 複数のCSVファイル内検索 完全一致 その2

[Python] 229の続きです。

検索で複数ヒットした場合の絞り込み方法を追記しました。

馬名IDを絞り込めたら、次はレース検索に進みます。

# 前回コードの続き

if len(horseID_l) >= 2:
    print('該当する馬が複数います。番号を入力してください。')
    for i,data in enumerate(horseID_l):
        print(f'{i+1} {data}')
    num = input()
    id = horseID_l[int(num)-1][1]

elif len(horseID_l) == 0:
    print('該当する馬はいません')
    id = 'なし'

else:
    id = horseID_l[1]

print(f'検索馬のID {id}')
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
ヒシマサル
year 1989 index 8573
year 2014 index 6933
ヒシマサル [[1989, 198908574, 'ヒシマサル'], [2014, 201405934, 'ヒシマサル']]
該当する馬が複数います。番号を入力してください。
1 [1989, 198908574, 'ヒシマサル']
2 [2014, 201405934, 'ヒシマサル']
1
検索馬のID 198908574

[Python] 229 複数のCSVファイル内検索 完全一致

[Python]228のコードを完全一致で書いてみました。

検索先の馬名ファイルからは○地などの記号を削除しています。

import glob,csv,re
import pandas as pd

print('検索したい馬名を入力してください')
name = input()

horseID_l = list()
for year in range(1986,2019 +1):
    namefile = f'/Volumes/DATA_HR/horse_racing/horse_list/horse{year}.csv'

    df = pd.read_csv(namefile,encoding="shift_jis")

    # 馬名ファイルの馬名と検索馬名が完全一致した場合にTrueとする縦向き配列を作成
    b_array = df[df.columns[1]]==name

    # ブール値の横向き配列として取り出しリスト化
    b_array_v = b_array.values.tolist()

    # Trueのインデックス値を算出しhorseIDを取得
    try:
        i = b_array_v.index(True)
    except:
        pass
    else:
        print(f'year {year} index {i}')
        horseID = df.iloc[i,0]
        horsename = df.iloc[i,1]

        horseID_l.append([year,horseID,horsename])

print(f'{name} {horseID_l}')
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
ヒシマサル
year 1989 index 8573
year 2014 index 6933
ヒシマサル [[1989, 198908574, 'ヒシマサル'], [2014, 201405934, 'ヒシマサル']]

[Python] 228 複数のCSVファイル内検索 部分一致

1986年以降の馬データを対象とする検索コードを書いてみました。

出力されたindexとhorseIDの番号が1つずれていますがそういう仕様です。

今回は部分一致で実行しましたが、完全一致にしないと実用的ではないでしょう。

import glob,csv,re
import pandas as pd

print('検索したい馬名を入力してください')
name = input()

horseID_l = list()
for year in range(1986,2019 +1):
    namefile = f'/horse_racing/horse_list/horse{year}.csv'

    df = pd.read_csv(namefile,encoding="shift_jis")

    # 各行の馬名セルに検索馬名を含む場合にTrueとする縦向き配列を作成(部分一致)
    b_array = df[df.columns[1]].str.contains(name)

    # ブール値の横向き配列として取り出しリスト化
    b_array_v = b_array.values.tolist()

    # Trueのインデックス値を算出しhorseIDを取得
    try:
        i = b_array_v.index(True)
    except:
        pass
    else:
        print(f'year {year} index {i}')
        horseID = df.iloc[i,0]
        horsename = df.iloc[i,1]

        horseID_l.append([year,horseID,horsename])

print(f'{name} {horseID_l}')
--------------------------------------------------

出力
--------------------------------------------------
検索したい馬名を入力してください
ヒシマサル
year 1989 index 8573
year 1999 index 183
year 2014 index 6060
ヒシマサル [[1989, 198908574, '○外ヒシマサル'], [1999, 199900184, '□地ニシネヒシマサル'], [2014, 201405934, 'ヒシマサル']]

[Python] 227 CSVファイルを縦方向に結合する

CSVファイルを縦方向に結合するコードです。

縦方向に結合する場合は読み込み時にheader=Noneを付けて、書き込み時にheader=Falseを付けます。

headerがないと列インデックスが自動的に付加されるため、これに従ってデータが縦方向に並びます。

横方向に結合する場合は読み込み時、書き込み時ともにheaderオプションはいらないはずです。

なおformat文字列はかつての名残りです。

# filenameNとfilenamePを縦方向に結合する
# 最初にfilenameNのみのCSVファイルを作成し、結合したファイルで上書きする
# この方法ではfilenamePが存在しなくてもfilenameNのみのファイルが結合ファイルとして残る
# else以下は1つ上の階層に置く方が良いと思ったが、検証ではNGだったのでこれで仮確定させた

files = [filenameN , filenameP]

row_list = []
for file in files:
    try:
        row_list.append(pd.read_csv(file,header= None,encoding='Shift_JIS'))

    except FileNotFoundError:
        print('FileNotFoundError')
        print(f'ファイルがありません {file}')

    else:
        df = pd.concat(row_list, sort=False)

        filename = filename_horse(str(year),str(thousand),str('{0:05d}'.format(horse_number)))

        with open(filename, mode="w", encoding="cp932", errors="ignore") as f:
            df.to_csv(f,index= False,header= False)