From 574cb795d81dbbc7d841258392fbe04e6c380aeb Mon Sep 17 00:00:00 2001 From: jake-fawcett Date: Thu, 23 Nov 2023 22:24:28 +0000 Subject: [PATCH] added position calculator with logic, added pre-commit linting, updated requirements and readme, added attribute rating for position calculator --- .pre-commit-config.yaml | 46 +++++++++ README.md | 15 ++- attribute_ratings.xlsx | Bin 0 -> 166264 bytes position_score_calculator.py | 162 +++++++---------------------- requirements.txt | 5 +- role_score_calculator.py | 192 +++++++++++++++++++++++++++++++++++ 6 files changed, 292 insertions(+), 128 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 attribute_ratings.xlsx create mode 100644 role_score_calculator.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..25c0680 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + args: [--maxkb=2500] + - id: check-ast + - id: check-case-conflict + - id: check-docstring-first + - id: check-json + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: name-tests-test + args: [--pytest-test-first] + # - id: no-commit-to-branch + # args: [--branch, main] +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) + args: ["--profile", "black"] +- repo: https://github.com/pre-commit/mirrors-yapf + rev: v0.32.0 + hooks: + - id: yapf + additional_dependencies: [toml] + args: [--style "google" ] +- repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + args: [--line-length, '180'] +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: [--docstring-convention, google, --max-line-length, '180', --ignore, 'D100,D101,D102,D103,D104'] + additional_dependencies: [flake8-bugbear, flake8-docstrings, pydocstyle==6.1.1] +- repo: https://github.com/pycqa/bandit + rev: 1.7.4 + hooks: + - id: bandit + args: [--skip, B608] diff --git a/README.md b/README.md index df46c86..bc4f4f7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ # Football Manager player role evaluation -Python code to evaluate Player attributes in Football Manager against roles based on role weightings. Work in progress. +Python code to evaluate Player attributes in Football Manager against roles based on role weightings. Work in progress. Inspired by squirrel_plays_FOF's video [FM24 player recruitment using python](https://www.youtube.com/watch?v=hnAuOakqR90) -Inspired by squirrel_plays_FOF's video [FM24 player recruitment using python](https://www.youtube.com/watch?v=hnAuOakqR90) +This is split into two scripts; position_score_calculator.py which calculates score based on positions ([Inspired by Mark on fm-arena](https://fm-arena.com/thread/1949-fm22-positional-filters-what-are-the-best-attributes-for-each-position/)) and role_score_calculator.py which caculates scores based on roles (this is missing plenty of roles at the moment!). ## Usage + +position_score_calculator.py: +``` +python3 role_score_calculator.py --input-filepath "squad.html" --output-filepath "squad_output.html" --roles gk fb dm w iw +``` + +role_score_calculator.py: +``` +python3 position_score_calculator.py --input-filepath "squad.html" --output-filepath "squad_output.html" ``` -python3 position_score_calculator.py --input-filepath "squad.html" --output-filepath "squad_output.html" --roles gk fb dm w iw -``` \ No newline at end of file diff --git a/attribute_ratings.xlsx b/attribute_ratings.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fefe71f1013112e2d56229623a27789fcb68695b GIT binary patch literal 166264 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ξ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 E2QJ*W`v3p{ literal 0 HcmV?d00001 diff --git a/position_score_calculator.py b/position_score_calculator.py index 34c7a55..7c1213d 100644 --- a/position_score_calculator.py +++ b/position_score_calculator.py @@ -1,91 +1,35 @@ -import pandas as pd import argparse -# Define Player attributes -# TODO: Add roles. -gk = { - "role_name": "gk", - "primary_multiplier": 5, - "primary_attributes": ["Agi", "Ref"], - "secondary_multiplier": 3, - "secondary_attributes": ["1v1", "Ant", "Cmd", "Cnt", "Kic", "Pos"], - "tertiary_multiplier": 1, - "tertiary_attributes": ["Acc", "Aer", "Cmp", "Dec", "Fir", "Han", "Pas", "Thr", "Vis"] -} +import pandas as pd -fb = { - "role_name": "fb", - "primary_multiplier": 5, - "primary_attributes": ["Wor", "Acc", "Pac", "Sta"], - "secondary_multiplier": 3, - "secondary_attributes": ["Cro", "Dri", "Mar", "OtB", "Tck", "Tea"], - "tertiary_multiplier": 1, - "tertiary_attributes": ["Agi", "Ant", "Cnt", "Dec", "Fir", "Pas", "Pos", "Tec"] -} -cd = { - "role_name": "cd", - "primary_multiplier": 3, - "primary_attributes": ["Cmp", "Hea", "Jum", "Mar", "Pas", "Pos", "Str", "Tck", "Pac"], - "secondary_multiplier": 1, - "secondary_attributes": ["Agg", "Ant", "Bra", "Cnt", "Dec", "Fir", "Tec", "Vis"] -} - -dm = { - "role_name": "dm", - "primary_multiplier": 5, - "primary_attributes": ["Wor", "Pac", "Sta", "Pas"], - "secondary_multiplier": 3, - "secondary_attributes": ["Tck", "Ant", "Cnt", "Pos", "Bal", "Agi"], - "tertiary_multiplier": 1, - "tertiary_attributes": ["Tea", "Fir", "Mar", "Agg", "Cmp", "Dec", "Str"] -} - -b2b = { - "role_name": "b2b", - "primary_multiplier": 5, - "primary_attributes": ["Pas", "Wor", "Sta"], - "secondary_multiplier": 3, - "secondary_attributes": ["Tck", "OtB", "Tea", "Vis", "Str", "Dec", "Pos", "Pac"], - "tertiary_multiplier": 1, - "tertiary_attributes": ["Agg", "Ant", "Fin", "Lon", "Cmp", "Acc", "Bal", "Fir", "Dri", "Tec"] -} - -w = { - "role_name": "w", - "primary_multiplier": 3, - "primary_attributes": ["Acc", "Cro", "Dri", "OtB", "Pac", "Tec"], - "secondary_multiplier": 1, - "secondary_attributes": ["Agi", "Fir", "Pas", "Sta", "Wor"], -} - -iw = { - "role_name": "iw", - "primary_multiplier": 5, - "primary_attributes": ["Acc", "Pac", "Wor"], - "secondary_multiplier": 3, - "secondary_attributes": ["Dri", "Pas", "Tec", "OtB"], - "tertiary_multiplier": 1, - "tertiary_attributes": ["Cro", "Fir", "Cmp", "Dec", "Vis", "Agi", "Sta"] -} +def load_xlsx_data_to_dataframe(filepath: str) -> pd.DataFrame: + """Read XLSX file into a Dataframe. + Keyword arguments: + filepath -- path to xlsx file + """ + df = pd.read_excel(filepath, engine="openpyxl", nrows=12) + return df def load_html_data_to_dataframe(filepath: str) -> pd.DataFrame: - """Read HTML file exported by FM into a Dataframe + """Read HTML file exported by FM into a Dataframe. Keyword arguments: filepath -- path to fm player html file """ - player_df = pd.read_html(filepath, header=0, encoding="utf-8", keep_default_na=False)[0] + df = pd.read_html(filepath, header=0, encoding="utf-8", keep_default_na=False)[0] # Clean Dataframe to get rid of unknown values and ability ranges (takes the lowest value) # This casts to a string to be able to split, so we have to cast back to an int later. - player_df = player_df.replace("-", 0) - player_df = player_df.map(lambda x: str(x).split("-")[0]) - return player_df + df = df.replace("-", 0) + df = df.map(lambda x: str(x).split("-")[0]) + return df + def export_html_from_dataframe(player_df: pd.DataFrame, filepath: str) -> str: - """Export Dataframe as html with jQuery Data Tables + """Export Dataframe as html with jQuery Data Tables. + Taken from: https://www.thepythoncode.com/article/convert-pandas-dataframe-to-html-table-python. Keyword arguments: @@ -115,71 +59,43 @@ def export_html_from_dataframe(player_df: pd.DataFrame, filepath: str) -> str: """ open(filepath, "w", encoding="utf-8").write(html) -# TODO: Do I even want this? -def calc_composite_scores(player_df: pd.DataFrame) -> pd.DataFrame: - """Calculate Speed, Workrate and Set Piece scores + +def calc_role_scores(player_df: pd.DataFrame, attribute_df: pd.DataFrame) -> pd.DataFrame: + """Calculate Player position scores based on selected attribute weightings. Keyword arguments: - player_df: Dataframe of Players and Attributes + player_df: Dataframe of Players and their Attributes + attribute_df: Dataframe of Attributes and their Weightings """ - player_df['Spd'] = ( player_df['Pac'] + player_df['Acc'] ) / 2 - player_df['Work'] = ( player_df['Wor'] + player_df['Sta'] ) / 2 - player_df['SetP'] = ( player_df['Jum'] + player_df['Bra'] ) / 2 + for _, weightings in attribute_df.iterrows(): + role = weightings["Ratings Weights"] + player_df[role] = 0 + for attribute in weightings.index[1:]: + weighting = weightings[attribute] + try: + player_df[role] += round(pd.to_numeric(player_df[attribute]) * weighting / 20, 2) + except Exception as e: # Used to Nat being used twice (Nationality and Natural Fitness) + print(e) + continue return player_df -def sum_attributes(player_df: pd.DataFrame, role: str, attribute_type: str, attributes: [str]) -> pd.DataFrame: - """Create a new Column containing the sum of provided attribute columns - - Keyword arguments: - player_df: Dataframe of Players and Attributes - role: Name of role to be used as additional column in dataframe - attribute_type: Type of Attribute [Primary, Secondary, Tertiary] - attributes: List of Attributes to Sum - """ - player_df[f'{role}_{attribute_type}'] = 0 - for attribute in attributes: - player_df[f'{role}_{attribute_type}'] += pd.to_numeric(player_df[attribute]) - player_df[f'{role}_{attribute_type}'] = round(player_df[f'{role}_{attribute_type}'] / len(attributes), 2) - return player_df - -def calc_role_scores(player_df: pd.DataFrame, role: dict) -> pd.DataFrame: - """Calculate Player Role scores based on selected attributes. - - Keyword arguments: - player_df: Dataframe of Players and Attributes - role: Dictionary containing role name, role attributes and role attribute weightings - """ - player_df = sum_attributes(player_df, role["role_name"], "primary", role["primary_attributes"]) - player_df = sum_attributes(player_df, role["role_name"], "secondary", role["secondary_attributes"]) - if "tertiary_attributes" in role: - print("here") - player_df = sum_attributes(player_df, role["role_name"], "tertiary", role["tertiary_attributes"]) - divisor = role["primary_multiplier"] + role["secondary_multiplier"] + role["tertiary_multiplier"] - player_df[f'{role["role_name"]}'] = round((((player_df[f'{role["role_name"]}_primary'] * 5) + (player_df[f'{role["role_name"]}_secondary'] * 3) + (player_df[f'{role["role_name"]}_tertiary'] * 1)) / divisor ), 2) - return player_df - -def calc_role_scores_for_tactic_roles(player_df: pd.DataFrame, tactic_roles: [dict]): - for role in tactic_roles: - player_df = calc_role_scores(player_df, role) - return player_df if __name__ == "__main__": # Parse Input args parser = argparse.ArgumentParser() parser.add_argument("-i", "--input-filepath", type=str, help="Path to Input Html file") parser.add_argument("-o", "--output-filepath", type=str, help="Path to Export resultant Html file") - parser.add_argument("-r", "--roles", nargs='+', type=str, help="Space seperated list of roles for Evaluation") + parser.add_argument("-a", "--attribute-filepath", type=str, help="Path to Attribute XLSX file", default="./attribute_ratings.xlsx") args = parser.parse_args() input_filepath = args.input_filepath output_filepath = args.output_filepath - roles = args.roles + attribute_filepath = args.attribute_filepath - # Take Role arg and convert to list of role dictionaries - tactic_roles = [] - for role in roles: - tactic_roles.append(globals()[role]) - - # Inport data, calculate scores for role, export results as html + # Inport data, calculate scores for role, + attribute_df = load_xlsx_data_to_dataframe(attribute_filepath) player_df = load_html_data_to_dataframe(input_filepath) - player_df = calc_role_scores_for_tactic_roles(player_df, tactic_roles) + player_df = calc_role_scores(player_df, attribute_df) + # trim attributes from final output + player_df = player_df.drop(player_df.columns[15:-11], axis=1) + # export results as html export_html_from_dataframe(player_df, output_filepath) diff --git a/requirements.txt b/requirements.txt index 1f2c5eb..83f6f96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ pandas -lxml \ No newline at end of file +numpy +lxml +openpyxl +pre-commit diff --git a/role_score_calculator.py b/role_score_calculator.py new file mode 100644 index 0000000..89ede0f --- /dev/null +++ b/role_score_calculator.py @@ -0,0 +1,192 @@ +import argparse + +import pandas as pd + +# Define Player attributes +# TODO: Add roles. +gk = { + "role_name": "gk", + "primary_multiplier": 5, + "primary_attributes": ["Agi", "Ref"], + "secondary_multiplier": 3, + "secondary_attributes": ["1v1", "Ant", "Cmd", "Cnt", "Kic", "Pos"], + "tertiary_multiplier": 1, + "tertiary_attributes": ["Acc", "Aer", "Cmp", "Dec", "Fir", "Han", "Pas", "Thr", "Vis"], +} + +fb = { + "role_name": "fb", + "primary_multiplier": 5, + "primary_attributes": ["Wor", "Acc", "Pac", "Sta"], + "secondary_multiplier": 3, + "secondary_attributes": ["Cro", "Dri", "Mar", "OtB", "Tck", "Tea"], + "tertiary_multiplier": 1, + "tertiary_attributes": ["Agi", "Ant", "Cnt", "Dec", "Fir", "Pas", "Pos", "Tec"], +} + +cd = { + "role_name": "cd", + "primary_multiplier": 3, + "primary_attributes": ["Cmp", "Hea", "Jum", "Mar", "Pas", "Pos", "Str", "Tck", "Pac"], + "secondary_multiplier": 1, + "secondary_attributes": ["Agg", "Ant", "Bra", "Cnt", "Dec", "Fir", "Tec", "Vis"], +} + +dm = { + "role_name": "dm", + "primary_multiplier": 5, + "primary_attributes": ["Wor", "Pac", "Sta", "Pas"], + "secondary_multiplier": 3, + "secondary_attributes": ["Tck", "Ant", "Cnt", "Pos", "Bal", "Agi"], + "tertiary_multiplier": 1, + "tertiary_attributes": ["Tea", "Fir", "Mar", "Agg", "Cmp", "Dec", "Str"], +} + +b2b = { + "role_name": "b2b", + "primary_multiplier": 5, + "primary_attributes": ["Pas", "Wor", "Sta"], + "secondary_multiplier": 3, + "secondary_attributes": ["Tck", "OtB", "Tea", "Vis", "Str", "Dec", "Pos", "Pac"], + "tertiary_multiplier": 1, + "tertiary_attributes": ["Agg", "Ant", "Fin", "Lon", "Cmp", "Acc", "Bal", "Fir", "Dri", "Tec"], +} + +w = { + "role_name": "w", + "primary_multiplier": 3, + "primary_attributes": ["Acc", "Cro", "Dri", "OtB", "Pac", "Tec"], + "secondary_multiplier": 1, + "secondary_attributes": ["Agi", "Fir", "Pas", "Sta", "Wor"], +} + +iw = { + "role_name": "iw", + "primary_multiplier": 5, + "primary_attributes": ["Acc", "Pac", "Wor"], + "secondary_multiplier": 3, + "secondary_attributes": ["Dri", "Pas", "Tec", "OtB"], + "tertiary_multiplier": 1, + "tertiary_attributes": ["Cro", "Fir", "Cmp", "Dec", "Vis", "Agi", "Sta"], +} + + +def load_html_data_to_dataframe(filepath: str) -> pd.DataFrame: + """Read HTML file exported by FM into a Dataframe. + + Keyword arguments: + filepath -- path to fm player html file + """ + player_df = pd.read_html(filepath, header=0, encoding="utf-8", keep_default_na=False)[0] + # Clean Dataframe to get rid of unknown values and ability ranges (takes the lowest value) + # This casts to a string to be able to split, so we have to cast back to an int later. + player_df = player_df.replace("-", 0) + player_df = player_df.map(lambda x: str(x).split("-")[0]) + return player_df + + +def export_html_from_dataframe(player_df: pd.DataFrame, filepath: str) -> str: + """Export Dataframe as html with jQuery Data Tables. Taken from: https://www.thepythoncode.com/article/convert-pandas-dataframe-to-html-table-python. + + Keyword arguments: + filepath -- path to fm player html file + """ + table_html = player_df.to_html(table_id="table", index=False) + html = f""" + +
+ +
+ + {table_html} + + + + + + """ + open(filepath, "w", encoding="utf-8").write(html) + + +# TODO: Do I even want this? +def calc_composite_scores(player_df: pd.DataFrame) -> pd.DataFrame: + """Calculate Speed, Workrate and Set Piece scores. + + Keyword arguments: + player_df: Dataframe of Players and Attributes + """ + player_df["Spd"] = (player_df["Pac"] + player_df["Acc"]) / 2 + player_df["Work"] = (player_df["Wor"] + player_df["Sta"]) / 2 + player_df["SetP"] = (player_df["Jum"] + player_df["Bra"]) / 2 + return player_df + + +def sum_attributes(player_df: pd.DataFrame, role: str, attribute_type: str, attributes: [str]) -> pd.DataFrame: + """Create a new Column containing the sum of provided attribute columns. + + Keyword arguments: + player_df: Dataframe of Players and Attributes + role: Name of role to be used as additional column in dataframe + attribute_type: Type of Attribute [Primary, Secondary, Tertiary] + attributes: List of Attributes to Sum + """ + player_df[f"{role}_{attribute_type}"] = 0 + for attribute in attributes: + player_df[f"{role}_{attribute_type}"] += pd.to_numeric(player_df[attribute]) + player_df[f"{role}_{attribute_type}"] = round(player_df[f"{role}_{attribute_type}"] / len(attributes), 2) + return player_df + + +def calc_role_scores(player_df: pd.DataFrame, role: dict) -> pd.DataFrame: + """Calculate Player Role scores based on selected attributes. + + Keyword arguments: + player_df: Dataframe of Players and Attributes + role: Dictionary containing role name, role attributes and role attribute weightings + """ + player_df = sum_attributes(player_df, role["role_name"], "primary", role["primary_attributes"]) + player_df = sum_attributes(player_df, role["role_name"], "secondary", role["secondary_attributes"]) + if "tertiary_attributes" in role: + print("here") + player_df = sum_attributes(player_df, role["role_name"], "tertiary", role["tertiary_attributes"]) + divisor = role["primary_multiplier"] + role["secondary_multiplier"] + role["tertiary_multiplier"] + player_df[f'{role["role_name"]}'] = round( + (((player_df[f'{role["role_name"]}_primary'] * 5) + (player_df[f'{role["role_name"]}_secondary'] * 3) + (player_df[f'{role["role_name"]}_tertiary'] * 1)) / divisor), 2 + ) + return player_df + + +def calc_role_scores_for_tactic_roles(player_df: pd.DataFrame, tactic_roles: [dict]): + for role in tactic_roles: + player_df = calc_role_scores(player_df, role) + return player_df + + +if __name__ == "__main__": + # Parse Input args + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--input-filepath", type=str, help="Path to Input Html file") + parser.add_argument("-o", "--output-filepath", type=str, help="Path to Export resultant Html file") + parser.add_argument("-r", "--roles", nargs="+", type=str, help="Space seperated list of roles for Evaluation") + args = parser.parse_args() + input_filepath = args.input_filepath + output_filepath = args.output_filepath + roles = args.roles + + # Take Role arg and convert to list of role dictionaries + tactic_roles = [] + for role in roles: + tactic_roles.append(globals()[role]) + + # Inport data, calculate scores for role, export results as html + player_df = load_html_data_to_dataframe(input_filepath) + player_df = calc_role_scores_for_tactic_roles(player_df, tactic_roles) + export_html_from_dataframe(player_df, output_filepath)