From 966cd3c6d5eb2491ecac0d3ad063ea2af6487ae5 Mon Sep 17 00:00:00 2001 From: jmaslek Date: Thu, 6 Jan 2022 13:12:38 -0500 Subject: [PATCH] New alternative data context + first sub context (Covid) (#1142) --- exports/alternative/covid/.gitkeep | 0 gamestonk_terminal/alternative/__init__.py | 0 .../alternative/alt_controller.py | 227 ++++++++++ .../alternative/covid/__init__.py | 0 .../alternative/covid/countries.txt | 198 +++++++++ .../alternative/covid/covid_controller.py | 419 ++++++++++++++++++ .../alternative/covid/covid_model.py | 108 +++++ .../alternative/covid/covid_view.py | 206 +++++++++ gamestonk_terminal/helper_funcs.py | 25 +- scripts/test_alt_covid.gst | 14 + terminal.py | 8 + website/content/alternative/_index.md | 5 + website/content/alternative/covid/_index.md | 5 + .../content/alternative/covid/cases/_index.md | 16 + .../alternative/covid/deaths/_index.md | 16 + .../content/alternative/covid/ov/_index.md | 16 + .../content/alternative/covid/rates/_index.md | 15 + .../alternative/covid/slopes/_index.md | 36 ++ website/data/menu/main.yml | 16 + 19 files changed, 1329 insertions(+), 1 deletion(-) create mode 100644 exports/alternative/covid/.gitkeep create mode 100644 gamestonk_terminal/alternative/__init__.py create mode 100644 gamestonk_terminal/alternative/alt_controller.py create mode 100644 gamestonk_terminal/alternative/covid/__init__.py create mode 100644 gamestonk_terminal/alternative/covid/countries.txt create mode 100644 gamestonk_terminal/alternative/covid/covid_controller.py create mode 100644 gamestonk_terminal/alternative/covid/covid_model.py create mode 100644 gamestonk_terminal/alternative/covid/covid_view.py create mode 100644 scripts/test_alt_covid.gst create mode 100644 website/content/alternative/_index.md create mode 100644 website/content/alternative/covid/_index.md create mode 100644 website/content/alternative/covid/cases/_index.md create mode 100644 website/content/alternative/covid/deaths/_index.md create mode 100644 website/content/alternative/covid/ov/_index.md create mode 100644 website/content/alternative/covid/rates/_index.md create mode 100644 website/content/alternative/covid/slopes/_index.md diff --git a/exports/alternative/covid/.gitkeep b/exports/alternative/covid/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/alternative/__init__.py b/gamestonk_terminal/alternative/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/alternative/alt_controller.py b/gamestonk_terminal/alternative/alt_controller.py new file mode 100644 index 00000000000..42cfed2a213 --- /dev/null +++ b/gamestonk_terminal/alternative/alt_controller.py @@ -0,0 +1,227 @@ +"""Alternative Data Controller Module""" +__docformat__ = "numpy" + +import argparse +import difflib +import logging +from typing import List, Union + +from prompt_toolkit.completion import NestedCompleter +from rich.console import Console +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import ( + get_flair, + system_clear, +) +from gamestonk_terminal.menu import session + +t_console = Console() +logger = logging.getLogger(__name__) +# pylint:disable=import-outside-toplevel + + +class AlternativeDataController: + """alternative Controller class""" + + # Command choices + CHOICES = [ + "cls", + "home", + "h", + "?", + "help", + "q", + "quit", + "..", + "exit", + "r", + "reset", + ] + + CHOICES_COMMANDS: List[str] = [] + CHOICES_MENUS = ["covid"] + CHOICES += CHOICES_COMMANDS + CHOICES_MENUS + + def __init__(self, queue: List[str] = None): + """Constructor""" + self.alt_parser = argparse.ArgumentParser(add_help=False, prog="alternative") + self.alt_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + self.queue = queue if queue else list() + self.completer: Union[None, NestedCompleter] = None + + if session and gtff.USE_PROMPT_TOOLKIT: + choices: dict = {c: {} for c in self.CHOICES} + + self.completer = NestedCompleter.from_nested_dict(choices) + + @staticmethod + def print_help(): + """Print help""" + help_str = """ +> covid cases, deaths, rates + """ + t_console.print(help_str) + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + List[str] + List of commands in the queue to execute + """ + # Empty command + if not an_input: + t_console.print("") + return self.queue + + # Navigation slash is being used + if "/" in an_input: + actions = an_input.split("/") + + # Absolute path is specified + if not actions[0]: + an_input = "home" + # Relative path so execute first instruction + else: + an_input = actions[0] + + # Add all instructions to the queue + for cmd in actions[1:][::-1]: + if cmd: + self.queue.insert(0, cmd) + logger.info(an_input) + (known_args, other_args) = self.alt_parser.parse_known_args(an_input.split()) + + # Redirect commands to their correct functions + if known_args.cmd: + if known_args.cmd in ("..", "q"): + known_args.cmd = "quit" + elif known_args.cmd in ("?", "h"): + known_args.cmd = "help" + elif known_args.cmd == "r": + known_args.cmd = "reset" + + getattr( + self, + "call_" + known_args.cmd, + lambda _: "Command not recognized!", + )(other_args) + + return self.queue + + def call_cls(self, _): + """Process cls command""" + system_clear() + + def call_home(self, _): + """Process home command""" + self.queue.insert(0, "quit") + + def call_help(self, _): + """Process help command""" + self.print_help() + + def call_quit(self, _): + """Process quit menu command""" + t_console.print("") + self.queue.insert(0, "quit") + + def call_exit(self, _): + """Process exit terminal command""" + self.queue.insert(0, "quit") + self.queue.insert(0, "quit") + + def call_reset(self, _): + """Process reset command""" + self.queue.insert(0, "alternative") + self.queue.insert(0, "reset") + self.queue.insert(0, "quit") + + def call_covid(self, _): + """Process covid command""" + from gamestonk_terminal.alternative.covid import covid_controller + + self.queue = covid_controller.menu(self.queue) + + +def menu(queue: List[str] = None): + """Resoualte Collection Menu""" + alt_controller = AlternativeDataController(queue) + an_input = "HELP_ME" + logger.info("AlternativeMenu") + + while True: + # There is a command in the queue + if alt_controller.queue and len(alt_controller.queue) > 0: + # If the command is quitting the menu we want to return in here + if alt_controller.queue[0] in ("q", "..", "quit"): + t_console.print("") + if len(alt_controller.queue) > 1: + return alt_controller.queue[1:] + return [] + + # Consume 1 element from the queue + an_input = alt_controller.queue[0] + alt_controller.queue = alt_controller.queue[1:] + + # Print the current location because this was an instruction and we want user to know what was the action + if an_input and an_input.split(" ")[0] in alt_controller.CHOICES_COMMANDS: + t_console.print(f"{get_flair()} /alternative/ $ {an_input}") + + # Get input command from user + else: + # Display help menu when entering on this menu from a level above + if an_input == "HELP_ME": + alt_controller.print_help() + + # Get input from user using auto-completion + if session and gtff.USE_PROMPT_TOOLKIT and alt_controller.completer: + try: + an_input = session.prompt( + f"{get_flair()} /alternative/ $ ", + completer=alt_controller.completer, + search_ignore_case=True, + ) + except KeyboardInterrupt: + # Exit in case of keyboard interrupt + an_input = "exit" + # Get input from user without auto-completion + else: + an_input = input(f"{get_flair()} /alternative/ $ ") + + try: + # Process the input command + alt_controller.queue = alt_controller.switch(an_input) + + except SystemExit: + t_console.print( + f"\nThe command '{an_input}' doesn't exist on the /alternative menu.\n" + ) + similar_cmd = difflib.get_close_matches( + an_input.split(" ")[0] if " " in an_input else an_input, + alt_controller.CHOICES, + n=1, + cutoff=0.7, + ) + if similar_cmd: + if " " in an_input: + candidate_input = ( + f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}" + ) + if candidate_input == an_input: + an_input = "" + alt_controller.queue = [] + t_console.print("") + continue + an_input = candidate_input + else: + an_input = similar_cmd[0] + + t_console.print(f" Replacing by '{an_input}'.") + alt_controller.queue.insert(0, an_input) + else: + print("") diff --git a/gamestonk_terminal/alternative/covid/__init__.py b/gamestonk_terminal/alternative/covid/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/alternative/covid/countries.txt b/gamestonk_terminal/alternative/covid/countries.txt new file mode 100644 index 00000000000..770d1bdee47 --- /dev/null +++ b/gamestonk_terminal/alternative/covid/countries.txt @@ -0,0 +1,198 @@ +Countries +Suriname +Qatar +Romania +Cabo Verde +Malawi +Micronesia +Belarus +Denmark +Fiji +Cambodia +Madagascar +Saint Vincent and the Grenadines +Paraguay +Eritrea +Barbados +Ethiopia +Laos +Zimbabwe +Lithuania +Comoros +Namibia +Spain +Papua New Guinea +Central African Republic +Honduras +Equatorial Guinea +Botswana +Canada +Italy +Jordan +Chad +Czechia +Uzbekistan +Azerbaijan +Poland +US +El Salvador +Ghana +Kiribati +Kuwait +Senegal +Holy See +Mauritius +Oman +Sierra Leone +Zambia +Nigeria +Kosovo +Brunei +China +Gabon +Seychelles +Guatemala +Somalia +Peru +Sao Tome and Principe +Sweden +Pakistan +Moldova +Togo +Niger +Ukraine +Sudan +Trinidad and Tobago +Netherlands +Philippines +Australia +Burkina Faso +Tajikistan +United Arab Emirates +Kyrgyzstan +Costa Rica +Portugal +Mozambique +Mauritania +Timor-Leste +Vietnam +Montenegro +Guyana +Mongolia +Bahrain +Cameroon +Palau +Jamaica +New Zealand +Iraq +Mali +Saint Kitts and Nevis +Brazil +Belgium +Maldives +Egypt +Panama +Rwanda +Israel +Russia +Nicaragua +Switzerland +Kazakhstan +Guinea-Bissau +Albania +Thailand +Cuba +Uruguay +Liechtenstein +Bosnia and Herzegovina +Haiti +Andorra +Taiwan* +Dominica +Congo (Kinshasa) +Ecuador +Malta +Vanuatu +Dominican Republic +Lesotho +Morocco +West Bank and Gaza +Lebanon +South Sudan +Armenia +Afghanistan +Grenada +Indonesia +Sri Lanka +Iran +Bolivia +Luxembourg +North Macedonia +Cyprus +Iceland +Serbia +Greece +United Kingdom +Georgia +Saint Lucia +Venezuela +Austria +Tonga +Saudi Arabia +Argentina +Summer Olympics 2020 +Angola +Algeria +Latvia +Yemen +Singapore +Turkey +Belize +Burundi +Ireland +South Africa +Bulgaria +India +Diamond Princess +Cote d'Ivoire +Libya +Slovenia +Benin +Djibouti +Congo (Brazzaville) +Tunisia +Nepal +Bahamas +Bhutan +Colombia +Hungary +Malaysia +Germany +Tanzania +Samoa +San Marino +Syria +Liberia +Korea, South +Uganda +Gambia +Marshall Islands +Kenya +Finland +Bangladesh +Eswatini +Slovakia +Burma +MS Zaandam +Japan +Guinea +Monaco +Mexico +Solomon Islands +Estonia +Chile +Croatia +Antigua and Barbuda +Norway +France +All \ No newline at end of file diff --git a/gamestonk_terminal/alternative/covid/covid_controller.py b/gamestonk_terminal/alternative/covid/covid_controller.py new file mode 100644 index 00000000000..23c03bb5a40 --- /dev/null +++ b/gamestonk_terminal/alternative/covid/covid_controller.py @@ -0,0 +1,419 @@ +"""COVID Controller Module""" +__docformat__ = "numpy" + +import argparse +import difflib +import logging +import pathlib +from typing import List, Union + +import pandas as pd +from prompt_toolkit.completion import NestedCompleter +from rich.console import Console + +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.alternative.covid import covid_view +from gamestonk_terminal.helper_funcs import ( + get_flair, + parse_known_args_and_warn, + system_clear, + try_except, + EXPORT_BOTH_RAW_DATA_AND_FIGURES, + EXPORT_ONLY_RAW_DATA_ALLOWED, + check_positive, +) +from gamestonk_terminal.menu import session + +logger = logging.getLogger(__name__) +t_console = Console() + +country_file = pathlib.Path(__file__).parent.joinpath("countries.txt") + + +class CovidController: + """Covid Controller class""" + + # Command choices + CHOICES = [ + "cls", + "home", + "h", + "?", + "help", + "q", + "quit", + "..", + "exit", + "r", + "reset", + ] + + CHOICES_COMMANDS = ["country", "ov", "deaths", "cases", "rates", "slopes"] + + CHOICES += CHOICES_COMMANDS + + def __init__(self, queue: List[str] = None): + """Constructor""" + self.covid_parser = argparse.ArgumentParser(add_help=False, prog="covid") + self.covid_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + self.queue = queue if queue else list() + self.completer: Union[None, NestedCompleter] = None + self.country = "US" + self.COUNTRY_LIST = pd.read_csv(country_file, sep="\n", index_col=None)[ + "Countries" + ].to_list() + if session and gtff.USE_PROMPT_TOOLKIT: + choices: dict = {c: {} for c in self.CHOICES} + choices["country"] = {c: None for c in self.COUNTRY_LIST} + + self.completer = NestedCompleter.from_nested_dict(choices) + + def print_help(self): + """Print help""" + help_str = f""" + slopes get countries with highest slope in cases + country select country for data + +Country: [cyan]{self.country}[/cyan] + + ov get overview (cases and deaths) for selected country + deaths get deaths for selected country + cases get cases for selected country + rates get death/cases rate for selected country + """ + t_console.print(help_str) + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + List[str] + List of commands in the queue to execute + """ + # Empty command + if not an_input: + t_console.print("") + return self.queue + + # Navigation slash is being used + if "/" in an_input: + actions = an_input.split("/") + + # Absolute path is specified + if not actions[0]: + an_input = "home" + # Relative path so execute first instruction + else: + an_input = actions[0] + + # Add all instructions to the queue + for cmd in actions[1:][::-1]: + if cmd: + self.queue.insert(0, cmd) + + logger.info(an_input) + (known_args, other_args) = self.covid_parser.parse_known_args(an_input.split()) + + # Redirect commands to their correct functions + if known_args.cmd: + if known_args.cmd in ("..", "q"): + known_args.cmd = "quit" + elif known_args.cmd in ("?", "h"): + known_args.cmd = "help" + elif known_args.cmd == "r": + known_args.cmd = "reset" + + getattr( + self, + "call_" + known_args.cmd, + lambda _: "Command not recognized!", + )(other_args) + + return self.queue + + def call_cls(self, _): + """Process cls command""" + system_clear() + + def call_home(self, _): + """Process home command""" + self.queue.insert(0, "quit") + self.queue.insert(0, "quit") + + def call_help(self, _): + """Process help command""" + self.print_help() + + def call_quit(self, _): + """Process quit menu command""" + t_console.print("") + self.queue.insert(0, "quit") + + def call_exit(self, _): + """Process exit terminal command""" + self.queue.insert(0, "quit") + self.queue.insert(0, "quit") + self.queue.insert(0, "quit") + + def call_reset(self, _): + """Process reset command""" + self.queue.insert(0, f"country {self.country}") + self.queue.insert(0, "covid") + self.queue.insert(0, "alternative") + self.queue.insert(0, "reset") + self.queue.insert(0, "quit") + self.queue.insert(0, "quit") + + def call_country(self, other_args: List[str]): + """Process country command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="country", + description="Select a country to look at data for.", + ) + parser.add_argument( + "-c", + "--country", + nargs="+", + type=str, + dest="country", + help="Country to get data for.", + ) + if other_args and "-" not in other_args[0][0]: + other_args.insert(0, "-c") + ns_parser = parse_known_args_and_warn(parser, other_args) + if ns_parser: + if ns_parser.country: + country = " ".join(ns_parser.country) + if country not in self.COUNTRY_LIST: + t_console.print(f"[red]{country} not a valid selection.[/red]\n") + return + self.country = country + t_console.print(f"[cyan]{country}[/cyan] loaded\n") + else: + t_console.print("[red]Please input a country.[/red]\n") + + @try_except + def call_ov(self, other_args: List[str]): + """Process hist command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ov", + description="Show historical cases and deaths by country.", + ) + ns_parser = parse_known_args_and_warn( + parser, + other_args, + export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES, + raw=True, + limit=10, + ) + if ns_parser: + covid_view.display_covid_ov( + self.country, + raw=ns_parser.raw, + limit=ns_parser.limit, + export=ns_parser.export, + ) + + @try_except + def call_rates(self, other_args: List[str]): + """Process hist command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="rates", + description="Show historical rates country.", + ) + ns_parser = parse_known_args_and_warn( + parser, + other_args, + export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES, + raw=True, + limit=10, + ) + if ns_parser: + covid_view.display_covid_stat( + self.country, + stat="rates", + raw=ns_parser.raw, + limit=ns_parser.limit, + export=ns_parser.export, + ) + + @try_except + def call_deaths(self, other_args: List[str]): + """Process deaths command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="deaths", + description="Show historical deaths by country.", + ) + ns_parser = parse_known_args_and_warn( + parser, + other_args, + export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES, + raw=True, + limit=10, + ) + if ns_parser: + covid_view.display_covid_stat( + self.country, + stat="deaths", + raw=ns_parser.raw, + limit=ns_parser.limit, + export=ns_parser.export, + ) + + @try_except + def call_cases(self, other_args: List[str]): + """Process cases command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="cases", + description="Show historical cases for country.", + ) + ns_parser = parse_known_args_and_warn( + parser, + other_args, + export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES, + raw=True, + limit=10, + ) + if ns_parser: + covid_view.display_covid_stat( + self.country, + stat="cases", + raw=ns_parser.raw, + limit=ns_parser.limit, + export=ns_parser.export, + ) + + @try_except + def call_slopes(self, other_args: List[str]): + """Process cases command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="slopes", + description="Show countries with highest slopes.", + ) + parser.add_argument( + "-d", + "--days", + type=check_positive, + help="Number of days back to look", + dest="days", + default=30, + ) + parser.add_argument( + "-a", + "--ascend", + action="store_true", + default=False, + help="Show in ascending order", + ) + parser.add_argument( + "-t", + "--threshold", + default=10000, + dest="threshold", + help="Threshold for total cases over period", + type=check_positive, + ) + ns_parser = parse_known_args_and_warn( + parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED, limit=10 + ) + if ns_parser: + covid_view.display_country_slopes( + days_back=ns_parser.days, + limit=ns_parser.limit, + ascend=ns_parser.ascend, + threshold=ns_parser.threshold, + ) + + +def menu(queue: List[str] = None): + """Covid Menu""" + covid_controller = CovidController(queue) + an_input = "HELP_ME" + logger.info("CovidMenu") + while True: + # There is a command in the queue + if covid_controller.queue and len(covid_controller.queue) > 0: + # If the command is quitting the menu we want to return in here + if covid_controller.queue[0] in ("q", "..", "quit"): + t_console.print("") + if len(covid_controller.queue) > 1: + return covid_controller.queue[1:] + return [] + + # Consume 1 element from the queue + an_input = covid_controller.queue[0] + covid_controller.queue = covid_controller.queue[1:] + + # Print the current location because this was an instruction and we want user to know what was the action + if an_input and an_input.split(" ")[0] in covid_controller.CHOICES_COMMANDS: + t_console.print(f"{get_flair()} /alternative/covid/ $ {an_input}") + + # Get input command from user + else: + # Display help menu when entering on this menu from a level above + if an_input == "HELP_ME": + covid_controller.print_help() + + # Get input from user using auto-completion + if session and gtff.USE_PROMPT_TOOLKIT and covid_controller.completer: + try: + an_input = session.prompt( + f"{get_flair()} /alternative/covid/ $ ", + completer=covid_controller.completer, + search_ignore_case=True, + ) + except KeyboardInterrupt: + # Exit in case of keyboard interrupt + an_input = "exit" + # Get input from user without auto-completion + else: + an_input = input(f"{get_flair()} /alternative/covid/ $ ") + + try: + # Process the input command + covid_controller.queue = covid_controller.switch(an_input) + + except SystemExit: + t_console.print( + f"\nThe command '{an_input}' doesn't exist on the /alternative/covid menu." + ) + similar_cmd = difflib.get_close_matches( + an_input.split(" ")[0] if " " in an_input else an_input, + covid_controller.CHOICES, + n=1, + cutoff=0.7, + ) + if similar_cmd: + if " " in an_input: + candidate_input = ( + f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}" + ) + if candidate_input == an_input: + an_input = "" + covid_controller.queue = [] + t_console.print("\n") + continue + an_input = candidate_input + else: + an_input = similar_cmd[0] + + t_console.print(f" Replacing by '{an_input}'.") + covid_controller.queue.insert(0, an_input) + else: + t_console.print("") diff --git a/gamestonk_terminal/alternative/covid/covid_model.py b/gamestonk_terminal/alternative/covid/covid_model.py new file mode 100644 index 00000000000..a1fcc4ebbf7 --- /dev/null +++ b/gamestonk_terminal/alternative/covid/covid_model.py @@ -0,0 +1,108 @@ +"""Covid Model""" +__docformat__ = "numpy" + +import warnings +import pandas as pd +import numpy as np + + +global_cases_time_series = ( + "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_" + "covid_19_time_series/time_series_covid19_confirmed_global.csv" +) +global_deaths_time_series = ( + "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_" + "covid_19_time_series/time_series_covid19_deaths_global.csv" +) + + +def get_global_cases(country: str) -> pd.DataFrame: + """Get historical cases for given country + + Parameters + ---------- + country: str + Country to search for + + Returns + ------- + pd.DataFrame + Dataframe of historical cases + """ + cases = pd.read_csv(global_cases_time_series) + cases = cases.rename(columns={"Country/Region": "Country"}) + cases = ( + cases.drop(columns=["Province/State", "Lat", "Long"]) + .groupby("Country") + .agg("sum") + .T + ) + cases.index = pd.to_datetime(cases.index) + cases = pd.DataFrame(cases[country]).diff().dropna() + if cases.shape[1] > 1: + return pd.DataFrame(cases.sum(axis=1)) + return cases + + +def get_global_deaths(country: str) -> pd.DataFrame: + """Get historical deaths for given country + + Parameters + ---------- + country: str + Country to search for + + Returns + ------- + pd.DataFrame + Dataframe of historical deaths + """ + deaths = pd.read_csv(global_deaths_time_series) + deaths = deaths.rename(columns={"Country/Region": "Country"}) + deaths = ( + deaths.drop(columns=["Province/State", "Lat", "Long"]) + .groupby("Country") + .agg("sum") + .T + ) + deaths.index = pd.to_datetime(deaths.index) + deaths = pd.DataFrame(deaths[country]).diff().dropna() + if deaths.shape[1] > 1: + return pd.DataFrame(deaths.sum(axis=1)) + return deaths + + +def get_case_slopes(days_back: int = 30, threshold: int = 10000) -> pd.DataFrame: + """Load cases and find slope over period + + Parameters + ---------- + days_back: int + Number of historical days to consider + threshold: int + Threshold for total number of cases + Returns + ------- + pd.DataFrame + Dataframe containing slopes + """ + # Ignore the pandas warning for setting a slace with a value + warnings.filterwarnings("ignore") + cases = pd.read_csv(global_cases_time_series) + cases = cases.rename(columns={"Country/Region": "Country"}) + cases = ( + ( + cases.drop(columns=["Province/State", "Lat", "Long"]) + .groupby("Country") + .agg("sum") + ) + .diff() + .dropna() + ) + hist = cases.iloc[:, -days_back:] + hist["Sum"] = hist.sum(axis=1) + hist = hist[hist.Sum > threshold].drop(columns="Sum") + hist["Slope"] = hist.apply( + lambda x: np.polyfit(np.arange(days_back), x, 1)[0], axis=1 + ) + return pd.DataFrame(hist["Slope"]) diff --git a/gamestonk_terminal/alternative/covid/covid_view.py b/gamestonk_terminal/alternative/covid/covid_view.py new file mode 100644 index 00000000000..03384999a3d --- /dev/null +++ b/gamestonk_terminal/alternative/covid/covid_view.py @@ -0,0 +1,206 @@ +"""Covid View""" +__docformat__ = "numpy" + +import os + +import matplotlib.dates as mdates +import matplotlib.pyplot as plt +import pandas as pd +from rich.console import Console + +import gamestonk_terminal.feature_flags as gtff +from gamestonk_terminal.alternative.covid import covid_model +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import ( + plot_autoscale, + export_data, + rich_table_from_df, +) + +t_console = Console() + + +def display_covid_ov(country, raw: bool = False, limit: int = 10, export: str = ""): + """Show historical cases and deaths by country + + Parameters + ---------- + country: str + Country to get data for + raw: bool + Flag to display raw data + limit: int + Number of raw data to show + export: str + Format to export data + """ + t_console.print("") + cases = covid_model.get_global_cases(country) / 1_000 + deaths = covid_model.get_global_deaths(country) + ov = pd.concat([cases, deaths], axis=1) + ov.columns = ["Cases", "Deaths"] + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + ax.plot(cases.index, cases, alpha=0.2, c="b") + ax.plot(cases.index, cases.rolling(7).mean(), lw=4, c="b") + ax.set_ylabel("Cases (1k)", color="blue") + ax.tick_params(axis="y", labelcolor="blue") + + ax2 = ax.twinx() + ax2.plot(deaths.index, deaths, "r", alpha=0.2) + ax2.plot(deaths.index, deaths.rolling(7).mean(), "r", lw=4) + ax2.grid() + ax2.set_title(f"Overview for {country.upper()}") + ax2.set_xlabel("Date") + ax2.set_ylabel("Deaths", color="red") + ax2.tick_params(axis="y", labelcolor="red") + + dateFmt = mdates.DateFormatter("%Y-%m-%d") + ax.xaxis.set_major_formatter(dateFmt) + ax.tick_params(axis="x", labelrotation=45) + ax.set_xlim(ov.index[0], ov.index[-1]) + + fig.tight_layout(pad=2) + if gtff.USE_ION: + plt.ion() + plt.show() + + if raw: + ov.index = [x.strftime("%Y-%m-%d") for x in ov.index] + if gtff.USE_TABULATE_DF: + t_console.print( + rich_table_from_df( + ov.tail(limit), + headers=[x.title() for x in ov.columns], + show_index=True, + index_name="Date", + title=f"[bold]{country} COVID Numbers[/bold]", + ) + ) + else: + t_console.print(ov.tail(limit).to_string()) + + t_console.print("") + + if export: + export_data(export, os.path.dirname(os.path.abspath(__file__)), "ov", ov) + + +def display_covid_stat( + country, stat: str = "cases", raw: bool = False, limit: int = 10, export: str = "" +): + """Show historical cases and deaths by country + + Parameters + ---------- + country: str + Country to get data for + stat: str + Statistic to get. Either "cases", "deaths" or "rates" + raw: bool + Flag to display raw data + limit: int + Number of raw data to show + export: str + Format to export data + """ + t_console.print("") + if stat == "cases": + data = covid_model.get_global_cases(country) / 1_000 + elif stat == "deaths": + data = covid_model.get_global_deaths(country) + elif stat == "rates": + cases = covid_model.get_global_cases(country) + deaths = covid_model.get_global_deaths(country) + data = (deaths / cases).fillna(0) * 100 + else: + t_console.print("Invalid stat selected.\n") + return + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + ax.plot(data.index, data, alpha=0.2, c="b") + ax.plot(data.index, data.rolling(7).mean(), lw=4, c="b") + if stat == "cases": + ax.set_ylabel(stat.title() + " (1k)", color="blue") + else: + ax.set_ylabel(stat.title(), color="blue") + ax.tick_params(axis="y", labelcolor="blue") + ax.grid("on") + dateFmt = mdates.DateFormatter("%Y-%m-%d") + ax.xaxis.set_major_formatter(dateFmt) + ax.tick_params(axis="x", labelrotation=45) + ax.spines["right"].set_visible(False) + ax.spines["top"].set_visible(False) + ax.set_title(f"{country} COVID {stat}") + ax.set_xlim(data.index[0], data.index[-1]) + fig.tight_layout(pad=2) + if gtff.USE_ION: + plt.ion() + plt.show() + + if raw: + data.index = [x.strftime("%Y-%m-%d") for x in data.index] + if gtff.USE_TABULATE_DF: + t_console.print( + rich_table_from_df( + data.tail(limit), + headers=[stat.title()], + show_index=True, + index_name="Date", + title=f"[bold]{country} COVID {stat}[/bold]", + ) + ) + else: + t_console.print(data.tail(limit).to_string()) + + t_console.print("") + + export_data(export, os.path.dirname(os.path.abspath(__file__)), stat, data) + + +def display_country_slopes( + days_back: int = 30, + limit: int = 10, + ascend: bool = False, + threshold: int = 10000, + export: str = "", +): + """ + + Parameters + ---------- + days_back: int + Number of historical days to get slope for + limit: int + Number to show in table + ascend: bool + Boolean to sort in ascending order + threshold: int + Threshold for total cases over period + export : str + Format to export data + """ + hist_slope = covid_model.get_case_slopes(days_back, threshold).sort_values( + by="Slope", ascending=ascend + ) + if gtff.USE_TABULATE_DF: + t_console.print( + rich_table_from_df( + hist_slope.head(limit), + show_index=True, + index_name="Country", + title=f"[bold]{('Highest','Lowest')[ascend]} Sloping Cases[/bold] (Cases/Day)", + ) + ) + else: + t_console.print(hist_slope.head(limit).to_string()) + t_console.print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"slopes_{days_back}day", + hist_slope, + ) diff --git a/gamestonk_terminal/helper_funcs.py b/gamestonk_terminal/helper_funcs.py index 04492f58e9d..115239218f0 100644 --- a/gamestonk_terminal/helper_funcs.py +++ b/gamestonk_terminal/helper_funcs.py @@ -565,6 +565,8 @@ def parse_known_args_and_warn( parser: argparse.ArgumentParser, other_args: List[str], export_allowed: int = NO_EXPORT, + raw: bool = False, + limit: int = 0, ): """Parses list of arguments into the supplied parser @@ -577,7 +579,10 @@ def parse_known_args_and_warn( export_allowed: int Choose from NO_EXPORT, EXPORT_ONLY_RAW_DATA_ALLOWED, EXPORT_ONLY_FIGURES_ALLOWED and EXPORT_BOTH_RAW_DATA_AND_FIGURES - + raw: bool + Add the --raw flag + limit: int + Add a --limit flag with this number default Returns ------- ns_parser: @@ -609,6 +614,24 @@ def parse_known_args_and_warn( help=help_export, ) + if raw: + parser.add_argument( + "--raw", + dest="raw", + action="store_true", + default=False, + help="Flag to display raw data", + ) + if limit > 0: + parser.add_argument( + "-l", + "--limit", + dest="limit", + default=limit, + help="Number of entries to show in data.", + type=int, + ) + if gtff.USE_CLEAR_AFTER_CMD: system_clear() diff --git a/scripts/test_alt_covid.gst b/scripts/test_alt_covid.gst new file mode 100644 index 00000000000..76d0f10aa45 --- /dev/null +++ b/scripts/test_alt_covid.gst @@ -0,0 +1,14 @@ +alternative +covid +country Australia +reset +slopes +country US +ov +country Russia +deaths +country United Kingdom +cases +country Canada +rates +exit \ No newline at end of file diff --git a/terminal.py b/terminal.py index 604f1a18a0f..01b892cab16 100644 --- a/terminal.py +++ b/terminal.py @@ -66,6 +66,7 @@ class TerminalController: "resources", "jupyter", "funds", + "alternative", ] CHOICES += CHOICES_COMMANDS CHOICES += CHOICES_MENUS @@ -133,6 +134,7 @@ Timezone: {get_user_timezone_or_invalid()} > portfolio > jupyter > resources +> alternative """ print(help_text) @@ -266,6 +268,12 @@ Timezone: {get_user_timezone_or_invalid()} self.queue = resources_controller.menu(self.queue) + def call_alternative(self, _): + """Process resources command""" + from gamestonk_terminal.alternative import alt_controller + + self.queue = alt_controller.menu(self.queue) + def call_portfolio(self, _): """Process portfolio command""" from gamestonk_terminal.portfolio import portfolio_controller diff --git a/website/content/alternative/_index.md b/website/content/alternative/_index.md new file mode 100644 index 00000000000..093e02e215d --- /dev/null +++ b/website/content/alternative/_index.md @@ -0,0 +1,5 @@ +--- +geekdocCollapseSection: true +--- + +{{< toc-tree >}} diff --git a/website/content/alternative/covid/_index.md b/website/content/alternative/covid/_index.md new file mode 100644 index 00000000000..093e02e215d --- /dev/null +++ b/website/content/alternative/covid/_index.md @@ -0,0 +1,5 @@ +--- +geekdocCollapseSection: true +--- + +{{< toc-tree >}} diff --git a/website/content/alternative/covid/cases/_index.md b/website/content/alternative/covid/cases/_index.md new file mode 100644 index 00000000000..576a0df381d --- /dev/null +++ b/website/content/alternative/covid/cases/_index.md @@ -0,0 +1,16 @@ +``` +usage: cases [-h] [--export {csv,json,xlsx,png,jpg,pdf,svg}] [--raw] [-l LIMIT] +``` + +Show historical cases for country. +``` +optional arguments: + -h, --help show this help message (default: False) + --export {csv,json,xlsx,png,jpg,pdf,svg} + Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --raw Flag to display raw data (default: False) + -l LIMIT, --limit LIMIT + Number of entries to show in data. (default: 10) +``` + +![cases](https://user-images.githubusercontent.com/18151143/148297650-1f4bf956-0ede-4118-bef5-195685e5a137.png) diff --git a/website/content/alternative/covid/deaths/_index.md b/website/content/alternative/covid/deaths/_index.md new file mode 100644 index 00000000000..b803a2883a9 --- /dev/null +++ b/website/content/alternative/covid/deaths/_index.md @@ -0,0 +1,16 @@ +```u +sage: deaths [-h] [--export {csv,json,xlsx,png,jpg,pdf,svg}] [--raw] [-l LIMIT] +``` +Show historical deaths by country. + +``` +optional arguments: + -h, --help show this help message (default: False) + --export {csv,json,xlsx,png,jpg,pdf,svg} + Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --raw Flag to display raw data (default: False) + -l LIMIT, --limit LIMIT + Number of entries to show in data. (default: 10) +``` + +![deaths](https://user-images.githubusercontent.com/18151143/148297683-7f263f7c-9db5-496b-831b-254c9324e660.png) diff --git a/website/content/alternative/covid/ov/_index.md b/website/content/alternative/covid/ov/_index.md new file mode 100644 index 00000000000..21d1bb937ad --- /dev/null +++ b/website/content/alternative/covid/ov/_index.md @@ -0,0 +1,16 @@ +``` +usage: ov [-h] [--export {csv,json,xlsx,png,jpg,pdf,svg}] [--raw] [-l LIMIT] +``` +Show historical cases and deaths by country. + +``` +optional arguments: + -h, --help show this help message (default: False) + --export {csv,json,xlsx,png,jpg,pdf,svg} + Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --raw Flag to display raw data (default: False) + -l LIMIT, --limit LIMIT + Number of entries to show in data. (default: 10) +``` + +![ov](https://user-images.githubusercontent.com/18151143/148297718-cde24b47-468b-4929-935b-b36c2140007a.png) diff --git a/website/content/alternative/covid/rates/_index.md b/website/content/alternative/covid/rates/_index.md new file mode 100644 index 00000000000..05ae5aecda5 --- /dev/null +++ b/website/content/alternative/covid/rates/_index.md @@ -0,0 +1,15 @@ +``` +usage: rates [-h] [--export {csv,json,xlsx,png,jpg,pdf,svg}] [--raw] [-l LIMIT] +``` +Show historical rates country. +``` +optional arguments: + -h, --help show this help message (default: False) + --export {csv,json,xlsx,png,jpg,pdf,svg} + Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --raw Flag to display raw data (default: False) + -l LIMIT, --limit LIMIT + Number of entries to show in data. (default: 10) +``` + +![rates](https://user-images.githubusercontent.com/18151143/148297759-b4baba6f-3556-4da4-babf-846a9754ab4b.png) diff --git a/website/content/alternative/covid/slopes/_index.md b/website/content/alternative/covid/slopes/_index.md new file mode 100644 index 00000000000..d9636e88c2a --- /dev/null +++ b/website/content/alternative/covid/slopes/_index.md @@ -0,0 +1,36 @@ +``` +usage: slopes [-d DAYS] [-a] [-t THRESHOLD] [-h] [--export {csv,json,xlsx}] [-l LIMIT] +``` + +Show countries with highest slopes in cases. + +``` +optional arguments: + -d DAYS, --days DAYS Number of days back to look (default: 30) + -a, --ascend Show in ascending order (default: False) + -t THRESHOLD, --threshold THRESHOLD + Threshold for total cases over period (default: 10000) + -h, --help show this help message (default: False) + --export {csv,json,xlsx} + Export raw data into csv, json, xlsx (default: ) + -l LIMIT, --limit LIMIT + Number of entries to show in data. (default: 10) +``` + +``` +2022 Jan 05, 17:01 (✨) /alternative/covid/ $ slopes -l 4 + +Highest Sloping Cases +┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓ +┃ Country ┃ Slope ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ +│ US │ 210153.96 │ +├────────────────┼───────────┤ +│ United Kingdom │ 104611.68 │ +├────────────────┼───────────┤ +│ France │ 81275.88 │ +├────────────────┼───────────┤ +│ Spain │ 47986.82 │ +└────────────────┴───────────┘ + +``` \ No newline at end of file diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index b21412571ae..d476e4e483a 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -1076,3 +1076,19 @@ main: ref: "/forex/candle" - name: resources ref: "/resources" + - name: alternative + ref: "/alternative" + sub: + - name: covid + ref: "/alternative/covid" + sub: + - name: slopes + ref: "/alternative/covid/slopes" + - name: ov + ref: "/alternative/covid/ov" + - name: deaths + ref: "/alternative/covid/deaths" + - name: cases + ref: "/alternative/covid/cases" + - name: rates + ref: "/alternative/covid/rates" \ No newline at end of file