Python-Skript, das MLB-Spieldaten im Wert von 15 Jahren in 10 Minuten in MySQL speichert (Baseball Hack!)

Was ich sagen will

** Das Python-Skript, dessen Erfassung und Erstellung von Übereinstimmungsdaten für ein Jahr etwas mehr als 20 Minuten dauerte, wurde so weit verbessert, dass Daten für 15 Jahre in 10 Minuten erstellt werden können. ** **.

Die Ergebnisse finden Sie in diesem Repository.

Shinichi-Nakagawa/py-retrosheet

Es ist uns gelungen, die Leistung gegenüber dem Original um das 20-fache zu verbessern!

Ich benutze jedoch nicht viel Technologie und warte auf Ihre Meinung.

Diese Geschichte ist eine Fortsetzung der Baseball-Geschichte von PyCon JP 2015.

Spielstart ~ Nach PyCon JP Ankündigung

Dies ist Qiitas erster Beitrag. Ich freue mich darauf, mit Dir zu arbeiten.

Auf der PyCon JP 2015 Baseball Hack! ~ Datenanalyse und -visualisierung mit Python Ich habe MLB-Daten heruntergeladen und migriert und sie mit IPython Notebook und Pandas analysiert und visualisiert! Ich stellte die Geschichte vor.

Darunter "[py-retrosheet](https://github.com/wellsoliver/py-retrosheet" py-retrosheet ")"

** Laden Sie die MLB-Spiel- und Spielerleistungsdaten "[RETROSHEET](http://retrosheet.org/" retrosheet.org ")" herunter und migrieren Sie zu Ihrer Lieblingsdatenbank! ** **.

Ich stellte die Bibliothek mit einem doy Gesicht vor, fand aber verschiedene unangenehme Dinge in dieser Bibliothek, obwohl es kein Fehler ist.

Prise! ~ py-retrosheet Dunkelheit

Ohne py-retrosheet könnte ich es nicht zu PyCon schaffen und nicht mit Joey Bot oder Adam Dan spielen. Ich bin den Autoren und Mitwirkenden der Bibliothek sehr dankbar, aber als ich versuchte, sie zu verwenden, hatte ich die folgenden nicht zu vernachlässigenden Probleme.

  1. Wie auch immer, der Prozess ist langsam!
  2. Die Wartbarkeit des Codes selbst ist sehr schlecht!
  3. Python 2-Serie, allgemein nur als "Legacy Python" bekannt, Python 3 wird nicht unterstützt

Retrosheet-Daten bestehen aus etwa 190.000 bis 200.000 Zeilen mit Aufzeichnungen pro Saison, aber wo machen Sie einen Umweg?

** Vom Herunterladen der Daten bis zum Erstellen der Datenbank dauerte es 20 Minuten. ** **.

Das Erstellen von 10.000 Datenzeilen dauert 1 Minute, was unmöglich ist.

Wenn Sie den Code für einen Moment lesen, können Sie für alle Datensätze zusätzliches Scraping durchführen, eine mysteriöse SQL-Anweisung ausgeben (z. B. eine Select-Anweisung mit einer eindeutigen Schlüsselprüfung auslösen, um eine Datenbank aus einem neuen Status zu erstellen) usw. Ich habe Zeilen nacheinander eingefügt und so weiter.

** Und fast kopieren und einfügen! ** **.

Da ich Python 3 persönlich zur Hauptentwicklung machen möchte, dachte ich, dass eine Bibliothek, die nur Python 2 unterstützt, das allgemein als "Legacy Python" bekannt ist, nicht cool wäre, und entschied mich daher, die Bibliothek zu verbessern.

Verbesserung! ~ Python 3 kompatibel und neu gemacht

Also gabelte ich sofort das ursprüngliche Repository und trug selbst bei. -Nakagawa / py-retrosheet "py-retrosheet") Bereit und begann sich zu verbessern. Verbesserungen wurden in der folgenden Reihenfolge vorgenommen.

  1. Unterstützt Python 3 unter Beibehaltung des ursprünglichen Codes
  2. ~~ Refactor das Erscheinungsbild des Originalcodes ~~ (Aufgeben) Erstellen Sie ein neues Py-Retrosheet für Python 3.5

Das Problem der langsamen Verarbeitung ist schwerwiegender als alles andere, und die ursprüngliche Reihenfolge soll von dort aus beginnen. Ich habe mich jedoch entschlossen, den Inhalt des Codes genau zu verstehen und den Engpass zu finden. Zunächst ist der ursprüngliche Code Python 3 Ich habe an der entsprechenden Stelle angefangen. Zusätzlich zum Erfassen der Spezifikationen waren die Testbedingungen klar (es ist in Ordnung, wenn dieselbe Bewegung wie das Original ausgeführt wird).

Danach wollte ich den ursprünglichen Code umgestalten, die Wartbarkeit verbessern und den Engpass der Geschwindigkeitsverzögerung angreifen.

** Der Code war so hässlich, dass ich beschlossen habe, ihn neu zu erstellen. ** **.

Unterstützt Python 3

Konvertieren Sie den Code mit 2to3, einem Standard bei der Migration von Python 2 auf 3, und antworten Sie, während Ausnahmen stetig beseitigt werden. Hat. Übrigens habe ich so viel wie möglich behoben, wo es nicht pep8 folgt, und es scheint besser (oder kann behoben werden), es zu beheben.

Der schwierige Teil war, dass eine solche Importanweisung unvermeidlich platzte.

scripts/download.py


import os
import requests
try:
    # Python 3.x
    from configparser import ConfigParser
    import queue
except ImportError:
    # Python 2.x (Nein, ich will nicht schreiben...orz)
    from ConfigParser import ConfigParser
    import Queue as queue

Ich konnte entkommen, indem ich die Importeinheit änderte und den Alias schnitt.

Nach dieser Renovierung habe ich Pull Request to Original Author ausgegeben.

Erstellen Sie ein neues Py-Retrosheet für Python 3.5

Nachdem ich Python 3 unterstützt hatte, dachte ich darüber nach, einen Test zu schreiben und ihn umzugestalten, aber ich war wütend und entschied mich, ihn neu zu schreiben, weil es schwierig war, einen Test zu schreiben und der Code überhaupt nicht gut war.

py-retrosheet

  1. Laden Sie die Daten von [RETROSHEET] herunter (http://retrosheet.org/ "retrosheet.org").
  2. Verwenden Sie eine Bibliothek namens Chadwick, um die Daten in einem migrierbaren Format neu zu erstellen.
  3. Implementierung von Maigure

Es ist wirklich einfach zu tun, aber ich hatte das Gefühl, dass ich diese einfache Sache absichtlich gemacht habe. Gleichzeitig mit der Überprüfung der Konfiguration selbst habe ich die Spezifikationen hinzugefügt, die aus Python 3.5 hinzugefügt wurden (weil ich es persönlich ausprobieren möchte). Machen wir es zu einer genutzten Bibliothek! Also habe ich einige Verbesserungen vorgenommen.

Darüber hinaus wurde das ursprüngliche Skript so konzipiert, dass nur Daten für eine Saison (ein Jahr) pro Ausführung erfasst und erstellt werden. Da jedoch Daten für zwei Jahre oder länger mit tatsächlichen Problemen angefordert wurden, haben wir uns entschlossen, dies ebenfalls zu unterstützen. ..

Zunächst sieht der Download so aus. Dies ist eigentlich fast das gleiche wie der ursprüngliche Code.

scripts/retrosheet_download.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Download .
Python 3.5.0+ (don't know about 3.4- and 2.x, sorry)
MySQL 5.6.0+ (don't know about 5.5- , sorry)
"""

import os
import click
from configparser import ConfigParser
from classes.fetcher import Fetcher
from queue import Queue

__author__ = 'Shinichi Nakagawa'


class RetrosheetDownload(object):

    FILES = (
        {
            'name': 'eventfiles',
            'config_flg': 'dl_eventfiles',
            'url': 'eventfiles_url',
            'pattern': r'({year})eve\.zip',
            'download_url': 'http://www.retrosheet.org/events/{year}eve.zip',
        },
        {
            'name': 'gamelogs',
            'config_flg': 'dl_gamelogs',
            'url': 'gamelogs_url',
            'pattern': r'gl({year})\.zip',
            'download_url': 'http://www.retrosheet.org/gamelogs/gl{year}.zip',
        }
    )

    def __init__(self, configfile: str):
        """
        initialize
        :param configfile: configuration file
        """
        # configuration
        self.config = ConfigParser()
        self.config.read(configfile)
        self.num_threads = self.config.getint('download', 'num_threads')
        self.path = self.config.get('download', 'directory')
        self.absolute_path = os.path.abspath(self.path)

    def download(self, queue):
        """
        Download & a Archives
        :param from_year: Season(from)
        :param to_year: Season(to)
        :param configfile: Config file
        """
        threads = []
        for i in range(self.num_threads):
            t = Fetcher(queue, self.absolute_path, {'verbose': self.config.get('debug', 'verbose')})
            t.start()
            threads.append(t)
        for thread in threads:
            thread.join()

    @classmethod
    def run(cls, from_year: int, to_year: int, configfile: str):
        """
        :param from_year: Season(from)
        :param to_year: Season(to)
        :param configfile: Config file
        """
        client = RetrosheetDownload(configfile)

        if not os.path.exists(client.absolute_path):
            print("Directory %s does not exist, creating..." % client.absolute_path)
            os.makedirs(client.absolute_path)

        urls = Queue()
        for year in range(from_year, to_year + 1):
            for _file in RetrosheetDownload.FILES:
                urls.put(_file['download_url'].format(year=year))

        client.download(urls)


@click.command()
@click.option('--from_year', '-f', default=2001, help='From Season')
@click.option('--to_year', '-t', default=2014, help='To Season')
@click.option('--configfile', '-c', default='config.ini', help='Config File')
def download(from_year, to_year, configfile):
    """
    :param from_year: Season(from)
    :param to_year: Season(to)
    :param configfile: Config file
    """
    # from <= to check
    if from_year > to_year:
        print('not From <= To({from_year} <= {to_year})'.format(from_year=from_year, to_year=to_year))
        raise SystemExit

    RetrosheetDownload.run(from_year, to_year, configfile)


if __name__ == '__main__':
    download()

Hören Sie auf, unnötige Seiten zu bekommen und zu kratzen.

In Bezug auf das Problem der "komplexen Behandlung von Argumenten in der Befehlszeile und in der Konfiguration", das im Allgemeinen ein Wartungsproblem für py-retrosheet darstellte, lautet das erstere click. Es wurde durch Klicken auf ") vereinfacht, und letzteres wurde gelöst, indem der Erfassungsort von config in der anfänglichen Verarbeitung der Klasse aggregiert wurde.

Dies gilt auch für die Datenerfassung und -erstellung, die später eingeführt werden.

Als nächstes folgt der Teil, um Daten mit Chadwick abzurufen.

scripts/parse_csv.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Parse to Event Files, Game Logs and Roster.
Python 3.5.0+ (don't know about 3.4- and 2.x, sorry)
"""
import os
import subprocess
import click
from configparser import ConfigParser

__author__ = 'Shinichi Nakagawa'


class ParseCsv(object):

    CW_EVENT = '{chadwick_path}cwevent'
    CW_GAME = '{chadwick_path}cwgame'
    CW_EVENT_CMD = '{chadwick_path}cwevent -q -n -f 0-96 -x 0-62 -y {year} {year}*.EV* > {csvpath}/events-{year}.csv'
    CW_GAME_CMD = '{chadwick_path}cwgame -q -n -f 0-83 -y {year} {year}*.EV* > {csvpath}/games-{year}.csv'
    EV_FILE_PATTERN = '{path}/{year}*.EV*'
    EVENT_FILE = '{csvpath}/events-{year}.csv'
    GAME_FILE = '{csvpath}/games-{year}.csv'
    CSV_PATH = 'csv'

    def __init__(self):
        pass

    @classmethod
    def exists_chadwick(cls, chadwick_path: str):
        """
        exists chadwick binary
        :param chadwick_path: chadwick path
        :return: True or False
        """
        if os.path.exists(chadwick_path) \
            & os.path.exists(cls.CW_EVENT.format(chadwick_path=chadwick_path)) \
                & os.path.exists(cls.CW_GAME.format(chadwick_path=chadwick_path)):
            return True
        return False

    @classmethod
    def generate_files(
            cls,
            year: int,
            cmd_format: str,
            filename_format: str,
            chadwick_path: str,
            verbose: bool,
            csvpath: str
    ):
        """
        Generate CSV file
        :param year: Season
        :param cmd_format: Command format
        :param filename_format: Filename format
        :param chadwick_path: Chadwick Command Path
        :param verbose: Debug flg
        :param csvpath: csv output path
        """
        cmd = cmd_format.format(csvpath=csvpath, year=year, chadwick_path=chadwick_path)
        filename = filename_format.format(csvpath=csvpath, year=year)
        if os.path.isfile(filename):
            os.remove(filename)
        if verbose:
            print('calling {cmd}'.format(cmd=cmd))
        subprocess.call(cmd, shell=True)

    @classmethod
    def generate_retrosheet_files(
            cls,
            from_year: int,
            to_year: int,
            chadwick_path: str,
            verbose: str,
            csvpath: str
    ):
        """
        Generate CSV file
        :param from_year: Season(from)
        :param to_year: Season(to)
        :param chadwick_path: Chadwick Command Path
        :param verbose: Debug flg
        :param csvpath: csv output path
        """
        # generate files
        for year in [year for year in range(from_year, to_year + 1)]:
            # game
            ParseCsv.generate_files(
                year=year,
                cmd_format=ParseCsv.CW_GAME_CMD,
                filename_format=ParseCsv.GAME_FILE,
                chadwick_path=chadwick_path,
                verbose=verbose,
                csvpath=csvpath
            )
            # event
            ParseCsv.generate_files(
                year=year,
                cmd_format=ParseCsv.CW_EVENT_CMD,
                filename_format=ParseCsv.EVENT_FILE,
                chadwick_path=chadwick_path,
                verbose=verbose,
                csvpath=csvpath
            )

    @classmethod
    def run(cls, from_year: int, to_year: int, configfile: str):
        """
        :param from_year: Season(from)
        :param to_year: Season(to)
        :param configfile: Config file
        """
        config = ConfigParser()
        config.read(configfile)
        verbose = config.getboolean('debug', 'verbose')
        chadwick = config.get('chadwick', 'directory')
        path = os.path.abspath(config.get('download', 'directory'))
        csv_path = '{path}/csv'.format(path=path)

        # command exists check
        if not cls.exists_chadwick(chadwick):
            print('chadwick does not exist in {chadwick} - exiting'.format(chadwick=chadwick))
            raise SystemExit

        # make directory
        os.chdir(path)
        if not os.path.exists(ParseCsv.CSV_PATH):
            os.makedirs(ParseCsv.CSV_PATH)

        # generate files
        cls.generate_retrosheet_files(
            from_year=from_year,
            to_year=to_year,
            chadwick_path=chadwick,
            verbose=verbose,
            csvpath=csv_path
        )

        # change directory
        os.chdir(os.path.dirname(os.path.abspath(__file__)))


@click.command()
@click.option('--from_year', '-f', default=2001, help='From Season')
@click.option('--to_year', '-t', default=2014, help='To Season')
@click.option('--configfile', '-c', default='config.ini', help='Config File')
def create_retrosheet_csv(from_year, to_year, configfile):
    """
    :param from_year: Season(from)
    :param to_year: Season(to)
    :param configfile: Config file
    """
    # from <= to check
    if from_year > to_year:
        print('not From <= To({from_year} <= {to_year})'.format(from_year=from_year, to_year=to_year))
        raise SystemExit
    ParseCsv.run(from_year, to_year, configfile)


if __name__ == '__main__':
    create_retrosheet_csv()


Ich habe die Spieldaten (Spielprotokoll) und die Ereignisdaten (Ereignisprotokoll) während des Spiels verarbeitet, z. B. beim Schlagen und beim separaten Stehlen, aber ich habe dasselbe getan (nur der verwendete Befehl und das Ausgabeziel sind unterschiedlich) Die Verarbeitung ist in Methoden gruppiert und die Definition erfolgt von außen. Dies gilt auch für die Implementierung der Datenerstellung.

scripts/retrosheet_mysql.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Migrate Database, Game Logs and Roster.
Python 3.5.0+ (don't know about 3.4- and 2.x, sorry)
MySQL 5.6.0+ (don't know about 5.5- , sorry)
"""
import os
import csv
import click
import sqlalchemy
from glob import glob
from configparser import ConfigParser, NoOptionError

__author__ = 'Shinichi Nakagawa'


class RetrosheetMySql(object):

    DATABASE_ENGINE = 'mysql+pymysql'
    ENGINE = '{engine}://{user}:{password}@{host}/{database}'
    TABLES = (
        {
            'name': 'teams',
            'header': False,
            'year': False,
            'mask': '{path}/TEAM{year}*',
            'select': "SELECT * FROM teams WHERE team_id = '{key_0}'",
            'where_index': [0],
            'insert': 'INSERT INTO teams VALUES {values}',
        },
        {
            'name': 'rosters',
            'header': False,
            'year': True,
            'mask': '{path}/*{year}*.ROS',
            'select': "SELECT * FROM rosters WHERE year = {year} AND player_id = '{key_0}' AND team_tx = '{key_1}'",
            'where_index': [0, 5],
            'insert': 'INSERT INTO rosters VALUES {values}',
        },
        {
            'name': 'games',
            'header': True,
            'year': False,
            'mask': '{path}/csv/games-{year}*.csv',
            'select': "SELECT * FROM games WHERE game_id = '{key_0}'",
            'where_index': [0],
            'insert': 'INSERT INTO games({columns}) VALUES {values}',
        },
        {
            'name': 'events',
            'header': True,
            'year': False,
            'mask': '{path}/csv/events-{year}*.csv',
            'select': "SELECT * FROM events WHERE game_id = '{key_0}' AND event_id = '{key_1}'",
            'where_index': [0, 96],
            'insert': 'INSERT INTO events({columns}) VALUES {values}',
        },
    )

    def __init__(self, configfile: str):
        """
        initialize
        :param configfile: configuration file
        """
        # configuration
        config = ConfigParser()
        config.read(configfile)
        self.path = os.path.abspath(config.get('download', 'directory'))
        self.record_check = config.getboolean('retrosheet_mysql', 'record_check')
        self.multiple_insert_rows = config.getint('retrosheet_mysql', 'multiple_insert_rows')
        # connection
        self.connection = self._generate_connection(config)

    @classmethod
    def _generate_connection(cls, config: ConfigParser):
        """
        generate database connection
        :param config: ConfigParser object
        :return:
        """

        try:
            database_engine = cls.DATABASE_ENGINE
            database = config.get('database', 'database')

            host = config.get('database', 'host')
            user = config.get('database', 'user')
            password = config.get('database', 'password')
        except NoOptionError:
            print('Need to define engine, user, password, host, and database parameters')
            raise SystemExit

        db = sqlalchemy.create_engine(
            cls.ENGINE.format(
                engine=database_engine,
                user=user,
                password=password,
                host=host,
                database=database,
            )
        )

        return db.connect()

    @classmethod
    def _exists_record(cls, year: int, table: dict, csv_row: list, connection):
        where = {'key_{i}'.format(i=i): csv_row[v] for i, v in enumerate(table['where_index'])}
        where['year'] = year
        sql = table['select'].format(**where)
        res = connection.execute(sql)
        if res.rowcount > 0:
            return True
        return False

    def _multiple_insert(self, query: str, columns: list, values: list):
        params = {
            'columns': columns,
            'values': ', '.join(values),
        }
        sql = query.format(**params)
        try:
            self.connection.execute(sql)
        except Exception as e:
            print(e)
            raise SystemExit

    def _create_record(self, year: int, csv_file: str, table: dict):
        reader = csv.reader(open(csv_file))
        headers = []
        values = []
        if table['header']:
            headers = next(reader)
        columns = ', '.join(headers)
        for row in reader:
            # Record Exists Check
            if self.record_check and RetrosheetMySql._exists_record(year, table, row, self.connection):
                continue
            # append record values
            if table['year']: row.insert(0, str(year))
            values.append('("{rows}")'.format(rows='", "'.join(row)))
            if len(values) == self.multiple_insert_rows:
                # inserts
                self._multiple_insert(table['insert'], columns, values)
                values = []
        if len(values) > 0:
            # inserts
            self._multiple_insert(table['insert'], columns, values)

    def execute(self, year: int):
        for table in self.TABLES:
            for csv_file in glob(table['mask'].format(path=self.path, year=year)):
                self._create_record(year, csv_file, table)

    @classmethod
    def run(cls, from_year: int, to_year: int, configfile: str):
        """
        :param from_year: Season(from)
        :param to_year: Season(to)
        :param configfile: Config file
        """
        client = RetrosheetMySql(configfile)
        for year in range(from_year, to_year + 1):
            client.execute(year)
        client.connection.close()


@click.command()
@click.option('--from_year', '-f', default=2001, help='From Season')
@click.option('--to_year', '-t', default=2014, help='To Season')
@click.option('--configfile', '-c', default='config.ini', help='Config File')
def migration(from_year, to_year, configfile):
    """
    :param from_year: Season(from)
    :param to_year: Season(to)
    :param configfile: Config file
    """
    # from <= to check
    if from_year > to_year:
        print('not From <= To({from_year} <= {to_year})'.format(from_year=from_year, to_year=to_year))
        raise SystemExit

    RetrosheetMySql.run(from_year, to_year, configfile)


if __name__ == '__main__':
    migration()

Die meisten Verbesserungen in dieser Implementierung,

Die Auswahlanweisungsprüfung wird implementiert, aber nicht verwendet (sie kann durch Ändern der Konfigurationseinstellung verwendet werden).

Die Geschwindigkeit hat sich dramatisch verbessert, indem die zeilenweise gut benommene Einfügeanweisung in 1000 Zeilen geändert wurde (die auch mit config angepasst werden kann).

Außerdem hat sich der allgemeine Ausblick für den Code verbessert, sodass das Hinzufügen von All-Star- und Playoff-Daten, die Sie noch nicht verwendet haben, einfach zu handhaben ist!

Benchmark

Nachdem ich mich bisher verbessert hatte, versuchte ich, die Daten von 2014 (ca. 190.000 Zeilen) zu vergleichen.

Das Ziel ist

ist. Messung ist

Ich habe es unter der Bedingung getan.

#Vor der Verbesserung
$ time python download.py -y 2014
Queuing up Event Files for download (2014 only).
Queuing up Game Logs for download (2014 only).
Fetching 2014eve.zip
Fetching gl2014.zip
Zip file detected. Extracting gl2014.zip
Zip file detected. Extracting 2014eve.zip

real	0m5.816s
user	0m0.276s
sys	0m0.066s
$ time python parse.py -y 2014
calling '/usr/local/bin//cwevent -q -n -f 0-96 -x 0-62 -y 2014 2014*.EV* > /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/events-2014.csv'
calling '/usr/local/bin//cwgame -q -n -f 0-83 -y 2014 2014*.EV* > /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/games-2014.csv'
processing TEAM2014
processing ANA2014.ROS
processing ARI2014.ROS
processing ATL2014.ROS
processing BAL2014.ROS
processing BOS2014.ROS
processing CHA2014.ROS
processing CHN2014.ROS
processing CIN2014.ROS
processing CLE2014.ROS
processing COL2014.ROS
processing DET2014.ROS
processing HOU2014.ROS
processing KCA2014.ROS
processing LAN2014.ROS
processing MIA2014.ROS
processing MIL2014.ROS
processing MIN2014.ROS
processing NYA2014.ROS
processing NYN2014.ROS
processing OAK2014.ROS
processing PHI2014.ROS
processing PIT2014.ROS
processing SDN2014.ROS
processing SEA2014.ROS
processing SFN2014.ROS
processing SLN2014.ROS
processing TBA2014.ROS
processing TEX2014.ROS
processing TOR2014.ROS
processing WAS2014.ROS
processing /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/games-2014.csv
processing /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/events-2014.csv

real	19m53.224s
user	13m16.074s
sys	0m26.294s

#Nach der Verbesserung
$ time python retrosheet_download.py -f 2014 -t 2014
Fetching 2014eve.zip
Fetching gl2014.zip
Zip file detected. Extracting gl2014.zip
Zip file detected. Extracting 2014eve.zip

real	0m4.630s
user	0m0.217s
sys	0m0.055s
$ time python parse_csv.py -f 2014 -t 2014
calling /usr/local/bin/cwgame -q -n -f 0-83 -y 2014 2014*.EV* > /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/games-2014.csv
calling /usr/local/bin/cwevent -q -n -f 0-96 -x 0-62 -y 2014 2014*.EV* > /Users/shinyorke_mbp/PycharmProjects/py-retrosheet/scripts/files/csv/events-2014.csv

real	0m8.713s
user	0m8.321s
sys	0m0.221s
$ time python retrosheet_mysql.py -f 2014 -t 2014

real	0m21.435s
user	0m3.580s
sys	0m0.416s

** Der Prozess, der ursprünglich 20 Minuten dauerte, wurde auf weniger als 1 Minute verbessert! ** **.

Selbst wenn Sie eine große Menge an Spiel- / Sitzdaten benötigen, können Sie diese problemlos abrufen oder erstellen.

Versuchen Sie, Daten für 15 Jahre zu machen

Es wird interessant, deshalb habe ich beschlossen, Daten für 15 Jahre von 2000 bis 2014 zu erstellen.

Die Gesamtzahl der Datensätze beträgt ungefähr 2,9 Millionen, nicht ungefähr 3 Millionen.

Ich habe das Skript für die Ausführung auf einmal neu transkribiert.

scripts/migration.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Migrate Retrosheet Database.
Python 3.5.0+ (don't know about 3.4- and 2.x, sorry)
MySQL 5.6.0+ (don't know about 5.5- , sorry)
"""
import logging
import click
from retrosheet_download import RetrosheetDownload
from retrosheet_mysql import RetrosheetMySql
from parse_csv import ParseCsv

__author__ = 'Shinichi Nakagawa'


@click.command()
@click.option('--from_year', '-f', default=2010, help='From Season')
@click.option('--to_year', '-t', default=2014, help='To Season')
@click.option('--configfile', '-c', default='config.ini', help='Config File')
def main(from_year, to_year, configfile):
    """
    :param from_year: Season(from)
    :param to_year: Season(to)
    :param configfile: Config file
    """
    # from <= to check
    if from_year > to_year:
        print('not From <= To({from_year} <= {to_year})'.format(from_year=from_year, to_year=to_year))
        raise SystemExit

    # logging setting
    logging.basicConfig(
        level=logging.INFO,
        format="Time:%(asctime)s.%(msecs)03d\t message:%(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )

    # Download
    logging.info('File Download Start({from_year}-{to_year})'.format(from_year=from_year, to_year=to_year))
    RetrosheetDownload.run(from_year, to_year, configfile)
    logging.info('File Download End')

    # Parse Csv
    logging.info('Csv Parse Start({from_year}-{to_year})'.format(from_year=from_year, to_year=to_year))
    ParseCsv.run(from_year, to_year, configfile)
    logging.info('Csv Parse End')

    # Migrate MySQL Database
    logging.info('Migrate Database Start({from_year}-{to_year})'.format(from_year=from_year, to_year=to_year))
    RetrosheetMySql.run(from_year, to_year, configfile)
    logging.info('Migrate Database End')


if __name__ == '__main__':
    main()

Klicken Sie hier für Ergebnisse

$ python migration.py -f 2000 -t 2014
Time:2015-11-15 15:23:48.291	 message:File Download Start(2000-2014)
Directory /Users/shinyorke_mbp/PycharmProjects/hatteberg/py-retrosheet/scripts/files does not exist, creating...
Time:2015-11-15 15:23:59.673	 message:File Download End
Time:2015-11-15 15:23:59.673	 message:Csv Parse Start(2000-2014)
Time:2015-11-15 15:26:37.219	 message:Csv Parse End
Time:2015-11-15 15:26:37.220	 message:Migrate Database Start(2000-2014)
Time:2015-11-15 15:32:50.070	 message:Migrate Database End

** Es dauerte weniger als 10 Minuten von der Datenerfassung bis zur Fertigstellung der Abbildung! !! !! ** **.

Zusammenfassung

Dies macht ein bisschen mehr Spaß Baseball Hack!

Recommended Posts

Python-Skript, das MLB-Spieldaten im Wert von 15 Jahren in 10 Minuten in MySQL speichert (Baseball Hack!)
Eine Reihe von Skriptdateien, die Wordcloud mit Python3 ausführen
[Baseball Hack] Ich habe versucht, das Python MLB Score & Grade-Datenerfassungsskript mit Go in einem halben Tag zu kopieren
Code lesen von faker, einer Bibliothek, die Testdaten in Python generiert
Aufgezeichnete Umgebung für die Datenanalyse mit Python
[Python] Ein Programm, das die kürzeste Anzahl von Schritten in einem Spiel findet, das Wolken überquert
Veröffentlichung einer Bibliothek, die Zeichendaten in Python-Bildern verbirgt
[Python] Erstellen eines GUI-Tools, das die CSV von Temperaturanstiegsdaten in Excel automatisch verarbeitet
Erstellen wir ein Skript, das sich bei Ideone.com in Python registriert.
Ein Python-Skript, das Oracle-Datenbankdaten in CSV konvertiert
Python-Skript, das den Inhalt zweier Verzeichnisse vergleicht
Eine einfache Datenanalyse von Bitcoin, die von CoinMetrics in Python bereitgestellt wird
Eine Funktion, die die Verarbeitungszeit einer Methode in Python misst
Implementierung eines Lebensspiels in Python
So senden Sie ein visualisiertes Bild der in Python erstellten Daten an Typetalk
Python-E-Book-Zusammenfassung nützlich für die frei lesbare Datenanalyse
Ich habe versucht, Trumps Kartenspiel in Python zu implementieren
Zeigen Sie eine Liste der Alphabete in Python 3 an
Führen Sie den Python-Interpreter im Skript aus
Ich habe in Python ein Programm erstellt, das FX-CSV-Daten liest und eine große Anzahl von Diagrammbildern erstellt
Grundlegende Datenrahmenoperationen, die von Anfängern in einer Woche Python-Lernen geschrieben wurden
Bis Sie tägliche Daten für mehrere Jahre japanischer Bestände erhalten und diese in einer einzigen CSV (Python) speichern
Zeichnen Sie in Python ein Diagramm einer quadratischen Funktion
Ein Memo, das ich schnell in Python geschrieben habe
Holen Sie sich den Aufrufer einer Funktion in Python
Ich habe versucht, mit Python ein Tippspiel zu spielen
Ein Programm, das doppelte Anweisungen in Python entfernt
Kopieren Sie die Liste in Python
Echtzeitvisualisierung von Thermografie AMG8833-Daten in Python
Umschreiben von Elementen in einer Listenschleife (Python)
Mach ein Janken-Spiel in einer Zeile (Python)
Datenanalyse in Python: Ein Hinweis zu line_profiler
"Python Kit", das Python-Skripte von Swift aufruft
Die Geschichte des Lesens von HSPICE-Daten in Python
Machen Sie mit Python eine Joyplot-ähnliche Handlung von R.
Rufen Sie Python-Skripte aus Embedded Python in C ++ / C ++ auf
Ausgabe in Form eines Python-Arrays
Lassen Sie uns einen Teil des maschinellen Lernens mit Python berühren
[Docker] Erstellen Sie in 3 Minuten eine jupyterLab (Python) -Umgebung!
Zusammenfassung der statistischen Datenanalysemethoden mit Python, die im Geschäftsleben verwendet werden können
Ein Python-Skript, das die Anzahl der Jobs für eine bestimmte Bedingung von Indeed.com abruft
Ein Hinweis, der einen Job in Python implementiert, der eine GCS-Datei in BigQuery lädt
Konsolidieren Sie eine große Anzahl von CSV-Dateien in Ordnern mit Python (Daten ohne Header).
Programm, das die CSV-Daten der Transaktionshistorie der SBI Securities-Aktie zusammenfasst [Python3]
Ich habe in Python ein Programm erstellt, das die 1-Minuten-Daten von FX auf eine beliebige Zeit (1 Stunde usw.) ändert.