{"id":25023247,"url":"https://github.com/corey-richardson/archery-linear-regression","last_synced_at":"2026-05-11T02:02:16.015Z","repository":{"id":189234116,"uuid":"680302691","full_name":"corey-richardson/archery-linear-regression","owner":"corey-richardson","description":"A Flask web application that allows the user to view and add scores to a dataframe, and uses these values in a linear regression model to estimate their average arrow score at different distances.","archived":false,"fork":false,"pushed_at":"2023-11-30T19:39:40.000Z","size":2563,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-05T14:40:58.483Z","etag":null,"topics":["css","csv","flask","html","matplotlib-pyplot","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/corey-richardson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-18T21:11:46.000Z","updated_at":"2023-08-18T22:01:49.000Z","dependencies_parsed_at":"2023-11-30T20:45:59.473Z","dependency_job_id":null,"html_url":"https://github.com/corey-richardson/archery-linear-regression","commit_stats":null,"previous_names":["corey-richardson/archery-linear-regression"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corey-richardson%2Farchery-linear-regression","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corey-richardson%2Farchery-linear-regression/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corey-richardson%2Farchery-linear-regression/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corey-richardson%2Farchery-linear-regression/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/corey-richardson","download_url":"https://codeload.github.com/corey-richardson/archery-linear-regression/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246320130,"owners_count":20758407,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","csv","flask","html","matplotlib-pyplot","python3"],"created_at":"2025-02-05T14:39:07.928Z","updated_at":"2026-05-11T02:02:10.989Z","avatar_url":"https://github.com/corey-richardson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# archery-linear-regression\nA Flask web application that allows the user to view and add scores to a dataframe, and uses these values in a linear regression model to estimate their average arrow score at different distances.\n\n---\n\n## Contents\n\n- [dataframe](#dataframe)\n- [the-regressor-model](#the-regressor-model)\n- [the-index-route](#the-index-route)\n- [forms-py-getscoredata](#formspy---getscoredata)\n- [index-html](#indexhtml)\n- [the-submit-route](#the-submit-route)\n- [submit-html](#submithtml)\n- [style-css](#stylecss)\n- [predicted-results](#predicted-results)\n- [post-competition-changes](#post-competition-changes)\n- [summary-statistics](#summary-statistics)\n- [plotting](#plotting)\n\n---\n\n## Dataframe\n\nUses the Pandas module to read `archery.csv` into a Dataframe.\n```py\nscore_data = pd.read_csv(\"static/archery.csv\", header = 0)\n```\n\nNew columns `\"days_since_first_entry\"` and `\"gold_pct\"` are created. These features are used within the model as a predictive and predicted variable respectively.\n```py\nscore_data.date = pd.to_datetime(score_data.date)\nscore_data[\"golds_pct\"] = (score_data.golds / score_data.arrows)*100\nscore_data[\"days_since_first_entry\"] = (\n    score_data.date - min(score_data.date) ).dt.days\n\nmost_recent_date = max(score_data.date).strftime(\"%Y-%m-%d\")\n```\n\n---\n\n## The Regressor Model\n\nThe data is split into training and testing subsets. The parameter `random_state = 123` ensures the model is the same each time it is ran. `123` was found to be the value that maximises the average model score between the training and testing subsets. \u003cbr\u003e\nThis is also where the model is initialised and trained.\n```py\nX_train, X_test, y_train, y_test = train_test_split(\n    features, scored, test_size = 0.2, random_state = 123)\n\nmodel = linear_model.LinearRegression()\nmodel.fit(X_train, y_train)\n\ntrain_score = model.score(X_train, y_train)\ntest_score = model.score(X_test, y_test)\nprint(f\"Train Model Score: {train_score}\")\nprint(f\"Test Model Score: {test_score}\\n\")\n```\n\n---\n\n## The 'index' Route\n\n```py\n@app.route('/', methods=[\"GET\",\"POST\"])\ndef index():\n    get_score_data = GetScoreData()\n    # On submission...\n    if get_score_data.validate_on_submit():\n        # Getters for form data\n        distance = get_score_data.distance.data\n        days_till = get_score_data.days_till.data\n        is_comp = get_score_data.is_comp.data\n        \n        # Sanitise input\n        distance, days_till = float(distance), float(days_till)\n        \n        # Use the trained model to predict output variables from input variables\n        # max(score_data.days_since_first_entry) + days_till \n        # --\u003e Most recent entry to .csv file + user specified number of days\n        guesses = model.predict(\n            [[distance, \n              max(score_data.days_since_first_entry) + days_till, \n              is_comp]]\n        )\n        \n        # Sanitise output\n        # Max possible score is 10\n        # Max gold_pct is 100%\n        if guesses[0][0] \u003e 10:\n            guesses[0][0] == f\"10.00 {guesses[0][0]}\"\n        if guesses[0][1] \u003e 100:\n            guesses[0][1] = 100\n\n        # Save vars to server-side-stored session data\n        session[\"distance\"] = distance\n        session[\"days_till\"] = days_till\n        session[\"avg_score\"] = guesses[0][0]\n        session[\"gold_pct\"] = guesses[0][1]\n        session[\"is_comp\"] = is_comp\n        \n        # Redirect to the 'submitted' path\n        return redirect(url_for(\n            'submitted',\n            _external=True, \n            scheme='https'\n        ))\n     \n    return render_template(\n        \"index.html\", \n        get_score_data=get_score_data, most_recent_date = most_recent_date\n    )\n```\n\nIn the index route - `127.0.0.1:5000/` - the Form object is called and validated.\n```py\n@app.route('/', methods=[\"GET\",\"POST\"])\ndef index():\n    get_score_data = GetScoreData()\n    if get_score_data.validate_on_submit():\n        distance = get_score_data.distance.data\n        days_till = get_score_data.days_till.data\n```\n\nThe model then predicts the average arrow score and percentage of arrows scoring 9.\n```py\nguesses = model.predict([[distance, max(score_data.days_since_first_entry) + days_till]])\n```\n\u003e `max(score_data.days_since_first_entry)` corresponds to the most recent entry to the csv file. \u003cbr\u003e\n\u003e `+ days_till` then adds the desired elapsed number of days to this value.\n\nData sanitisation is used here for when the model predicts values which are out of range of the real possible values. For example, the maximum score on a 122cm target is 10, and you can't have over 100% of your arrows being gold.\n\u003e 10 is the highest possible score when shooting a metric round using 10-zone scoring (10,9,8,7,6,5,4,3,2,1); when shooting an imperial round using 5-zone scoring (9,7,5,3,1) the maximum score is 9.\n```py\nif guesses[0][0] \u003e 10:\n    guesses[0][0] == f\"10.00 {guesses[0][0]}\"\nif guesses[0][1] \u003e 100:\n    guesses[0][1] = 100\n```\n\n![122cm face](https://cdn.shopify.com/s/files/1/1530/4477/products/paper-archery-target-face-122cm-fita_large.png?v=1552558054)\n\nHere I used the `flask.session` method to save the data server-side to later be access in the '/submitted' route. \u003cbr\u003e\nThen, I `redirect` to the '/submitted' route to display the model's prediction.\n```py\nsession[\"distance\"] = distance\nsession[\"days_till\"] = days_till\nsession[\"avg_score\"] = guesses[0][0]\nsession[\"gold_pct\"] = guesses[0][1]\n\nreturn redirect(url_for(\n    'submitted',\n    _external=True, \n    scheme='https'\n))\n```\n\nWhilst the form has not been validated, the template `\"index.html\"` is displayed.\n```py\nreturn render_template(\n    \"index.html\", \n    get_score_data=get_score_data, \n    most_recent_date = most_recent_date\n)\n```\n\n![index-route](/archery_predictor/readme_assets/index.PNG)\n\u003e I have a competition shooting a National (48 arrows at 60 yards, 24 arrows at 50 yards) on 2023-05-28 so I will use a \"Days Till Shoot\" value of 5 in my example images.\n\n## forms.py - GetScoreData\n\n```py\nfrom flask_wtf import FlaskForm\nfrom wtforms import RadioField, SubmitField, IntegerField\nfrom wtforms.validators import DataRequired\n\nclass GetScoreData(FlaskForm):\n    distance = RadioField(\n        \"Distance\",\n        choices = [\n            (10.0, \"10 yards\"),\n            (19.685, \"18 metres\"),\n            (20.0, \"20 yards\"),\n            (30.0, \"30 yards\"),\n            (40.0, \"40 yards\"),\n            (50.0, \"50 yards\"),\n            (54.6807, \"50 metres\"),\n            (60, \"60 yards\"),\n            (76.5529, \"70 metres\"),\n            (80.0, \"80 yards\"),\n            (100.0, \"100 yards\")\n        ]\n    )\n    days_till = IntegerField(\"Days Till Shoot: \", validators=[DataRequired()])\n    submit = SubmitField(\"Submit\")\n```\n\n## index.html\n\n```html\n\u003clink href=\"static/style.css\" rel=\"stylesheet\" /\u003e\n\n\u003cform action=\"/\" method=\"post\"\u003e\n\n    {{ get_score_data.hidden_tag() }}\n\n\n    \u003ch2\u003e {{ get_score_data.distance.label }} \u003c/h2\u003e\n    \u003ctable\u003e\n        \u003ctr\u003e\n            {% for btn in get_score_data.distance %}\n            \u003ctd\u003e{{ btn()     }}\u003c/td\u003e\n            \u003ctd\u003e{{ btn.label }}\u003c/td\u003e\n            {% endfor %}\n        \u003c/tr\u003e\n    \u003c/table\u003e \u003cbr\u003e\n\n    \u003ch2\u003e {{get_score_data.days_till.label}}\u003c/h2\u003e\n    \u003ch4\u003e(since {{ most_recent_date }})\u003c/h3\u003e\n    {{ get_score_data.days_till() }}\n\n    {{ get_score_data.submit() }}\n\n\u003c/form\u003e\n```\n\nThe `hidden_tag()` template argument generates a hidden field that includes a token that is used to protect the form against CSRF attacks. \n```html\n{{ get_score_data.hidden_tag() }}\n```\n\nNext, I create a table object and iterate through each radio button option defined in the form definition.\n```html\n\u003ch2\u003e {{ get_score_data.distance.label }} \u003c/h2\u003e\n\u003ctable\u003e\n    \u003ctr\u003e\n        {% for btn in get_score_data.distance %}\n        \u003ctd\u003e{{ btn()     }}\u003c/td\u003e\n        \u003ctd\u003e{{ btn.label }}\u003c/td\u003e\n        {% endfor %}\n    \u003c/tr\u003e\n\u003c/table\u003e \u003cbr\u003e\n```\n\nI also display a text field object allowing the user to enter the \"Days Till Shoot\" value. The `\u003ch4\u003e` element here is used to display the date of the latest entry in the csv file.\n```html\n\u003ch2\u003e {{get_score_data.days_till.label}}\u003c/h2\u003e\n\u003ch4\u003e(since {{ most_recent_date }})\u003c/h3\u003e\n{{ get_score_data.days_till() }}\n```\n\n---\n\n## The 'submit' Route\n\n```py\n@app.route('/submitted', methods=[\"GET\",\"POST\"])\ndef submitted():\n    \n    distance = session[\"distance\"]\n    days_till = session[\"days_till\"]\n    avg_score = session[\"avg_score\"]\n    gold_pct = session[\"gold_pct\"]\n        \n    return render_template( # should use redirect here but wouldnt work oops\n        \"submit.html\",\n        distance = distance,\n        days_till = days_till,\n        avg_score = f\"{avg_score:.3f}\",\n        gold_pct = f\"{gold_pct:.2f}\",\n        _external=True, _scheme='https')\n```\n\nThe 'submitted' route - `127.0.0.1/submitted' - displays the output of the model.\n\nFirstly, I retrieve the variables I want to display from the server-side `flask.session` storage.\n```py\ndistance = session[\"distance\"]\ndays_till = session[\"days_till\"]\navg_score = session[\"avg_score\"]\ngold_pct = session[\"gold_pct\"]\n```\n\nThen, I display the `\"submit.html\"` template passing `distance`, `days_till`, `avg_score` and `gold_pct` as arguments. `avg_score` and `gold_pct` are directly derived and formatted from the models output.\n```py\nreturn render_template( # should use redirect here but wouldnt work oops\n    \"submit.html\",\n    distance = distance,\n    days_till = days_till,\n    avg_score = f\"{avg_score:.3f}\",\n    gold_pct = f\"{gold_pct:.2f}\",\n    _external=True, _scheme='https')\n```\n\n![submitted](/archery_predictor/readme_assets/submitted.PNG)\n\n---\n\n## submit.html\n\n```html\n\u003clink href=\"static/style.css\" rel=\"stylesheet\" /\u003e\n\n\u003cp\u003eIn \u003cb\u003e{{ days_till | int }} days\u003c/b\u003e you could be scoring an average arrow score of \u003cb\u003e{{ avg_score }}\u003c/b\u003e at \u003cb\u003e{{ distance }} yards / {{ (distance / 1.094) | round(1)\n}} metres\u003c/b\u003e with \u003cb\u003e{{ gold_pct }}%\u003c/b\u003e being golds. (122cm Target Face)\u003c/p\u003e\n\u003cp\u003eWant to try another distance? Click \u003ca href=\"/\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n```\n\n- `{{ days_till | int }}` - cast from a `float` to an `int`\n- `{{ avg_score }}`\n- `{{ distance }} yards / {{ (distance / 1.094) | round(1)\n}} metres` - display the distance in both yards and metres. The model works entirely in yards and so the values for the 18m / 50m /70m radio button options need to be converted to metres again before being displayed to the user.\n- `{{ gold_pct }}`\n\n---\n\n## style.css\n\nDefine variables for later use. These are taken directly from my [Yelverton Bowmen Website Prototype](https://github.com/corey-richardson/yelverton-bowmen/tree/main) project and as such are not all used here.\n```css\n:root {\n    --yb_blue: #0080FE;\n    --yb_yellow: #FFFC00;\n    --yb_light_blue: #E0E8FF;\n    --yb_dark_blue: #002447;\n}\n```\n\nSet ALL elements to the 'Lucida Grande' font.\n```css\n* {\n    font-family: \"Lucida Grande\", \"Lucida Sans Unicode\";\n    padding: 0;\n}\n```\n\n```css\na {\n    color: var(--yb_blue);\n}\n\na:hover {\n    color: var(--yb_dark_blue);\n}\n```\n\n```css\nh1, h2, h3, h4 {\n    color: var(--yb_blue);\n    margin-top: 0px;\n    margin-bottom: 0px;\n}\n```\n\nSet the bold elements to be blue.\n```css\nb {\n    color: var(--yb_blue);\n}\n```\n\n---\n\n## Predicted Results\n\nDuring my competition on 2023-05-28 I will be shooting a National Round. This comes `5` days after the most recent entry to the csv file. \n\nA National round consists of 48 arrows at 60 yards and 24 arrows at 50 yards.\n\nUsing my model, I can predict an average arrow score of **8.005** with **53.83%** of my shots scoring a 9 at 60 yards and an average arrow score of **8.297** with **66.11%** of my shots scoring a 9 at 50 yards.\n\n$8.005 \\times 48 = 384.24$\n\n$8.297 \\times 24 = 199.128$\n\n$384.24 + 199.128 = 583.368 \\approx 583$\n\nThis score would be enough to achieve a Bowman 3rd Class classification.\n\n*After the competition:* \n- 60 yards: 7.63 / 47.9%\n- 50 yards: 7.67 / 50%\n- Classification: Archer 1st Class\n\n`:(`\n\n---\n\n## Post-Competition Changes\n\nThe Brixham Archers Open Competition showed me how scores can change with the added pressure. As such, I added a feature to `is_comp` to the model. This value takes a `0` or `1` value for where `1` is True. \n\n```py\nprint(f\"Score Data grouped by Competition Status: \\n{score_data.groupby([score_data.distance, score_data.is_comp])[['arrow_average','arrows','golds_pct']].mean()}\\n\")\n```\n```\nScore Data grouped by Competition Status: \n                  arrow_average     arrows  golds_pct\ndistance is_comp                                     \n30       0             8.750000  50.000000  87.478956\n40       0             8.351250  30.000000  69.097222\n50       0             8.085714  30.857143  61.111111\n         1             7.670000  24.000000  50.000000\n60       0             8.140000  36.000000  56.944444\n         1             7.630000  48.000000  47.916667\n```\n\nA `BooleanField` from `wtforms` is used to create a checkbox taking the input.\n```py\nis_comp = BooleanField(\"Competition? \")\n```\n\nThe returned value is then passed through into `'index.html'` to display the box.\n```html\n    \u003ch2\u003e {{ get_score_data.is_comp.label }} \u003c/h2\u003e\n    {{ get_score_data.is_comp() }}\n```\n\nA current limitation of this feature is the lack of datapoints for the model to observe from. Only 2 datapoints does not provide the model enough data to accurately predict the impact (coefficient) to the average arrow score feature.\n```\n7.63,60,\"2023-05-28\",23,48,1\n7.67,50,\"2023-05-28\",12,24,1\n```\n\n---\n\n## Summary Statistics\n\nThese lines use `Pandas` `.groupby()` method to display mean statistics for the `arrow_average` [average arrow score], `arrows` [arrows shot] and `golds_pct` [golds percentage] columns.\n\nIt seperates these statistics by:\n- Day of Week\n- Month and Year\n- Distance to Target\n- Is it a competition shoot?\n\n```py\n# .groupby() the desired columns\n# Select only the columns to display\n# Create dataframe with mean values\n# Create dataframe with .count() of single column\n# Merge the dataframes into one\n# Print\n\n# OR\n\n# .groupby() the desired columns\n# Select only the columns to display\n# Create a dataframe with mean values\n# Print\n\n# Display the trends depending on day of week\nday_of_week = score_data.groupby(score_data.day_of_week)\nday_of_week_cols = day_of_week[['arrow_average','arrows','golds_pct']]\nday_of_week_summary = day_of_week_cols.mean()\nday_of_week_count = score_data.groupby(score_data.day_of_week)['date'].count()\nday_of_week_merged = day_of_week_summary.merge(day_of_week_count, on=[\"day_of_week\"])\nprint(f\"\\n\\nScore Data grouped by Day of Week: \\n{day_of_week_merged}\\n\")\n\n# Display the trends depending on month and year\nmonth_and_year = score_data.groupby(\n    [score_data.date.dt.year, score_data.date.dt.month])\nmonth_and_year_cols = month_and_year[['arrow_average','arrows','golds_pct']]\nmonth_and_year_summary = month_and_year_cols.mean()\nprint(f\"Score Data grouped by Month: \\n{month_and_year_summary}\\n\")\n\n# Display the trends depending on month and year ALSO seperated by Distance to target\nmonth_year_dist = score_data.groupby(\n    [score_data.distance, score_data.date.dt.year, score_data.date.dt.month] )\nmonth_year_dist_cols = month_year_dist[['arrow_average','arrows','golds_pct']]\nmonth_year_dist_summary = month_year_dist_cols.mean()\nprint(f\"Score Data grouped by Distance by Month: \\n{month_year_dist_summary}\\n\")\n\n# Display the trends depending on distance\ndist = score_data.groupby(['distance'])\ndist_cols = dist[['arrow_average','arrows','golds_pct']]\ndist_summary = dist_cols.mean()\nprint(f\"Score Data grouped by Distance: \\n{dist_summary}\\n\")\n\n# Display the trends depending on whether or not the shoot was at a competition\ndist_comp = score_data.groupby([score_data.distance, score_data.is_comp])\ndist_comp_cols = dist_comp[['arrow_average','arrows','golds_pct']]\ndist_comp_summary = dist_comp_cols.mean()\nprint(f\"Score Data grouped by Competition Status: \\n{dist_comp_summary}\\n\")\n```\n\u003e These were originally single-line expressions but I expanded them out to multiple lines to be (mostly) within the 80 character line rule and for \"readability\"; I'm not sure it had the desired effect. Was originally:\n```py\nprint(f\"Score Data grouped by Month: \\n{score_data.groupby([score_data.date.dt.year, score_data.date.dt.month])[['arrow_average','arrows','golds_pct']].mean()}\\n\")\n```\n\nThis outputs:\n```\nScore Data grouped by Day of Week: \n             arrow_average  arrows  golds_pct  date\nday_of_week                                        \n1                 8.330000   27.75  69.444444     8\n4                 8.343333   32.00  68.981481     6\n6                 8.315000   39.00  70.199315    14\n\nScore Data grouped by Month: \n           arrow_average      arrows  golds_pct\ndate date                                      \n2023 4          8.656250   33.000000  83.159722\n     5          8.149444   29.666667  62.692901\n     6          8.640000  132.000000  81.818182\n\nScore Data grouped by Distance by Month: \n                    arrow_average      arrows  golds_pct\ndistance date date                                      \n30       2023 4          8.797500   33.000000  89.930556\n              5          8.670000   36.000000  83.333333\n              6          8.640000  132.000000  81.818182\n40       2023 4          8.515000   33.000000  76.388889\n              5          8.284000   22.800000  66.111111\n50       2023 5          8.076667   29.333333  61.419753\n60       2023 5          7.970000   40.000000  53.935185\n\nScore Data grouped by Distance: \n          arrow_average     arrows  golds_pct\ndistance                                     \n30             8.750000  50.000000  87.478956\n40             8.386667  27.333333  70.679012\n50             8.076667  29.333333  61.419753\n60             7.970000  40.000000  53.935185\n\nScore Data grouped by Competition Status: \n                  arrow_average     arrows  golds_pct\ndistance is_comp                                     \n30       0             8.750000  50.000000  87.478956\n40       0             8.386667  27.333333  70.679012\n50       0             8.127500  30.000000  62.847222\n         1             7.670000  24.000000  50.000000\n60       0             8.140000  36.000000  56.944444\n         1             7.630000  48.000000  47.916667\n```\n\nThe aim of these statistics is to highlight relationship affecting my scores. For example:\n- Does shooting on a Tuesday after work decrease my average score per arrow?\n- Does shooting on a Sunday in the morning increase my average score per arrow? \n- Does my average score increase as time goes on? Am I improving?\n- Has my average score increase or decrease during the time period after making a change to my equipement? \n- By how much does my score drop when I increase the distance?\n- Does shooting in a competition - with added nerves - decrease my average score?\n\n---\n\n## Plotting\n\nI used Matplotlib's `pyplot` module and the `seaborn` module to plot how my minimum, average and maximum arrow average score is affected by the distance being shot, the day of week the shooting occurs and whether or not it is done at a competition event\n\n```py\nfrom matplotlib import pyplot as plt\nfrom matplotlib.colors import ListedColormap\nimport seaborn as sns\nprint(\"plotting\")\n\n# Figure labels\nplt.xlabel(\"Distance\")\nplt.ylabel(\"Average Arrow Score\")\n\n# Colour Map used to differentiate days of week or by month\ncmap_seven = plt.cm.get_cmap('viridis', 7)\ncmap_twelve = plt.cm.get_cmap('viridis', 12)\n\n# Scatterplot of distance against arrow average\n# Style: O markers if not competition, X markers if is competition\n# Hue: Change colour of marker depending on day of week of shoot, \n#      uses 'cmap' to discern colours\ndef plot_by(hue_type, label, cmap):\n    plt.clf()\n    sns.scatterplot(\n        data = score_data,\n        x = \"distance\", y = \"arrow_average\",\n        style = \"is_comp\",\n        hue = hue_type,\n        palette = cmap\n    )\n\n    # Plot lines for min / avg / max arrow scores at each distance\n    plt.plot(score_data.distance.unique(), score_data.groupby(['distance']).max().arrow_average, \"k:\")\n    plt.plot(score_data.distance.unique(), score_data.groupby(['distance']).mean().arrow_average, \"g--\")\n    plt.plot(score_data.distance.unique(), score_data.groupby(['distance']).min().arrow_average, \"k:\")\n    \n    plt.show(block=False)\n    plt.savefig(f\"{label}_fig.png\")\n\nplot_by(score_data.day_of_week, \"day_of_week\", cmap_seven)    \nplot_by(score_data.date.dt.month, \"month\", cmap_twelve)\n```\n\nThis outputs the following graph.\n\n![fig.png](/archery_predictor/day_of_week_fig.png)\n\nThese lines clear the figure and plot all the datapoints as a scatter plot.\n```py\n    plt.clf()\n    sns.scatterplot(\n        data = score_data,\n        x = \"distance\", y = \"arrow_average\",\n        style = \"is_comp\",\n        hue = hue_type,\n        palette = cmap\n    )\n```\n\n`style = \"is_comp\"` controls the marker style with circle markers representing non-competition shoots and X markers representing competition shoots.\n\n`hue = hue_type, palette = cmap` controls the colour styling with each colour representing either a different day of the week or different month depending on how the function is called. \n```py\nplot_by(score_data.day_of_week, \"day_of_week\", cmap_seven)    \nplot_by(score_data.date.dt.month, \"month\", cmap_twelve)\n```\n\nThe function takes in as parameters:\n- the data to plot\n- the label to assign to the saved image\n- the colour map to use; either 'viridis' spaced into 7 or 12 bins\n\n![month fig](/archery_predictor/month_fig.png)\n\nThese lines plot the minimum, average and maximum score values for each distance as line graphs.\n```py\nplt.plot(score_data.distance.unique(), score_data.groupby(['distance']).max().arrow_average, \"k:\")\nplt.plot(score_data.distance.unique(), score_data.groupby(['distance']).mean().arrow_average, \"g--\")\nplt.plot(score_data.distance.unique(), score_data.groupby(['distance']).min().arrow_average, \"k:\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorey-richardson%2Farchery-linear-regression","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcorey-richardson%2Farchery-linear-regression","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorey-richardson%2Farchery-linear-regression/lists"}