diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f22ff37 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 120 + +[{*.py,*.pyw}] +max_line_length = 88 + +[{*.yaml,*.yml}] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8f352ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: 'OliverSieweke' + +--- +[comment]: # (Specify the version used below.) +**version:** v0.1.0 + +### Description +[comment]: # (A clear and concise description of what the bug is.) + +### Expected behavior +[comment]: # (A clear and concise description of what you expected to happen.) + +### Configurations +[comment]: # (Paste the workflow YAML file here.) + +```yaml +``` + +### Stack Trace +[comment]: # (Paste your actions error stack trace here if applicable.) + +```txt +``` + +### Additional context +[comment]: # (Any additonal information that may be relevant.) + + +[comment]: # (Awesome! Thanks!) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0a073cd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +# Description +[comment]: # (Please include a summary of the change and which issue is fixed if applicable.) + +Fixes # (issue) + +## Type of change + +[comment]: # (Please delete options that are not relevant.) + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92e8f09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Documentation Build: +/docs/build diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dictionaries/os.xml b/.idea/dictionaries/os.xml new file mode 100644 index 0000000..625b424 --- /dev/null +++ b/.idea/dictionaries/os.xml @@ -0,0 +1,21 @@ + + + + autodoc + docformatter + dotenv + genindex + isort + kaggle + matplotlib + modindex + noninfringement + oliversieweke + seaborn + sieweke + stefanzweifel + ymax + ymin + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..33d501a --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,145 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..083b565 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/kaggle-graph.iml b/.idea/kaggle-graph.iml new file mode 100644 index 0000000..527c7bc --- /dev/null +++ b/.idea/kaggle-graph.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2efefd2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c49ca88 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/docs.xml b/.idea/runConfigurations/docs.xml new file mode 100644 index 0000000..56f013c --- /dev/null +++ b/.idea/runConfigurations/docs.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 0000000..e85a6ef --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..e12428f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/source/conf.py + +# Python version and requirements to build the docs +python: + version: 3.7 + install: + - requirements: requirements-docs.txt + +# Build docs in all formats: +formats: all diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..beb01fb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +Contributions are very welcome! + +This is a small and manageable project, so feel free to contribute in whatever way suits you best. GitHub issues, pull requests, emails are all great! + +Make sure you check out the [Developer Guide](https://kaggle-graph.readthedocs.io/en/latest/?badge=latest) 🐒 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7ea2a10 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.7-slim + +ADD . / +RUN python -m pip install -e . +ENTRYPOINT python -m kaggle_graph diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c81d691 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Oliver Sieweke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..72e1b41 --- /dev/null +++ b/README.rst @@ -0,0 +1,132 @@ +Kaggle Graph +==================== + +|license| |version| |docs| + +.. |license| image:: https://img.shields.io/github/license/OliverSieweke/kaggle-graph + :target: https://choosealicense.com/licenses/mit + :alt: License: MIT +.. |version| image:: https://img.shields.io/github/v/tag/OliverSieweke/kaggle-graph + :target: https://github.com/OliverSieweke/kaggle-graph/releases + :alt: Versions +.. |docs| image:: https://readthedocs.org/projects/kaggle-graph/badge/?version=latest + :target: https://kaggle-graph.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +GitHub action to automatically generate graphs of your submissions to Kaggle +competitions and display them in your Github repository’s ``README`` to track +your progress. + +.. image:: ./example-graph.png + :alt: Example Graph + +See it in action `here`_. + +.. getting_started + +Getting Started +--------------- + +1. Add the :code:`KAGGLE_KEY` secret to your GitHub repository under + ``Settings > Secrets > Add a new secret``. + +2. Define a GitHub workflow_ by adding a ``.github/workflow/kaggle-graph.yaml`` + to your repository using the template below: + +.. code:: yaml + + name: Kaggle Graph Generation + + on: + push: + branches: master + schedule: + - cron: '00 00 * * *' + + jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v2 + - name: Kaggle Graph Generation + uses: OliverSieweke/kaggle-graph@v0.1.0 + with: + KAGGLE_KEY: ${{ secrets.KAGGLE_KEY }} + KAGGLE_USERNAME: oliversieweke + KAGGLE_COMPETITION_ID: titanic + - name: Git Auto Commit + uses: stefanzweifel/git-auto-commit-action@v4.1.6 + with: + commit_message: Automatic Kaggle graph generation + +3. Add the graph to your ``README``: + + - Markdown: + .. code:: md + + [!Kaggle Submissions Graph](./kaggle-submissions-graph.png) + - reStructuredText: + .. code:: rst + + .. image:: ./kaggle-submissions-graph.png + :alt: Kaggle Submissions Graph + +.. before_notes + +**NB 1:** + - The :code:`KAGGLE_KEY` key can be generated on Kaggle_ under ``My Profile + > Edit Profile > API > Create New Api Token``. + - The :code:`KAGGLE_USERNAME` can be found on Kaggle_ under ``My Profile > Edit + Profile``. + - The :code:`KAGGLE_COMPETITION_ID` can be read from the Kaggle_ competition's URL. + +**NB 2:** + **Kaggle Graph** is designed to be used in conjunction with the following + GitHub actions: + + - checkout_ + - git-auto-commit_ + +.. after_notes + +Configurations +-------------- + +The action can be further configured over the input keys below: + ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| Name | Description | Required | Default | Type | ++===============================+==========================+===========+====================================+=========================================================+ +| :code:`KAGGLE_USERNAME` | Kaggle username. | ``true`` | ``-`` | :code:`str` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`KAGGLE_KEY` | Kaggle key. | ``true`` | ``-`` | :code:`str` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`KAGGLE_COMPETITION_ID` | Kaggle competition ID. | ``true`` | ``-`` | :code:`str` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`GRAPH_NAME` | Output graph file name. | ``false`` | :code:`"kaggle-submissions-graph"` | :code:`str` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`Y_MIN` | Y-axis minimum boundary. | ``false`` | ``-`` | :code:`float` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`Y_MAX` | Y-axis maximum boundary. | ``false`` | ``-`` | :code:`float` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`SCORE` | Score type. | ``false`` | :code:`positive` | :code:`str` (:code:`positive` or :code:`negative`) | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ +| :code:`OBJECTIVE` | Score objective. | ``false`` | ``-`` | :code:`float` | ++-------------------------------+--------------------------+-----------+------------------------------------+---------------------------------------------------------+ + +.. _here: https://github.com/OliverSieweke/bike-sharing-demand +.. _.github/workflow/kaggle-graph.yaml: ./workflow.template.yaml +.. _Kaggle: https://www.kaggle.com/ +.. _workflow: https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions +.. _checkout: https://github.com/actions/checkout +.. _git-auto-commit: https://github.com/stefanzweifel/git-auto-commit-action + +.. before_references + +References +---------- + +- Documentation_ + +.. _Documentation: https://kaggle-graph.readthedocs.io/en/latest/?badge=latest diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..f6b768b --- /dev/null +++ b/action.yaml @@ -0,0 +1,38 @@ +name: "Kaggle Graph Generation" +author: "Oliver Sieweke" +description: "Generate a graph of your submissions to a Kaggle competition." +branding: + icon: "trending-up" + color: "blue" + +inputs: + KAGGLE_USERNAME: + required: true + description: Kaggle username, to be found on Kaggle under My Profile > Edit Profile. + KAGGLE_KEY: + required: true + description: Kaggle key ID, to be read from the competition's URL. + KAGGLE_COMPETITION_ID: + required: true + description: Kaggle Competition ID, to be read from the competition's URL. + GRAPH_NAME: + required: false + description: Output graph file name. + Y_MIN: + required: false + description: Y-axis mimumium boundary. + Y_MAX: + required: false + description: Y-axis maximum boundary. + SCORE: + required: false + description: Whether the score is to be maximized (positive) or minimized (negative). + default: "positive" + OBJECTIVE: + required: false + description: Score objective to be shown on the graph. + +runs: + using: "docker" + image: "Dockerfile" + diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/example-graph.png b/docs/source/_static/example-graph.png new file mode 100644 index 0000000..bad4ccd Binary files /dev/null and b/docs/source/_static/example-graph.png differ diff --git a/docs/source/_static/graph-logo.svg b/docs/source/_static/graph-logo.svg new file mode 100644 index 0000000..6e4922f --- /dev/null +++ b/docs/source/_static/graph-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/source/action_inputs.rst b/docs/source/action_inputs.rst new file mode 100644 index 0000000..c4d38dd --- /dev/null +++ b/docs/source/action_inputs.rst @@ -0,0 +1,2 @@ +.. automodule:: kaggle_graph.action_inputs + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..5d47267 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,65 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +# Standard Library --------------------------------------------------------------------- +import os +import sys + + +sys.path.insert(0, os.path.abspath("../../")) + +# -- Project information ----------------------------------------------------- + +project = "Kaggle Graph" +copyright = "2020, Oliver Sieweke" +author = "Oliver Sieweke" + +# The full version, including alpha/beta/rc tags +release = "0.1.0" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx_rtd_theme", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "sphinx_rtd_theme" +html_logo = "_static/graph-logo.svg" +html_favicon = "_static/graph-logo.svg" +html_theme_options = { + "style_nav_header_background": "#0283B3", +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] diff --git a/docs/source/developer_guide.rst b/docs/source/developer_guide.rst new file mode 100644 index 0000000..3062ae3 --- /dev/null +++ b/docs/source/developer_guide.rst @@ -0,0 +1,17 @@ +Developer Guide +=============== + +.. toctree:: + :maxdepth: 2 + + kaggle_graph + action_inputs + submissions + plot + + +Indices +------- + +* :ref:`genindex` +* :ref:`modindex` diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..8a5e897 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,15 @@ +Kaggle Graph +============ + +GitHub action to automatically generate graphs of your submissions to Kaggle competitions and display them in your Github repository's ``README`` to track your progress. + +.. image:: ./_static/example-graph.png + :alt: Example Graph + :align: center + :scale: 70% + +.. toctree:: + :maxdepth: 2 + + user_guide + developer_guide diff --git a/docs/source/kaggle_graph.rst b/docs/source/kaggle_graph.rst new file mode 100644 index 0000000..5d32a83 --- /dev/null +++ b/docs/source/kaggle_graph.rst @@ -0,0 +1,2 @@ +.. automodule:: kaggle_graph + :members: diff --git a/docs/source/plot.rst b/docs/source/plot.rst new file mode 100644 index 0000000..17e8f7f --- /dev/null +++ b/docs/source/plot.rst @@ -0,0 +1,2 @@ +.. automodule:: kaggle_graph.plot + :members: diff --git a/docs/source/submissions.rst b/docs/source/submissions.rst new file mode 100644 index 0000000..b0fc317 --- /dev/null +++ b/docs/source/submissions.rst @@ -0,0 +1,2 @@ +.. automodule:: kaggle_graph.submissions + :members: fetch, parse diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst new file mode 100644 index 0000000..0831a7f --- /dev/null +++ b/docs/source/user_guide.rst @@ -0,0 +1,47 @@ +User Guide +========== + +.. include:: ../../README.rst + :start-after: .. getting_started + :end-before: .. before_notes + +.. note:: + - The :code:`KAGGLE_KEY` key can be generated on Kaggle_ under ``My Profile + > Edit Profile > API > Create New Api Token``. + - The :code:`KAGGLE_USERNAME` can be found on Kaggle_ under ``My Profile > Edit + Profile``. + - The :code:`KAGGLE_COMPETITION_ID` can be read from the competition's URL. + +.. note:: + **Kaggle Graph** is designed to be used in conjunction with the following + GitHub actions: + + - checkout_ + - git-auto-commit_ + +.. include:: ../../README.rst + :start-after: .. after_notes + :end-before: .. before_references + +Troubleshooting +--------------- + +- My graph does not update: + GitHub seems to be caching images. Although the graph updates, the display + may take a couple of minutes to reflect the change. + +References +---------- + + - `Creating and storing encrypted secrets`_ + - `Workflow syntax for GitHub Actions`_ + - `checkout action`_ + - `git-auto-commit action`_ + +.. _Kaggle: https://www.kaggle.com/ +.. _`Creating and storing encrypted secrets`: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets +.. _`Workflow syntax for GitHub Actions`: https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions +.. _checkout: https://github.com/actions/checkout +.. _git-auto-commit: https://github.com/stefanzweifel/git-auto-commit-action +.. _checkout action: https://github.com/actions/checkout +.. _git-auto-commit action: https://github.com/stefanzweifel/git-auto-commit-action diff --git a/example-graph.png b/example-graph.png new file mode 100644 index 0000000..bad4ccd Binary files /dev/null and b/example-graph.png differ diff --git a/kaggle_graph/__init__.py b/kaggle_graph/__init__.py new file mode 100644 index 0000000..dd0089b --- /dev/null +++ b/kaggle_graph/__init__.py @@ -0,0 +1,11 @@ +""" +Kaggle Graph +============ + +This is the top level module, which includes a :code:`__main__` script +to generate the Kaggle graph by loading the required environment +variables (including the Kaggle credentials) using +:py:mod:`kaggle_graph.action_inputs` and then composing the methods +exposed in :py:mod:`kaggle_graph.plot` and +:py:mod:`kaggle_graph.submissions`. +""" diff --git a/kaggle_graph/__main__.py b/kaggle_graph/__main__.py new file mode 100644 index 0000000..023281e --- /dev/null +++ b/kaggle_graph/__main__.py @@ -0,0 +1,19 @@ +# Third Party -------------------------------------------------------------------------- +from kaggle_graph.action_inputs import get_action_inputs +from kaggle_graph.plot import kaggle_submission_scores +from kaggle_graph.submissions import fetch, parse + + +# Load action inputs: +action_inputs = get_action_inputs() + + +# Generate and save Kaggle graph: +kaggle_submission_scores( + parse(fetch(action_inputs["KAGGLE_COMPETITION_ID"])), + graph_name=action_inputs["GRAPH_NAME"], + ymin=action_inputs["Y_MIN"], + ymax=action_inputs["Y_MAX"], + objective=action_inputs["OBJECTIVE"], + score=action_inputs["SCORE"], +) diff --git a/kaggle_graph/action_inputs/__init__.py b/kaggle_graph/action_inputs/__init__.py new file mode 100644 index 0000000..05570df --- /dev/null +++ b/kaggle_graph/action_inputs/__init__.py @@ -0,0 +1,63 @@ +""" +Action Inputs +============= + +This module contains utility methods for retrieving action inputs from +the environment. +""" + +# Standard Library --------------------------------------------------------------------- +import os + +# Third Party -------------------------------------------------------------------------- +from dotenv import load_dotenv + + +input_schema = { + "KAGGLE_USERNAME": str, + "KAGGLE_KEY": str, + "KAGGLE_COMPETITION_ID": str, + "GRAPH_NAME": str, + "Y_MIN": float, + "Y_MAX": float, + "OBJECTIVE": float, + "SCORE": str, +} + + +def get_action_inputs() -> dict: + """Return a dictionary containing all action inputs. + + .. note:: + This method further loads the Kaggle credentials into + the environment. + + Returns + ------- + :code:`dict` + Dictionary containing all action inputs. + + Example + ------- + >>> get_action_inputs() + {'KAGGLE_USERNAME': 'oliversieweke', + 'KAGGLE_KEY': '***', + 'KAGGLE_COMPETITION_ID': 'titanic', + 'GRAPH_NAME': None, + 'Y_MIN': 0, + 'Y_MAX': 1, + 'OBJECTIVE': 0.8, + 'SCORE': 'positive'} + """ + load_dotenv() + + action_inputs = { + key: (cast(os.getenv(f"INPUT_{key}")) if os.getenv(f"INPUT_{key}") else None) + for (key, cast) in input_schema.items() + } + + # Loading Kaggle credentials into the environment: + os.environ["KAGGLE_USERNAME"] = action_inputs["KAGGLE_USERNAME"] + os.environ["KAGGLE_KEY"] = action_inputs["KAGGLE_KEY"] + + return action_inputs diff --git a/kaggle_graph/plot/__init__.py b/kaggle_graph/plot/__init__.py new file mode 100644 index 0000000..0bbdddb --- /dev/null +++ b/kaggle_graph/plot/__init__.py @@ -0,0 +1,121 @@ +""" +Plot +==== + +This module contains methods for generating and saving graphs of Kaggle +submission scores. +""" + +# Standard Library --------------------------------------------------------------------- +from typing import Optional + +# Third Party -------------------------------------------------------------------------- +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.dates import DateFormatter, DayLocator + +# Data Science +import pandas as pd + + +def kaggle_submission_scores( + submissions_df: pd.DataFrame, + graph_name: Optional[str] = "kaggle-submissions-graph", + ymin: Optional[float] = None, + ymax: Optional[float] = None, + objective: Optional[float] = None, + score: Optional[str] = "positive", +) -> None: + """Save plot of Kaggle submission scores. + + Parameters + ---------- + submissions_df + Submissions data frame + graph_name: optional + Name of the graph image file to be saved (defaults to + :code:`"kaggle-submissions-graph"`). + ymin: optional + Y-axis mimumium boundary (set automatically if not provided). + ymax: optional + Y-axis maximum boundary (set automatically if not provided). + objective: optional + Score objective, to be displayed on the graph (defaults + to :code:`None`). + score: optional + Specifies whether the objective is to :code:`"maximize"` or + :code:`"minimize"` the score (defaults to :code:`"maximize"`) + + Returns + ------- + None + While nothing is returned, a plot of the Kaggle submission + scores is saved, under :code:`f"./{graph_name}.png"`. + + Note + ---- + This function is intended to be used in conjunction with + :meth:`kaggle_graph.submissions.parse`. + + Example + ------- + >>> from kaggle_graph.submissions import fetch, parse + >>> kaggle_submission_scores(parse(fetch("titanic"))) + """ + if graph_name is None: + graph_name = "kaggle-submissions-graph" + + # Clean and Sort Data + submissions_df.dropna(subset=["publicScore"], inplace=True) + submissions_df.sort_values(by=["date"], inplace=True) + + # Graph + sns.set() + g = sns.lineplot(x="date", y="publicScore", data=submissions_df, marker="o") + + # Title and Labels + g.set(title="Kaggle", xlabel="Date", ylabel="Public Score") + g.xaxis.set_major_locator(DayLocator(interval=1)) + g.xaxis.set_major_formatter(DateFormatter("%d %b %y")) + + # Boundaries + if ymin is not None: + plt.ylim(ymin=ymin) + if ymax is not None: + plt.ylim(ymax=ymax) + + # Color Areas + if score is None: + score = "positive" + plt.fill_between( + submissions_df["date"], submissions_df["publicScore"], color="b", alpha=0.3 + ) + if objective: + g.axhline(objective, ls="-", color="r", alpha=0.3) + plt.fill_between( + submissions_df["date"], + submissions_df["publicScore"], + objective, + where=submissions_df["publicScore"] < objective, + interpolate=True, + color="g" if score == "negative" else "r", + alpha=0.3, + ) + plt.fill_between( + submissions_df["date"], + objective, + submissions_df["publicScore"], + where=objective <= submissions_df["publicScore"], + interpolate=True, + color="r" if score == "negative" else "g", + alpha=0.3, + ) + + # Save + graph_file_name = f"{graph_name}.png" + + plt.savefig( + graph_file_name, + # Prevent labels to be cut off in saved image: + bbox_inches="tight", + ) diff --git a/kaggle_graph/submissions/__init__.py b/kaggle_graph/submissions/__init__.py new file mode 100644 index 0000000..faa1c97 --- /dev/null +++ b/kaggle_graph/submissions/__init__.py @@ -0,0 +1,141 @@ +""" +Submissions +=========== + +This module contains methods for fetching and parsing submissions to +`Kaggle Competitions`_. + +.. _Kaggle Competitions: https://www.kaggle.com/competitions +""" + +# Standard Library --------------------------------------------------------------------- +import subprocess +from io import StringIO + +# Data Science +import pandas as pd + + +class KaggleError(Exception): + """Raised when a Kaggle API call exits with a non-zero error code.""" + + ... + + +def fetch(competition_id: str) -> str: + """Fetch submissions to the Kaggle competition. + + .. warning:: + + This method relies on Kaggle credentials to be exposed under + the environment variables: + + - :code:`KAGGLE_USERNAME` + - :code:`KAGGLE_KEY` + + Parameters + ---------- + competition_id + Kaggle competition ID (can be read from the Kaggle competition + URL). + + Returns + ------- + :code:`str` + Kaggle submissions table in CSV format. + + The table contains the following columns and associated types:: + + fileName date description status publicScore privateScore + --------------------- ------------------- ----------------- -------- ----------- ------------ + gender_submission.csv 2020-04-27 10:56:14 Gender Submission complete 0.76555 0.75443 + + Raises + ------ + KaggleError + If the Kaggle API call fails. + + Example + ------- + >>> fetch("titanic") + fileName,date,description,status,publicScore,privateScore + gender_submission.csv,2020-04-27 10:56:14,Gender Submission,complete,0.76555,None + + References + ---------- + - `Kaggle API Credentials`_ + - `Kaggle Submissions API`_ + + .. _Kaggle API Credentials: https://github.com/Kaggle/kaggle-api#api-credentials + .. _Kaggle Submissions API: https://github.com/Kaggle/kaggle-api/#list-competition-submissions + """ + result = subprocess.run( + ["kaggle", "competitions", "submissions", "--csv", competition_id], + capture_output=True, + ) + + if result.returncode == 1: + output = str(result.stderr or result.stdout, "utf-8").replace("\n", "\n\t\t") + raise KaggleError( + f"Unexpected error in fetching Kaggle submissions:\n" + f"\tReturn Code: {result.returncode}\n" + f"\tOutput: {output}\n" + "\tPlease verify the Kaggle competition ID and credentials " + "provided in the environment" + ) + elif result.returncode == 2: + output = str(result.stderr or result.stdout, "utf-8").replace("\n", "\n\t\t") + raise KaggleError( + f"Misused Kaggle command in fetching submissions:\n" + f"\tReturn Code: {result.returncode}\n" + f"\tOutput:\n\t\t{output}\n" + "\tPlease check the documentation: https://github.com/Kaggle/kaggle-api#kaggle-api" + ) + else: + return str(result.stdout, "utf-8") + + +def parse(kaggle_csv_submissions_table: str) -> pd.DataFrame: + """ + + Parameters + ---------- + kaggle_csv_submissions_table + Kaggle submissions table in CSV format. + + The table should contain the following columns and associated + types:: + + fileName date description status publicScore privateScore + --------------------- ------------------- ----------------- -------- ----------- ------------ + gender_submission.csv 2020-04-27 10:56:14 Gender Submission complete 0.76555 0.75443 + + + Returns + ------- + :code:`pd.DataFrame` + Kaggle submissions data frame. + + Note + ---- + This function is intended to be used in conjunction with :meth:`fetch`. + + Example + ------- + >>> parse(fetch("titanic")) + fileName date ... publicScore privateScore + 0 gender_submission.csv 2020-04-27 10:56:14 ... 0.76555 0.75443 + """ + submissions_df = pd.read_csv(StringIO(kaggle_csv_submissions_table),) + + submissions_df["date"] = pd.to_datetime( + submissions_df["date"], format="%Y-%m-%d %H:%M:%S" + ) + submissions_df["publicScore"] = pd.to_numeric( + submissions_df["publicScore"], errors="coerce" + ) + submissions_df["privateScore"] = pd.to_numeric( + submissions_df["privateScore"], errors="coerce" + ) + + return submissions_df diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9286d89 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,82 @@ +[tool.black] # https://black.readthedocs.io/en/stable/pyproject_toml.html +target-version = ['py37', 'py38'] +include = '\.pyi?$' # Default +exclude = ''' +# Directory exclusions +/( + # Defaults: + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + # Add additional patterns here: + # | my_directory +)/ + +| # File exclusions +/( + # Add additional file patterns here: + # my_file\.py +) +''' + +[tool.isort] # https://github.com/timothycrosley/isort/wiki/isort-Settings#full-reference-of-isort-settings +# Compatibility with Black (https://black.readthedocs.io/en/stable/the_black_code_style.html#how-black-wraps-lines) +# Style --------------------- +line_length=88 +wrap_length=88 +multi_line_output=3 +combine_star='True' +combine_as_imports='True' +balanced_wrapping='True' +indent=4 +lines_after_imports=2 +lines_between_types=0 +include_trailing_comma='True' +use_parentheses='True' +#force_single_line #Would override mult-line_output +#force_grid_wrap #Would override mult-line_output +# Sections ------------------ +default_section="THIRDPARTY" +known_datascience=['pandas', 'numpy', 'scipy', 'sklearn'] +import_heading_stdlib="Standard Library ---------------------------------------------------------------------" +import_heading_thirdparty="Third Party --------------------------------------------------------------------------" +import_heading_datascience="Data Science" +import_heading_firstparty="Project ------------------------------------------------------------------------------" +#forced_separate="" +# Order --------------------- +sections=['FUTURE','STDLIB', 'THIRDPARTY', 'DATASCIENCE', 'FIRSTPARTY','LOCALFOLDER'] +case_sensitive='False' +length_sort='False' +order_by_type='True' +force_alphabetical_sort='False' +force_sort_within_sections='False' +from_first='False' +reverse-relative='True' +#force_to_top="" +# Inclusions/Exclusions ----- +#skip # Requires full file path +skip_glob=[ + ".eggs/**", + ".git/*", + ".hg/*", + ".mypy_cache/*", + ".tox/*", + ".venv/*", + "_build/*", + "buck-out/*", + "build/*", + "dist/*", + # Add additional patterns here: + # "my_directory/*" +] +#not_skip=[] +#filter_files # Does not seem to work... +# Other --------------------- +atomic='False' diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..6dc6368 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-r requirements-docs.txt + +black==19.10b0 +isort==4.3.21 +docformatter==1.3.1 diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..f89f55c --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,2 @@ +sphinx==3.0.3 +sphinx-rtd-theme==0.4.3 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d6e1198 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +-e . diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6bb8c49 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +# Third Party -------------------------------------------------------------------------- +from setuptools import setup + + +# This minimal package configuration is solely meant to make the module +# installable for use in the action Docker container and not to publish +# it on PyPi. +setup( + name="kaggle_graph", + install_requires=[ + "kaggle==1.5.6", + "python-dotenv==0.13.0", + "pandas==1.0.3", + "seaborn==0.10.1", + "matplotlib==3.2.1", + ], +) diff --git a/workflow.template.yaml b/workflow.template.yaml new file mode 100644 index 0000000..e0718a0 --- /dev/null +++ b/workflow.template.yaml @@ -0,0 +1,24 @@ +name: Kaggle Graph Generation + +on: + push: + branches: master + schedule: + - cron: '00 00 * * *' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v2 + - name: Kaggle Graph Generation + uses: OliverSieweke/kaggle-graph@v0.1.0 + with: + KAGGLE_KEY: ${{ secrets.KAGGLE_KEY }} + KAGGLE_USERNAME: oliversieweke + KAGGLE_COMPETITION_ID: titanic + - name: Git Auto Commit + uses: stefanzweifel/git-auto-commit-action@v4.1.6 + with: + commit_message: Automatic Kaggle graph generation