From d4ca5ca4bf365fe57b4cb031273b921b8b0cc705 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 18 Mar 2024 16:35:15 -0400 Subject: [PATCH 01/66] to make nssp run in staging --- ansible/templates/nssp-params-prod.json.j2 | 13 +++++++++++++ ansible/vars.yaml | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 ansible/templates/nssp-params-prod.json.j2 diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 new file mode 100644 index 000000000..0aea90714 --- /dev/null +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -0,0 +1,13 @@ +{ + "common": { + "export_dir": "/common/covidcast/receiving/nssp", + "log_filename": "/var/log/indicators/nssp.log", + "log_exceptions": false + }, + "indicator": { + "wip_signal": true, + "export_start_date": "2020-02-01", + "static_file_dir": "./static", + "socrata_token": "{{ nwss_wastewater_token }}" + } +} diff --git a/ansible/vars.yaml b/ansible/vars.yaml index 8e059c873..ff9ba135c 100644 --- a/ansible/vars.yaml +++ b/ansible/vars.yaml @@ -56,6 +56,9 @@ nchs_mortality_token: "{{ vault_cdc_socrata_token }}" # NWSS nwss_wastewater_token: "{{ vault_cdc_socrata_token }}" +# nssp +nssp_token: "{{ vault_cdc_socrata_token }}" + # SirCAL sir_complainsalot_api_key: "{{ vault_sir_complainsalot_api_key }}" sir_complainsalot_slack_token: "{{ vault_sir_complainsalot_slack_token }}" From 11ff7d003a19b491794a047b1baba8bae97a7a79 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 20 Mar 2024 14:11:05 -0400 Subject: [PATCH 02/66] add nssp to Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0052fd215..3011ebde7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,7 @@ - TODO: #527 Get this list automatically from python-ci.yml at runtime. */ -def indicator_list = ["backfill_corrections", "changehc", "claims_hosp", "google_symptoms", "hhs_hosp", "nchs_mortality", "quidel_covidtest", "sir_complainsalot", "doctor_visits", "nwss_wastewater"] +def indicator_list = ["backfill_corrections", "changehc", "claims_hosp", "google_symptoms", "hhs_hosp", "nchs_mortality", "quidel_covidtest", "sir_complainsalot", "doctor_visits", "nwss_wastewater", "nssp"] def build_package_main = [:] def build_package_prod = [:] def deploy_staging = [:] From d76d6ce71efb7c520cc0dbb0f3e04c9d3cee5676 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 20 Mar 2024 14:23:08 -0400 Subject: [PATCH 03/66] nssp_token name change --- ansible/templates/nssp-params-prod.json.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 index 0aea90714..c261cf70a 100644 --- a/ansible/templates/nssp-params-prod.json.j2 +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -8,6 +8,6 @@ "wip_signal": true, "export_start_date": "2020-02-01", "static_file_dir": "./static", - "socrata_token": "{{ nwss_wastewater_token }}" + "socrata_token": "{{ nssp_token }}" } } From c85c5dd89b7aafbcf2e8e341e8259525479e7c43 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 17 Apr 2024 17:08:53 -0400 Subject: [PATCH 04/66] et code --- nssp/.pylintrc | 22 ++++++ nssp/DETAILS.md | 13 ++++ nssp/Makefile | 29 ++++++++ nssp/README.md | 75 +++++++++++++++++++ nssp/REVIEW.md | 38 ++++++++++ nssp/cache/.gitignore | 0 nssp/delphi_nssp/__init__.py | 14 ++++ nssp/delphi_nssp/__main__.py | 12 +++ nssp/delphi_nssp/constants.py | 29 ++++++++ nssp/delphi_nssp/pull.py | 83 +++++++++++++++++++++ nssp/delphi_nssp/run.py | 133 ++++++++++++++++++++++++++++++++++ nssp/params.json.template | 13 ++++ nssp/receiving/.gitignore | 1 + nssp/setup.py | 32 ++++++++ nssp/tests/test_data/page.txt | 0 nssp/tests/test_pull.py | 34 +++++++++ nssp/tests/test_run.py | 28 +++++++ 17 files changed, 556 insertions(+) create mode 100644 nssp/.pylintrc create mode 100644 nssp/DETAILS.md create mode 100644 nssp/Makefile create mode 100644 nssp/README.md create mode 100644 nssp/REVIEW.md create mode 100644 nssp/cache/.gitignore create mode 100644 nssp/delphi_nssp/__init__.py create mode 100644 nssp/delphi_nssp/__main__.py create mode 100644 nssp/delphi_nssp/constants.py create mode 100644 nssp/delphi_nssp/pull.py create mode 100644 nssp/delphi_nssp/run.py create mode 100644 nssp/params.json.template create mode 100644 nssp/receiving/.gitignore create mode 100644 nssp/setup.py create mode 100644 nssp/tests/test_data/page.txt create mode 100644 nssp/tests/test_pull.py create mode 100644 nssp/tests/test_run.py diff --git a/nssp/.pylintrc b/nssp/.pylintrc new file mode 100644 index 000000000..f30837c7e --- /dev/null +++ b/nssp/.pylintrc @@ -0,0 +1,22 @@ + +[MESSAGES CONTROL] + +disable=logging-format-interpolation, + too-many-locals, + too-many-arguments, + # Allow pytest functions to be part of a class. + no-self-use, + # Allow pytest classes to have one test. + too-few-public-methods + +[BASIC] + +# Allow arbitrarily short-named variables. +variable-rgx=[a-z_][a-z0-9_]* +argument-rgx=[a-z_][a-z0-9_]* +attr-rgx=[a-z_][a-z0-9_]* + +[DESIGN] + +# Don't complain about pytest "unused" arguments. +ignored-argument-names=(_.*|run_as_module) \ No newline at end of file diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md new file mode 100644 index 000000000..9b955a5f3 --- /dev/null +++ b/nssp/DETAILS.md @@ -0,0 +1,13 @@ +# NSSP data + +We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage data, from the CDC website. The data is available in county level, state level and national level. + +## Geographical Levels +* `state`: reported using two-letter postal code +* `county`: reported using fips code +* `national`: just `us` for now +## Metrics +* `percent_visits_covid`, `percent_visits_rsv`, `percent_visits_influenza`: percentage of emergency department patient visits for specified pathogen. +* `percent_visits_combined`: sum of the three percentages of visits for flu, rsv and covid. +* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: Smoothed percentage of emergency department patient visits for specified pathogen. +* `smoothed_percent_visits_combined`: Smoothed sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file diff --git a/nssp/Makefile b/nssp/Makefile new file mode 100644 index 000000000..bc88f1fec --- /dev/null +++ b/nssp/Makefile @@ -0,0 +1,29 @@ +.PHONY = venv, lint, test, clean + +dir = $(shell find ./delphi_* -name __init__.py | grep -o 'delphi_[_[:alnum:]]*' | head -1) +venv: + python3.8 -m venv env + +install: venv + . env/bin/activate; \ + pip install wheel ; \ + pip install -e ../_delphi_utils_python ;\ + pip install -e . + +install-ci: venv + . env/bin/activate; \ + pip install wheel ; \ + pip install ../_delphi_utils_python ;\ + pip install . + +lint: + . env/bin/activate; pylint $(dir) + . env/bin/activate; pydocstyle $(dir) + +test: + . env/bin/activate ;\ + (cd tests && ../env/bin/pytest --cov=$(dir) --cov-report=term-missing) + +clean: + rm -rf env + rm -f params.json diff --git a/nssp/README.md b/nssp/README.md new file mode 100644 index 000000000..b39ca126b --- /dev/null +++ b/nssp/README.md @@ -0,0 +1,75 @@ +# NWSS wastewater data + +We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. +For details see the `DETAILS.md` file in this directory. + +## Create a MyAppToken +`MyAppToken` is required when fetching data from SODA Consumer API +(https://dev.socrata.com/foundry/data.cdc.gov/r8kw-7aab). Follow the +steps below to create a MyAppToken. +- Click the `Sign up for an app token` button in the linked website +- Sign In or Sign Up with Socrata ID +- Click the `Create New App Token` button +- Fill in `Application Name` and `Description` (You can just use delphi_wastewater + for both) and click `Save` +- Copy the `App Token` + + +## Running the Indicator + +The indicator is run by directly executing the Python module contained in this +directory. The safest way to do this is to create a virtual environment, +installed the common DELPHI tools, and then install the module and its +dependencies. To do this, run the following command from this directory: + +``` +make install +``` + +This command will install the package in editable mode, so you can make changes that +will automatically propagate to the installed package. + +All of the user-changable parameters are stored in `params.json`. To execute +the module and produce the output datasets (by default, in `receiving`), run +the following: + +``` +env/bin/python -m delphi_nwss +``` + +If you want to enter the virtual environment in your shell, +you can run `source env/bin/activate`. Run `deactivate` to leave the virtual environment. + +Once you are finished, you can remove the virtual environment and +params file with the following: + +``` +make clean +``` + +## Testing the code + +To run static tests of the code style, run the following command: + +``` +make lint +``` + +Unit tests are also included in the module. To execute these, run the following +command from this directory: + +``` +make test +``` + +To run individual tests, run the following: + +``` +(cd tests && ../env/bin/pytest .py --cov=delphi_NAME --cov-report=term-missing) +``` + +The output will show the number of unit tests that passed and failed, along +with the percentage of code covered by the tests. + +None of the linting or unit tests should fail, and the code lines that are not covered by unit tests should be small and +should not include critical sub-routines. diff --git a/nssp/REVIEW.md b/nssp/REVIEW.md new file mode 100644 index 000000000..03f87b17a --- /dev/null +++ b/nssp/REVIEW.md @@ -0,0 +1,38 @@ +## Code Review (Python) + +A code review of this module should include a careful look at the code and the +output. To assist in the process, but certainly not in replace of it, please +check the following items. + +**Documentation** + +- [ ] the README.md file template is filled out and currently accurate; it is +possible to load and test the code using only the instructions given +- [ ] minimal docstrings (one line describing what the function does) are +included for all functions; full docstrings describing the inputs and expected +outputs should be given for non-trivial functions + +**Structure** + +- [ ] code should pass lint checks (`make lint`) +- [ ] any required metadata files are checked into the repository and placed +within the directory `static` +- [ ] any intermediate files that are created and stored by the module should +be placed in the directory `cache` +- [ ] final expected output files to be uploaded to the API are placed in the +`receiving` directory; output files should not be committed to the respository +- [ ] all options and API keys are passed through the file `params.json` +- [ ] template parameter file (`params.json.template`) is checked into the +code; no personal (i.e., usernames) or private (i.e., API keys) information is +included in this template file + +**Testing** + +- [ ] module can be installed in a new virtual environment (`make install`) +- [ ] reasonably high level of unit test coverage covering all of the main logic +of the code (e.g., missing coverage for raised errors that do not currently seem +possible to reach are okay; missing coverage for options that will be needed are +not) +- [ ] all unit tests run without errors (`make test`) +- [ ] indicator directory has been added to GitHub CI +(`covidcast-indicators/.github/workflows/python-ci.yml`) diff --git a/nssp/cache/.gitignore b/nssp/cache/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/nssp/delphi_nssp/__init__.py b/nssp/delphi_nssp/__init__.py new file mode 100644 index 000000000..2dc99fa87 --- /dev/null +++ b/nssp/delphi_nssp/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +"""Module to pull and clean indicators from the NSSP source. + +This file defines the functions that are made public by the module. As the +module is intended to be executed though the main method, these are primarily +for testing. +""" + +from __future__ import absolute_import + +from . import pull +from . import run + +__version__ = "0.1.0" diff --git a/nssp/delphi_nssp/__main__.py b/nssp/delphi_nssp/__main__.py new file mode 100644 index 000000000..fc82bd813 --- /dev/null +++ b/nssp/delphi_nssp/__main__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""Call the function run_module when executed. + +This file indicates that calling the module (`python -m delphi_NSSP`) will +call the function `run_module` found within the run.py file. There should be +no need to change this template. +""" + +from delphi_utils import read_params +from .run import run_module # pragma: no cover + +run_module(read_params()) # pragma: no cover diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py new file mode 100644 index 000000000..1441fe48f --- /dev/null +++ b/nssp/delphi_nssp/constants.py @@ -0,0 +1,29 @@ +"""Registry for variations.""" + +GEOS = [ + "nation", + "state", + "county", +] + +METRICS = ['percent_visits_covid','percent_visits_influenza', + 'percent_visits_rsv','percent_visits_combined', + 'percent_visits_smoothed_covid','percent_visits_smoothed_influenza', + 'percent_visits_smoothed_rsv','percent_visits_smoothed_combined'] + +SENSORS = ['percent_visits_covid','percent_visits_influenza', + 'percent_visits_rsv','percent_visits_combined', + 'smoothed_percent_visits_covid','smoothed_percent_visits_influenza', + 'smoothed_percent_visits_rsv','smoothed_percent_visits_combined'] + +NEWLINE = "\n" + +CSV_COLS = [ + "geo_id", + "val", + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size" + ] diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py new file mode 100644 index 000000000..ea1138720 --- /dev/null +++ b/nssp/delphi_nssp/pull.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""Functions for pulling NCHS mortality data API.""" + +import numpy as np +import pandas as pd +from sodapy import Socrata + +from .constants import ( + METRICS, + NEWLINE, +) + + +def construct_typedicts(): + """Create the type conversion dictionary for dataframe.""" + # basic type conversion + type_dict = {key: float for key in METRICS} + type_dict["timestamp"] = "datetime64[ns]" + type_dict["geography"] = str + type_dict["county"] = str + type_dict["fips"] = int + return type_dict + + +def warn_string(df, type_dict): + """Format the warning string.""" + return f""" +Expected column(s) missed, The dataset schema may +have changed. Please investigate and amend the code. + +Columns needed: +{NEWLINE.join(sorted(type_dict.keys()))} + +Columns available: +{NEWLINE.join(sorted(df.columns))} +""" + + +def pull_nssp_data(socrata_token: str): + """Pull the latest NWSS Wastewater data, and conforms it into a dataset. + + The output dataset has: + + - Each row corresponds to a single observation + - Each row additionally has columns for the signals in METRICS + + Parameters + ---------- + socrata_token: str + My App Token for pulling the NWSS data (could be the same as the nchs data) + test_file: Optional[str] + When not null, name of file from which to read test data + + Returns + ------- + pd.DataFrame + Dataframe as described above. + """ + type_dict = construct_typedicts() + + # Pull data from Socrata API + client = Socrata("data.cdc.gov", socrata_token) + results = [] + offset = 0 + limit = 50000 # maximum limit allowed by SODA 2.0 + while True: + page = client.get("rdmq-nq56", limit=limit, offset=offset) + if not page: + break # exit the loop if no more results + results.extend(page) + offset += limit + df_ervisits = pd.DataFrame.from_records(results) + df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp", + "percent_visits_smoothed":"percent_visits_smoothed_combined", + "percent_visits_smoothed_1":"percent_visits_smoothed_influenza",}) + + try: + df_ervisits = df_ervisits.astype(type_dict) + except KeyError as exc: + raise ValueError(warn_string(df_ervisits, type_dict)) from exc + + keep_columns = ["timestamp", "geography", "county", "fips"] + return df_ervisits[METRICS + keep_columns] diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py new file mode 100644 index 000000000..bfe40e803 --- /dev/null +++ b/nssp/delphi_nssp/run.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +"""Functions to call when running the function. + +This module should contain a function called `run_module`, that is executed +when the module is run with `python -m MODULE_NAME`. `run_module`'s lone argument should be a +nested dictionary of parameters loaded from the params.json file. We expect the `params` to have +the following structure: + - "common": + - "export_dir": str, directory to write daily output + - "log_filename": (optional) str, path to log file + - "log_exceptions" (optional): bool, whether to log exceptions to file + - "indicator": (optional) + - "wip_signal": (optional) Any[str, bool], list of signals that are + works in progress, or True if all signals in the registry are works + in progress, or False if only unpublished signals are. See + `delphi_utils.add_prefix()` + - "test_file" (optional): str, name of file from which to read test data + - "socrata_token": str, authentication for upstream data pull + - "archive" (optional): if provided, output will be archived with S3 + - "aws_credentials": Dict[str, str], AWS login credentials (see S3 documentation) + - "bucket_name: str, name of S3 bucket to read/write + - "cache_dir": str, directory of locally cached data +""" +import time +from datetime import datetime +import pdb +import numpy as np +import pandas as pd +from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv +from delphi_utils.nancodes import add_default_nancodes + +from .constants import GEOS, METRICS, CSV_COLS, SENSORS +from .pull import pull_nssp_data + +import us + + + + +def add_needed_columns(df, col_names=None): + """Short util to add expected columns not found in the dataset.""" + if col_names is None: + col_names = [ + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size"] + + for col_name in col_names: + df[col_name] = np.nan + df = add_default_nancodes(df) + return df + + +def logging(start_time, run_stats, logger): + """Boilerplate making logs.""" + elapsed_time_in_seconds = round(time.time() - start_time, 2) + min_max_date = run_stats and min(s[0] for s in run_stats) + csv_export_count = sum(s[-1] for s in run_stats) + max_lag_in_days = min_max_date and (datetime.now() - min_max_date).days + formatted_min_max_date = min_max_date and min_max_date.strftime("%Y-%m-%d") + logger.info( + "Completed indicator run", + elapsed_time_in_seconds=elapsed_time_in_seconds, + csv_export_count=csv_export_count, + max_lag_in_days=max_lag_in_days, + oldest_final_export_date=formatted_min_max_date, + ) + + +def run_module(params): + """ + Run the indicator. + + Arguments + -------- + params: Dict[str, Any] + Nested dictionary of parameters. + """ + start_time = time.time() + logger = get_structured_logger( + __name__, + filename=params["common"].get("log_filename"), + log_exceptions=params["common"].get("log_exceptions", True), + ) + export_dir = params["common"]["export_dir"] + socrata_token = params["indicator"]["socrata_token"] + # if "archive" in params: + # daily_arch_diff = S3ArchiveDiffer( + # params["archive"]["cache_dir"], + # export_dir, + # params["archive"]["bucket_name"], + # "nchs_mortality", + # params["archive"]["aws_credentials"], + # ) + # daily_arch_diff.update_cache() + + run_stats = [] + ## build the base version of the signal at the most detailed geo level you can get. + ## compute stuff here or farm out to another function or file + df_pull = pull_nssp_data(socrata_token) + sensor_i = 0 + ## aggregate + for metric in METRICS: + + for geo in GEOS: + df = df_pull.copy() + df["val"] = df[metric] + missing_cols = set(CSV_COLS) - set(df.columns) + df = add_needed_columns(df, col_names=list(missing_cols)) + logger.info("Generating signal and exporting to CSV", metric=metric) + if geo == "nation": + df = df[df["geography"] == "United States"] + df["geo_id"] = "us" + elif geo == "state": + df = df[(df['county'] == "All") & (df["geography"] != "United States")] + df["geo_id"] = df["geography"].apply(lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else 'dc') + else: + df = df[df['county'] != "All"] + df["geo_id"] = df["fips"] + # add se, sample_size, and na codes + df_csv = df[CSV_COLS+["timestamp"]] + # print(df_csv.columns) + # actual export + dates = create_export_csv( + df_csv, geo_res=geo, export_dir=export_dir, sensor=SENSORS[sensor_i], weekly_dates=True + ) + if len(dates) > 0: + run_stats.append((max(dates), len(dates))) + sensor_i += 1 + ## log this indicator run + logging(start_time, run_stats, logger) diff --git a/nssp/params.json.template b/nssp/params.json.template new file mode 100644 index 000000000..e4f5b064c --- /dev/null +++ b/nssp/params.json.template @@ -0,0 +1,13 @@ +{ + "common": { + "export_dir": "./receiving", + "log_filename": "./nssp.log", + "log_exceptions": false + }, + "indicator": { + "wip_signal": true, + "export_start_date": "", + "static_file_dir": "./static", + "socrata_token": "" + } +} diff --git a/nssp/receiving/.gitignore b/nssp/receiving/.gitignore new file mode 100644 index 000000000..afed0735d --- /dev/null +++ b/nssp/receiving/.gitignore @@ -0,0 +1 @@ +*.csv diff --git a/nssp/setup.py b/nssp/setup.py new file mode 100644 index 000000000..4d750e489 --- /dev/null +++ b/nssp/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup +from setuptools import find_packages + +required = [ + "numpy", + "pandas", + "pydocstyle", + "pytest", + "pytest-cov", + "pylint==2.8.3", + "delphi-utils", + "sodapy", + "epiweeks", + "freezegun", + "us", +] + +setup( + name="delphi_nssp", + version="0.0.1", + description="Indicators NSSP Emergency Department Visit", + author="Minh Le", + author_email="minhkhul@andrew.cmu.edu", + url="https://github.com/cmu-delphi/covidcast-indicators", + install_requires=required, + classifiers=[ + "Development Status :: 1 - Planning", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.8", + ], + packages=find_packages(), +) diff --git a/nssp/tests/test_data/page.txt b/nssp/tests/test_data/page.txt new file mode 100644 index 000000000..e69de29bb diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py new file mode 100644 index 000000000..4ead5cf4b --- /dev/null +++ b/nssp/tests/test_pull.py @@ -0,0 +1,34 @@ +from datetime import datetime, date +import json +from unittest.mock import patch +import tempfile +import os +import time +from datetime import datetime + +import pandas as pd +import pandas.api.types as ptypes + +from delphi_nssp.pull import ( + construct_typedicts, + warn_string, +) +import numpy as np + + + +def test_column_type_dicts(): + type_dict = construct_typedicts() + assert type_dict == {'timestamp': 'datetime64[ns]', + 'percent_visits_covid': float, + 'percent_visits_influenza': float, + 'percent_visits_rsv': float, + 'percent_visits_combined': float, + 'percent_visits_smoothed_covid': float, + 'percent_visits_smoothed_influenza': float, + 'percent_visits_smoothed_rsv': float, + 'percent_visits_smoothed_combined': float, + 'geography': str, + 'county': str, + 'fips': int} + diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py new file mode 100644 index 000000000..8aca70329 --- /dev/null +++ b/nssp/tests/test_run.py @@ -0,0 +1,28 @@ +from datetime import datetime, date +import json +from unittest.mock import patch +import tempfile +import os +import time +from datetime import datetime + +import numpy as np +import pandas as pd +from pandas.testing import assert_frame_equal +from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv, Nans + +from delphi_nssp.constants import GEOS, METRICS, CSV_COLS, SENSORS +from delphi_nssp.run import ( + add_needed_columns +) + +def test_add_needed_columns(): + df = pd.DataFrame({'geo_id': ['us'], 'val': [1]}) + df = add_needed_columns(df, col_names=None) + assert df.columns.tolist() == [ + "geo_id","val", "se", "sample_size", + "missing_val", "missing_se", "missing_sample_size"] + + assert df["se"].isnull().all() + assert df["sample_size"].isnull().all() + From 3014997da33a81c5b47744f011c2a5f331d114a7 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:18:01 -0400 Subject: [PATCH 05/66] Update nssp/delphi_nssp/run.py Co-authored-by: David Weber --- nssp/delphi_nssp/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index bfe40e803..083e1137f 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -23,7 +23,6 @@ """ import time from datetime import datetime -import pdb import numpy as np import pandas as pd from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv From 4a9059145525e10df14f65dbbc061749335b0b69 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:21:03 -0400 Subject: [PATCH 06/66] Update nssp/README.md Co-authored-by: David Weber --- nssp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/README.md b/nssp/README.md index b39ca126b..7c06db6eb 100644 --- a/nssp/README.md +++ b/nssp/README.md @@ -1,4 +1,4 @@ -# NWSS wastewater data +# NSSP Emergency Room data We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. For details see the `DETAILS.md` file in this directory. From 638c51dba689c46f9a2b273d2706fc8ca307ed07 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:21:13 -0400 Subject: [PATCH 07/66] Update nssp/DETAILS.md Co-authored-by: David Weber --- nssp/DETAILS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md index 9b955a5f3..00424ca39 100644 --- a/nssp/DETAILS.md +++ b/nssp/DETAILS.md @@ -9,5 +9,5 @@ We import the NSSP Emergency Department Visit data, including percentage and smo ## Metrics * `percent_visits_covid`, `percent_visits_rsv`, `percent_visits_influenza`: percentage of emergency department patient visits for specified pathogen. * `percent_visits_combined`: sum of the three percentages of visits for flu, rsv and covid. -* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: Smoothed percentage of emergency department patient visits for specified pathogen. -* `smoothed_percent_visits_combined`: Smoothed sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file +* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: 3 week moving average of the percentage of emergency department patient visits for specified pathogen. +* `smoothed_percent_visits_combined`: 3 week moving average of the sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file From 82367b49e9b03f40a5c3e74e55623d22f68cc815 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:48:54 -0400 Subject: [PATCH 08/66] Update nssp/delphi_nssp/__main__.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/__main__.py b/nssp/delphi_nssp/__main__.py index fc82bd813..13d071c1d 100644 --- a/nssp/delphi_nssp/__main__.py +++ b/nssp/delphi_nssp/__main__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Call the function run_module when executed. -This file indicates that calling the module (`python -m delphi_NSSP`) will +This file indicates that calling the module (`python -m delphi_nssp`) will call the function `run_module` found within the run.py file. There should be no need to change this template. """ From 68a6154caf9acfdaedfc6b1247f1219716d7bd1c Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:52:31 -0400 Subject: [PATCH 09/66] Update nssp/delphi_nssp/pull.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/pull.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index ea1138720..8649e2044 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Functions for pulling NCHS mortality data API.""" +"""Functions for pulling NSSP ER data.""" import numpy as np import pandas as pd From 8851bd63eea5ebb91473ba74ccebf58b23b49a0a Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:52:56 -0400 Subject: [PATCH 10/66] Update nssp/delphi_nssp/run.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 083e1137f..fef9e69a3 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -2,7 +2,7 @@ """Functions to call when running the function. This module should contain a function called `run_module`, that is executed -when the module is run with `python -m MODULE_NAME`. `run_module`'s lone argument should be a +when the module is run with `python -m delphi_nssp`. `run_module`'s lone argument should be a nested dictionary of parameters loaded from the params.json file. We expect the `params` to have the following structure: - "common": From 39e2cbdec58d38be3d6b0321e40fb8a4bca58932 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 22 Apr 2024 14:00:41 -0400 Subject: [PATCH 11/66] readme update --- nssp/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nssp/README.md b/nssp/README.md index 7c06db6eb..4bba6f626 100644 --- a/nssp/README.md +++ b/nssp/README.md @@ -1,6 +1,6 @@ -# NSSP Emergency Room data +# NSSP Emergency Department Visit data -We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. +We import the NSSP Emergency Department Visit data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. For details see the `DETAILS.md` file in this directory. ## Create a MyAppToken @@ -34,7 +34,7 @@ the module and produce the output datasets (by default, in `receiving`), run the following: ``` -env/bin/python -m delphi_nwss +env/bin/python -m delphi_nssp ``` If you want to enter the virtual environment in your shell, From 95bdac15983b7abaefee95e5093a374ecac4ee94 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 22 Apr 2024 15:04:30 -0400 Subject: [PATCH 12/66] column names mapping + signals name standardization to fit with other available sources and signals" --- nssp/delphi_nssp/constants.py | 21 +++++++++++++-------- nssp/delphi_nssp/pull.py | 15 ++++++++------- nssp/delphi_nssp/run.py | 11 +++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 1441fe48f..8524d75fa 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -6,15 +6,20 @@ "county", ] -METRICS = ['percent_visits_covid','percent_visits_influenza', - 'percent_visits_rsv','percent_visits_combined', - 'percent_visits_smoothed_covid','percent_visits_smoothed_influenza', - 'percent_visits_smoothed_rsv','percent_visits_smoothed_combined'] +SIGNALS_MAP = { + "percent_visits_covid": "pct_visits_covid", + "percent_visits_influenza": "pct_visits_influenza", + "percent_visits_rsv": "pct_visits_rsv", + "percent_visits_combined": "pct_visits_combined", + "percent_visits_smoothed_covid": "smoothed_pct_visits_covid", + "percent_visits_smoothed_1": "smoothed_pct_visits_influenza", + "percent_visits_smoothed_rsv": "smoothed_pct_visits_rsv", + "percent_visits_smoothed": "smoothed_pct_visits_combined", +} -SENSORS = ['percent_visits_covid','percent_visits_influenza', - 'percent_visits_rsv','percent_visits_combined', - 'smoothed_percent_visits_covid','smoothed_percent_visits_influenza', - 'smoothed_percent_visits_rsv','smoothed_percent_visits_combined'] +SIGNALS = ["pct_visits_covid", "pct_visits_influenza", "pct_visits_rsv", "pct_visits_combined", + "smoothed_pct_visits_covid", "smoothed_pct_visits_influenza", + "smoothed_pct_visits_rsv", "smoothed_pct_visits_combined"] NEWLINE = "\n" diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 8649e2044..39d538e12 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -6,15 +6,16 @@ from sodapy import Socrata from .constants import ( - METRICS, + SIGNALS, NEWLINE, + SIGNALS_MAP, ) def construct_typedicts(): """Create the type conversion dictionary for dataframe.""" # basic type conversion - type_dict = {key: float for key in METRICS} + type_dict = {key: float for key in SIGNALS} type_dict["timestamp"] = "datetime64[ns]" type_dict["geography"] = str type_dict["county"] = str @@ -42,7 +43,7 @@ def pull_nssp_data(socrata_token: str): The output dataset has: - Each row corresponds to a single observation - - Each row additionally has columns for the signals in METRICS + - Each row additionally has columns for the signals in SIGNALS Parameters ---------- @@ -70,9 +71,9 @@ def pull_nssp_data(socrata_token: str): results.extend(page) offset += limit df_ervisits = pd.DataFrame.from_records(results) - df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp", - "percent_visits_smoothed":"percent_visits_smoothed_combined", - "percent_visits_smoothed_1":"percent_visits_smoothed_influenza",}) + print(df_ervisits.columns) + df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp"}) + df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) try: df_ervisits = df_ervisits.astype(type_dict) @@ -80,4 +81,4 @@ def pull_nssp_data(socrata_token: str): raise ValueError(warn_string(df_ervisits, type_dict)) from exc keep_columns = ["timestamp", "geography", "county", "fips"] - return df_ervisits[METRICS + keep_columns] + return df_ervisits[SIGNALS + keep_columns] diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index fef9e69a3..2cc9d4a24 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -28,7 +28,7 @@ from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv from delphi_utils.nancodes import add_default_nancodes -from .constants import GEOS, METRICS, CSV_COLS, SENSORS +from .constants import GEOS, SIGNALS, CSV_COLS from .pull import pull_nssp_data import us @@ -101,14 +101,13 @@ def run_module(params): df_pull = pull_nssp_data(socrata_token) sensor_i = 0 ## aggregate - for metric in METRICS: - + for signal in SIGNALS: for geo in GEOS: df = df_pull.copy() - df["val"] = df[metric] + df["val"] = df[signal] missing_cols = set(CSV_COLS) - set(df.columns) df = add_needed_columns(df, col_names=list(missing_cols)) - logger.info("Generating signal and exporting to CSV", metric=metric) + logger.info("Generating signal and exporting to CSV", metric=signal) if geo == "nation": df = df[df["geography"] == "United States"] df["geo_id"] = "us" @@ -123,7 +122,7 @@ def run_module(params): # print(df_csv.columns) # actual export dates = create_export_csv( - df_csv, geo_res=geo, export_dir=export_dir, sensor=SENSORS[sensor_i], weekly_dates=True + df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True ) if len(dates) > 0: run_stats.append((max(dates), len(dates))) From 583e24eef7a6d7e75448976b89ab3d58e3e296dc Mon Sep 17 00:00:00 2001 From: minhkhul Date: Tue, 23 Apr 2024 12:24:59 -0400 Subject: [PATCH 13/66] improve readability --- nssp/delphi_nssp/pull.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 39d538e12..66b29b6ea 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -4,6 +4,7 @@ import numpy as np import pandas as pd from sodapy import Socrata +import textwrap from .constants import ( SIGNALS, @@ -25,20 +26,23 @@ def construct_typedicts(): def warn_string(df, type_dict): """Format the warning string.""" - return f""" -Expected column(s) missed, The dataset schema may -have changed. Please investigate and amend the code. -Columns needed: -{NEWLINE.join(sorted(type_dict.keys()))} + warn_string = textwrap.dedent(f""" + Expected column(s) missed, The dataset schema may + have changed. Please investigate and amend the code. -Columns available: -{NEWLINE.join(sorted(df.columns))} -""" + Columns needed: + {NEWLINE.join(sorted(type_dict.keys()))} + + Columns available: + {NEWLINE.join(sorted(df.columns))} + """) + + return warn_string def pull_nssp_data(socrata_token: str): - """Pull the latest NWSS Wastewater data, and conforms it into a dataset. + """Pull the latest NSSP ER visits data, and conforms it into a dataset. The output dataset has: @@ -71,7 +75,6 @@ def pull_nssp_data(socrata_token: str): results.extend(page) offset += limit df_ervisits = pd.DataFrame.from_records(results) - print(df_ervisits.columns) df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp"}) df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) From 971968e11bc69c0631e11c534df6b319598f529b Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 24 Apr 2024 13:20:07 -0400 Subject: [PATCH 14/66] Add type_dict constant --- nssp/delphi_nssp/pull.py | 16 +++------------- nssp/tests/test_pull.py | 9 +++++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 66b29b6ea..c13ac04d9 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -10,19 +10,10 @@ SIGNALS, NEWLINE, SIGNALS_MAP, + TYPE_DICT ) -def construct_typedicts(): - """Create the type conversion dictionary for dataframe.""" - # basic type conversion - type_dict = {key: float for key in SIGNALS} - type_dict["timestamp"] = "datetime64[ns]" - type_dict["geography"] = str - type_dict["county"] = str - type_dict["fips"] = int - return type_dict - def warn_string(df, type_dict): """Format the warning string.""" @@ -61,7 +52,6 @@ def pull_nssp_data(socrata_token: str): pd.DataFrame Dataframe as described above. """ - type_dict = construct_typedicts() # Pull data from Socrata API client = Socrata("data.cdc.gov", socrata_token) @@ -79,9 +69,9 @@ def pull_nssp_data(socrata_token: str): df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) try: - df_ervisits = df_ervisits.astype(type_dict) + df_ervisits = df_ervisits.astype(TYPE_DICT) except KeyError as exc: - raise ValueError(warn_string(df_ervisits, type_dict)) from exc + raise ValueError(warn_string(df_ervisits, TYPE_DICT)) from exc keep_columns = ["timestamp", "geography", "county", "fips"] return df_ervisits[SIGNALS + keep_columns] diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py index 4ead5cf4b..fe5956c8f 100644 --- a/nssp/tests/test_pull.py +++ b/nssp/tests/test_pull.py @@ -10,15 +10,20 @@ import pandas.api.types as ptypes from delphi_nssp.pull import ( - construct_typedicts, warn_string, ) +from delphi_nssp.constants import ( + SIGNALS, + NEWLINE, + SIGNALS_MAP, + TYPE_DICT +) import numpy as np def test_column_type_dicts(): - type_dict = construct_typedicts() + type_dict = TYPE_DICT assert type_dict == {'timestamp': 'datetime64[ns]', 'percent_visits_covid': float, 'percent_visits_influenza': float, From de9ef62de484c36c2d2d262c4ac9171d8660bfec Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 24 Apr 2024 13:32:02 -0400 Subject: [PATCH 15/66] more type_dict --- nssp/delphi_nssp/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 8524d75fa..415937984 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -32,3 +32,9 @@ "missing_se", "missing_sample_size" ] + +TYPE_DICT = {key: float for key in SIGNALS} +TYPE_DICT.update({"timestamp": "datetime64[ns]", + "geography": str, + "county": str, + "fips": int}) \ No newline at end of file From 900fcc9636b29ae82d323ad05ff807506402f603 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 00:54:48 -0400 Subject: [PATCH 16/66] add more unit test pull --- nssp/tests/test_pull.py | 52 +++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py index fe5956c8f..19f78ef37 100644 --- a/nssp/tests/test_pull.py +++ b/nssp/tests/test_pull.py @@ -1,16 +1,17 @@ from datetime import datetime, date import json -from unittest.mock import patch +import unittest +from unittest.mock import patch, MagicMock import tempfile import os import time from datetime import datetime - +import pdb import pandas as pd import pandas.api.types as ptypes from delphi_nssp.pull import ( - warn_string, + pull_nssp_data, ) from delphi_nssp.constants import ( SIGNALS, @@ -18,22 +19,37 @@ SIGNALS_MAP, TYPE_DICT ) -import numpy as np +class TestPullNSSPData(unittest.TestCase): + @patch('delphi_nssp.pull.Socrata') + def test_pull_nssp_data(self, mock_socrata): + # Load test data + with open('test_data/page.txt', 'r') as f: + test_data = json.load(f) + + # Mock Socrata client and its get method + mock_client = MagicMock() + mock_client.get.side_effect = [test_data, []] # Return test data on first call, empty list on second call + mock_socrata.return_value = mock_client + + # Call function with test token + test_token = 'test_token' + result = pull_nssp_data(test_token) + print(result) + # Check that Socrata client was initialized with correct arguments + mock_socrata.assert_called_once_with("data.cdc.gov", test_token) + # Check that get method was called with correct arguments + mock_client.get.assert_any_call("rdmq-nq56", limit=50000, offset=0) -def test_column_type_dicts(): - type_dict = TYPE_DICT - assert type_dict == {'timestamp': 'datetime64[ns]', - 'percent_visits_covid': float, - 'percent_visits_influenza': float, - 'percent_visits_rsv': float, - 'percent_visits_combined': float, - 'percent_visits_smoothed_covid': float, - 'percent_visits_smoothed_influenza': float, - 'percent_visits_smoothed_rsv': float, - 'percent_visits_smoothed_combined': float, - 'geography': str, - 'county': str, - 'fips': int} + # Check result + assert result['timestamp'].notnull().all(), "timestamp has rogue NaN" + assert result['geography'].notnull().all(), "geography has rogue NaN" + assert result['county'].notnull().all(), "county has rogue NaN" + assert result['fips'].notnull().all(), "fips has rogue NaN" + # Check for each signal in SIGNALS + for signal in SIGNALS: + assert result[signal].notnull().all(), f"{signal} has rogue NaN" +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 0678564846ffbacb7721812a458dd1e2312ffa32 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 01:27:22 -0400 Subject: [PATCH 17/66] data for unit test of pull --- nssp/tests/test_data/page.txt | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/nssp/tests/test_data/page.txt b/nssp/tests/test_data/page.txt index e69de29bb..af5f25922 100644 --- a/nssp/tests/test_data/page.txt +++ b/nssp/tests/test_data/page.txt @@ -0,0 +1,65 @@ +[ + { + "week_end": "2022-10-01T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "2.84", + "percent_visits_covid": "1.84", + "percent_visits_influenza": "0.48", + "percent_visits_rsv": "0.55", + "percent_visits_smoothed": "2.83", + "percent_visits_smoothed_covid": "2.07", + "percent_visits_smoothed_1": "0.34", + "percent_visits_smoothed_rsv": "0.44", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + }, + { + "week_end": "2022-10-08T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "2.93", + "percent_visits_covid": "1.68", + "percent_visits_influenza": "0.68", + "percent_visits_rsv": "0.6", + "percent_visits_smoothed": "2.85", + "percent_visits_smoothed_covid": "1.85", + "percent_visits_smoothed_1": "0.49", + "percent_visits_smoothed_rsv": "0.53", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + }, + { + "week_end": "2022-10-15T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "3.25", + "percent_visits_covid": "1.64", + "percent_visits_influenza": "0.9", + "percent_visits_rsv": "0.74", + "percent_visits_smoothed": "3.01", + "percent_visits_smoothed_covid": "1.72", + "percent_visits_smoothed_1": "0.69", + "percent_visits_smoothed_rsv": "0.63", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + } +] From 38cd523571218ec2405c79cec0fc6b427ee86de8 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 02:02:50 -0400 Subject: [PATCH 18/66] hrr + msa geos --- nssp/delphi_nssp/constants.py | 2 + nssp/delphi_nssp/run.py | 44 ++++++++++++++++-- nssp/tests/test_run.py | 85 ++++++++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 415937984..603587343 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -1,6 +1,8 @@ """Registry for variations.""" GEOS = [ + "hrr", + "msa", "nation", "state", "county", diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 2cc9d4a24..1def424e1 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -31,10 +31,41 @@ from .constants import GEOS, SIGNALS, CSV_COLS from .pull import pull_nssp_data +from delphi_utils.geomap import GeoMapper import us +def sum_all_nan(x): + """Return a normal sum unless everything is NaN, then return that.""" + all_nan = np.isnan(x).all() + if all_nan: + return np.nan + return np.nansum(x) +def weighted_geo_sum(df: pd.DataFrame, geo: str, sensor: str): + """Sum sensor, weighted by population for non NA's, grouped by state.""" + agg_df = df.groupby(["timestamp", geo]).agg( + {f"relevant_pop_{sensor}": "sum", f"weighted_{sensor}": sum_all_nan} + ) + agg_df["val"] = agg_df[f"weighted_{sensor}"] / agg_df[f"relevant_pop_{sensor}"] + agg_df = agg_df.reset_index() + return agg_df +def generate_weights(df, column_aggregating="val"): + """ + Weigh column_aggregating by population. + + generate the relevant population amounts, and create a weighted but + unnormalized column, derived from `column_aggregating` + """ + # set the weight of places with na's to zero + df[f"relevant_pop_{column_aggregating}"] = ( + df["population"] * np.abs(df[column_aggregating]).notna() + ) + # generate the weighted version + df[f"weighted_{column_aggregating}"] = ( + df[column_aggregating] * df[f"relevant_pop_{column_aggregating}"] + ) + return df def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" @@ -101,12 +132,11 @@ def run_module(params): df_pull = pull_nssp_data(socrata_token) sensor_i = 0 ## aggregate + geo_mapper = GeoMapper() for signal in SIGNALS: for geo in GEOS: df = df_pull.copy() df["val"] = df[signal] - missing_cols = set(CSV_COLS) - set(df.columns) - df = add_needed_columns(df, col_names=list(missing_cols)) logger.info("Generating signal and exporting to CSV", metric=signal) if geo == "nation": df = df[df["geography"] == "United States"] @@ -114,12 +144,20 @@ def run_module(params): elif geo == "state": df = df[(df['county'] == "All") & (df["geography"] != "United States")] df["geo_id"] = df["geography"].apply(lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else 'dc') + elif geo == "hrr" or geo == "msa": + df = df[['fips', 'val', 'timestamp']] + df = geo_mapper.add_population_column(df, geocode_type="fips", geocode_col="fips") + df = geo_mapper.add_geocode(df, "fips", geo, from_col="fips", new_col="geo_id") + df = generate_weights(df, "val") + df = weighted_geo_sum(df, "geo_id", "val") + df = df.groupby(["timestamp","geo_id", "val"]).sum(numeric_only=True).reset_index() else: df = df[df['county'] != "All"] df["geo_id"] = df["fips"] # add se, sample_size, and na codes + missing_cols = set(CSV_COLS) - set(df.columns) + df = add_needed_columns(df, col_names=list(missing_cols)) df_csv = df[CSV_COLS+["timestamp"]] - # print(df_csv.columns) # actual export dates = create_export_csv( df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py index 8aca70329..8b41ad69f 100644 --- a/nssp/tests/test_run.py +++ b/nssp/tests/test_run.py @@ -9,11 +9,12 @@ import numpy as np import pandas as pd from pandas.testing import assert_frame_equal -from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv, Nans - -from delphi_nssp.constants import GEOS, METRICS, CSV_COLS, SENSORS +from delphi_nssp.constants import GEOS, SIGNALS, CSV_COLS from delphi_nssp.run import ( - add_needed_columns + add_needed_columns, + generate_weights, + sum_all_nan, + weighted_geo_sum, ) def test_add_needed_columns(): @@ -22,7 +23,81 @@ def test_add_needed_columns(): assert df.columns.tolist() == [ "geo_id","val", "se", "sample_size", "missing_val", "missing_se", "missing_sample_size"] - assert df["se"].isnull().all() assert df["sample_size"].isnull().all() +def test_sum_all_nan(): + """Check that sum_all_nan returns NaN iff everything is a NaN""" + no_nans = np.array([3, 5]) + assert sum_all_nan(no_nans) == 8 + partial_nan = np.array([np.nan, 3, 5]) + assert np.isclose(sum_all_nan([np.nan, 3, 5]), 8) + + oops_all_nans = np.array([np.nan, np.nan]) + assert np.isnan(oops_all_nans).all() + + +def test_weight_generation(): + dataFrame = pd.DataFrame( + { + "a": [1, 2, 3, 4, np.nan], + "b": [5, 6, 7, 8, 9], + "population": [10, 5, 8, 1, 3], + } + ) + weighted = generate_weights(dataFrame, column_aggregating="a") + weighted_by_hand = pd.DataFrame( + { + "a": [1, 2, 3, 4, np.nan], + "b": [5, 6, 7, 8, 9], + "population": [10, 5, 8, 1, 3], + "relevant_pop_a": [10, 5, 8, 1, 0], + "weighted_a": [10.0, 2 * 5.0, 3 * 8, 4.0 * 1, np.nan * 0], + } + ) + assert_frame_equal(weighted, weighted_by_hand) + # operations are in-place + assert_frame_equal(weighted, dataFrame) + + +def test_weighted_state_sum(): + dataFrame = pd.DataFrame( + { + "geo_id": [ + "1", + "1", + "101", + "101", + "102", + ], + "timestamp": np.zeros(5), + "a": [1, 2, 3, 4, 12], + "b": [5, 6, 7, np.nan, np.nan], + "population": [10, 5, 8, 1, 3], + } + ) + weighted = generate_weights(dataFrame, column_aggregating="b") + agg = weighted_geo_sum(weighted, "geo_id", "b") + expected_agg = pd.DataFrame( + { + "timestamp": np.zeros(3), + "geo_id": ["1", "101", "102"], + "relevant_pop_b": [10 + 5, 8 + 0, 0], + "weighted_b": [5 * 10 + 6 * 5, 7 * 8 + 0, np.nan], + "val": [80 / 15, 56 / 8, np.nan], + } + ) + assert_frame_equal(agg, expected_agg) + + weighted = generate_weights(dataFrame, column_aggregating="a") + agg_a = weighted_geo_sum(weighted, "geo_id", "a") + expected_agg_a = pd.DataFrame( + { + "timestamp": np.zeros(3), + "geo_id": ["1", "101", "102"], + "relevant_pop_a": [10 + 5, 8 + 1, 3], + "weighted_a": [1 * 10 + 2 * 5, 3 * 8 + 1 * 4, 12 * 3], + "val": [20 / 15, 28 / 9, 36 / 3], + } + ) + assert_frame_equal(agg_a, expected_agg_a) From 5807cdba315e46630470bb32db522f754ec362cb Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 02:07:01 -0400 Subject: [PATCH 19/66] use enumerate for clarity --- nssp/delphi_nssp/run.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 1def424e1..3830478bd 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -130,10 +130,9 @@ def run_module(params): ## build the base version of the signal at the most detailed geo level you can get. ## compute stuff here or farm out to another function or file df_pull = pull_nssp_data(socrata_token) - sensor_i = 0 ## aggregate geo_mapper = GeoMapper() - for signal in SIGNALS: + for signal_i, signal in enumerate(SIGNALS): for geo in GEOS: df = df_pull.copy() df["val"] = df[signal] @@ -160,10 +159,10 @@ def run_module(params): df_csv = df[CSV_COLS+["timestamp"]] # actual export dates = create_export_csv( - df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True + df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[signal_i], weekly_dates=True ) if len(dates) > 0: run_stats.append((max(dates), len(dates))) - sensor_i += 1 + ## log this indicator run logging(start_time, run_stats, logger) From e974afb107f014af7af651a5c8eaac8a0189ccfd Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:34:29 -0400 Subject: [PATCH 20/66] set nssp sircal max_age to 13 days --- ansible/templates/sir_complainsalot-params-prod.json.j2 | 4 ++++ sir_complainsalot/params.json.template | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ansible/templates/sir_complainsalot-params-prod.json.j2 b/ansible/templates/sir_complainsalot-params-prod.json.j2 index 16918208a..2aae9e0f4 100644 --- a/ansible/templates/sir_complainsalot-params-prod.json.j2 +++ b/ansible/templates/sir_complainsalot-params-prod.json.j2 @@ -49,6 +49,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":13, + "maintainers": [] } } } diff --git a/sir_complainsalot/params.json.template b/sir_complainsalot/params.json.template index 35f0d7d83..6224cb175 100644 --- a/sir_complainsalot/params.json.template +++ b/sir_complainsalot/params.json.template @@ -49,6 +49,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":13, + "maintainers": [] } } } From 85e7b8b9b476888c1daba4f66829d6a273cd7a1b Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:34:29 -0400 Subject: [PATCH 21/66] set nssp sircal max_age to 15 days, to account for nighttime run --- ansible/templates/sir_complainsalot-params-prod.json.j2 | 4 ++++ sir_complainsalot/params.json.template | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ansible/templates/sir_complainsalot-params-prod.json.j2 b/ansible/templates/sir_complainsalot-params-prod.json.j2 index 16918208a..4e008d9b9 100644 --- a/ansible/templates/sir_complainsalot-params-prod.json.j2 +++ b/ansible/templates/sir_complainsalot-params-prod.json.j2 @@ -49,6 +49,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":15, + "maintainers": [] } } } diff --git a/sir_complainsalot/params.json.template b/sir_complainsalot/params.json.template index 35f0d7d83..df8b1a9b1 100644 --- a/sir_complainsalot/params.json.template +++ b/sir_complainsalot/params.json.template @@ -49,6 +49,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":15, + "maintainers": [] } } } From 2bfd5fc17f1eae6cd0bd68736e87b6927142ac9d Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:55:41 -0400 Subject: [PATCH 22/66] add validation to params --- ansible/templates/nssp-params-prod.json.j2 | 18 +++++++++++++++++- nssp/params.json.template | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 index c261cf70a..f99f914c8 100644 --- a/ansible/templates/nssp-params-prod.json.j2 +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -6,8 +6,24 @@ }, "indicator": { "wip_signal": true, - "export_start_date": "2020-02-01", "static_file_dir": "./static", "socrata_token": "{{ nssp_token }}" + }, + "validation": { + "common": { + "data_source": "nssp", + "api_credentials": "{{ validation_api_key }}", + "span_length": 15, + "min_expected_lag": {"all": "8"}, + "max_expected_lag": {"all": "15"}, + "dry_run": true, + "suppressed_errors": [] + }, + "static": { + "minimum_sample_size": 0, + "missing_se_allowed": true, + "missing_sample_size_allowed": true + }, + "dynamic": {} } } diff --git a/nssp/params.json.template b/nssp/params.json.template index e4f5b064c..ed99cd331 100644 --- a/nssp/params.json.template +++ b/nssp/params.json.template @@ -6,8 +6,25 @@ }, "indicator": { "wip_signal": true, - "export_start_date": "", "static_file_dir": "./static", "socrata_token": "" + }, + "validation": { + "common": { + "data_source": "nssp", + "api_credentials": "{{ validation_api_key }}", + "span_length": 15, + "min_expected_lag": {"all": "8"}, + "max_expected_lag": {"all": "15"}, + "dry_run": true, + "suppressed_errors": [] + }, + "static": { + "minimum_sample_size": 0, + "missing_se_allowed": true, + "missing_sample_size_allowed": true + }, + "dynamic": {} } } + From c65796f4f7f2b551ba2d059349d9da6688d4de48 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:38:38 -0400 Subject: [PATCH 23/66] Update nssp/DETAILS.md Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/DETAILS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md index 00424ca39..539697baa 100644 --- a/nssp/DETAILS.md +++ b/nssp/DETAILS.md @@ -1,6 +1,6 @@ # NSSP data -We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage data, from the CDC website. The data is available in county level, state level and national level. +We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage of ER visits attributable to a given pathogen, from the CDC website. The data is provided at the county level, state level and national level; we do a population-weighted mean to aggregate from county data up to the HRR and MSA levels. ## Geographical Levels * `state`: reported using two-letter postal code From a7a869dd9eeec6c2dd3baa531e4bd0e141ac2852 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:42:27 -0400 Subject: [PATCH 24/66] Update nssp/delphi_nssp/constants.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/constants.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 603587343..337abc4f7 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -19,10 +19,7 @@ "percent_visits_smoothed": "smoothed_pct_visits_combined", } -SIGNALS = ["pct_visits_covid", "pct_visits_influenza", "pct_visits_rsv", "pct_visits_combined", - "smoothed_pct_visits_covid", "smoothed_pct_visits_influenza", - "smoothed_pct_visits_rsv", "smoothed_pct_visits_combined"] - +SIGNALS = [val for (key, val) in SIGNALS_MAP.items()] NEWLINE = "\n" CSV_COLS = [ From c074a454fd72a7137b43e06d8d528f13ffbb4726 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 17 Apr 2024 17:08:53 -0400 Subject: [PATCH 25/66] et code --- nssp/.pylintrc | 22 ++++++ nssp/DETAILS.md | 13 ++++ nssp/Makefile | 29 ++++++++ nssp/README.md | 75 +++++++++++++++++++ nssp/REVIEW.md | 38 ++++++++++ nssp/cache/.gitignore | 0 nssp/delphi_nssp/__init__.py | 14 ++++ nssp/delphi_nssp/__main__.py | 12 +++ nssp/delphi_nssp/constants.py | 29 ++++++++ nssp/delphi_nssp/pull.py | 83 +++++++++++++++++++++ nssp/delphi_nssp/run.py | 133 ++++++++++++++++++++++++++++++++++ nssp/params.json.template | 13 ++++ nssp/receiving/.gitignore | 1 + nssp/setup.py | 32 ++++++++ nssp/tests/test_data/page.txt | 0 nssp/tests/test_pull.py | 34 +++++++++ nssp/tests/test_run.py | 28 +++++++ 17 files changed, 556 insertions(+) create mode 100644 nssp/.pylintrc create mode 100644 nssp/DETAILS.md create mode 100644 nssp/Makefile create mode 100644 nssp/README.md create mode 100644 nssp/REVIEW.md create mode 100644 nssp/cache/.gitignore create mode 100644 nssp/delphi_nssp/__init__.py create mode 100644 nssp/delphi_nssp/__main__.py create mode 100644 nssp/delphi_nssp/constants.py create mode 100644 nssp/delphi_nssp/pull.py create mode 100644 nssp/delphi_nssp/run.py create mode 100644 nssp/params.json.template create mode 100644 nssp/receiving/.gitignore create mode 100644 nssp/setup.py create mode 100644 nssp/tests/test_data/page.txt create mode 100644 nssp/tests/test_pull.py create mode 100644 nssp/tests/test_run.py diff --git a/nssp/.pylintrc b/nssp/.pylintrc new file mode 100644 index 000000000..f30837c7e --- /dev/null +++ b/nssp/.pylintrc @@ -0,0 +1,22 @@ + +[MESSAGES CONTROL] + +disable=logging-format-interpolation, + too-many-locals, + too-many-arguments, + # Allow pytest functions to be part of a class. + no-self-use, + # Allow pytest classes to have one test. + too-few-public-methods + +[BASIC] + +# Allow arbitrarily short-named variables. +variable-rgx=[a-z_][a-z0-9_]* +argument-rgx=[a-z_][a-z0-9_]* +attr-rgx=[a-z_][a-z0-9_]* + +[DESIGN] + +# Don't complain about pytest "unused" arguments. +ignored-argument-names=(_.*|run_as_module) \ No newline at end of file diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md new file mode 100644 index 000000000..9b955a5f3 --- /dev/null +++ b/nssp/DETAILS.md @@ -0,0 +1,13 @@ +# NSSP data + +We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage data, from the CDC website. The data is available in county level, state level and national level. + +## Geographical Levels +* `state`: reported using two-letter postal code +* `county`: reported using fips code +* `national`: just `us` for now +## Metrics +* `percent_visits_covid`, `percent_visits_rsv`, `percent_visits_influenza`: percentage of emergency department patient visits for specified pathogen. +* `percent_visits_combined`: sum of the three percentages of visits for flu, rsv and covid. +* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: Smoothed percentage of emergency department patient visits for specified pathogen. +* `smoothed_percent_visits_combined`: Smoothed sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file diff --git a/nssp/Makefile b/nssp/Makefile new file mode 100644 index 000000000..bc88f1fec --- /dev/null +++ b/nssp/Makefile @@ -0,0 +1,29 @@ +.PHONY = venv, lint, test, clean + +dir = $(shell find ./delphi_* -name __init__.py | grep -o 'delphi_[_[:alnum:]]*' | head -1) +venv: + python3.8 -m venv env + +install: venv + . env/bin/activate; \ + pip install wheel ; \ + pip install -e ../_delphi_utils_python ;\ + pip install -e . + +install-ci: venv + . env/bin/activate; \ + pip install wheel ; \ + pip install ../_delphi_utils_python ;\ + pip install . + +lint: + . env/bin/activate; pylint $(dir) + . env/bin/activate; pydocstyle $(dir) + +test: + . env/bin/activate ;\ + (cd tests && ../env/bin/pytest --cov=$(dir) --cov-report=term-missing) + +clean: + rm -rf env + rm -f params.json diff --git a/nssp/README.md b/nssp/README.md new file mode 100644 index 000000000..b39ca126b --- /dev/null +++ b/nssp/README.md @@ -0,0 +1,75 @@ +# NWSS wastewater data + +We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. +For details see the `DETAILS.md` file in this directory. + +## Create a MyAppToken +`MyAppToken` is required when fetching data from SODA Consumer API +(https://dev.socrata.com/foundry/data.cdc.gov/r8kw-7aab). Follow the +steps below to create a MyAppToken. +- Click the `Sign up for an app token` button in the linked website +- Sign In or Sign Up with Socrata ID +- Click the `Create New App Token` button +- Fill in `Application Name` and `Description` (You can just use delphi_wastewater + for both) and click `Save` +- Copy the `App Token` + + +## Running the Indicator + +The indicator is run by directly executing the Python module contained in this +directory. The safest way to do this is to create a virtual environment, +installed the common DELPHI tools, and then install the module and its +dependencies. To do this, run the following command from this directory: + +``` +make install +``` + +This command will install the package in editable mode, so you can make changes that +will automatically propagate to the installed package. + +All of the user-changable parameters are stored in `params.json`. To execute +the module and produce the output datasets (by default, in `receiving`), run +the following: + +``` +env/bin/python -m delphi_nwss +``` + +If you want to enter the virtual environment in your shell, +you can run `source env/bin/activate`. Run `deactivate` to leave the virtual environment. + +Once you are finished, you can remove the virtual environment and +params file with the following: + +``` +make clean +``` + +## Testing the code + +To run static tests of the code style, run the following command: + +``` +make lint +``` + +Unit tests are also included in the module. To execute these, run the following +command from this directory: + +``` +make test +``` + +To run individual tests, run the following: + +``` +(cd tests && ../env/bin/pytest .py --cov=delphi_NAME --cov-report=term-missing) +``` + +The output will show the number of unit tests that passed and failed, along +with the percentage of code covered by the tests. + +None of the linting or unit tests should fail, and the code lines that are not covered by unit tests should be small and +should not include critical sub-routines. diff --git a/nssp/REVIEW.md b/nssp/REVIEW.md new file mode 100644 index 000000000..03f87b17a --- /dev/null +++ b/nssp/REVIEW.md @@ -0,0 +1,38 @@ +## Code Review (Python) + +A code review of this module should include a careful look at the code and the +output. To assist in the process, but certainly not in replace of it, please +check the following items. + +**Documentation** + +- [ ] the README.md file template is filled out and currently accurate; it is +possible to load and test the code using only the instructions given +- [ ] minimal docstrings (one line describing what the function does) are +included for all functions; full docstrings describing the inputs and expected +outputs should be given for non-trivial functions + +**Structure** + +- [ ] code should pass lint checks (`make lint`) +- [ ] any required metadata files are checked into the repository and placed +within the directory `static` +- [ ] any intermediate files that are created and stored by the module should +be placed in the directory `cache` +- [ ] final expected output files to be uploaded to the API are placed in the +`receiving` directory; output files should not be committed to the respository +- [ ] all options and API keys are passed through the file `params.json` +- [ ] template parameter file (`params.json.template`) is checked into the +code; no personal (i.e., usernames) or private (i.e., API keys) information is +included in this template file + +**Testing** + +- [ ] module can be installed in a new virtual environment (`make install`) +- [ ] reasonably high level of unit test coverage covering all of the main logic +of the code (e.g., missing coverage for raised errors that do not currently seem +possible to reach are okay; missing coverage for options that will be needed are +not) +- [ ] all unit tests run without errors (`make test`) +- [ ] indicator directory has been added to GitHub CI +(`covidcast-indicators/.github/workflows/python-ci.yml`) diff --git a/nssp/cache/.gitignore b/nssp/cache/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/nssp/delphi_nssp/__init__.py b/nssp/delphi_nssp/__init__.py new file mode 100644 index 000000000..2dc99fa87 --- /dev/null +++ b/nssp/delphi_nssp/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +"""Module to pull and clean indicators from the NSSP source. + +This file defines the functions that are made public by the module. As the +module is intended to be executed though the main method, these are primarily +for testing. +""" + +from __future__ import absolute_import + +from . import pull +from . import run + +__version__ = "0.1.0" diff --git a/nssp/delphi_nssp/__main__.py b/nssp/delphi_nssp/__main__.py new file mode 100644 index 000000000..fc82bd813 --- /dev/null +++ b/nssp/delphi_nssp/__main__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""Call the function run_module when executed. + +This file indicates that calling the module (`python -m delphi_NSSP`) will +call the function `run_module` found within the run.py file. There should be +no need to change this template. +""" + +from delphi_utils import read_params +from .run import run_module # pragma: no cover + +run_module(read_params()) # pragma: no cover diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py new file mode 100644 index 000000000..1441fe48f --- /dev/null +++ b/nssp/delphi_nssp/constants.py @@ -0,0 +1,29 @@ +"""Registry for variations.""" + +GEOS = [ + "nation", + "state", + "county", +] + +METRICS = ['percent_visits_covid','percent_visits_influenza', + 'percent_visits_rsv','percent_visits_combined', + 'percent_visits_smoothed_covid','percent_visits_smoothed_influenza', + 'percent_visits_smoothed_rsv','percent_visits_smoothed_combined'] + +SENSORS = ['percent_visits_covid','percent_visits_influenza', + 'percent_visits_rsv','percent_visits_combined', + 'smoothed_percent_visits_covid','smoothed_percent_visits_influenza', + 'smoothed_percent_visits_rsv','smoothed_percent_visits_combined'] + +NEWLINE = "\n" + +CSV_COLS = [ + "geo_id", + "val", + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size" + ] diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py new file mode 100644 index 000000000..ea1138720 --- /dev/null +++ b/nssp/delphi_nssp/pull.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""Functions for pulling NCHS mortality data API.""" + +import numpy as np +import pandas as pd +from sodapy import Socrata + +from .constants import ( + METRICS, + NEWLINE, +) + + +def construct_typedicts(): + """Create the type conversion dictionary for dataframe.""" + # basic type conversion + type_dict = {key: float for key in METRICS} + type_dict["timestamp"] = "datetime64[ns]" + type_dict["geography"] = str + type_dict["county"] = str + type_dict["fips"] = int + return type_dict + + +def warn_string(df, type_dict): + """Format the warning string.""" + return f""" +Expected column(s) missed, The dataset schema may +have changed. Please investigate and amend the code. + +Columns needed: +{NEWLINE.join(sorted(type_dict.keys()))} + +Columns available: +{NEWLINE.join(sorted(df.columns))} +""" + + +def pull_nssp_data(socrata_token: str): + """Pull the latest NWSS Wastewater data, and conforms it into a dataset. + + The output dataset has: + + - Each row corresponds to a single observation + - Each row additionally has columns for the signals in METRICS + + Parameters + ---------- + socrata_token: str + My App Token for pulling the NWSS data (could be the same as the nchs data) + test_file: Optional[str] + When not null, name of file from which to read test data + + Returns + ------- + pd.DataFrame + Dataframe as described above. + """ + type_dict = construct_typedicts() + + # Pull data from Socrata API + client = Socrata("data.cdc.gov", socrata_token) + results = [] + offset = 0 + limit = 50000 # maximum limit allowed by SODA 2.0 + while True: + page = client.get("rdmq-nq56", limit=limit, offset=offset) + if not page: + break # exit the loop if no more results + results.extend(page) + offset += limit + df_ervisits = pd.DataFrame.from_records(results) + df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp", + "percent_visits_smoothed":"percent_visits_smoothed_combined", + "percent_visits_smoothed_1":"percent_visits_smoothed_influenza",}) + + try: + df_ervisits = df_ervisits.astype(type_dict) + except KeyError as exc: + raise ValueError(warn_string(df_ervisits, type_dict)) from exc + + keep_columns = ["timestamp", "geography", "county", "fips"] + return df_ervisits[METRICS + keep_columns] diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py new file mode 100644 index 000000000..bfe40e803 --- /dev/null +++ b/nssp/delphi_nssp/run.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +"""Functions to call when running the function. + +This module should contain a function called `run_module`, that is executed +when the module is run with `python -m MODULE_NAME`. `run_module`'s lone argument should be a +nested dictionary of parameters loaded from the params.json file. We expect the `params` to have +the following structure: + - "common": + - "export_dir": str, directory to write daily output + - "log_filename": (optional) str, path to log file + - "log_exceptions" (optional): bool, whether to log exceptions to file + - "indicator": (optional) + - "wip_signal": (optional) Any[str, bool], list of signals that are + works in progress, or True if all signals in the registry are works + in progress, or False if only unpublished signals are. See + `delphi_utils.add_prefix()` + - "test_file" (optional): str, name of file from which to read test data + - "socrata_token": str, authentication for upstream data pull + - "archive" (optional): if provided, output will be archived with S3 + - "aws_credentials": Dict[str, str], AWS login credentials (see S3 documentation) + - "bucket_name: str, name of S3 bucket to read/write + - "cache_dir": str, directory of locally cached data +""" +import time +from datetime import datetime +import pdb +import numpy as np +import pandas as pd +from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv +from delphi_utils.nancodes import add_default_nancodes + +from .constants import GEOS, METRICS, CSV_COLS, SENSORS +from .pull import pull_nssp_data + +import us + + + + +def add_needed_columns(df, col_names=None): + """Short util to add expected columns not found in the dataset.""" + if col_names is None: + col_names = [ + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size"] + + for col_name in col_names: + df[col_name] = np.nan + df = add_default_nancodes(df) + return df + + +def logging(start_time, run_stats, logger): + """Boilerplate making logs.""" + elapsed_time_in_seconds = round(time.time() - start_time, 2) + min_max_date = run_stats and min(s[0] for s in run_stats) + csv_export_count = sum(s[-1] for s in run_stats) + max_lag_in_days = min_max_date and (datetime.now() - min_max_date).days + formatted_min_max_date = min_max_date and min_max_date.strftime("%Y-%m-%d") + logger.info( + "Completed indicator run", + elapsed_time_in_seconds=elapsed_time_in_seconds, + csv_export_count=csv_export_count, + max_lag_in_days=max_lag_in_days, + oldest_final_export_date=formatted_min_max_date, + ) + + +def run_module(params): + """ + Run the indicator. + + Arguments + -------- + params: Dict[str, Any] + Nested dictionary of parameters. + """ + start_time = time.time() + logger = get_structured_logger( + __name__, + filename=params["common"].get("log_filename"), + log_exceptions=params["common"].get("log_exceptions", True), + ) + export_dir = params["common"]["export_dir"] + socrata_token = params["indicator"]["socrata_token"] + # if "archive" in params: + # daily_arch_diff = S3ArchiveDiffer( + # params["archive"]["cache_dir"], + # export_dir, + # params["archive"]["bucket_name"], + # "nchs_mortality", + # params["archive"]["aws_credentials"], + # ) + # daily_arch_diff.update_cache() + + run_stats = [] + ## build the base version of the signal at the most detailed geo level you can get. + ## compute stuff here or farm out to another function or file + df_pull = pull_nssp_data(socrata_token) + sensor_i = 0 + ## aggregate + for metric in METRICS: + + for geo in GEOS: + df = df_pull.copy() + df["val"] = df[metric] + missing_cols = set(CSV_COLS) - set(df.columns) + df = add_needed_columns(df, col_names=list(missing_cols)) + logger.info("Generating signal and exporting to CSV", metric=metric) + if geo == "nation": + df = df[df["geography"] == "United States"] + df["geo_id"] = "us" + elif geo == "state": + df = df[(df['county'] == "All") & (df["geography"] != "United States")] + df["geo_id"] = df["geography"].apply(lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else 'dc') + else: + df = df[df['county'] != "All"] + df["geo_id"] = df["fips"] + # add se, sample_size, and na codes + df_csv = df[CSV_COLS+["timestamp"]] + # print(df_csv.columns) + # actual export + dates = create_export_csv( + df_csv, geo_res=geo, export_dir=export_dir, sensor=SENSORS[sensor_i], weekly_dates=True + ) + if len(dates) > 0: + run_stats.append((max(dates), len(dates))) + sensor_i += 1 + ## log this indicator run + logging(start_time, run_stats, logger) diff --git a/nssp/params.json.template b/nssp/params.json.template new file mode 100644 index 000000000..e4f5b064c --- /dev/null +++ b/nssp/params.json.template @@ -0,0 +1,13 @@ +{ + "common": { + "export_dir": "./receiving", + "log_filename": "./nssp.log", + "log_exceptions": false + }, + "indicator": { + "wip_signal": true, + "export_start_date": "", + "static_file_dir": "./static", + "socrata_token": "" + } +} diff --git a/nssp/receiving/.gitignore b/nssp/receiving/.gitignore new file mode 100644 index 000000000..afed0735d --- /dev/null +++ b/nssp/receiving/.gitignore @@ -0,0 +1 @@ +*.csv diff --git a/nssp/setup.py b/nssp/setup.py new file mode 100644 index 000000000..4d750e489 --- /dev/null +++ b/nssp/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup +from setuptools import find_packages + +required = [ + "numpy", + "pandas", + "pydocstyle", + "pytest", + "pytest-cov", + "pylint==2.8.3", + "delphi-utils", + "sodapy", + "epiweeks", + "freezegun", + "us", +] + +setup( + name="delphi_nssp", + version="0.0.1", + description="Indicators NSSP Emergency Department Visit", + author="Minh Le", + author_email="minhkhul@andrew.cmu.edu", + url="https://github.com/cmu-delphi/covidcast-indicators", + install_requires=required, + classifiers=[ + "Development Status :: 1 - Planning", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.8", + ], + packages=find_packages(), +) diff --git a/nssp/tests/test_data/page.txt b/nssp/tests/test_data/page.txt new file mode 100644 index 000000000..e69de29bb diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py new file mode 100644 index 000000000..4ead5cf4b --- /dev/null +++ b/nssp/tests/test_pull.py @@ -0,0 +1,34 @@ +from datetime import datetime, date +import json +from unittest.mock import patch +import tempfile +import os +import time +from datetime import datetime + +import pandas as pd +import pandas.api.types as ptypes + +from delphi_nssp.pull import ( + construct_typedicts, + warn_string, +) +import numpy as np + + + +def test_column_type_dicts(): + type_dict = construct_typedicts() + assert type_dict == {'timestamp': 'datetime64[ns]', + 'percent_visits_covid': float, + 'percent_visits_influenza': float, + 'percent_visits_rsv': float, + 'percent_visits_combined': float, + 'percent_visits_smoothed_covid': float, + 'percent_visits_smoothed_influenza': float, + 'percent_visits_smoothed_rsv': float, + 'percent_visits_smoothed_combined': float, + 'geography': str, + 'county': str, + 'fips': int} + diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py new file mode 100644 index 000000000..8aca70329 --- /dev/null +++ b/nssp/tests/test_run.py @@ -0,0 +1,28 @@ +from datetime import datetime, date +import json +from unittest.mock import patch +import tempfile +import os +import time +from datetime import datetime + +import numpy as np +import pandas as pd +from pandas.testing import assert_frame_equal +from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv, Nans + +from delphi_nssp.constants import GEOS, METRICS, CSV_COLS, SENSORS +from delphi_nssp.run import ( + add_needed_columns +) + +def test_add_needed_columns(): + df = pd.DataFrame({'geo_id': ['us'], 'val': [1]}) + df = add_needed_columns(df, col_names=None) + assert df.columns.tolist() == [ + "geo_id","val", "se", "sample_size", + "missing_val", "missing_se", "missing_sample_size"] + + assert df["se"].isnull().all() + assert df["sample_size"].isnull().all() + From 1be5b28439c4066a31cba5eb2f0fe0e4e722f4e7 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:18:01 -0400 Subject: [PATCH 26/66] Update nssp/delphi_nssp/run.py Co-authored-by: David Weber --- nssp/delphi_nssp/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index bfe40e803..083e1137f 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -23,7 +23,6 @@ """ import time from datetime import datetime -import pdb import numpy as np import pandas as pd from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv From bd5c782a1db6de78ac520500c5a0a3109201838e Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:21:03 -0400 Subject: [PATCH 27/66] Update nssp/README.md Co-authored-by: David Weber --- nssp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/README.md b/nssp/README.md index b39ca126b..7c06db6eb 100644 --- a/nssp/README.md +++ b/nssp/README.md @@ -1,4 +1,4 @@ -# NWSS wastewater data +# NSSP Emergency Room data We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. For details see the `DETAILS.md` file in this directory. From 86acc03bfc5fc181735c98a4e04a07f871e2c8a6 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:21:13 -0400 Subject: [PATCH 28/66] Update nssp/DETAILS.md Co-authored-by: David Weber --- nssp/DETAILS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md index 9b955a5f3..00424ca39 100644 --- a/nssp/DETAILS.md +++ b/nssp/DETAILS.md @@ -9,5 +9,5 @@ We import the NSSP Emergency Department Visit data, including percentage and smo ## Metrics * `percent_visits_covid`, `percent_visits_rsv`, `percent_visits_influenza`: percentage of emergency department patient visits for specified pathogen. * `percent_visits_combined`: sum of the three percentages of visits for flu, rsv and covid. -* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: Smoothed percentage of emergency department patient visits for specified pathogen. -* `smoothed_percent_visits_combined`: Smoothed sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file +* `smoothed_percent_visits_covid`, `smoothed_percent_visits_rsv`, `smoothed_percent_visits_influenza`: 3 week moving average of the percentage of emergency department patient visits for specified pathogen. +* `smoothed_percent_visits_combined`: 3 week moving average of the sum of the three percentages of visits for flu, rsv and covid. \ No newline at end of file From 9a539232a08c1308832fd0984dcd4126e787fce0 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:48:54 -0400 Subject: [PATCH 29/66] Update nssp/delphi_nssp/__main__.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/__main__.py b/nssp/delphi_nssp/__main__.py index fc82bd813..13d071c1d 100644 --- a/nssp/delphi_nssp/__main__.py +++ b/nssp/delphi_nssp/__main__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Call the function run_module when executed. -This file indicates that calling the module (`python -m delphi_NSSP`) will +This file indicates that calling the module (`python -m delphi_nssp`) will call the function `run_module` found within the run.py file. There should be no need to change this template. """ From 54094ebaf3878968349b08295f2518840a4939fd Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:52:31 -0400 Subject: [PATCH 30/66] Update nssp/delphi_nssp/pull.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/pull.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index ea1138720..8649e2044 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Functions for pulling NCHS mortality data API.""" +"""Functions for pulling NSSP ER data.""" import numpy as np import pandas as pd From 15525045fa0b8df491e0dd1087fcb6a389e3c71f Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:52:56 -0400 Subject: [PATCH 31/66] Update nssp/delphi_nssp/run.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 083e1137f..fef9e69a3 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -2,7 +2,7 @@ """Functions to call when running the function. This module should contain a function called `run_module`, that is executed -when the module is run with `python -m MODULE_NAME`. `run_module`'s lone argument should be a +when the module is run with `python -m delphi_nssp`. `run_module`'s lone argument should be a nested dictionary of parameters loaded from the params.json file. We expect the `params` to have the following structure: - "common": From 6ccddfc1f06acda3af0d74966362f40910fe0f35 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 22 Apr 2024 14:00:41 -0400 Subject: [PATCH 32/66] readme update --- nssp/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nssp/README.md b/nssp/README.md index 7c06db6eb..4bba6f626 100644 --- a/nssp/README.md +++ b/nssp/README.md @@ -1,6 +1,6 @@ -# NSSP Emergency Room data +# NSSP Emergency Department Visit data -We import the wastewater data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. +We import the NSSP Emergency Department Visit data, currently only the smoothed concentration, from the CDC website, aggregate to the state and national level from the wastewater sample site level, and export the aggregated data. For details see the `DETAILS.md` file in this directory. ## Create a MyAppToken @@ -34,7 +34,7 @@ the module and produce the output datasets (by default, in `receiving`), run the following: ``` -env/bin/python -m delphi_nwss +env/bin/python -m delphi_nssp ``` If you want to enter the virtual environment in your shell, From e5603939c53f1f6a94297f21077c1512baf86a04 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 22 Apr 2024 15:04:30 -0400 Subject: [PATCH 33/66] column names mapping + signals name standardization to fit with other available sources and signals" --- nssp/delphi_nssp/constants.py | 21 +++++++++++++-------- nssp/delphi_nssp/pull.py | 15 ++++++++------- nssp/delphi_nssp/run.py | 11 +++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 1441fe48f..8524d75fa 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -6,15 +6,20 @@ "county", ] -METRICS = ['percent_visits_covid','percent_visits_influenza', - 'percent_visits_rsv','percent_visits_combined', - 'percent_visits_smoothed_covid','percent_visits_smoothed_influenza', - 'percent_visits_smoothed_rsv','percent_visits_smoothed_combined'] +SIGNALS_MAP = { + "percent_visits_covid": "pct_visits_covid", + "percent_visits_influenza": "pct_visits_influenza", + "percent_visits_rsv": "pct_visits_rsv", + "percent_visits_combined": "pct_visits_combined", + "percent_visits_smoothed_covid": "smoothed_pct_visits_covid", + "percent_visits_smoothed_1": "smoothed_pct_visits_influenza", + "percent_visits_smoothed_rsv": "smoothed_pct_visits_rsv", + "percent_visits_smoothed": "smoothed_pct_visits_combined", +} -SENSORS = ['percent_visits_covid','percent_visits_influenza', - 'percent_visits_rsv','percent_visits_combined', - 'smoothed_percent_visits_covid','smoothed_percent_visits_influenza', - 'smoothed_percent_visits_rsv','smoothed_percent_visits_combined'] +SIGNALS = ["pct_visits_covid", "pct_visits_influenza", "pct_visits_rsv", "pct_visits_combined", + "smoothed_pct_visits_covid", "smoothed_pct_visits_influenza", + "smoothed_pct_visits_rsv", "smoothed_pct_visits_combined"] NEWLINE = "\n" diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 8649e2044..39d538e12 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -6,15 +6,16 @@ from sodapy import Socrata from .constants import ( - METRICS, + SIGNALS, NEWLINE, + SIGNALS_MAP, ) def construct_typedicts(): """Create the type conversion dictionary for dataframe.""" # basic type conversion - type_dict = {key: float for key in METRICS} + type_dict = {key: float for key in SIGNALS} type_dict["timestamp"] = "datetime64[ns]" type_dict["geography"] = str type_dict["county"] = str @@ -42,7 +43,7 @@ def pull_nssp_data(socrata_token: str): The output dataset has: - Each row corresponds to a single observation - - Each row additionally has columns for the signals in METRICS + - Each row additionally has columns for the signals in SIGNALS Parameters ---------- @@ -70,9 +71,9 @@ def pull_nssp_data(socrata_token: str): results.extend(page) offset += limit df_ervisits = pd.DataFrame.from_records(results) - df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp", - "percent_visits_smoothed":"percent_visits_smoothed_combined", - "percent_visits_smoothed_1":"percent_visits_smoothed_influenza",}) + print(df_ervisits.columns) + df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp"}) + df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) try: df_ervisits = df_ervisits.astype(type_dict) @@ -80,4 +81,4 @@ def pull_nssp_data(socrata_token: str): raise ValueError(warn_string(df_ervisits, type_dict)) from exc keep_columns = ["timestamp", "geography", "county", "fips"] - return df_ervisits[METRICS + keep_columns] + return df_ervisits[SIGNALS + keep_columns] diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index fef9e69a3..2cc9d4a24 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -28,7 +28,7 @@ from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv from delphi_utils.nancodes import add_default_nancodes -from .constants import GEOS, METRICS, CSV_COLS, SENSORS +from .constants import GEOS, SIGNALS, CSV_COLS from .pull import pull_nssp_data import us @@ -101,14 +101,13 @@ def run_module(params): df_pull = pull_nssp_data(socrata_token) sensor_i = 0 ## aggregate - for metric in METRICS: - + for signal in SIGNALS: for geo in GEOS: df = df_pull.copy() - df["val"] = df[metric] + df["val"] = df[signal] missing_cols = set(CSV_COLS) - set(df.columns) df = add_needed_columns(df, col_names=list(missing_cols)) - logger.info("Generating signal and exporting to CSV", metric=metric) + logger.info("Generating signal and exporting to CSV", metric=signal) if geo == "nation": df = df[df["geography"] == "United States"] df["geo_id"] = "us" @@ -123,7 +122,7 @@ def run_module(params): # print(df_csv.columns) # actual export dates = create_export_csv( - df_csv, geo_res=geo, export_dir=export_dir, sensor=SENSORS[sensor_i], weekly_dates=True + df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True ) if len(dates) > 0: run_stats.append((max(dates), len(dates))) From 309b6c787288660644c9ea3cfd5d364a91134ce3 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Tue, 23 Apr 2024 12:24:59 -0400 Subject: [PATCH 34/66] improve readability --- nssp/delphi_nssp/pull.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 39d538e12..66b29b6ea 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -4,6 +4,7 @@ import numpy as np import pandas as pd from sodapy import Socrata +import textwrap from .constants import ( SIGNALS, @@ -25,20 +26,23 @@ def construct_typedicts(): def warn_string(df, type_dict): """Format the warning string.""" - return f""" -Expected column(s) missed, The dataset schema may -have changed. Please investigate and amend the code. -Columns needed: -{NEWLINE.join(sorted(type_dict.keys()))} + warn_string = textwrap.dedent(f""" + Expected column(s) missed, The dataset schema may + have changed. Please investigate and amend the code. -Columns available: -{NEWLINE.join(sorted(df.columns))} -""" + Columns needed: + {NEWLINE.join(sorted(type_dict.keys()))} + + Columns available: + {NEWLINE.join(sorted(df.columns))} + """) + + return warn_string def pull_nssp_data(socrata_token: str): - """Pull the latest NWSS Wastewater data, and conforms it into a dataset. + """Pull the latest NSSP ER visits data, and conforms it into a dataset. The output dataset has: @@ -71,7 +75,6 @@ def pull_nssp_data(socrata_token: str): results.extend(page) offset += limit df_ervisits = pd.DataFrame.from_records(results) - print(df_ervisits.columns) df_ervisits = df_ervisits.rename(columns={"week_end": "timestamp"}) df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) From 813d28935d00fea7288af0b6f6f958d1090c6a9b Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 24 Apr 2024 13:20:07 -0400 Subject: [PATCH 35/66] Add type_dict constant --- nssp/delphi_nssp/pull.py | 16 +++------------- nssp/tests/test_pull.py | 9 +++++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 66b29b6ea..c13ac04d9 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -10,19 +10,10 @@ SIGNALS, NEWLINE, SIGNALS_MAP, + TYPE_DICT ) -def construct_typedicts(): - """Create the type conversion dictionary for dataframe.""" - # basic type conversion - type_dict = {key: float for key in SIGNALS} - type_dict["timestamp"] = "datetime64[ns]" - type_dict["geography"] = str - type_dict["county"] = str - type_dict["fips"] = int - return type_dict - def warn_string(df, type_dict): """Format the warning string.""" @@ -61,7 +52,6 @@ def pull_nssp_data(socrata_token: str): pd.DataFrame Dataframe as described above. """ - type_dict = construct_typedicts() # Pull data from Socrata API client = Socrata("data.cdc.gov", socrata_token) @@ -79,9 +69,9 @@ def pull_nssp_data(socrata_token: str): df_ervisits = df_ervisits.rename(columns=SIGNALS_MAP) try: - df_ervisits = df_ervisits.astype(type_dict) + df_ervisits = df_ervisits.astype(TYPE_DICT) except KeyError as exc: - raise ValueError(warn_string(df_ervisits, type_dict)) from exc + raise ValueError(warn_string(df_ervisits, TYPE_DICT)) from exc keep_columns = ["timestamp", "geography", "county", "fips"] return df_ervisits[SIGNALS + keep_columns] diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py index 4ead5cf4b..fe5956c8f 100644 --- a/nssp/tests/test_pull.py +++ b/nssp/tests/test_pull.py @@ -10,15 +10,20 @@ import pandas.api.types as ptypes from delphi_nssp.pull import ( - construct_typedicts, warn_string, ) +from delphi_nssp.constants import ( + SIGNALS, + NEWLINE, + SIGNALS_MAP, + TYPE_DICT +) import numpy as np def test_column_type_dicts(): - type_dict = construct_typedicts() + type_dict = TYPE_DICT assert type_dict == {'timestamp': 'datetime64[ns]', 'percent_visits_covid': float, 'percent_visits_influenza': float, From db1f8aefc7ff89fcbce4d5793be531b567edd05f Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 24 Apr 2024 13:32:02 -0400 Subject: [PATCH 36/66] more type_dict --- nssp/delphi_nssp/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 8524d75fa..415937984 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -32,3 +32,9 @@ "missing_se", "missing_sample_size" ] + +TYPE_DICT = {key: float for key in SIGNALS} +TYPE_DICT.update({"timestamp": "datetime64[ns]", + "geography": str, + "county": str, + "fips": int}) \ No newline at end of file From 2a357c842a9f87361ef53746ee58efe52c6adc9a Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 00:54:48 -0400 Subject: [PATCH 37/66] add more unit test pull --- nssp/tests/test_pull.py | 52 +++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py index fe5956c8f..19f78ef37 100644 --- a/nssp/tests/test_pull.py +++ b/nssp/tests/test_pull.py @@ -1,16 +1,17 @@ from datetime import datetime, date import json -from unittest.mock import patch +import unittest +from unittest.mock import patch, MagicMock import tempfile import os import time from datetime import datetime - +import pdb import pandas as pd import pandas.api.types as ptypes from delphi_nssp.pull import ( - warn_string, + pull_nssp_data, ) from delphi_nssp.constants import ( SIGNALS, @@ -18,22 +19,37 @@ SIGNALS_MAP, TYPE_DICT ) -import numpy as np +class TestPullNSSPData(unittest.TestCase): + @patch('delphi_nssp.pull.Socrata') + def test_pull_nssp_data(self, mock_socrata): + # Load test data + with open('test_data/page.txt', 'r') as f: + test_data = json.load(f) + + # Mock Socrata client and its get method + mock_client = MagicMock() + mock_client.get.side_effect = [test_data, []] # Return test data on first call, empty list on second call + mock_socrata.return_value = mock_client + + # Call function with test token + test_token = 'test_token' + result = pull_nssp_data(test_token) + print(result) + # Check that Socrata client was initialized with correct arguments + mock_socrata.assert_called_once_with("data.cdc.gov", test_token) + # Check that get method was called with correct arguments + mock_client.get.assert_any_call("rdmq-nq56", limit=50000, offset=0) -def test_column_type_dicts(): - type_dict = TYPE_DICT - assert type_dict == {'timestamp': 'datetime64[ns]', - 'percent_visits_covid': float, - 'percent_visits_influenza': float, - 'percent_visits_rsv': float, - 'percent_visits_combined': float, - 'percent_visits_smoothed_covid': float, - 'percent_visits_smoothed_influenza': float, - 'percent_visits_smoothed_rsv': float, - 'percent_visits_smoothed_combined': float, - 'geography': str, - 'county': str, - 'fips': int} + # Check result + assert result['timestamp'].notnull().all(), "timestamp has rogue NaN" + assert result['geography'].notnull().all(), "geography has rogue NaN" + assert result['county'].notnull().all(), "county has rogue NaN" + assert result['fips'].notnull().all(), "fips has rogue NaN" + # Check for each signal in SIGNALS + for signal in SIGNALS: + assert result[signal].notnull().all(), f"{signal} has rogue NaN" +if __name__ == '__main__': + unittest.main() \ No newline at end of file From d9687893d7262f761321174023d6c49d9a6a3e25 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 01:27:22 -0400 Subject: [PATCH 38/66] data for unit test of pull --- nssp/tests/test_data/page.txt | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/nssp/tests/test_data/page.txt b/nssp/tests/test_data/page.txt index e69de29bb..af5f25922 100644 --- a/nssp/tests/test_data/page.txt +++ b/nssp/tests/test_data/page.txt @@ -0,0 +1,65 @@ +[ + { + "week_end": "2022-10-01T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "2.84", + "percent_visits_covid": "1.84", + "percent_visits_influenza": "0.48", + "percent_visits_rsv": "0.55", + "percent_visits_smoothed": "2.83", + "percent_visits_smoothed_covid": "2.07", + "percent_visits_smoothed_1": "0.34", + "percent_visits_smoothed_rsv": "0.44", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + }, + { + "week_end": "2022-10-08T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "2.93", + "percent_visits_covid": "1.68", + "percent_visits_influenza": "0.68", + "percent_visits_rsv": "0.6", + "percent_visits_smoothed": "2.85", + "percent_visits_smoothed_covid": "1.85", + "percent_visits_smoothed_1": "0.49", + "percent_visits_smoothed_rsv": "0.53", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + }, + { + "week_end": "2022-10-15T00:00:00.000", + "geography": "United States", + "county": "All", + "percent_visits_combined": "3.25", + "percent_visits_covid": "1.64", + "percent_visits_influenza": "0.9", + "percent_visits_rsv": "0.74", + "percent_visits_smoothed": "3.01", + "percent_visits_smoothed_covid": "1.72", + "percent_visits_smoothed_1": "0.69", + "percent_visits_smoothed_rsv": "0.63", + "ed_trends_covid": "Decreasing", + "ed_trends_influenza": "Increasing", + "ed_trends_rsv": "Increasing", + "hsa": "All", + "hsa_counties": "All", + "hsa_nci_id": "All", + "fips": "0", + "trend_source": "United States" + } +] From 24a638d5952a648dea33be0ec6c973f8dffa70e7 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 02:02:50 -0400 Subject: [PATCH 39/66] hrr + msa geos --- nssp/delphi_nssp/constants.py | 2 + nssp/delphi_nssp/run.py | 44 ++++++++++++++++-- nssp/tests/test_run.py | 85 ++++++++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 415937984..603587343 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -1,6 +1,8 @@ """Registry for variations.""" GEOS = [ + "hrr", + "msa", "nation", "state", "county", diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 2cc9d4a24..1def424e1 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -31,10 +31,41 @@ from .constants import GEOS, SIGNALS, CSV_COLS from .pull import pull_nssp_data +from delphi_utils.geomap import GeoMapper import us +def sum_all_nan(x): + """Return a normal sum unless everything is NaN, then return that.""" + all_nan = np.isnan(x).all() + if all_nan: + return np.nan + return np.nansum(x) +def weighted_geo_sum(df: pd.DataFrame, geo: str, sensor: str): + """Sum sensor, weighted by population for non NA's, grouped by state.""" + agg_df = df.groupby(["timestamp", geo]).agg( + {f"relevant_pop_{sensor}": "sum", f"weighted_{sensor}": sum_all_nan} + ) + agg_df["val"] = agg_df[f"weighted_{sensor}"] / agg_df[f"relevant_pop_{sensor}"] + agg_df = agg_df.reset_index() + return agg_df +def generate_weights(df, column_aggregating="val"): + """ + Weigh column_aggregating by population. + + generate the relevant population amounts, and create a weighted but + unnormalized column, derived from `column_aggregating` + """ + # set the weight of places with na's to zero + df[f"relevant_pop_{column_aggregating}"] = ( + df["population"] * np.abs(df[column_aggregating]).notna() + ) + # generate the weighted version + df[f"weighted_{column_aggregating}"] = ( + df[column_aggregating] * df[f"relevant_pop_{column_aggregating}"] + ) + return df def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" @@ -101,12 +132,11 @@ def run_module(params): df_pull = pull_nssp_data(socrata_token) sensor_i = 0 ## aggregate + geo_mapper = GeoMapper() for signal in SIGNALS: for geo in GEOS: df = df_pull.copy() df["val"] = df[signal] - missing_cols = set(CSV_COLS) - set(df.columns) - df = add_needed_columns(df, col_names=list(missing_cols)) logger.info("Generating signal and exporting to CSV", metric=signal) if geo == "nation": df = df[df["geography"] == "United States"] @@ -114,12 +144,20 @@ def run_module(params): elif geo == "state": df = df[(df['county'] == "All") & (df["geography"] != "United States")] df["geo_id"] = df["geography"].apply(lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else 'dc') + elif geo == "hrr" or geo == "msa": + df = df[['fips', 'val', 'timestamp']] + df = geo_mapper.add_population_column(df, geocode_type="fips", geocode_col="fips") + df = geo_mapper.add_geocode(df, "fips", geo, from_col="fips", new_col="geo_id") + df = generate_weights(df, "val") + df = weighted_geo_sum(df, "geo_id", "val") + df = df.groupby(["timestamp","geo_id", "val"]).sum(numeric_only=True).reset_index() else: df = df[df['county'] != "All"] df["geo_id"] = df["fips"] # add se, sample_size, and na codes + missing_cols = set(CSV_COLS) - set(df.columns) + df = add_needed_columns(df, col_names=list(missing_cols)) df_csv = df[CSV_COLS+["timestamp"]] - # print(df_csv.columns) # actual export dates = create_export_csv( df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py index 8aca70329..8b41ad69f 100644 --- a/nssp/tests/test_run.py +++ b/nssp/tests/test_run.py @@ -9,11 +9,12 @@ import numpy as np import pandas as pd from pandas.testing import assert_frame_equal -from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv, Nans - -from delphi_nssp.constants import GEOS, METRICS, CSV_COLS, SENSORS +from delphi_nssp.constants import GEOS, SIGNALS, CSV_COLS from delphi_nssp.run import ( - add_needed_columns + add_needed_columns, + generate_weights, + sum_all_nan, + weighted_geo_sum, ) def test_add_needed_columns(): @@ -22,7 +23,81 @@ def test_add_needed_columns(): assert df.columns.tolist() == [ "geo_id","val", "se", "sample_size", "missing_val", "missing_se", "missing_sample_size"] - assert df["se"].isnull().all() assert df["sample_size"].isnull().all() +def test_sum_all_nan(): + """Check that sum_all_nan returns NaN iff everything is a NaN""" + no_nans = np.array([3, 5]) + assert sum_all_nan(no_nans) == 8 + partial_nan = np.array([np.nan, 3, 5]) + assert np.isclose(sum_all_nan([np.nan, 3, 5]), 8) + + oops_all_nans = np.array([np.nan, np.nan]) + assert np.isnan(oops_all_nans).all() + + +def test_weight_generation(): + dataFrame = pd.DataFrame( + { + "a": [1, 2, 3, 4, np.nan], + "b": [5, 6, 7, 8, 9], + "population": [10, 5, 8, 1, 3], + } + ) + weighted = generate_weights(dataFrame, column_aggregating="a") + weighted_by_hand = pd.DataFrame( + { + "a": [1, 2, 3, 4, np.nan], + "b": [5, 6, 7, 8, 9], + "population": [10, 5, 8, 1, 3], + "relevant_pop_a": [10, 5, 8, 1, 0], + "weighted_a": [10.0, 2 * 5.0, 3 * 8, 4.0 * 1, np.nan * 0], + } + ) + assert_frame_equal(weighted, weighted_by_hand) + # operations are in-place + assert_frame_equal(weighted, dataFrame) + + +def test_weighted_state_sum(): + dataFrame = pd.DataFrame( + { + "geo_id": [ + "1", + "1", + "101", + "101", + "102", + ], + "timestamp": np.zeros(5), + "a": [1, 2, 3, 4, 12], + "b": [5, 6, 7, np.nan, np.nan], + "population": [10, 5, 8, 1, 3], + } + ) + weighted = generate_weights(dataFrame, column_aggregating="b") + agg = weighted_geo_sum(weighted, "geo_id", "b") + expected_agg = pd.DataFrame( + { + "timestamp": np.zeros(3), + "geo_id": ["1", "101", "102"], + "relevant_pop_b": [10 + 5, 8 + 0, 0], + "weighted_b": [5 * 10 + 6 * 5, 7 * 8 + 0, np.nan], + "val": [80 / 15, 56 / 8, np.nan], + } + ) + assert_frame_equal(agg, expected_agg) + + weighted = generate_weights(dataFrame, column_aggregating="a") + agg_a = weighted_geo_sum(weighted, "geo_id", "a") + expected_agg_a = pd.DataFrame( + { + "timestamp": np.zeros(3), + "geo_id": ["1", "101", "102"], + "relevant_pop_a": [10 + 5, 8 + 1, 3], + "weighted_a": [1 * 10 + 2 * 5, 3 * 8 + 1 * 4, 12 * 3], + "val": [20 / 15, 28 / 9, 36 / 3], + } + ) + assert_frame_equal(agg_a, expected_agg_a) From b11c528e4b3f21f7334cb8ac4e532a96422a6147 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 02:07:01 -0400 Subject: [PATCH 40/66] use enumerate for clarity --- nssp/delphi_nssp/run.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 1def424e1..3830478bd 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -130,10 +130,9 @@ def run_module(params): ## build the base version of the signal at the most detailed geo level you can get. ## compute stuff here or farm out to another function or file df_pull = pull_nssp_data(socrata_token) - sensor_i = 0 ## aggregate geo_mapper = GeoMapper() - for signal in SIGNALS: + for signal_i, signal in enumerate(SIGNALS): for geo in GEOS: df = df_pull.copy() df["val"] = df[signal] @@ -160,10 +159,10 @@ def run_module(params): df_csv = df[CSV_COLS+["timestamp"]] # actual export dates = create_export_csv( - df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[sensor_i], weekly_dates=True + df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[signal_i], weekly_dates=True ) if len(dates) > 0: run_stats.append((max(dates), len(dates))) - sensor_i += 1 + ## log this indicator run logging(start_time, run_stats, logger) From 8169655c12db9745ebf2812999e7eb070595e4cf Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 18 Mar 2024 16:35:15 -0400 Subject: [PATCH 41/66] to make nssp run in staging --- ansible/templates/nssp-params-prod.json.j2 | 13 +++++++++++++ ansible/vars.yaml | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 ansible/templates/nssp-params-prod.json.j2 diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 new file mode 100644 index 000000000..0aea90714 --- /dev/null +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -0,0 +1,13 @@ +{ + "common": { + "export_dir": "/common/covidcast/receiving/nssp", + "log_filename": "/var/log/indicators/nssp.log", + "log_exceptions": false + }, + "indicator": { + "wip_signal": true, + "export_start_date": "2020-02-01", + "static_file_dir": "./static", + "socrata_token": "{{ nwss_wastewater_token }}" + } +} diff --git a/ansible/vars.yaml b/ansible/vars.yaml index 8e059c873..ff9ba135c 100644 --- a/ansible/vars.yaml +++ b/ansible/vars.yaml @@ -56,6 +56,9 @@ nchs_mortality_token: "{{ vault_cdc_socrata_token }}" # NWSS nwss_wastewater_token: "{{ vault_cdc_socrata_token }}" +# nssp +nssp_token: "{{ vault_cdc_socrata_token }}" + # SirCAL sir_complainsalot_api_key: "{{ vault_sir_complainsalot_api_key }}" sir_complainsalot_slack_token: "{{ vault_sir_complainsalot_slack_token }}" From fde926401cb98cc782a38f347cc5f78a2f86a45a Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 20 Mar 2024 14:11:05 -0400 Subject: [PATCH 42/66] add nssp to Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0052fd215..3011ebde7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,7 @@ - TODO: #527 Get this list automatically from python-ci.yml at runtime. */ -def indicator_list = ["backfill_corrections", "changehc", "claims_hosp", "google_symptoms", "hhs_hosp", "nchs_mortality", "quidel_covidtest", "sir_complainsalot", "doctor_visits", "nwss_wastewater"] +def indicator_list = ["backfill_corrections", "changehc", "claims_hosp", "google_symptoms", "hhs_hosp", "nchs_mortality", "quidel_covidtest", "sir_complainsalot", "doctor_visits", "nwss_wastewater", "nssp"] def build_package_main = [:] def build_package_prod = [:] def deploy_staging = [:] From bd545c86eb49ec752e272f29b24a2dded3df8f17 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Wed, 20 Mar 2024 14:23:08 -0400 Subject: [PATCH 43/66] nssp_token name change --- ansible/templates/nssp-params-prod.json.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 index 0aea90714..c261cf70a 100644 --- a/ansible/templates/nssp-params-prod.json.j2 +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -8,6 +8,6 @@ "wip_signal": true, "export_start_date": "2020-02-01", "static_file_dir": "./static", - "socrata_token": "{{ nwss_wastewater_token }}" + "socrata_token": "{{ nssp_token }}" } } From 7a3807e31d428dc9843164248d32300122c567c7 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:34:29 -0400 Subject: [PATCH 44/66] set nssp sircal max_age to 15 days, to account for nighttime run --- ansible/templates/sir_complainsalot-params-prod.json.j2 | 4 ++++ sir_complainsalot/params.json.template | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ansible/templates/sir_complainsalot-params-prod.json.j2 b/ansible/templates/sir_complainsalot-params-prod.json.j2 index d6f5da081..ec75bc66b 100644 --- a/ansible/templates/sir_complainsalot-params-prod.json.j2 +++ b/ansible/templates/sir_complainsalot-params-prod.json.j2 @@ -50,6 +50,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":15, + "maintainers": [] } } } diff --git a/sir_complainsalot/params.json.template b/sir_complainsalot/params.json.template index b07b197a4..43edd97a9 100644 --- a/sir_complainsalot/params.json.template +++ b/sir_complainsalot/params.json.template @@ -50,6 +50,10 @@ "hhs": { "max_age":15, "maintainers": [] + }, + "nssp": { + "max_age":15, + "maintainers": [] } } } From 3623b5fce9775117815c1edf6a66b73cb52dfc37 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:34:29 -0400 Subject: [PATCH 45/66] set nssp sircal max_age to 13 days --- ansible/templates/sir_complainsalot-params-prod.json.j2 | 1 + sir_complainsalot/params.json.template | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/templates/sir_complainsalot-params-prod.json.j2 b/ansible/templates/sir_complainsalot-params-prod.json.j2 index ec75bc66b..d7f50a3e5 100644 --- a/ansible/templates/sir_complainsalot-params-prod.json.j2 +++ b/ansible/templates/sir_complainsalot-params-prod.json.j2 @@ -53,6 +53,7 @@ }, "nssp": { "max_age":15, + "max_age":13, "maintainers": [] } } diff --git a/sir_complainsalot/params.json.template b/sir_complainsalot/params.json.template index 43edd97a9..e742c63bc 100644 --- a/sir_complainsalot/params.json.template +++ b/sir_complainsalot/params.json.template @@ -52,7 +52,7 @@ "maintainers": [] }, "nssp": { - "max_age":15, + "max_age":13, "maintainers": [] } } From daee0332c2fd2252932c3e20c5b8fef4f1e0a878 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Thu, 25 Apr 2024 15:55:41 -0400 Subject: [PATCH 46/66] add validation to params --- ansible/templates/nssp-params-prod.json.j2 | 18 +++++++++++++++++- nssp/params.json.template | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 index c261cf70a..f99f914c8 100644 --- a/ansible/templates/nssp-params-prod.json.j2 +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -6,8 +6,24 @@ }, "indicator": { "wip_signal": true, - "export_start_date": "2020-02-01", "static_file_dir": "./static", "socrata_token": "{{ nssp_token }}" + }, + "validation": { + "common": { + "data_source": "nssp", + "api_credentials": "{{ validation_api_key }}", + "span_length": 15, + "min_expected_lag": {"all": "8"}, + "max_expected_lag": {"all": "15"}, + "dry_run": true, + "suppressed_errors": [] + }, + "static": { + "minimum_sample_size": 0, + "missing_se_allowed": true, + "missing_sample_size_allowed": true + }, + "dynamic": {} } } diff --git a/nssp/params.json.template b/nssp/params.json.template index e4f5b064c..ed99cd331 100644 --- a/nssp/params.json.template +++ b/nssp/params.json.template @@ -6,8 +6,25 @@ }, "indicator": { "wip_signal": true, - "export_start_date": "", "static_file_dir": "./static", "socrata_token": "" + }, + "validation": { + "common": { + "data_source": "nssp", + "api_credentials": "{{ validation_api_key }}", + "span_length": 15, + "min_expected_lag": {"all": "8"}, + "max_expected_lag": {"all": "15"}, + "dry_run": true, + "suppressed_errors": [] + }, + "static": { + "minimum_sample_size": 0, + "missing_se_allowed": true, + "missing_sample_size_allowed": true + }, + "dynamic": {} } } + From 566a826284c7b53ba90c57a179c4fe5442d6bf3c Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:38:38 -0400 Subject: [PATCH 47/66] Update nssp/DETAILS.md Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/DETAILS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssp/DETAILS.md b/nssp/DETAILS.md index 00424ca39..539697baa 100644 --- a/nssp/DETAILS.md +++ b/nssp/DETAILS.md @@ -1,6 +1,6 @@ # NSSP data -We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage data, from the CDC website. The data is available in county level, state level and national level. +We import the NSSP Emergency Department Visit data, including percentage and smoothed percentage of ER visits attributable to a given pathogen, from the CDC website. The data is provided at the county level, state level and national level; we do a population-weighted mean to aggregate from county data up to the HRR and MSA levels. ## Geographical Levels * `state`: reported using two-letter postal code From cfa1b94536a602014d860e33a409b4f0a81812bd Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:42:27 -0400 Subject: [PATCH 48/66] Update nssp/delphi_nssp/constants.py Co-authored-by: nmdefries <42820733+nmdefries@users.noreply.github.com> --- nssp/delphi_nssp/constants.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 603587343..337abc4f7 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -19,10 +19,7 @@ "percent_visits_smoothed": "smoothed_pct_visits_combined", } -SIGNALS = ["pct_visits_covid", "pct_visits_influenza", "pct_visits_rsv", "pct_visits_combined", - "smoothed_pct_visits_covid", "smoothed_pct_visits_influenza", - "smoothed_pct_visits_rsv", "smoothed_pct_visits_combined"] - +SIGNALS = [val for (key, val) in SIGNALS_MAP.items()] NEWLINE = "\n" CSV_COLS = [ From 425b1fe056edecaa5783d0a4fa9836c37fde3bd6 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Thu, 9 May 2024 13:06:12 -0500 Subject: [PATCH 49/66] nssp correlation rmd and general notebook folder --- notebooks/.Rprofile | 1 + notebooks/nssp/cor_dashboard.Rmd | 258 ++++ ...covid_ER_admissions_state_correlations.pdf | Bin 0 -> 87505 bytes .../covid_ER_admissions_state_lag_cor.pdf | Bin 0 -> 7178 bytes .../covid_ER_admissions_time_correlations.pdf | Bin 0 -> 5255 bytes .../flu_ER_admissions_state_correlations.pdf | Bin 0 -> 87492 bytes .../nssp/flu_ER_admissions_state_lag_cor.pdf | Bin 0 -> 7170 bytes .../flu_ER_admissions_time_correlations.pdf | Bin 0 -> 5276 bytes notebooks/renv.lock | 1367 +++++++++++++++++ notebooks/renv/.gitignore | 7 + notebooks/renv/activate.R | 1220 +++++++++++++++ notebooks/renv/settings.json | 19 + 12 files changed, 2872 insertions(+) create mode 100644 notebooks/.Rprofile create mode 100644 notebooks/nssp/cor_dashboard.Rmd create mode 100644 notebooks/nssp/covid_ER_admissions_state_correlations.pdf create mode 100644 notebooks/nssp/covid_ER_admissions_state_lag_cor.pdf create mode 100644 notebooks/nssp/covid_ER_admissions_time_correlations.pdf create mode 100644 notebooks/nssp/flu_ER_admissions_state_correlations.pdf create mode 100644 notebooks/nssp/flu_ER_admissions_state_lag_cor.pdf create mode 100644 notebooks/nssp/flu_ER_admissions_time_correlations.pdf create mode 100644 notebooks/renv.lock create mode 100644 notebooks/renv/.gitignore create mode 100644 notebooks/renv/activate.R create mode 100644 notebooks/renv/settings.json diff --git a/notebooks/.Rprofile b/notebooks/.Rprofile new file mode 100644 index 000000000..81b960f5c --- /dev/null +++ b/notebooks/.Rprofile @@ -0,0 +1 @@ +source("renv/activate.R") diff --git a/notebooks/nssp/cor_dashboard.Rmd b/notebooks/nssp/cor_dashboard.Rmd new file mode 100644 index 000000000..2ee75a438 --- /dev/null +++ b/notebooks/nssp/cor_dashboard.Rmd @@ -0,0 +1,258 @@ +--- +title: "Correlation Analyses for COVID-19 Indicators" +author: "Delphi Group" +date: "`r format(Sys.time(), '%B %d, %Y')`" +output: + html_document: + code_folding: hide +--- + +```{r, include = FALSE} +knitr::opts_chunk$set(message = FALSE, warning = FALSE, fig.width = 8, + fig.height = 7) +``` + +### Getting data +This requires that you've already run the nssp pipeline. See the `nssp` directory for instructions on doing that. +First loading some libraries and reading the results from the pipeline: +```{r} +library(covidcast) +library(epidatr) +library(dplyr) +library(ggplot2) + +library(purrr) +library(tidyverse) +library(dplyr) +library(readr) +files <- list.files(here::here("nssp/receiving"), pattern="\\.csv$", full.names = TRUE) +read_row <- function(filename) { + split_name <- filename %>% + tools::file_path_sans_ext() %>% + strsplit("/") %>% `[[`(1) %>% tail(n=1) %>% + strsplit("_") %>% `[[`(1) + week_number <- split_name[[2]] + geo_type <- split_name[[3]] + col_name <- split_name[-(1:3)] %>% paste(collapse = "_") + + read_csv(filename, show_col_types = FALSE) %>% + as_tibble %>% + mutate(signal = col_name, + geo_type = geo_type, + week_number = week_number) %>% + mutate(across(geo_id, factor)) %>% + rename(geo_value = geo_id, time_value = week_number) %>% + select(-missing_se, -se, -sample_size, -missing_sample_size) %>% + return +} +res <- map(files, read_row) +nssp_data <- bind_rows(res) +nssp_state <- nssp_data %>% + filter(geo_type == "state") %>% + mutate(time_value = epidatr:::parse_api_week(time_value)) %>% + as_epi_df(time_type = "week", geo_type = "state") %>% + select(-missing_val, -geo_type) +unique(nssp_data$time_value) +``` +And epidatr versions of hhs for comparison +```{r} +library(epidatr) +eval_time <- epidatr::epirange(from = "2020-01-01", to = Sys.Date()) +fetch_args <- epidatr::fetch_args_list(return_empty = TRUE, timeout_seconds = 300) + +flu_hhs <- epidatr::pub_covidcast( + source = "hhs", + signals = "confirmed_admissions_influenza_1d_prop_7dav", + geo_type = "state", + time_type = "day", + geo_values = "*", + time_values = eval_time, + fetch_args = fetch_args + ) %>% + select(-signal, -source, - time_type) + +covid_hhs <- epidatr::pub_covidcast( + source = "hhs", + signals = "confirmed_admissions_covid_1d_prop_7dav", + geo_type = "state", + time_type = "day", + geo_values = "*", + time_values = eval_time, + fetch_args = fetch_args + ) %>% + select(-signal, -source, - time_type) + + +nchs <- epidatr::pub_covidcast( + source = "nchs-mortality", + signals = "deaths_allcause_incidence_num", + geo_type = "state", + time_type = "week", + geo_values = "*", + time_values = epidatr::epirange(from = "202001", to = "202418"), + fetch_args = epidatr::fetch_args_list(return_empty = TRUE, timeout_seconds = 300) + ) +``` +# Flu +```{r} +library(epiprocess) +nssp_flu_state <- nssp_state %>% filter(signal == "pct_visits_influenza") %>% select(-signal) %>% drop_na %>% rename(pct_flu_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") +week_starts <- nssp_flu_state$time_value %>% unique() +flu_hhs_weekly <- flu_hhs %>% select(geo_value, time_value, value) %>% filter(time_value %in% week_starts) %>% rename(conf_admission = value) %>% drop_na %>% as_epi_df(time_type = "week", geo_type = "state") +joined <- nssp_flu_state %>% left_join(flu_hhs_weekly) +``` + +After the necessary joining, lets look at the average correlations +```{r} +cor(joined$pct_flu_visits, joined$conf_admission, method = "spearman") +``` +So the overall correlation is pretty high. + +## Correlations sliced by state +```{r} +correlations_space_flu <- epi_cor(joined, pct_flu_visits, conf_admission, cor_by = "geo_value", use = "complete.obs", method = "spearman") +library(maps) # For map data +states_map <- map_data("state") +mapped <- states_map %>% as_tibble %>% mutate(geo_value = setNames(tolower(state.abb), tolower(state.name))[region]) %>% right_join(correlations_space_flu) %>% arrange(group, order) +library(viridis) +ggplot(mapped, aes(x = long, y = lat, group = group, fill = cor)) + + geom_polygon(colour = "black") + + scale_fill_viridis(discrete=FALSE, option="viridis", limits = c(0,1)) + + coord_map("polyconic") + + labs(title = "Spearman Correlations between Flu ER visits and Flu hospital admissions") +ggsave("flu_ER_admissions_state_correlations.pdf") +``` +Over space, hospital admissions look like they're highly correlated with ER visits (which makes sense, frequently when one is admitted it is via the ER). +The lowest overall correlation is +```{r} +correlations_space_flu %>% summarize(across(where(is.numeric), .fns = list(min = min, median = median, mean = mean, std = sd, q25 = ~quantile(.,0.25), q75 = ~quantile(.,0.75), max = max))) +``` +### Lag evaluation +```{r} +library(purrr) +lags <- 0:35 + +lagged_flu_state <- map_dfr(lags, function(lag) { + epi_cor(joined, pct_flu_visits, conf_admission, cor_by = geo_value, dt1 = lag, use = "complete.obs", method = "spearman") %>% + mutate(lag = .env$lag) +}) + +lagged_flu_state %>% + group_by(lag) %>% + summarize(mean = mean(cor, na.rm = TRUE)) %>% + ggplot(aes(x = lag, y = mean)) + + geom_line() + + geom_point() + + labs(x = "Lag", y = "Mean correlation", title = "Lag comparison for state spearman correlations for flu ER and Hosp admissions") +ggsave("flu_ER_admissions_state_lag_cor.pdf") +``` +Somewhat unsurprisingly, the correlation is highest immediately afterward. +## Correlations sliced by time +```{r} +correlations_time_flu <- epi_cor(joined, pct_flu_visits, conf_admission, cor_by = "time_value", use = "complete.obs", method = "spearman") +correlations_time_flu +ggplot(correlations_time_flu, aes(x = time_value, y = cor)) + geom_line() + lims(y=c(0,1)) + labs(title = "Spearman Correlations between Flu ER visits and Flu hospital admissions") +ggsave("flu_ER_admissions_time_correlations.pdf") +``` +Strangely, sliced by time, we get significantly lower correlations +```{r} +correlations_time_flu %>% summarize(across(where(is.numeric), .fns = list(min = min, median = median, mean = mean, std = sd, q25 = ~quantile(.,0.25), q75 = ~quantile(.,0.75), max = max))) +``` +Seems like we have a Simpson's paradox adjacent result, since for any given location the signals are fairly well correlated when averaged over time, but at a given time, averaging over different locations suggests they're not very well correlated. +If the typical explanation applies, this means that there are large differences in the number of points. + +so, getting the counts: +```{r} +joined %>% group_by(geo_value) %>% count %>% arrange(n) %>% ungroup %>% summarise(across(where(is.numeric), .fns = list(min = min, max = max))) +``` +Each location has 82 + +```{r} +joined %>% group_by(time_value) %>% count %>% arrange(n) %>% ungroup %>% summarise(across(where(is.numeric), .fns = list(min = min, max = max))) +``` +# Covid +```{r} +library(epiprocess) +nssp_data %>% pull(signal) %>% unique +nssp_state <- nssp_data %>% + filter(geo_type == "state") %>% + mutate(time_value = epidatr:::parse_api_week(time_value)) %>% + as_epi_df(time_type = "week", geo_type = "state") %>% + select(-missing_val, -geo_type) +nssp_covid_state <- nssp_state %>% filter(signal == "pct_visits_covid") %>% select(-signal) %>% drop_na %>% rename(pct_covid_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") +week_starts <- nssp_covid_state$time_value %>% unique() +covid_hhs_weekly <- covid_hhs %>% select(geo_value, time_value, value) %>% filter(time_value %in% week_starts) %>% rename(conf_admission = value) %>% drop_na %>% as_epi_df(time_type = "week", geo_type = "state") +joined_covid <- nssp_covid_state %>% left_join(covid_hhs_weekly) +``` + +After the necessary joining, lets look at the average correlations +```{r} +cor(joined_covid$pct_covid_visits, joined_covid$conf_admission, method = "spearman") +``` +So the overall correlation is pretty high, but lower than flu. + +## Correlations sliced by state +```{r} +correlations_space_covid <- epi_cor(joined_covid, pct_covid_visits, conf_admission, cor_by = "geo_value", use = "complete.obs", method = "spearman") +library(maps) # For map data +states_map <- map_data("state") +mapped <- states_map %>% as_tibble %>% mutate(geo_value = setNames(tolower(state.abb), tolower(state.name))[region]) %>% right_join(correlations_space_covid) %>% arrange(group, order) +library(viridis) +ggplot(mapped, aes(x = long, y = lat, group = group, fill = cor)) + + geom_polygon(colour = "black") + + scale_fill_viridis(discrete=FALSE, option="viridis", limits = c(0,1)) + + coord_map("polyconic") + + labs(title = "Spearman Correlations between covid ER visits and covid hospital admissions") +ggsave("covid_ER_admissions_state_correlations.pdf") +ggsave("covid_ER_admissions_state_correlations.png") +``` +Over space, hospital admissions look like they're highly correlated with ER visits (which makes sense, frequently when one is admitted it is via the ER). +The lowest overall correlation is +```{r} +correlations_space_covid %>% summarize(across(where(is.numeric), .fns = list(min = min, median = median, mean = mean, std = sd, q25 = ~quantile(.,0.25), q75 = ~quantile(.,0.75), max = max))) +``` +### Lag evaluation +```{r} +library(purrr) +lags <- 0:35 + +lagged_covid_state <- map_dfr(lags, function(lag) { + epi_cor(joined_covid, pct_covid_visits, conf_admission, cor_by = geo_value, dt1 = -lag, use = "complete.obs", method = "spearman") %>% + mutate(lag = .env$lag) +}) + +lagged_covid_state %>% + group_by(lag) %>% + summarize(mean = mean(cor, na.rm = TRUE)) %>% + ggplot(aes(x = lag, y = mean)) + + geom_line() + + geom_point() + + labs(x = "Lag", y = "Mean correlation", title = "Lag comparison for state spearman correlations for covid ER and Hosp admissions") +ggsave("covid_ER_admissions_state_lag_cor.pdf") +ggsave("covid_ER_admissions_state_lag_cor.png") +``` +Somewhat unsurprisingly, the correlation is highest immediately afterward, though its significantly lower than in the flu case. +## Correlations sliced by time +```{r} +correlations_time_covid <- epi_cor(joined_covid, pct_covid_visits, conf_admission, cor_by = "time_value", use = "complete.obs", method = "spearman") +correlations_time_covid +ggplot(correlations_time_covid, aes(x = time_value, y = cor)) + geom_line() + lims(y=c(0,1)) + labs(title = "Spearman Correlations between covid ER visits and covid hospital admissions") +ggsave("covid_ER_admissions_time_correlations.pdf") +ggsave("covid_ER_admissions_time_correlations.png") +``` +Strangely, sliced by time, we get significantly lower correlations, some of them are even negative +```{r} +correlations_time_covid %>% summarize(across(where(is.numeric), .fns = list(min = min, median = median, mean = mean, std = sd, q25 = ~quantile(.,0.25), q75 = ~quantile(.,0.75), max = max))) +``` +Seems like we have a Simpson's paradox adjacent result, since for any given location the signals are fairly well correlated when averaged over time, but at a given time, averaging over different locations suggests they're not very well correlated. +If the typical explanation applies, this means that there are large differences in the number of points. + +so, getting the counts: +```{r} +joined_covid %>% group_by(geo_value) %>% count %>% arrange(n) %>% ungroup %>% summarise(across(where(is.numeric), .fns = list(min = min, max = max))) +``` +Each location has 82 + +```{r} +joined_covid %>% group_by(time_value) %>% count %>% arrange(n) %>% ungroup %>% summarise(across(where(is.numeric), .fns = list(min = min, max = max))) +``` diff --git a/notebooks/nssp/covid_ER_admissions_state_correlations.pdf b/notebooks/nssp/covid_ER_admissions_state_correlations.pdf new file mode 100644 index 0000000000000000000000000000000000000000..35f272b039d02de08fd3941e895c00d158f4c726 GIT binary patch literal 87505 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 literal 0 HcmV?d00001 diff --git a/notebooks/nssp/covid_ER_admissions_state_lag_cor.pdf b/notebooks/nssp/covid_ER_admissions_state_lag_cor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..86852e048901296dadf4d13c2f530cb02a681db6 GIT binary patch literal 7178 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 U8T+#`UrXtn<06+&SH2?qr literal 0 HcmV?d00001 diff --git a/notebooks/nssp/covid_ER_admissions_time_correlations.pdf b/notebooks/nssp/covid_ER_admissions_time_correlations.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be131cd9226b2f5a5d16d029c5df003d8e70022b GIT binary patch literal 5255 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 literal 0 HcmV?d00001 diff --git a/notebooks/nssp/flu_ER_admissions_state_correlations.pdf b/notebooks/nssp/flu_ER_admissions_state_correlations.pdf new file mode 100644 index 0000000000000000000000000000000000000000..56e04209dea5a383bcb981257d1f6c24158350fc GIT binary patch literal 87492 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 AA^-pY literal 0 HcmV?d00001 diff --git a/notebooks/nssp/flu_ER_admissions_state_lag_cor.pdf b/notebooks/nssp/flu_ER_admissions_state_lag_cor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d08c84c32fd46d6cb6756bde92bdc2dc8ed0fa9d GIT binary patch literal 7170 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 YWnaA1KKWxBvhE literal 0 HcmV?d00001 diff --git a/notebooks/nssp/flu_ER_admissions_time_correlations.pdf b/notebooks/nssp/flu_ER_admissions_time_correlations.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c00c1479e13b71aa83c4913478ea569dd9df215a GIT binary patch literal 5276 zcmZ`-c|6qX_ZQhFWe+8W2o;~1F~-Pe_B~tCMp=@gawQ?VD3Tca z8d)oAR47^Uo$220_kQpF{XVbP=a1+8oaZ^`Ie(n@Id5qTU440|0vseAH4`-()g9IE zLIOd-5HQj6H;}qI2tbjrXbO%<&_z?QU|C&N7z74~C_xmViV!3WAqN6XiC+Kjo(+zI zr=PL{8<5ZezBmjSY)+*FP$~3d79^q<6@w))cHs(e1qdDc>5IsKP7_EAFd4QBLxtcY7P2rvZ zHI|ZpOO$NbHJd%XKSVD|vv0EBylz-atLS{49CB47b@FXpUHg?HJ`KpVP6dGwhyH{G z<>m3cTx34r?z-3UWn-{(Ygos60!AB#>`|rbah|Z`4TJ8 zBi7f_D(I7qHB*D5lM94E-gs!=rmfJP-(IY59!Im9kuk>)so3x!k8{#}=T1iDv}jD2 z3~!G$R67a9hyZNa$!qn(ok7r7WwrBkp!m##u2q)tuKN(ld9fBrg?BHuxY$_~di6Ag zI|CrEYHFMgu_=mjrFE#QrC1%>N3Cw*FQ|sMD9%b?mDIXWuT|Z+Vv{>ieLnTV0`9IN z8JZY!wLsefPcK(OB438ZShGibOw`pWFKFhp$5iT>S^Lrb<;xe**H5dM`ZY@wN8I9+ zh(>T7w;LJJYY{2&I2xc({aR|I1z%k=V0f}K+EcVY_HOWvTQiA|S&X_A1Y6~P%ZV+c zbs5v%>yAH3IwZ`=m5Q{r_jhgbl!!+Zm(0yuK_VW$oeXZGRXiT`Ntdqn+@5woB^~L6 zXZPIU&(E|xR(WZnjz^YcRV7dm8K=WqKB&nS2(>h66?DF2xpZUd+~B6?mz%9N$%RIN zy)oAJPZB&1V@*;FlehS#ae-FM>AFx+UPyYufu~yb?|ox$Nyx=pfx3+Epjn3BNyf&m zwREATI8NJDW0y*5?>d#fbr%fAl$#$h2l;9)y7yNw8C+(ai$dYQUWRjs2KZM{kw4dGl@f=r7&16?qKQK^ZYFcRyXm6R=clxx z^dRm%ZF1g0Wf>%QMUpo#g@ON&E;rzAe=~oXSVvrO<|xi%N82PGSUMrj2Gyo$bbl!k zs+@2tpO6uYe-nZlPWH7_D2Yl7QlE+KjxP$~j zC^n-td1lS$&%B43QRd*n^N zZ~9Rlmt=JHO+>$aeC}^$+a?hraZzrpSlZ3i6zz1`eG$Jmilj0LanAkd^V*Twza-?i z+YV;q5_z)p?RdJZ=y8AAN(+FBJZsCajn{Z^XZSVQQuW6Neml059s$IY2$)jf#CC1Pxm z1;HMUTJ?JdvrSLJgMM5pjKc1I+7ds=GWU2n!3v@kU$!t-t09Jr--me?4kvQ0wTAEd zjtj?ZT5kE3zf^K#=XtNQ4%JwjzEE?ur~eZ&dj*)RbMHaDX%81$(R#*`ZRTnq$1y#U zkhUSg`VAiv$xT!cBUUYGHM;Og);aiVmI9M~wBp_z#Ydtm!3dX2j@mw~6`OBZBF^sx znDy-0>+~7vaCK+&p0Nzkf237n|7kVL+pDg±kWVZ(bP=t4wQf!6RPcYsrIxHTlq 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 Q8Mulv5+p6HXRZ(W4>J)Xga7~l literal 0 HcmV?d00001 diff --git a/notebooks/renv.lock b/notebooks/renv.lock new file mode 100644 index 000000000..dd103cd87 --- /dev/null +++ b/notebooks/renv.lock @@ -0,0 +1,1367 @@ +{ + "R": { + "Version": "4.4.0", + "Repositories": [ + { + "Name": "RSPM", + "URL": "https://packagemanager.posit.co/all/latest" + } + ] + }, + "Packages": { + "DBI": { + "Package": "DBI", + "Version": "1.2.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "methods" + ], + "Hash": "164809cd72e1d5160b4cb3aa57f510fe" + }, + "KernSmooth": { + "Package": "KernSmooth", + "Version": "2.23-22", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats" + ], + "Hash": "2fecebc3047322fa5930f74fae5de70f" + }, + "MASS": { + "Package": "MASS", + "Version": "7.3-60.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "graphics", + "methods", + "stats", + "utils" + ], + "Hash": "2f342c46163b0b54d7b64d1f798e2c78" + }, + "MMWRweek": { + "Package": "MMWRweek", + "Version": "0.1.3", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "4329e57e2536e12afe479e8571416dbc" + }, + "Matrix": { + "Package": "Matrix", + "Version": "1.7-0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "graphics", + "grid", + "lattice", + "methods", + "stats", + "utils" + ], + "Hash": "1920b2f11133b12350024297d8a4ff4a" + }, + "R6": { + "Package": "R6", + "Version": "2.5.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "470851b6d5d0ac559e9d01bb352b4021" + }, + "RColorBrewer": { + "Package": "RColorBrewer", + "Version": "1.1-3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "45f0398006e83a5b10b72a90663d8d8c" + }, + "Rcpp": { + "Package": "Rcpp", + "Version": "1.0.12", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "methods", + "utils" + ], + "Hash": "5ea2700d21e038ace58269ecdbeb9ec0" + }, + "askpass": { + "Package": "askpass", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "sys" + ], + "Hash": "cad6cf7f1d5f6e906700b9d3e718c796" + }, + "backports": { + "Package": "backports", + "Version": "1.4.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "c39fbec8a30d23e721980b8afb31984c" + }, + "base64enc": { + "Package": "base64enc", + "Version": "0.1-3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "543776ae6848fde2f48ff3816d0628bc" + }, + "bit": { + "Package": "bit", + "Version": "4.0.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "d242abec29412ce988848d0294b208fd" + }, + "bit64": { + "Package": "bit64", + "Version": "4.0.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "bit", + "methods", + "stats", + "utils" + ], + "Hash": "9fe98599ca456d6552421db0d6772d8f" + }, + "bslib": { + "Package": "bslib", + "Version": "0.7.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "base64enc", + "cachem", + "fastmap", + "grDevices", + "htmltools", + "jquerylib", + "jsonlite", + "lifecycle", + "memoise", + "mime", + "rlang", + "sass" + ], + "Hash": "8644cc53f43828f19133548195d7e59e" + }, + "cachem": { + "Package": "cachem", + "Version": "1.0.8", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "fastmap", + "rlang" + ], + "Hash": "c35768291560ce302c0a6589f92e837d" + }, + "checkmate": { + "Package": "checkmate", + "Version": "2.3.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "backports", + "utils" + ], + "Hash": "c01cab1cb0f9125211a6fc99d540e315" + }, + "class": { + "Package": "class", + "Version": "7.3-22", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "MASS", + "R", + "stats", + "utils" + ], + "Hash": "f91f6b29f38b8c280f2b9477787d4bb2" + }, + "classInt": { + "Package": "classInt", + "Version": "0.4-10", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "KernSmooth", + "R", + "class", + "e1071", + "grDevices", + "graphics", + "stats" + ], + "Hash": "f5a40793b1ae463a7ffb3902a95bf864" + }, + "cli": { + "Package": "cli", + "Version": "3.6.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "utils" + ], + "Hash": "1216ac65ac55ec0058a6f75d7ca0fd52" + }, + "clipr": { + "Package": "clipr", + "Version": "0.8.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "utils" + ], + "Hash": "3f038e5ac7f41d4ac41ce658c85e3042" + }, + "colorspace": { + "Package": "colorspace", + "Version": "2.1-0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "graphics", + "methods", + "stats" + ], + "Hash": "f20c47fd52fae58b4e377c37bb8c335b" + }, + "covidcast": { + "Package": "covidcast", + "Version": "0.5.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "MMWRweek", + "R", + "dplyr", + "ggplot2", + "grDevices", + "httr", + "purrr", + "rlang", + "sf", + "tidyr", + "xml2" + ], + "Hash": "ee88255e014ff787bd3db3f4735fb24a" + }, + "cpp11": { + "Package": "cpp11", + "Version": "0.4.7", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "5a295d7d963cc5035284dcdbaf334f4e" + }, + "crayon": { + "Package": "crayon", + "Version": "1.5.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "grDevices", + "methods", + "utils" + ], + "Hash": "e8a1e41acf02548751f45c718d55aa6a" + }, + "credentials": { + "Package": "credentials", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "askpass", + "curl", + "jsonlite", + "openssl", + "sys" + ], + "Hash": "c7844b32098dcbd1c59cbd8dddb4ecc6" + }, + "curl": { + "Package": "curl", + "Version": "5.2.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "411ca2c03b1ce5f548345d2fc2685f7a" + }, + "desc": { + "Package": "desc", + "Version": "1.4.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "cli", + "utils" + ], + "Hash": "99b79fcbd6c4d1ce087f5c5c758b384f" + }, + "digest": { + "Package": "digest", + "Version": "0.6.35", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "utils" + ], + "Hash": "698ece7ba5a4fa4559e3d537e7ec3d31" + }, + "dplyr": { + "Package": "dplyr", + "Version": "1.1.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "cli", + "generics", + "glue", + "lifecycle", + "magrittr", + "methods", + "pillar", + "rlang", + "tibble", + "tidyselect", + "utils", + "vctrs" + ], + "Hash": "fedd9d00c2944ff00a0e2696ccf048ec" + }, + "e1071": { + "Package": "e1071", + "Version": "1.7-14", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "class", + "grDevices", + "graphics", + "methods", + "proxy", + "stats", + "utils" + ], + "Hash": "4ef372b716824753719a8a38b258442d" + }, + "epidatr": { + "Package": "epidatr", + "Version": "1.1.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "MMWRweek", + "R", + "cachem", + "checkmate", + "cli", + "glue", + "httr", + "jsonlite", + "magrittr", + "openssl", + "purrr", + "rappdirs", + "readr", + "tibble", + "usethis", + "xml2" + ], + "Hash": "cf6f60be321bfd49298e27717be8c2b2" + }, + "evaluate": { + "Package": "evaluate", + "Version": "0.23", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "methods" + ], + "Hash": "daf4a1246be12c1fa8c7705a0935c1a0" + }, + "fansi": { + "Package": "fansi", + "Version": "1.0.6", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "utils" + ], + "Hash": "962174cf2aeb5b9eea581522286a911f" + }, + "farver": { + "Package": "farver", + "Version": "2.1.1", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "8106d78941f34855c440ddb946b8f7a5" + }, + "fastmap": { + "Package": "fastmap", + "Version": "1.1.1", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "f7736a18de97dea803bde0a2daaafb27" + }, + "fontawesome": { + "Package": "fontawesome", + "Version": "0.5.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "htmltools", + "rlang" + ], + "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" + }, + "fs": { + "Package": "fs", + "Version": "1.6.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "methods" + ], + "Hash": "15aeb8c27f5ea5161f9f6a641fafd93a" + }, + "generics": { + "Package": "generics", + "Version": "0.1.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "methods" + ], + "Hash": "15e9634c0fcd294799e9b2e929ed1b86" + }, + "gert": { + "Package": "gert", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "askpass", + "credentials", + "openssl", + "rstudioapi", + "sys", + "zip" + ], + "Hash": "f70d3fe2d9e7654213a946963d1591eb" + }, + "ggplot2": { + "Package": "ggplot2", + "Version": "3.5.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "MASS", + "R", + "cli", + "glue", + "grDevices", + "grid", + "gtable", + "isoband", + "lifecycle", + "mgcv", + "rlang", + "scales", + "stats", + "tibble", + "vctrs", + "withr" + ], + "Hash": "44c6a2f8202d5b7e878ea274b1092426" + }, + "gh": { + "Package": "gh", + "Version": "1.4.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "gitcreds", + "glue", + "httr2", + "ini", + "jsonlite", + "lifecycle", + "rlang" + ], + "Hash": "fbbbc48eba7a6626a08bb365e44b563b" + }, + "gitcreds": { + "Package": "gitcreds", + "Version": "0.1.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "ab08ac61f3e1be454ae21911eb8bc2fe" + }, + "glue": { + "Package": "glue", + "Version": "1.7.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "methods" + ], + "Hash": "e0b3a53876554bd45879e596cdb10a52" + }, + "gtable": { + "Package": "gtable", + "Version": "0.3.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "glue", + "grid", + "lifecycle", + "rlang" + ], + "Hash": "e18861963cbc65a27736e02b3cd3c4a0" + }, + "highr": { + "Package": "highr", + "Version": "0.10", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "xfun" + ], + "Hash": "06230136b2d2b9ba5805e1963fa6e890" + }, + "hms": { + "Package": "hms", + "Version": "1.1.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "lifecycle", + "methods", + "pkgconfig", + "rlang", + "vctrs" + ], + "Hash": "b59377caa7ed00fa41808342002138f9" + }, + "htmltools": { + "Package": "htmltools", + "Version": "0.5.8.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "base64enc", + "digest", + "fastmap", + "grDevices", + "rlang", + "utils" + ], + "Hash": "81d371a9cc60640e74e4ab6ac46dcedc" + }, + "httr": { + "Package": "httr", + "Version": "1.4.7", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "curl", + "jsonlite", + "mime", + "openssl" + ], + "Hash": "ac107251d9d9fd72f0ca8049988f1d7f" + }, + "httr2": { + "Package": "httr2", + "Version": "1.0.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "cli", + "curl", + "glue", + "lifecycle", + "magrittr", + "openssl", + "rappdirs", + "rlang", + "vctrs", + "withr" + ], + "Hash": "03d741c92fda96d98c3a3f22494e3b4a" + }, + "ini": { + "Package": "ini", + "Version": "0.3.1", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "6154ec2223172bce8162d4153cda21f7" + }, + "isoband": { + "Package": "isoband", + "Version": "0.2.7", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "grid", + "utils" + ], + "Hash": "0080607b4a1a7b28979aecef976d8bc2" + }, + "jquerylib": { + "Package": "jquerylib", + "Version": "0.1.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "htmltools" + ], + "Hash": "5aab57a3bd297eee1c1d862735972182" + }, + "jsonlite": { + "Package": "jsonlite", + "Version": "1.8.8", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "methods" + ], + "Hash": "e1b9c55281c5adc4dd113652d9e26768" + }, + "knitr": { + "Package": "knitr", + "Version": "1.46", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "evaluate", + "highr", + "methods", + "tools", + "xfun", + "yaml" + ], + "Hash": "6e008ab1d696a5283c79765fa7b56b47" + }, + "labeling": { + "Package": "labeling", + "Version": "0.4.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "graphics", + "stats" + ], + "Hash": "b64ec208ac5bc1852b285f665d6368b3" + }, + "lattice": { + "Package": "lattice", + "Version": "0.22-6", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "graphics", + "grid", + "stats", + "utils" + ], + "Hash": "cc5ac1ba4c238c7ca9fa6a87ca11a7e2" + }, + "lifecycle": { + "Package": "lifecycle", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "glue", + "rlang" + ], + "Hash": "b8552d117e1b808b09a832f589b79035" + }, + "magrittr": { + "Package": "magrittr", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "7ce2733a9826b3aeb1775d56fd305472" + }, + "memoise": { + "Package": "memoise", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "cachem", + "rlang" + ], + "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" + }, + "mgcv": { + "Package": "mgcv", + "Version": "1.9-1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "graphics", + "methods", + "nlme", + "splines", + "stats", + "utils" + ], + "Hash": "110ee9d83b496279960e162ac97764ce" + }, + "mime": { + "Package": "mime", + "Version": "0.12", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "tools" + ], + "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + }, + "munsell": { + "Package": "munsell", + "Version": "0.5.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "colorspace", + "methods" + ], + "Hash": "4fd8900853b746af55b81fda99da7695" + }, + "nlme": { + "Package": "nlme", + "Version": "3.1-164", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "graphics", + "lattice", + "stats", + "utils" + ], + "Hash": "a623a2239e642806158bc4dc3f51565d" + }, + "openssl": { + "Package": "openssl", + "Version": "2.1.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "askpass" + ], + "Hash": "ea2475b073243d9d338aa8f086ce973e" + }, + "pillar": { + "Package": "pillar", + "Version": "1.9.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "cli", + "fansi", + "glue", + "lifecycle", + "rlang", + "utf8", + "utils", + "vctrs" + ], + "Hash": "15da5a8412f317beeee6175fbc76f4bb" + }, + "pkgconfig": { + "Package": "pkgconfig", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "utils" + ], + "Hash": "01f28d4278f15c76cddbea05899c5d6f" + }, + "prettyunits": { + "Package": "prettyunits", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "6b01fc98b1e86c4f705ce9dcfd2f57c7" + }, + "progress": { + "Package": "progress", + "Version": "1.2.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "crayon", + "hms", + "prettyunits" + ], + "Hash": "f4625e061cb2865f111b47ff163a5ca6" + }, + "proxy": { + "Package": "proxy", + "Version": "0.4-27", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "stats", + "utils" + ], + "Hash": "e0ef355c12942cf7a6b91a6cfaea8b3e" + }, + "purrr": { + "Package": "purrr", + "Version": "1.0.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "lifecycle", + "magrittr", + "rlang", + "vctrs" + ], + "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc" + }, + "rappdirs": { + "Package": "rappdirs", + "Version": "0.3.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "5e3c5dc0b071b21fa128676560dbe94d" + }, + "readr": { + "Package": "readr", + "Version": "2.1.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "cli", + "clipr", + "cpp11", + "crayon", + "hms", + "lifecycle", + "methods", + "rlang", + "tibble", + "tzdb", + "utils", + "vroom" + ], + "Hash": "9de96463d2117f6ac49980577939dfb3" + }, + "renv": { + "Package": "renv", + "Version": "1.0.7", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "utils" + ], + "Hash": "397b7b2a265bc5a7a06852524dabae20" + }, + "rlang": { + "Package": "rlang", + "Version": "1.1.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "utils" + ], + "Hash": "42548638fae05fd9a9b5f3f437fbbbe2" + }, + "rmarkdown": { + "Package": "rmarkdown", + "Version": "2.26", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "bslib", + "evaluate", + "fontawesome", + "htmltools", + "jquerylib", + "jsonlite", + "knitr", + "methods", + "tinytex", + "tools", + "utils", + "xfun", + "yaml" + ], + "Hash": "9b148e7f95d33aac01f31282d49e4f44" + }, + "rprojroot": { + "Package": "rprojroot", + "Version": "2.0.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "4c8415e0ec1e29f3f4f6fc108bef0144" + }, + "rstudioapi": { + "Package": "rstudioapi", + "Version": "0.16.0", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "96710351d642b70e8f02ddeb237c46a7" + }, + "s2": { + "Package": "s2", + "Version": "1.1.6", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "Rcpp", + "wk" + ], + "Hash": "32f7b1a15bb01ae809022960abad5363" + }, + "sass": { + "Package": "sass", + "Version": "0.4.9", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R6", + "fs", + "htmltools", + "rappdirs", + "rlang" + ], + "Hash": "d53dbfddf695303ea4ad66f86e99b95d" + }, + "scales": { + "Package": "scales", + "Version": "1.3.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "RColorBrewer", + "cli", + "farver", + "glue", + "labeling", + "lifecycle", + "munsell", + "rlang", + "viridisLite" + ], + "Hash": "c19df082ba346b0ffa6f833e92de34d1" + }, + "sf": { + "Package": "sf", + "Version": "1.0-16", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "DBI", + "R", + "Rcpp", + "classInt", + "grDevices", + "graphics", + "grid", + "magrittr", + "methods", + "s2", + "stats", + "tools", + "units", + "utils" + ], + "Hash": "ad57b543f7c3fca05213ba78ff63df9b" + }, + "stringi": { + "Package": "stringi", + "Version": "1.8.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "stats", + "tools", + "utils" + ], + "Hash": "058aebddea264f4c99401515182e656a" + }, + "stringr": { + "Package": "stringr", + "Version": "1.5.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "magrittr", + "rlang", + "stringi", + "vctrs" + ], + "Hash": "960e2ae9e09656611e0b8214ad543207" + }, + "sys": { + "Package": "sys", + "Version": "3.4.2", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "3a1be13d68d47a8cd0bfd74739ca1555" + }, + "tibble": { + "Package": "tibble", + "Version": "3.2.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "fansi", + "lifecycle", + "magrittr", + "methods", + "pillar", + "pkgconfig", + "rlang", + "utils", + "vctrs" + ], + "Hash": "a84e2cc86d07289b3b6f5069df7a004c" + }, + "tidyr": { + "Package": "tidyr", + "Version": "1.3.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "cpp11", + "dplyr", + "glue", + "lifecycle", + "magrittr", + "purrr", + "rlang", + "stringr", + "tibble", + "tidyselect", + "utils", + "vctrs" + ], + "Hash": "915fb7ce036c22a6a33b5a8adb712eb1" + }, + "tidyselect": { + "Package": "tidyselect", + "Version": "1.2.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "rlang", + "vctrs", + "withr" + ], + "Hash": "829f27b9c4919c16b593794a6344d6c0" + }, + "tinytex": { + "Package": "tinytex", + "Version": "0.50", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "xfun" + ], + "Hash": "be7a76845222ad20adb761f462eed3ea" + }, + "tzdb": { + "Package": "tzdb", + "Version": "0.4.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cpp11" + ], + "Hash": "f561504ec2897f4d46f0c7657e488ae1" + }, + "units": { + "Package": "units", + "Version": "0.8-5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "Rcpp" + ], + "Hash": "119d19da480e873f72241ff6962ffd83" + }, + "usethis": { + "Package": "usethis", + "Version": "2.2.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "clipr", + "crayon", + "curl", + "desc", + "fs", + "gert", + "gh", + "glue", + "jsonlite", + "lifecycle", + "purrr", + "rappdirs", + "rlang", + "rprojroot", + "rstudioapi", + "stats", + "utils", + "whisker", + "withr", + "yaml" + ], + "Hash": "d524fd42c517035027f866064417d7e6" + }, + "utf8": { + "Package": "utf8", + "Version": "1.2.4", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "62b65c52671e6665f803ff02954446e9" + }, + "vctrs": { + "Package": "vctrs", + "Version": "0.6.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "rlang" + ], + "Hash": "c03fa420630029418f7e6da3667aac4a" + }, + "viridisLite": { + "Package": "viridisLite", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "c826c7c4241b6fc89ff55aaea3fa7491" + }, + "vroom": { + "Package": "vroom", + "Version": "1.6.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "bit64", + "cli", + "cpp11", + "crayon", + "glue", + "hms", + "lifecycle", + "methods", + "progress", + "rlang", + "stats", + "tibble", + "tidyselect", + "tzdb", + "vctrs", + "withr" + ], + "Hash": "390f9315bc0025be03012054103d227c" + }, + "whisker": { + "Package": "whisker", + "Version": "0.4.1", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "c6abfa47a46d281a7d5159d0a8891e88" + }, + "withr": { + "Package": "withr", + "Version": "3.0.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "grDevices", + "graphics" + ], + "Hash": "d31b6c62c10dcf11ec530ca6b0dd5d35" + }, + "wk": { + "Package": "wk", + "Version": "0.9.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "5d4545e140e36476f35f20d0ca87963e" + }, + "xfun": { + "Package": "xfun", + "Version": "0.43", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "grDevices", + "stats", + "tools" + ], + "Hash": "ab6371d8653ce5f2f9290f4ec7b42a8e" + }, + "xml2": { + "Package": "xml2", + "Version": "1.3.6", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "methods", + "rlang" + ], + "Hash": "1d0336142f4cd25d8d23cd3ba7a8fb61" + }, + "yaml": { + "Package": "yaml", + "Version": "2.3.8", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "29240487a071f535f5e5d5a323b7afbd" + }, + "zip": { + "Package": "zip", + "Version": "2.3.1", + "Source": "Repository", + "Repository": "RSPM", + "Hash": "fcc4bd8e6da2d2011eb64a5e5cc685ab" + } + } +} diff --git a/notebooks/renv/.gitignore b/notebooks/renv/.gitignore new file mode 100644 index 000000000..0ec0cbba2 --- /dev/null +++ b/notebooks/renv/.gitignore @@ -0,0 +1,7 @@ +library/ +local/ +cellar/ +lock/ +python/ +sandbox/ +staging/ diff --git a/notebooks/renv/activate.R b/notebooks/renv/activate.R new file mode 100644 index 000000000..d13f9932a --- /dev/null +++ b/notebooks/renv/activate.R @@ -0,0 +1,1220 @@ + +local({ + + # the requested version of renv + version <- "1.0.7" + attr(version, "sha") <- NULL + + # the project directory + project <- Sys.getenv("RENV_PROJECT") + if (!nzchar(project)) + project <- getwd() + + # use start-up diagnostics if enabled + diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE") + if (diagnostics) { + start <- Sys.time() + profile <- tempfile("renv-startup-", fileext = ".Rprof") + utils::Rprof(profile) + on.exit({ + utils::Rprof(NULL) + elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L) + writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed))) + writeLines(sprintf("- Profile: %s", profile)) + print(utils::summaryRprof(profile)) + }, add = TRUE) + } + + # figure out whether the autoloader is enabled + enabled <- local({ + + # first, check config option + override <- getOption("renv.config.autoloader.enabled") + if (!is.null(override)) + return(override) + + # if we're being run in a context where R_LIBS is already set, + # don't load -- presumably we're being run as a sub-process and + # the parent process has already set up library paths for us + rcmd <- Sys.getenv("R_CMD", unset = NA) + rlibs <- Sys.getenv("R_LIBS", unset = NA) + if (!is.na(rlibs) && !is.na(rcmd)) + return(FALSE) + + # next, check environment variables + # TODO: prefer using the configuration one in the future + envvars <- c( + "RENV_CONFIG_AUTOLOADER_ENABLED", + "RENV_AUTOLOADER_ENABLED", + "RENV_ACTIVATE_PROJECT" + ) + + for (envvar in envvars) { + envval <- Sys.getenv(envvar, unset = NA) + if (!is.na(envval)) + return(tolower(envval) %in% c("true", "t", "1")) + } + + # enable by default + TRUE + + }) + + # bail if we're not enabled + if (!enabled) { + + # if we're not enabled, we might still need to manually load + # the user profile here + profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") + if (file.exists(profile)) { + cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE") + if (tolower(cfg) %in% c("true", "t", "1")) + sys.source(profile, envir = globalenv()) + } + + return(FALSE) + + } + + # avoid recursion + if (identical(getOption("renv.autoloader.running"), TRUE)) { + warning("ignoring recursive attempt to run renv autoloader") + return(invisible(TRUE)) + } + + # signal that we're loading renv during R startup + options(renv.autoloader.running = TRUE) + on.exit(options(renv.autoloader.running = NULL), add = TRUE) + + # signal that we've consented to use renv + options(renv.consent = TRUE) + + # load the 'utils' package eagerly -- this ensures that renv shims, which + # mask 'utils' packages, will come first on the search path + library(utils, lib.loc = .Library) + + # unload renv if it's already been loaded + if ("renv" %in% loadedNamespaces()) + unloadNamespace("renv") + + # load bootstrap tools + `%||%` <- function(x, y) { + if (is.null(x)) y else x + } + + catf <- function(fmt, ..., appendLF = TRUE) { + + quiet <- getOption("renv.bootstrap.quiet", default = FALSE) + if (quiet) + return(invisible()) + + msg <- sprintf(fmt, ...) + cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") + + invisible(msg) + + } + + header <- function(label, + ..., + prefix = "#", + suffix = "-", + n = min(getOption("width"), 78)) + { + label <- sprintf(label, ...) + n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) + if (n <= 0) + return(paste(prefix, label)) + + tail <- paste(rep.int(suffix, n), collapse = "") + paste0(prefix, " ", label, " ", tail) + + } + + heredoc <- function(text, leave = 0) { + + # remove leading, trailing whitespace + trimmed <- gsub("^\\s*\\n|\\n\\s*$", "", text) + + # split into lines + lines <- strsplit(trimmed, "\n", fixed = TRUE)[[1L]] + + # compute common indent + indent <- regexpr("[^[:space:]]", lines) + common <- min(setdiff(indent, -1L)) - leave + paste(substring(lines, common), collapse = "\n") + + } + + startswith <- function(string, prefix) { + substring(string, 1, nchar(prefix)) == prefix + } + + bootstrap <- function(version, library) { + + friendly <- renv_bootstrap_version_friendly(version) + section <- header(sprintf("Bootstrapping renv %s", friendly)) + catf(section) + + # attempt to download renv + catf("- Downloading renv ... ", appendLF = FALSE) + withCallingHandlers( + tarball <- renv_bootstrap_download(version), + error = function(err) { + catf("FAILED") + stop("failed to download:\n", conditionMessage(err)) + } + ) + catf("OK") + on.exit(unlink(tarball), add = TRUE) + + # now attempt to install + catf("- Installing renv ... ", appendLF = FALSE) + withCallingHandlers( + status <- renv_bootstrap_install(version, tarball, library), + error = function(err) { + catf("FAILED") + stop("failed to install:\n", conditionMessage(err)) + } + ) + catf("OK") + + # add empty line to break up bootstrapping from normal output + catf("") + + return(invisible()) + } + + renv_bootstrap_tests_running <- function() { + getOption("renv.tests.running", default = FALSE) + } + + renv_bootstrap_repos <- function() { + + # get CRAN repository + cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") + + # check for repos override + repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) + if (!is.na(repos)) { + + # check for RSPM; if set, use a fallback repository for renv + rspm <- Sys.getenv("RSPM", unset = NA) + if (identical(rspm, repos)) + repos <- c(RSPM = rspm, CRAN = cran) + + return(repos) + + } + + # check for lockfile repositories + repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) + if (!inherits(repos, "error") && length(repos)) + return(repos) + + # retrieve current repos + repos <- getOption("repos") + + # ensure @CRAN@ entries are resolved + repos[repos == "@CRAN@"] <- cran + + # add in renv.bootstrap.repos if set + default <- c(FALLBACK = "https://cloud.r-project.org") + extra <- getOption("renv.bootstrap.repos", default = default) + repos <- c(repos, extra) + + # remove duplicates that might've snuck in + dupes <- duplicated(repos) | duplicated(names(repos)) + repos[!dupes] + + } + + renv_bootstrap_repos_lockfile <- function() { + + lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") + if (!file.exists(lockpath)) + return(NULL) + + lockfile <- tryCatch(renv_json_read(lockpath), error = identity) + if (inherits(lockfile, "error")) { + warning(lockfile) + return(NULL) + } + + repos <- lockfile$R$Repositories + if (length(repos) == 0) + return(NULL) + + keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) + vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) + names(vals) <- keys + + return(vals) + + } + + renv_bootstrap_download <- function(version) { + + sha <- attr(version, "sha", exact = TRUE) + + methods <- if (!is.null(sha)) { + + # attempting to bootstrap a development version of renv + c( + function() renv_bootstrap_download_tarball(sha), + function() renv_bootstrap_download_github(sha) + ) + + } else { + + # attempting to bootstrap a release version of renv + c( + function() renv_bootstrap_download_tarball(version), + function() renv_bootstrap_download_cran_latest(version), + function() renv_bootstrap_download_cran_archive(version) + ) + + } + + for (method in methods) { + path <- tryCatch(method(), error = identity) + if (is.character(path) && file.exists(path)) + return(path) + } + + stop("All download methods failed") + + } + + renv_bootstrap_download_impl <- function(url, destfile) { + + mode <- "wb" + + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 + fixup <- + Sys.info()[["sysname"]] == "Windows" && + substring(url, 1L, 5L) == "file:" + + if (fixup) + mode <- "w+b" + + args <- list( + url = url, + destfile = destfile, + mode = mode, + quiet = TRUE + ) + + if ("headers" %in% names(formals(utils::download.file))) + args$headers <- renv_bootstrap_download_custom_headers(url) + + do.call(utils::download.file, args) + + } + + renv_bootstrap_download_custom_headers <- function(url) { + + headers <- getOption("renv.download.headers") + if (is.null(headers)) + return(character()) + + if (!is.function(headers)) + stopf("'renv.download.headers' is not a function") + + headers <- headers(url) + if (length(headers) == 0L) + return(character()) + + if (is.list(headers)) + headers <- unlist(headers, recursive = FALSE, use.names = TRUE) + + ok <- + is.character(headers) && + is.character(names(headers)) && + all(nzchar(names(headers))) + + if (!ok) + stop("invocation of 'renv.download.headers' did not return a named character vector") + + headers + + } + + renv_bootstrap_download_cran_latest <- function(version) { + + spec <- renv_bootstrap_download_cran_latest_find(version) + type <- spec$type + repos <- spec$repos + + baseurl <- utils::contrib.url(repos = repos, type = type) + ext <- if (identical(type, "source")) + ".tar.gz" + else if (Sys.info()[["sysname"]] == "Windows") + ".zip" + else + ".tgz" + name <- sprintf("renv_%s%s", version, ext) + url <- paste(baseurl, name, sep = "/") + + destfile <- file.path(tempdir(), name) + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (inherits(status, "condition")) + return(FALSE) + + # report success and return + destfile + + } + + renv_bootstrap_download_cran_latest_find <- function(version) { + + # check whether binaries are supported on this system + binary <- + getOption("renv.bootstrap.binary", default = TRUE) && + !identical(.Platform$pkgType, "source") && + !identical(getOption("pkgType"), "source") && + Sys.info()[["sysname"]] %in% c("Darwin", "Windows") + + types <- c(if (binary) "binary", "source") + + # iterate over types + repositories + for (type in types) { + for (repos in renv_bootstrap_repos()) { + + # retrieve package database + db <- tryCatch( + as.data.frame( + utils::available.packages(type = type, repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + # check for compatible entry + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + # found it; return spec to caller + spec <- list(entry = entry, type = type, repos = repos) + return(spec) + + } + } + + # if we got here, we failed to find renv + fmt <- "renv %s is not available from your declared package repositories" + stop(sprintf(fmt, version)) + + } + + renv_bootstrap_download_cran_archive <- function(version) { + + name <- sprintf("renv_%s.tar.gz", version) + repos <- renv_bootstrap_repos() + urls <- file.path(repos, "src/contrib/Archive/renv", name) + destfile <- file.path(tempdir(), name) + + for (url in urls) { + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (identical(status, 0L)) + return(destfile) + + } + + return(FALSE) + + } + + renv_bootstrap_download_tarball <- function(version) { + + # if the user has provided the path to a tarball via + # an environment variable, then use it + tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) + if (is.na(tarball)) + return() + + # allow directories + if (dir.exists(tarball)) { + name <- sprintf("renv_%s.tar.gz", version) + tarball <- file.path(tarball, name) + } + + # bail if it doesn't exist + if (!file.exists(tarball)) { + + # let the user know we weren't able to honour their request + fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." + msg <- sprintf(fmt, tarball) + warning(msg) + + # bail + return() + + } + + catf("- Using local tarball '%s'.", tarball) + tarball + + } + + renv_bootstrap_download_github <- function(version) { + + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") + if (!identical(enabled, "TRUE")) + return(FALSE) + + # prepare download options + pat <- Sys.getenv("GITHUB_PAT") + if (nzchar(Sys.which("curl")) && nzchar(pat)) { + fmt <- "--location --fail --header \"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "curl", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + fmt <- "--header=\"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "wget", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } + + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) + name <- sprintf("renv_%s.tar.gz", version) + destfile <- file.path(tempdir(), name) + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (!identical(status, 0L)) + return(FALSE) + + renv_bootstrap_download_augment(destfile) + + return(destfile) + + } + + # Add Sha to DESCRIPTION. This is stop gap until #890, after which we + # can use renv::install() to fully capture metadata. + renv_bootstrap_download_augment <- function(destfile) { + sha <- renv_bootstrap_git_extract_sha1_tar(destfile) + if (is.null(sha)) { + return() + } + + # Untar + tempdir <- tempfile("renv-github-") + on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) + untar(destfile, exdir = tempdir) + pkgdir <- dir(tempdir, full.names = TRUE)[[1]] + + # Modify description + desc_path <- file.path(pkgdir, "DESCRIPTION") + desc_lines <- readLines(desc_path) + remotes_fields <- c( + "RemoteType: github", + "RemoteHost: api.github.com", + "RemoteRepo: renv", + "RemoteUsername: rstudio", + "RemotePkgRef: rstudio/renv", + paste("RemoteRef: ", sha), + paste("RemoteSha: ", sha) + ) + writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) + + # Re-tar + local({ + old <- setwd(tempdir) + on.exit(setwd(old), add = TRUE) + + tar(destfile, compression = "gzip") + }) + invisible() + } + + # Extract the commit hash from a git archive. Git archives include the SHA1 + # hash as the comment field of the tarball pax extended header + # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) + # For GitHub archives this should be the first header after the default one + # (512 byte) header. + renv_bootstrap_git_extract_sha1_tar <- function(bundle) { + + # open the bundle for reading + # We use gzcon for everything because (from ?gzcon) + # > Reading from a connection which does not supply a 'gzip' magic + # > header is equivalent to reading from the original connection + conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) + on.exit(close(conn)) + + # The default pax header is 512 bytes long and the first pax extended header + # with the comment should be 51 bytes long + # `52 comment=` (11 chars) + 40 byte SHA1 hash + len <- 0x200 + 0x33 + res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) + + if (grepl("^52 comment=", res)) { + sub("52 comment=", "", res) + } else { + NULL + } + } + + renv_bootstrap_install <- function(version, tarball, library) { + + # attempt to install it into project library + dir.create(library, showWarnings = FALSE, recursive = TRUE) + output <- renv_bootstrap_install_impl(library, tarball) + + # check for successful install + status <- attr(output, "status") + if (is.null(status) || identical(status, 0L)) + return(status) + + # an error occurred; report it + header <- "installation of renv failed" + lines <- paste(rep.int("=", nchar(header)), collapse = "") + text <- paste(c(header, lines, output), collapse = "\n") + stop(text) + + } + + renv_bootstrap_install_impl <- function(library, tarball) { + + # invoke using system2 so we can capture and report output + bin <- R.home("bin") + exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" + R <- file.path(bin, exe) + + args <- c( + "--vanilla", "CMD", "INSTALL", "--no-multiarch", + "-l", shQuote(path.expand(library)), + shQuote(path.expand(tarball)) + ) + + system2(R, args, stdout = TRUE, stderr = TRUE) + + } + + renv_bootstrap_platform_prefix <- function() { + + # construct version prefix + version <- paste(R.version$major, R.version$minor, sep = ".") + prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") + + # include SVN revision for development versions of R + # (to avoid sharing platform-specific artefacts with released versions of R) + devel <- + identical(R.version[["status"]], "Under development (unstable)") || + identical(R.version[["nickname"]], "Unsuffered Consequences") + + if (devel) + prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") + + # build list of path components + components <- c(prefix, R.version$platform) + + # include prefix if provided by user + prefix <- renv_bootstrap_platform_prefix_impl() + if (!is.na(prefix) && nzchar(prefix)) + components <- c(prefix, components) + + # build prefix + paste(components, collapse = "/") + + } + + renv_bootstrap_platform_prefix_impl <- function() { + + # if an explicit prefix has been supplied, use it + prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) + if (!is.na(prefix)) + return(prefix) + + # if the user has requested an automatic prefix, generate it + auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) + if (is.na(auto) && getRversion() >= "4.4.0") + auto <- "TRUE" + + if (auto %in% c("TRUE", "True", "true", "1")) + return(renv_bootstrap_platform_prefix_auto()) + + # empty string on failure + "" + + } + + renv_bootstrap_platform_prefix_auto <- function() { + + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) + if (inherits(prefix, "error") || prefix %in% "unknown") { + + msg <- paste( + "failed to infer current operating system", + "please file a bug report at https://github.com/rstudio/renv/issues", + sep = "; " + ) + + warning(msg) + + } + + prefix + + } + + renv_bootstrap_platform_os <- function() { + + sysinfo <- Sys.info() + sysname <- sysinfo[["sysname"]] + + # handle Windows + macOS up front + if (sysname == "Windows") + return("windows") + else if (sysname == "Darwin") + return("macos") + + # check for os-release files + for (file in c("/etc/os-release", "/usr/lib/os-release")) + if (file.exists(file)) + return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) + + # check for redhat-release files + if (file.exists("/etc/redhat-release")) + return(renv_bootstrap_platform_os_via_redhat_release()) + + "unknown" + + } + + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { + + # read /etc/os-release + release <- utils::read.table( + file = file, + sep = "=", + quote = c("\"", "'"), + col.names = c("Key", "Value"), + comment.char = "#", + stringsAsFactors = FALSE + ) + + vars <- as.list(release$Value) + names(vars) <- release$Key + + # get os name + os <- tolower(sysinfo[["sysname"]]) + + # read id + id <- "unknown" + for (field in c("ID", "ID_LIKE")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + id <- vars[[field]] + break + } + } + + # read version + version <- "unknown" + for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + version <- vars[[field]] + break + } + } + + # join together + paste(c(os, id, version), collapse = "-") + + } + + renv_bootstrap_platform_os_via_redhat_release <- function() { + + # read /etc/redhat-release + contents <- readLines("/etc/redhat-release", warn = FALSE) + + # infer id + id <- if (grepl("centos", contents, ignore.case = TRUE)) + "centos" + else if (grepl("redhat", contents, ignore.case = TRUE)) + "redhat" + else + "unknown" + + # try to find a version component (very hacky) + version <- "unknown" + + parts <- strsplit(contents, "[[:space:]]")[[1L]] + for (part in parts) { + + nv <- tryCatch(numeric_version(part), error = identity) + if (inherits(nv, "error")) + next + + version <- nv[1, 1] + break + + } + + paste(c("linux", id, version), collapse = "-") + + } + + renv_bootstrap_library_root_name <- function(project) { + + # use project name as-is if requested + asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") + if (asis) + return(basename(project)) + + # otherwise, disambiguate based on project's path + id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) + paste(basename(project), id, sep = "-") + + } + + renv_bootstrap_library_root <- function(project) { + + prefix <- renv_bootstrap_profile_prefix() + + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) + if (!is.na(path)) + return(paste(c(path, prefix), collapse = "/")) + + path <- renv_bootstrap_library_root_impl(project) + if (!is.null(path)) { + name <- renv_bootstrap_library_root_name(project) + return(paste(c(path, prefix, name), collapse = "/")) + } + + renv_bootstrap_paths_renv("library", project = project) + + } + + renv_bootstrap_library_root_impl <- function(project) { + + root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(root)) + return(root) + + type <- renv_bootstrap_project_type(project) + if (identical(type, "package")) { + userdir <- renv_bootstrap_user_dir() + return(file.path(userdir, "library")) + } + + } + + renv_bootstrap_validate_version <- function(version, description = NULL) { + + # resolve description file + # + # avoid passing lib.loc to `packageDescription()` below, since R will + # use the loaded version of the package by default anyhow. note that + # this function should only be called after 'renv' is loaded + # https://github.com/rstudio/renv/issues/1625 + description <- description %||% packageDescription("renv") + + # check whether requested version 'version' matches loaded version of renv + sha <- attr(version, "sha", exact = TRUE) + valid <- if (!is.null(sha)) + renv_bootstrap_validate_version_dev(sha, description) + else + renv_bootstrap_validate_version_release(version, description) + + if (valid) + return(TRUE) + + # the loaded version of renv doesn't match the requested version; + # give the user instructions on how to proceed + dev <- identical(description[["RemoteType"]], "github") + remote <- if (dev) + paste("rstudio/renv", description[["RemoteSha"]], sep = "@") + else + paste("renv", description[["Version"]], sep = "@") + + # display both loaded version + sha if available + friendly <- renv_bootstrap_version_friendly( + version = description[["Version"]], + sha = if (dev) description[["RemoteSha"]] + ) + + fmt <- heredoc(" + renv %1$s was loaded from project library, but this project is configured to use renv %2$s. + - Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile. + - Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library. + ") + catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) + + FALSE + + } + + renv_bootstrap_validate_version_dev <- function(version, description) { + expected <- description[["RemoteSha"]] + is.character(expected) && startswith(expected, version) + } + + renv_bootstrap_validate_version_release <- function(version, description) { + expected <- description[["Version"]] + is.character(expected) && identical(expected, version) + } + + renv_bootstrap_hash_text <- function(text) { + + hashfile <- tempfile("renv-hash-") + on.exit(unlink(hashfile), add = TRUE) + + writeLines(text, con = hashfile) + tools::md5sum(hashfile) + + } + + renv_bootstrap_load <- function(project, libpath, version) { + + # try to load renv from the project library + if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) + return(FALSE) + + # warn if the version of renv loaded does not match + renv_bootstrap_validate_version(version) + + # execute renv load hooks, if any + hooks <- getHook("renv::autoload") + for (hook in hooks) + if (is.function(hook)) + tryCatch(hook(), error = warnify) + + # load the project + renv::load(project) + + TRUE + + } + + renv_bootstrap_profile_load <- function(project) { + + # if RENV_PROFILE is already set, just use that + profile <- Sys.getenv("RENV_PROFILE", unset = NA) + if (!is.na(profile) && nzchar(profile)) + return(profile) + + # check for a profile file (nothing to do if it doesn't exist) + path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) + if (!file.exists(path)) + return(NULL) + + # read the profile, and set it if it exists + contents <- readLines(path, warn = FALSE) + if (length(contents) == 0L) + return(NULL) + + # set RENV_PROFILE + profile <- contents[[1L]] + if (!profile %in% c("", "default")) + Sys.setenv(RENV_PROFILE = profile) + + profile + + } + + renv_bootstrap_profile_prefix <- function() { + profile <- renv_bootstrap_profile_get() + if (!is.null(profile)) + return(file.path("profiles", profile, "renv")) + } + + renv_bootstrap_profile_get <- function() { + profile <- Sys.getenv("RENV_PROFILE", unset = "") + renv_bootstrap_profile_normalize(profile) + } + + renv_bootstrap_profile_set <- function(profile) { + profile <- renv_bootstrap_profile_normalize(profile) + if (is.null(profile)) + Sys.unsetenv("RENV_PROFILE") + else + Sys.setenv(RENV_PROFILE = profile) + } + + renv_bootstrap_profile_normalize <- function(profile) { + + if (is.null(profile) || profile %in% c("", "default")) + return(NULL) + + profile + + } + + renv_bootstrap_path_absolute <- function(path) { + + substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( + substr(path, 1L, 1L) %in% c(letters, LETTERS) && + substr(path, 2L, 3L) %in% c(":/", ":\\") + ) + + } + + renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { + renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") + root <- if (renv_bootstrap_path_absolute(renv)) NULL else project + prefix <- if (profile) renv_bootstrap_profile_prefix() + components <- c(root, renv, prefix, ...) + paste(components, collapse = "/") + } + + renv_bootstrap_project_type <- function(path) { + + descpath <- file.path(path, "DESCRIPTION") + if (!file.exists(descpath)) + return("unknown") + + desc <- tryCatch( + read.dcf(descpath, all = TRUE), + error = identity + ) + + if (inherits(desc, "error")) + return("unknown") + + type <- desc$Type + if (!is.null(type)) + return(tolower(type)) + + package <- desc$Package + if (!is.null(package)) + return("package") + + "unknown" + + } + + renv_bootstrap_user_dir <- function() { + dir <- renv_bootstrap_user_dir_impl() + path.expand(chartr("\\", "/", dir)) + } + + renv_bootstrap_user_dir_impl <- function() { + + # use local override if set + override <- getOption("renv.userdir.override") + if (!is.null(override)) + return(override) + + # use R_user_dir if available + tools <- asNamespace("tools") + if (is.function(tools$R_user_dir)) + return(tools$R_user_dir("renv", "cache")) + + # try using our own backfill for older versions of R + envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") + for (envvar in envvars) { + root <- Sys.getenv(envvar, unset = NA) + if (!is.na(root)) + return(file.path(root, "R/renv")) + } + + # use platform-specific default fallbacks + if (Sys.info()[["sysname"]] == "Windows") + file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") + else if (Sys.info()[["sysname"]] == "Darwin") + "~/Library/Caches/org.R-project.R/R/renv" + else + "~/.cache/R/renv" + + } + + renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) { + sha <- sha %||% attr(version, "sha", exact = TRUE) + parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L))) + paste(parts, collapse = "") + } + + renv_bootstrap_exec <- function(project, libpath, version) { + if (!renv_bootstrap_load(project, libpath, version)) + renv_bootstrap_run(version, libpath) + } + + renv_bootstrap_run <- function(version, libpath) { + + # perform bootstrap + bootstrap(version, libpath) + + # exit early if we're just testing bootstrap + if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) + return(TRUE) + + # try again to load + if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { + return(renv::load(project = getwd())) + } + + # failed to download or load renv; warn the user + msg <- c( + "Failed to find an renv installation: the project will not be loaded.", + "Use `renv::activate()` to re-initialize the project." + ) + + warning(paste(msg, collapse = "\n"), call. = FALSE) + + } + + renv_json_read <- function(file = NULL, text = NULL) { + + jlerr <- NULL + + # if jsonlite is loaded, use that instead + if ("jsonlite" %in% loadedNamespaces()) { + + json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity) + if (!inherits(json, "error")) + return(json) + + jlerr <- json + + } + + # otherwise, fall back to the default JSON reader + json <- tryCatch(renv_json_read_default(file, text), error = identity) + if (!inherits(json, "error")) + return(json) + + # report an error + if (!is.null(jlerr)) + stop(jlerr) + else + stop(json) + + } + + renv_json_read_jsonlite <- function(file = NULL, text = NULL) { + text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") + jsonlite::fromJSON(txt = text, simplifyVector = FALSE) + } + + renv_json_read_default <- function(file = NULL, text = NULL) { + + # find strings in the JSON + text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") + pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + locs <- gregexpr(pattern, text, perl = TRUE)[[1]] + + # if any are found, replace them with placeholders + replaced <- text + strings <- character() + replacements <- character() + + if (!identical(c(locs), -1L)) { + + # get the string values + starts <- locs + ends <- locs + attr(locs, "match.length") - 1L + strings <- substring(text, starts, ends) + + # only keep those requiring escaping + strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) + + # compute replacements + replacements <- sprintf('"\032%i\032"', seq_along(strings)) + + # replace the strings + mapply(function(string, replacement) { + replaced <<- sub(string, replacement, replaced, fixed = TRUE) + }, strings, replacements) + + } + + # transform the JSON into something the R parser understands + transformed <- replaced + transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) + transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) + transformed <- gsub("[]}]", ")", transformed, perl = TRUE) + transformed <- gsub(":", "=", transformed, fixed = TRUE) + text <- paste(transformed, collapse = "\n") + + # parse it + json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] + + # construct map between source strings, replaced strings + map <- as.character(parse(text = strings)) + names(map) <- as.character(parse(text = replacements)) + + # convert to list + map <- as.list(map) + + # remap strings in object + remapped <- renv_json_read_remap(json, map) + + # evaluate + eval(remapped, envir = baseenv()) + + } + + renv_json_read_remap <- function(json, map) { + + # fix names + if (!is.null(names(json))) { + lhs <- match(names(json), names(map), nomatch = 0L) + rhs <- match(names(map), names(json), nomatch = 0L) + names(json)[rhs] <- map[lhs] + } + + # fix values + if (is.character(json)) + return(map[[json]] %||% json) + + # handle true, false, null + if (is.name(json)) { + text <- as.character(json) + if (text == "true") + return(TRUE) + else if (text == "false") + return(FALSE) + else if (text == "null") + return(NULL) + } + + # recurse + if (is.recursive(json)) { + for (i in seq_along(json)) { + json[i] <- list(renv_json_read_remap(json[[i]], map)) + } + } + + json + + } + + # load the renv profile, if any + renv_bootstrap_profile_load(project) + + # construct path to library root + root <- renv_bootstrap_library_root(project) + + # construct library prefix for platform + prefix <- renv_bootstrap_platform_prefix() + + # construct full libpath + libpath <- file.path(root, prefix) + + # run bootstrap code + renv_bootstrap_exec(project, libpath, version) + + invisible() + +}) diff --git a/notebooks/renv/settings.json b/notebooks/renv/settings.json new file mode 100644 index 000000000..ffdbb3200 --- /dev/null +++ b/notebooks/renv/settings.json @@ -0,0 +1,19 @@ +{ + "bioconductor.version": null, + "external.libraries": [], + "ignored.packages": [], + "package.dependency.fields": [ + "Imports", + "Depends", + "LinkingTo" + ], + "ppm.enabled": null, + "ppm.ignored.urls": [], + "r.version": null, + "snapshot.type": "implicit", + "use.cache": true, + "vcs.ignore.cellar": true, + "vcs.ignore.library": true, + "vcs.ignore.local": true, + "vcs.manage.ignores": true +} From 5ce26f071216cc35b5bbd987c08cde5560f57c1f Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Thu, 9 May 2024 13:30:13 -0500 Subject: [PATCH 50/66] making Black happy --- nssp/delphi_nssp/run.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 3830478bd..db6685b2e 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -21,6 +21,7 @@ - "bucket_name: str, name of S3 bucket to read/write - "cache_dir": str, directory of locally cached data """ + import time from datetime import datetime import numpy as np @@ -71,11 +72,12 @@ def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" if col_names is None: col_names = [ - "se", + "se", "sample_size", "missing_val", "missing_se", - "missing_sample_size"] + "missing_sample_size", + ] for col_name in col_names: df[col_name] = np.nan @@ -141,25 +143,31 @@ def run_module(params): df = df[df["geography"] == "United States"] df["geo_id"] = "us" elif geo == "state": - df = df[(df['county'] == "All") & (df["geography"] != "United States")] - df["geo_id"] = df["geography"].apply(lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else 'dc') + df = df[(df["county"] == "All") & (df["geography"] != "United States")] + df["geo_id"] = df["geography"].apply( + lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else "dc" + ) elif geo == "hrr" or geo == "msa": - df = df[['fips', 'val', 'timestamp']] + df = df[["fips", "val", "timestamp"]] df = geo_mapper.add_population_column(df, geocode_type="fips", geocode_col="fips") df = geo_mapper.add_geocode(df, "fips", geo, from_col="fips", new_col="geo_id") df = generate_weights(df, "val") df = weighted_geo_sum(df, "geo_id", "val") - df = df.groupby(["timestamp","geo_id", "val"]).sum(numeric_only=True).reset_index() + df = df.groupby(["timestamp", "geo_id", "val"]).sum(numeric_only=True).reset_index() else: - df = df[df['county'] != "All"] + df = df[df["county"] != "All"] df["geo_id"] = df["fips"] # add se, sample_size, and na codes missing_cols = set(CSV_COLS) - set(df.columns) df = add_needed_columns(df, col_names=list(missing_cols)) - df_csv = df[CSV_COLS+["timestamp"]] + df_csv = df[CSV_COLS + ["timestamp"]] # actual export dates = create_export_csv( - df_csv, geo_res=geo, export_dir=export_dir, sensor=SIGNALS[signal_i], weekly_dates=True + df_csv, + geo_res=geo, + export_dir=export_dir, + sensor=SIGNALS[signal_i], + weekly_dates=True, ) if len(dates) > 0: run_stats.append((max(dates), len(dates))) From 9b87133ef207243917160071237cd71d67622033 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 10:01:26 -0500 Subject: [PATCH 51/66] update to new geomapper function --- notebooks/nssp/cor_dashboard.Rmd | 1 - nssp/delphi_nssp/run.py | 54 +++++--------------------------- pyproject.toml | 4 +++ 3 files changed, 12 insertions(+), 47 deletions(-) diff --git a/notebooks/nssp/cor_dashboard.Rmd b/notebooks/nssp/cor_dashboard.Rmd index 2ee75a438..823389f0d 100644 --- a/notebooks/nssp/cor_dashboard.Rmd +++ b/notebooks/nssp/cor_dashboard.Rmd @@ -34,7 +34,6 @@ read_row <- function(filename) { week_number <- split_name[[2]] geo_type <- split_name[[3]] col_name <- split_name[-(1:3)] %>% paste(collapse = "_") - read_csv(filename, show_col_types = FALSE) %>% as_tibble %>% mutate(signal = col_name, diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index db6685b2e..27944a1b3 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -35,38 +35,6 @@ from delphi_utils.geomap import GeoMapper import us -def sum_all_nan(x): - """Return a normal sum unless everything is NaN, then return that.""" - all_nan = np.isnan(x).all() - if all_nan: - return np.nan - return np.nansum(x) - -def weighted_geo_sum(df: pd.DataFrame, geo: str, sensor: str): - """Sum sensor, weighted by population for non NA's, grouped by state.""" - agg_df = df.groupby(["timestamp", geo]).agg( - {f"relevant_pop_{sensor}": "sum", f"weighted_{sensor}": sum_all_nan} - ) - agg_df["val"] = agg_df[f"weighted_{sensor}"] / agg_df[f"relevant_pop_{sensor}"] - agg_df = agg_df.reset_index() - return agg_df - -def generate_weights(df, column_aggregating="val"): - """ - Weigh column_aggregating by population. - - generate the relevant population amounts, and create a weighted but - unnormalized column, derived from `column_aggregating` - """ - # set the weight of places with na's to zero - df[f"relevant_pop_{column_aggregating}"] = ( - df["population"] * np.abs(df[column_aggregating]).notna() - ) - # generate the weighted version - df[f"weighted_{column_aggregating}"] = ( - df[column_aggregating] * df[f"relevant_pop_{column_aggregating}"] - ) - return df def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" @@ -118,15 +86,6 @@ def run_module(params): ) export_dir = params["common"]["export_dir"] socrata_token = params["indicator"]["socrata_token"] - # if "archive" in params: - # daily_arch_diff = S3ArchiveDiffer( - # params["archive"]["cache_dir"], - # export_dir, - # params["archive"]["bucket_name"], - # "nchs_mortality", - # params["archive"]["aws_credentials"], - # ) - # daily_arch_diff.update_cache() run_stats = [] ## build the base version of the signal at the most detailed geo level you can get. @@ -147,13 +106,16 @@ def run_module(params): df["geo_id"] = df["geography"].apply( lambda x: us.states.lookup(x).abbr.lower() if us.states.lookup(x) else "dc" ) - elif geo == "hrr" or geo == "msa": + elif geo == "hrr": + df = df[["fips", "val", "timestamp"]] + # fips -> hrr has a weighted version + df = geo_mapper.replace_geocode(df, "fips", "hrr") + elif geo == "msa": df = df[["fips", "val", "timestamp"]] + # fips -> msa doesn't have a weighted version, so we need to add columns and sum ourselves df = geo_mapper.add_population_column(df, geocode_type="fips", geocode_col="fips") - df = geo_mapper.add_geocode(df, "fips", geo, from_col="fips", new_col="geo_id") - df = generate_weights(df, "val") - df = weighted_geo_sum(df, "geo_id", "val") - df = df.groupby(["timestamp", "geo_id", "val"]).sum(numeric_only=True).reset_index() + df = geo_mapper.add_geocode(df, "fips", "msa", from_col="fips", new_col="geo_id") + df = geo_mapper.aggregate_by_weighted_sum(df, "geo_id", "val", "timestamp", "population") else: df = df[df["county"] != "All"] df["geo_id"] = df["fips"] diff --git a/pyproject.toml b/pyproject.toml index 9a31b63a0..119401f15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,3 +2,7 @@ line-length = 120 target-version = ['py38'] include = '_delphi_utils_python' + +[tool.ruff] +line-length = 120 +target-version = 'py38' From d53ff835ee801bdbb4ac60ff0a6356e48c5a2000 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 10:29:50 -0500 Subject: [PATCH 52/66] following 120 line convention everywhere --- _delphi_utils_python/.pylintrc | 3 ++- _template_python/.pylintrc | 3 ++- changehc/.pylintrc | 3 ++- claims_hosp/.pylintrc | 3 ++- doctor_visits/.pylintrc | 1 + google_symptoms/.pylintrc | 1 + hhs_hosp/.pylintrc | 1 + nchs_mortality/.pylintrc | 3 ++- nssp/.pylintrc | 3 ++- nwss_wastewater/.pylintrc | 3 ++- quidel_covidtest/.pylintrc | 1 + 11 files changed, 18 insertions(+), 7 deletions(-) diff --git a/_delphi_utils_python/.pylintrc b/_delphi_utils_python/.pylintrc index ad0180ed7..037b0ffd6 100644 --- a/_delphi_utils_python/.pylintrc +++ b/_delphi_utils_python/.pylintrc @@ -19,4 +19,5 @@ attr-rgx=([a-z_][a-z0-9_]*|[a-zA-Z]) [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/_template_python/.pylintrc b/_template_python/.pylintrc index f30837c7e..27d86e441 100644 --- a/_template_python/.pylintrc +++ b/_template_python/.pylintrc @@ -19,4 +19,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/changehc/.pylintrc b/changehc/.pylintrc index c71c52434..81078780f 100644 --- a/changehc/.pylintrc +++ b/changehc/.pylintrc @@ -21,4 +21,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/claims_hosp/.pylintrc b/claims_hosp/.pylintrc index 7fc2f5c30..b63627460 100644 --- a/claims_hosp/.pylintrc +++ b/claims_hosp/.pylintrc @@ -20,4 +20,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/doctor_visits/.pylintrc b/doctor_visits/.pylintrc index a14b269cc..4259c9fa0 100644 --- a/doctor_visits/.pylintrc +++ b/doctor_visits/.pylintrc @@ -6,3 +6,4 @@ min-public-methods=0 [MESSAGES CONTROL] disable=R0801, C0200, C0330, E1101, E0611, E1136, C0114, C0116, C0103, R0913, R0914, R0915, W1401, W1202, W1203, W0702 +max-line-length=120 diff --git a/google_symptoms/.pylintrc b/google_symptoms/.pylintrc index f337ecf9c..f4b862b07 100644 --- a/google_symptoms/.pylintrc +++ b/google_symptoms/.pylintrc @@ -6,3 +6,4 @@ min-public-methods=1 [MESSAGES CONTROL] disable=R0801, E1101, E0611, C0114, C0116, C0103, R0913, R0914, W0702, W0707 +max-line-length=120 diff --git a/hhs_hosp/.pylintrc b/hhs_hosp/.pylintrc index 58c6edbba..27d86e441 100644 --- a/hhs_hosp/.pylintrc +++ b/hhs_hosp/.pylintrc @@ -20,3 +20,4 @@ attr-rgx=[a-z_][a-z0-9_]* # Don't complain about pytest "unused" arguments. ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/nchs_mortality/.pylintrc b/nchs_mortality/.pylintrc index c72b4e124..f330f8322 100644 --- a/nchs_mortality/.pylintrc +++ b/nchs_mortality/.pylintrc @@ -21,4 +21,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/nssp/.pylintrc b/nssp/.pylintrc index f30837c7e..27d86e441 100644 --- a/nssp/.pylintrc +++ b/nssp/.pylintrc @@ -19,4 +19,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/nwss_wastewater/.pylintrc b/nwss_wastewater/.pylintrc index f30837c7e..27d86e441 100644 --- a/nwss_wastewater/.pylintrc +++ b/nwss_wastewater/.pylintrc @@ -19,4 +19,5 @@ attr-rgx=[a-z_][a-z0-9_]* [DESIGN] # Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) \ No newline at end of file +ignored-argument-names=(_.*|run_as_module) +max-line-length=120 diff --git a/quidel_covidtest/.pylintrc b/quidel_covidtest/.pylintrc index 29bd9aac2..ae71fb054 100644 --- a/quidel_covidtest/.pylintrc +++ b/quidel_covidtest/.pylintrc @@ -22,3 +22,4 @@ attr-rgx=[a-z_][a-z0-9_]* # Don't complain about pytest "unused" arguments. ignored-argument-names=(_.*|run_as_module) +max-line-length=120 From 8eb6055ff39f4bc26de0edf3072120fd15e9680b Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 10:45:51 -0500 Subject: [PATCH 53/66] happy linter --- nssp/delphi_nssp/constants.py | 2 +- nssp/delphi_nssp/pull.py | 7 +++---- nssp/delphi_nssp/run.py | 8 +++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 337abc4f7..0d072e900 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -36,4 +36,4 @@ TYPE_DICT.update({"timestamp": "datetime64[ns]", "geography": str, "county": str, - "fips": int}) \ No newline at end of file + "fips": int}) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index c13ac04d9..8319b8e09 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- """Functions for pulling NSSP ER data.""" -import numpy as np +import textwrap import pandas as pd from sodapy import Socrata -import textwrap from .constants import ( SIGNALS, @@ -18,7 +17,7 @@ def warn_string(df, type_dict): """Format the warning string.""" - warn_string = textwrap.dedent(f""" + warn= textwrap.dedent(f""" Expected column(s) missed, The dataset schema may have changed. Please investigate and amend the code. @@ -29,7 +28,7 @@ def warn_string(df, type_dict): {NEWLINE.join(sorted(df.columns))} """) - return warn_string + return warn def pull_nssp_data(socrata_token: str): diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 27944a1b3..0bbeb2f90 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -25,16 +25,14 @@ import time from datetime import datetime import numpy as np -import pandas as pd -from delphi_utils import S3ArchiveDiffer, get_structured_logger, create_export_csv +from delphi_utils import get_structured_logger, create_export_csv from delphi_utils.nancodes import add_default_nancodes +from delphi_utils.geomap import GeoMapper +import us from .constants import GEOS, SIGNALS, CSV_COLS from .pull import pull_nssp_data -from delphi_utils.geomap import GeoMapper -import us - def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" From e32cc5533d7d973b9485f6d564c70d3de10aea62 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 10:55:30 -0500 Subject: [PATCH 54/66] happy black formatter in nssp --- nssp/delphi_nssp/constants.py | 28 ++++++++++++++++------------ nssp/delphi_nssp/pull.py | 4 ++-- nssp/tests/test_pull.py | 26 +++++++++++++++----------- nssp/tests/test_run.py | 14 +++++++++++--- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 0d072e900..d8e53d0ac 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -23,17 +23,21 @@ NEWLINE = "\n" CSV_COLS = [ - "geo_id", - "val", - "se", - "sample_size", - "missing_val", - "missing_se", - "missing_sample_size" - ] + "geo_id", + "val", + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size", +] TYPE_DICT = {key: float for key in SIGNALS} -TYPE_DICT.update({"timestamp": "datetime64[ns]", - "geography": str, - "county": str, - "fips": int}) +TYPE_DICT.update( + { + "timestamp": "datetime64[ns]", + "geography": str, + "county": str, + "fips": int, + } +) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 8319b8e09..c82b8beac 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -9,7 +9,7 @@ SIGNALS, NEWLINE, SIGNALS_MAP, - TYPE_DICT + TYPE_DICT, ) @@ -17,7 +17,7 @@ def warn_string(df, type_dict): """Format the warning string.""" - warn= textwrap.dedent(f""" + warn = textwrap.dedent(f""" Expected column(s) missed, The dataset schema may have changed. Please investigate and amend the code. diff --git a/nssp/tests/test_pull.py b/nssp/tests/test_pull.py index 19f78ef37..cdc85a908 100644 --- a/nssp/tests/test_pull.py +++ b/nssp/tests/test_pull.py @@ -17,22 +17,24 @@ SIGNALS, NEWLINE, SIGNALS_MAP, - TYPE_DICT + TYPE_DICT, ) + + class TestPullNSSPData(unittest.TestCase): - @patch('delphi_nssp.pull.Socrata') + @patch("delphi_nssp.pull.Socrata") def test_pull_nssp_data(self, mock_socrata): # Load test data - with open('test_data/page.txt', 'r') as f: + with open("test_data/page.txt", "r") as f: test_data = json.load(f) - + # Mock Socrata client and its get method mock_client = MagicMock() mock_client.get.side_effect = [test_data, []] # Return test data on first call, empty list on second call mock_socrata.return_value = mock_client # Call function with test token - test_token = 'test_token' + test_token = "test_token" result = pull_nssp_data(test_token) print(result) @@ -43,13 +45,15 @@ def test_pull_nssp_data(self, mock_socrata): mock_client.get.assert_any_call("rdmq-nq56", limit=50000, offset=0) # Check result - assert result['timestamp'].notnull().all(), "timestamp has rogue NaN" - assert result['geography'].notnull().all(), "geography has rogue NaN" - assert result['county'].notnull().all(), "county has rogue NaN" - assert result['fips'].notnull().all(), "fips has rogue NaN" + assert result["timestamp"].notnull().all(), "timestamp has rogue NaN" + assert result["geography"].notnull().all(), "geography has rogue NaN" + assert result["county"].notnull().all(), "county has rogue NaN" + assert result["fips"].notnull().all(), "fips has rogue NaN" # Check for each signal in SIGNALS for signal in SIGNALS: assert result[signal].notnull().all(), f"{signal} has rogue NaN" -if __name__ == '__main__': - unittest.main() \ No newline at end of file + + +if __name__ == "__main__": + unittest.main() diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py index 8b41ad69f..bbb5effc6 100644 --- a/nssp/tests/test_run.py +++ b/nssp/tests/test_run.py @@ -17,15 +17,23 @@ weighted_geo_sum, ) + def test_add_needed_columns(): - df = pd.DataFrame({'geo_id': ['us'], 'val': [1]}) + df = pd.DataFrame({"geo_id": ["us"], "val": [1]}) df = add_needed_columns(df, col_names=None) assert df.columns.tolist() == [ - "geo_id","val", "se", "sample_size", - "missing_val", "missing_se", "missing_sample_size"] + "geo_id", + "val", + "se", + "sample_size", + "missing_val", + "missing_se", + "missing_sample_size", + ] assert df["se"].isnull().all() assert df["sample_size"].isnull().all() + def test_sum_all_nan(): """Check that sum_all_nan returns NaN iff everything is a NaN""" no_nans = np.array([3, 5]) From adf5df49b9f0da9b32219c295a99045071e6d406 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 16:08:55 -0500 Subject: [PATCH 55/66] drop unneeded nssp tests --- nssp/tests/test_run.py | 82 +----------------------------------------- 1 file changed, 1 insertion(+), 81 deletions(-) diff --git a/nssp/tests/test_run.py b/nssp/tests/test_run.py index bbb5effc6..72346cff7 100644 --- a/nssp/tests/test_run.py +++ b/nssp/tests/test_run.py @@ -11,10 +11,7 @@ from pandas.testing import assert_frame_equal from delphi_nssp.constants import GEOS, SIGNALS, CSV_COLS from delphi_nssp.run import ( - add_needed_columns, - generate_weights, - sum_all_nan, - weighted_geo_sum, + add_needed_columns ) @@ -32,80 +29,3 @@ def test_add_needed_columns(): ] assert df["se"].isnull().all() assert df["sample_size"].isnull().all() - - -def test_sum_all_nan(): - """Check that sum_all_nan returns NaN iff everything is a NaN""" - no_nans = np.array([3, 5]) - assert sum_all_nan(no_nans) == 8 - partial_nan = np.array([np.nan, 3, 5]) - assert np.isclose(sum_all_nan([np.nan, 3, 5]), 8) - - oops_all_nans = np.array([np.nan, np.nan]) - assert np.isnan(oops_all_nans).all() - - -def test_weight_generation(): - dataFrame = pd.DataFrame( - { - "a": [1, 2, 3, 4, np.nan], - "b": [5, 6, 7, 8, 9], - "population": [10, 5, 8, 1, 3], - } - ) - weighted = generate_weights(dataFrame, column_aggregating="a") - weighted_by_hand = pd.DataFrame( - { - "a": [1, 2, 3, 4, np.nan], - "b": [5, 6, 7, 8, 9], - "population": [10, 5, 8, 1, 3], - "relevant_pop_a": [10, 5, 8, 1, 0], - "weighted_a": [10.0, 2 * 5.0, 3 * 8, 4.0 * 1, np.nan * 0], - } - ) - assert_frame_equal(weighted, weighted_by_hand) - # operations are in-place - assert_frame_equal(weighted, dataFrame) - - -def test_weighted_state_sum(): - dataFrame = pd.DataFrame( - { - "geo_id": [ - "1", - "1", - "101", - "101", - "102", - ], - "timestamp": np.zeros(5), - "a": [1, 2, 3, 4, 12], - "b": [5, 6, 7, np.nan, np.nan], - "population": [10, 5, 8, 1, 3], - } - ) - weighted = generate_weights(dataFrame, column_aggregating="b") - agg = weighted_geo_sum(weighted, "geo_id", "b") - expected_agg = pd.DataFrame( - { - "timestamp": np.zeros(3), - "geo_id": ["1", "101", "102"], - "relevant_pop_b": [10 + 5, 8 + 0, 0], - "weighted_b": [5 * 10 + 6 * 5, 7 * 8 + 0, np.nan], - "val": [80 / 15, 56 / 8, np.nan], - } - ) - assert_frame_equal(agg, expected_agg) - - weighted = generate_weights(dataFrame, column_aggregating="a") - agg_a = weighted_geo_sum(weighted, "geo_id", "a") - expected_agg_a = pd.DataFrame( - { - "timestamp": np.zeros(3), - "geo_id": ["1", "101", "102"], - "relevant_pop_a": [10 + 5, 8 + 1, 3], - "weighted_a": [1 * 10 + 2 * 5, 3 * 8 + 1 * 4, 12 * 3], - "val": [20 / 15, 28 / 9, 36 / 3], - } - ) - assert_frame_equal(agg_a, expected_agg_a) From 3559664a25bfab4c33b066a4c0d4c91441c47f60 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Fri, 10 May 2024 17:47:33 -0500 Subject: [PATCH 56/66] updates borked old tests, caught by @dshemetov --- _delphi_utils_python/tests/test_weekday.py | 24 +++++----------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/_delphi_utils_python/tests/test_weekday.py b/_delphi_utils_python/tests/test_weekday.py index 52e6f4f7e..cf27207b1 100644 --- a/_delphi_utils_python/tests/test_weekday.py +++ b/_delphi_utils_python/tests/test_weekday.py @@ -18,24 +18,10 @@ def test_get_params(self): result = Weekday.get_params(self.TEST_DATA, "den", ["num"], "date", [1], TEST_LOGGER) print(result) - expected_result = [ - -0.05993665, - -0.0727396, - -0.05618517, - 0.0343405, - 0.12534997, - 0.04561813, - -2.27669028, - -1.89564374, - -1.5695407, - -1.29838116, - -1.08216513, - -0.92089259, - -0.81456355, - -0.76317802, - -0.76673598, - -0.82523745, - ] + expected_result = np.array([[-0.05998306, -0.07269935, -0.05603804, 0.03437098, 0.12530953, + 0.04554737, -2.27674403, -1.89568887, -1.56957556, -1.29840412, + -1.08217453, -0.9208868 , -0.81454092, -0.76313691, -0.76667475, + -0.82515445]]) assert np.allclose(result, expected_result) def test_calc_adjustment_with_zero_parameters(self): @@ -71,4 +57,4 @@ def test_calc_adjustment(self): # The date and "den" column are unchanged by this function assert np.allclose(result["num"].values, expected_nums) assert np.allclose(result["den"].values, self.TEST_DATA["den"].values) - assert np.array_equal(result["date"].values, self.TEST_DATA["date"].values) \ No newline at end of file + assert np.array_equal(result["date"].values, self.TEST_DATA["date"].values) From 67601aa0b4162c6a820de387f3172356771da3e0 Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Mon, 13 May 2024 15:33:33 -0500 Subject: [PATCH 57/66] rebase woes and version consistency --- ansible/templates/sir_complainsalot-params-prod.json.j2 | 1 - nssp/setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ansible/templates/sir_complainsalot-params-prod.json.j2 b/ansible/templates/sir_complainsalot-params-prod.json.j2 index d7f50a3e5..a016fc470 100644 --- a/ansible/templates/sir_complainsalot-params-prod.json.j2 +++ b/ansible/templates/sir_complainsalot-params-prod.json.j2 @@ -52,7 +52,6 @@ "maintainers": [] }, "nssp": { - "max_age":15, "max_age":13, "maintainers": [] } diff --git a/nssp/setup.py b/nssp/setup.py index 4d750e489..a6cbf640a 100644 --- a/nssp/setup.py +++ b/nssp/setup.py @@ -17,7 +17,7 @@ setup( name="delphi_nssp", - version="0.0.1", + version="0.1.0", description="Indicators NSSP Emergency Department Visit", author="Minh Le", author_email="minhkhul@andrew.cmu.edu", From a79cff8223760b075dc365c4108ad07704cba312 Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Tue, 14 May 2024 01:17:23 -0400 Subject: [PATCH 58/66] Update nssp-params-prod.json.j2 min/max lag to 13 --- ansible/templates/nssp-params-prod.json.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/templates/nssp-params-prod.json.j2 b/ansible/templates/nssp-params-prod.json.j2 index f99f914c8..b131b6130 100644 --- a/ansible/templates/nssp-params-prod.json.j2 +++ b/ansible/templates/nssp-params-prod.json.j2 @@ -14,8 +14,8 @@ "data_source": "nssp", "api_credentials": "{{ validation_api_key }}", "span_length": 15, - "min_expected_lag": {"all": "8"}, - "max_expected_lag": {"all": "15"}, + "min_expected_lag": {"all": "7"}, + "max_expected_lag": {"all": "13"}, "dry_run": true, "suppressed_errors": [] }, From 9c6f31b5b0803af5f03fd674dfd5f40a607fb9aa Mon Sep 17 00:00:00 2001 From: minhkhul <118945681+minhkhul@users.noreply.github.com> Date: Tue, 14 May 2024 01:18:25 -0400 Subject: [PATCH 59/66] Update params.json.template min/max lag to 7 and 13 --- nssp/params.json.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nssp/params.json.template b/nssp/params.json.template index ed99cd331..df989ede7 100644 --- a/nssp/params.json.template +++ b/nssp/params.json.template @@ -14,8 +14,8 @@ "data_source": "nssp", "api_credentials": "{{ validation_api_key }}", "span_length": 15, - "min_expected_lag": {"all": "8"}, - "max_expected_lag": {"all": "15"}, + "min_expected_lag": {"all": "7"}, + "max_expected_lag": {"all": "13"}, "dry_run": true, "suppressed_errors": [] }, From 33a188e96548f60c591ea2bcdb5618c3a4fe8f6c Mon Sep 17 00:00:00 2001 From: dsweber2 Date: Tue, 14 May 2024 13:26:21 -0500 Subject: [PATCH 60/66] missed column renames for geo_mapper, unneeded index --- nssp/delphi_nssp/run.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 0bbeb2f90..4ade160b3 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -91,7 +91,7 @@ def run_module(params): df_pull = pull_nssp_data(socrata_token) ## aggregate geo_mapper = GeoMapper() - for signal_i, signal in enumerate(SIGNALS): + for signal in SIGNALS: for geo in GEOS: df = df_pull.copy() df["val"] = df[signal] @@ -108,12 +108,14 @@ def run_module(params): df = df[["fips", "val", "timestamp"]] # fips -> hrr has a weighted version df = geo_mapper.replace_geocode(df, "fips", "hrr") + df = df.rename(columns={"hrr": "geo_id"}) elif geo == "msa": df = df[["fips", "val", "timestamp"]] # fips -> msa doesn't have a weighted version, so we need to add columns and sum ourselves df = geo_mapper.add_population_column(df, geocode_type="fips", geocode_col="fips") df = geo_mapper.add_geocode(df, "fips", "msa", from_col="fips", new_col="geo_id") df = geo_mapper.aggregate_by_weighted_sum(df, "geo_id", "val", "timestamp", "population") + df = df.rename(columns={"weighted_val": "val"}) else: df = df[df["county"] != "All"] df["geo_id"] = df["fips"] @@ -126,7 +128,7 @@ def run_module(params): df_csv, geo_res=geo, export_dir=export_dir, - sensor=SIGNALS[signal_i], + sensor=signal, weekly_dates=True, ) if len(dates) > 0: From 24b25dd7bbc6c614fdfd3c139ecd33debc756139 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 5 Jun 2024 12:34:17 -0700 Subject: [PATCH 61/66] lint+fix: update from linter changes * add make format to nssp Makefile * run make format on nssp --- nssp/Makefile | 5 ++++- nssp/delphi_nssp/__init__.py | 3 +-- nssp/delphi_nssp/__main__.py | 1 + nssp/delphi_nssp/pull.py | 15 ++++++--------- nssp/delphi_nssp/run.py | 9 +++++---- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/nssp/Makefile b/nssp/Makefile index bc88f1fec..390113eef 100644 --- a/nssp/Makefile +++ b/nssp/Makefile @@ -17,9 +17,12 @@ install-ci: venv pip install . lint: - . env/bin/activate; pylint $(dir) + . env/bin/activate; pylint $(dir) --rcfile=../pyproject.toml . env/bin/activate; pydocstyle $(dir) +format: + . env/bin/activate; darker $(dir) + test: . env/bin/activate ;\ (cd tests && ../env/bin/pytest --cov=$(dir) --cov-report=term-missing) diff --git a/nssp/delphi_nssp/__init__.py b/nssp/delphi_nssp/__init__.py index 2dc99fa87..827935a53 100644 --- a/nssp/delphi_nssp/__init__.py +++ b/nssp/delphi_nssp/__init__.py @@ -8,7 +8,6 @@ from __future__ import absolute_import -from . import pull -from . import run +from . import pull, run __version__ = "0.1.0" diff --git a/nssp/delphi_nssp/__main__.py b/nssp/delphi_nssp/__main__.py index 13d071c1d..105f8e2d2 100644 --- a/nssp/delphi_nssp/__main__.py +++ b/nssp/delphi_nssp/__main__.py @@ -7,6 +7,7 @@ """ from delphi_utils import read_params + from .run import run_module # pragma: no cover run_module(read_params()) # pragma: no cover diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index c82b8beac..7d72e0944 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -2,22 +2,18 @@ """Functions for pulling NSSP ER data.""" import textwrap + import pandas as pd from sodapy import Socrata -from .constants import ( - SIGNALS, - NEWLINE, - SIGNALS_MAP, - TYPE_DICT, -) - +from .constants import NEWLINE, SIGNALS, SIGNALS_MAP, TYPE_DICT def warn_string(df, type_dict): """Format the warning string.""" - warn = textwrap.dedent(f""" + warn = textwrap.dedent( + f""" Expected column(s) missed, The dataset schema may have changed. Please investigate and amend the code. @@ -26,7 +22,8 @@ def warn_string(df, type_dict): Columns available: {NEWLINE.join(sorted(df.columns))} - """) + """ + ) return warn diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 4ade160b3..9f2e761b7 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -24,13 +24,14 @@ import time from datetime import datetime + import numpy as np -from delphi_utils import get_structured_logger, create_export_csv -from delphi_utils.nancodes import add_default_nancodes -from delphi_utils.geomap import GeoMapper import us +from delphi_utils import create_export_csv, get_structured_logger +from delphi_utils.geomap import GeoMapper +from delphi_utils.nancodes import add_default_nancodes -from .constants import GEOS, SIGNALS, CSV_COLS +from .constants import CSV_COLS, GEOS, SIGNALS from .pull import pull_nssp_data From 8daefe62113674781e06d6ebd84f3cc384222842 Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 5 Jun 2024 12:35:20 -0700 Subject: [PATCH 62/66] ci: update ci to lint nssp --- .github/workflows/python-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 13af97d63..3e1ee9689 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -31,6 +31,8 @@ jobs: dir: "delphi_hhs" - package: "nchs_mortality" dir: "delphi_nchs_mortality" + - package: "nssp" + dir: "delphi_nssp" - package: "nwss_wastewater" dir: "delphi_nwss" - package: "quidel_covidtest" From ec39773706195cfcdb8f872e112232d18b682d6b Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 5 Jun 2024 12:48:28 -0700 Subject: [PATCH 63/66] lint: linter happy --- nssp/.pylintrc | 23 ----------------------- nssp/delphi_nssp/constants.py | 5 ++--- nssp/delphi_nssp/run.py | 10 ++-------- 3 files changed, 4 insertions(+), 34 deletions(-) delete mode 100644 nssp/.pylintrc diff --git a/nssp/.pylintrc b/nssp/.pylintrc deleted file mode 100644 index 27d86e441..000000000 --- a/nssp/.pylintrc +++ /dev/null @@ -1,23 +0,0 @@ - -[MESSAGES CONTROL] - -disable=logging-format-interpolation, - too-many-locals, - too-many-arguments, - # Allow pytest functions to be part of a class. - no-self-use, - # Allow pytest classes to have one test. - too-few-public-methods - -[BASIC] - -# Allow arbitrarily short-named variables. -variable-rgx=[a-z_][a-z0-9_]* -argument-rgx=[a-z_][a-z0-9_]* -attr-rgx=[a-z_][a-z0-9_]* - -[DESIGN] - -# Don't complain about pytest "unused" arguments. -ignored-argument-names=(_.*|run_as_module) -max-line-length=120 diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index d8e53d0ac..77ffb3c29 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -22,15 +22,14 @@ SIGNALS = [val for (key, val) in SIGNALS_MAP.items()] NEWLINE = "\n" -CSV_COLS = [ - "geo_id", - "val", +AUXILIARY_COLS = [ "se", "sample_size", "missing_val", "missing_se", "missing_sample_size", ] +CSV_COLS = ["geo_id", "val"] + AUXILIARY_COLS TYPE_DICT = {key: float for key in SIGNALS} TYPE_DICT.update( diff --git a/nssp/delphi_nssp/run.py b/nssp/delphi_nssp/run.py index 9f2e761b7..7c5a3ffac 100644 --- a/nssp/delphi_nssp/run.py +++ b/nssp/delphi_nssp/run.py @@ -31,20 +31,14 @@ from delphi_utils.geomap import GeoMapper from delphi_utils.nancodes import add_default_nancodes -from .constants import CSV_COLS, GEOS, SIGNALS +from .constants import AUXILIARY_COLS, CSV_COLS, GEOS, SIGNALS from .pull import pull_nssp_data def add_needed_columns(df, col_names=None): """Short util to add expected columns not found in the dataset.""" if col_names is None: - col_names = [ - "se", - "sample_size", - "missing_val", - "missing_se", - "missing_sample_size", - ] + col_names = AUXILIARY_COLS for col_name in col_names: df[col_name] = np.nan From 2e178a8c52bcbf14f3ca53505145de9dac297ece Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 5 Jun 2024 12:52:01 -0700 Subject: [PATCH 64/66] lint: pydocstyle happy --- nssp/delphi_nssp/pull.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 7d72e0944..2d90061d4 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -11,7 +11,6 @@ def warn_string(df, type_dict): """Format the warning string.""" - warn = textwrap.dedent( f""" Expected column(s) missed, The dataset schema may From 90081df81b3e38927f6af7ec1ac5bcc9397c6bdd Mon Sep 17 00:00:00 2001 From: Dmitry Shemetov Date: Wed, 5 Jun 2024 12:54:23 -0700 Subject: [PATCH 65/66] lint: pydocstyle happy --- nssp/delphi_nssp/pull.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nssp/delphi_nssp/pull.py b/nssp/delphi_nssp/pull.py index 2d90061d4..5769cca82 100644 --- a/nssp/delphi_nssp/pull.py +++ b/nssp/delphi_nssp/pull.py @@ -47,7 +47,6 @@ def pull_nssp_data(socrata_token: str): pd.DataFrame Dataframe as described above. """ - # Pull data from Socrata API client = Socrata("data.cdc.gov", socrata_token) results = [] From 355d65bc7d899325a07360787d1615b4300c84a7 Mon Sep 17 00:00:00 2001 From: minhkhul Date: Mon, 10 Jun 2024 11:30:45 -0400 Subject: [PATCH 66/66] pct_visits to pct_ed_visits --- notebooks/nssp/cor_dashboard.Rmd | 4 ++-- nssp/delphi_nssp/constants.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/notebooks/nssp/cor_dashboard.Rmd b/notebooks/nssp/cor_dashboard.Rmd index 823389f0d..58d3ed6a0 100644 --- a/notebooks/nssp/cor_dashboard.Rmd +++ b/notebooks/nssp/cor_dashboard.Rmd @@ -95,7 +95,7 @@ nchs <- epidatr::pub_covidcast( # Flu ```{r} library(epiprocess) -nssp_flu_state <- nssp_state %>% filter(signal == "pct_visits_influenza") %>% select(-signal) %>% drop_na %>% rename(pct_flu_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") +nssp_flu_state <- nssp_state %>% filter(signal == "pct_ed_visits_influenza") %>% select(-signal) %>% drop_na %>% rename(pct_flu_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") week_starts <- nssp_flu_state$time_value %>% unique() flu_hhs_weekly <- flu_hhs %>% select(geo_value, time_value, value) %>% filter(time_value %in% week_starts) %>% rename(conf_admission = value) %>% drop_na %>% as_epi_df(time_type = "week", geo_type = "state") joined <- nssp_flu_state %>% left_join(flu_hhs_weekly) @@ -178,7 +178,7 @@ nssp_state <- nssp_data %>% mutate(time_value = epidatr:::parse_api_week(time_value)) %>% as_epi_df(time_type = "week", geo_type = "state") %>% select(-missing_val, -geo_type) -nssp_covid_state <- nssp_state %>% filter(signal == "pct_visits_covid") %>% select(-signal) %>% drop_na %>% rename(pct_covid_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") +nssp_covid_state <- nssp_state %>% filter(signal == "pct_ed_visits_covid") %>% select(-signal) %>% drop_na %>% rename(pct_covid_visits = val) %>% as_epi_df(time_type = "week", geo_type = "state") week_starts <- nssp_covid_state$time_value %>% unique() covid_hhs_weekly <- covid_hhs %>% select(geo_value, time_value, value) %>% filter(time_value %in% week_starts) %>% rename(conf_admission = value) %>% drop_na %>% as_epi_df(time_type = "week", geo_type = "state") joined_covid <- nssp_covid_state %>% left_join(covid_hhs_weekly) diff --git a/nssp/delphi_nssp/constants.py b/nssp/delphi_nssp/constants.py index 77ffb3c29..0abb68c29 100644 --- a/nssp/delphi_nssp/constants.py +++ b/nssp/delphi_nssp/constants.py @@ -9,14 +9,14 @@ ] SIGNALS_MAP = { - "percent_visits_covid": "pct_visits_covid", - "percent_visits_influenza": "pct_visits_influenza", - "percent_visits_rsv": "pct_visits_rsv", - "percent_visits_combined": "pct_visits_combined", - "percent_visits_smoothed_covid": "smoothed_pct_visits_covid", - "percent_visits_smoothed_1": "smoothed_pct_visits_influenza", - "percent_visits_smoothed_rsv": "smoothed_pct_visits_rsv", - "percent_visits_smoothed": "smoothed_pct_visits_combined", + "percent_visits_covid": "pct_ed_visits_covid", + "percent_visits_influenza": "pct_ed_visits_influenza", + "percent_visits_rsv": "pct_ed_visits_rsv", + "percent_visits_combined": "pct_ed_visits_combined", + "percent_visits_smoothed_covid": "smoothed_pct_ed_visits_covid", + "percent_visits_smoothed_1": "smoothed_pct_ed_visits_influenza", + "percent_visits_smoothed_rsv": "smoothed_pct_ed_visits_rsv", + "percent_visits_smoothed": "smoothed_pct_ed_visits_combined", } SIGNALS = [val for (key, val) in SIGNALS_MAP.items()]