Skip to content

Commit 5f7a08f

Browse files
authored
Merge pull request #1234 from cmu-delphi/release/indicators_v0.1.14_utils_v0.1.11
Release covidcast-indicators 0.1.14
2 parents f12f9f6 + f1f2700 commit 5f7a08f

32 files changed

+349
-173
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.13
2+
current_version = 0.1.14
33
commit = True
44
message = chore: bump covidcast-indicators to {new_version}
55
tag = False

_delphi_utils_python/.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.10
2+
current_version = 0.1.11
33
commit = True
44
message = chore: bump delphi_utils to {new_version}
55
tag = False

_delphi_utils_python/delphi_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
from .signal import add_prefix
1515
from .nancodes import Nans
1616

17-
__version__ = "0.1.10"
17+
__version__ = "0.1.11"

_delphi_utils_python/delphi_utils/validator/PLANS.md

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* Negative ‘val’ values
1313
* Out-of-range ‘val’ values (>0 for all signals, <=100 for percents, <=100 000 for proportions)
1414
* Missing ‘se’ values
15-
* Appropriate ‘se’ values, within a calculated reasonable range
1615
* Stderr != 0
1716
* If signal and stderr both = 0 (seen in Quidel data due to lack of Jeffreys correction, [issue 255](https://github.com/cmu-delphi/covidcast-indicators/issues/255#issuecomment-692196541))
1817
* Missing ‘sample_size’ values
@@ -30,11 +29,13 @@
3029

3130
## Current features
3231

33-
* Errors and warnings are summarized in class attribute and printed on exit
34-
* If any non-suppressed errors are raised, the validation process exits with non-zero status
32+
* Errors and warnings are summarized in class attribute and stored in log files (file path to be specified in params)
33+
* If any non-suppressed errors are raised and dry-run is set to False, the validation process exits with non-zero status
3534
* Various check settings are controllable via indicator-specific params.json files
3635
* User can manually disable specific checks for specific datasets using a field in the params.json file
3736
* User can enable test mode (checks only a small number of data files) using a field in the params.json file
37+
* User can enable dry-run mode (prevents system exit with error and ensures that success() method returns True) using a field in the params.json file
38+
3839

3940
## Checks + features wishlist, and problems to think about
4041

@@ -45,37 +46,33 @@
4546

4647
### Larger issues
4748

48-
* Set up validator to use Sir-complains-a-lot alerting functionality on a signal-by-signal basis (should send alert output as a slack message and "@" a set person), as a stop-gap before the logging server is ready
49-
* This is [how Sir-CAL works](https://github.com/benjaminysmith/covidcast-indicators/blob/main/sir_complainsalot/delphi_sir_complainsalot/run.py)
50-
* [Example output](https://delphi-org.slack.com/archives/C01E81A3YKF/p1605793508000100)
5149
* Expand framework to support nchs_mortality, which is provided on a weekly basis and has some differences from the daily data. E.g. filenames use a different format ("weekly_YYYYWW_geotype_signalname.csv")
5250
* Make backtesting framework so new checks can be run individually on historical indicator data to tune false positives, output verbosity, understand frequency of error raising, etc. Should pull data from API the first time and save locally in `cache` dir.
5351
* Add DETAILS.md doc with detailed descriptions of what each check does and how. Will be especially important for statistical/anomaly detection checks.
54-
* Improve errors and error report
55-
* Check if [errors raised from validating all signals](https://docs.google.com/spreadsheets/d/1_aRBDrNeaI-3ZwuvkRNSZuZ2wfHJk6Bxj35Ol_XZ9yQ/edit#gid=1226266834) are correct, not false positives, not overly verbose or repetitive
56-
* Easier suppression of many errors at once
57-
* Maybe store errors as dict of dicts. Keys could be check strings (e.g. "check_bad_se"), then next layer geo type, etc
58-
* Nicer formatting for error “report”.
59-
* Potentially set `__print__()` method in ValidationError class
60-
* E.g. if a single type of error is raised for many different datasets, summarize all error messages into a single message? But it still has to be clear how to suppress each individually
61-
* Check for erratic data sources that wrongly report all zeroes
62-
* E.g. the error with the Wisconsin data for the 10/26 forecasts
52+
* Easier-to-read error report
53+
* Potentially set `__print__()` method in ValidationError class
54+
* E.g. if a single type of error is raised for many different datasets, summarize all error messages into a single message? But it still has to be clear how to suppress each individually
55+
* Consider adding summary counts of each type of error, rather than just a combined number
56+
* Check for data sources that wrongly report all zeroes
57+
* E.g. the error with the Wisconsin data for the 10/26/2020 forecasts
6358
* Wary of a purely static check for this
64-
* Are there any geo regions where this might cause false positives? E.g. small counties or MSAs, certain signals (deaths, since it's << cases)
65-
* This test is partially captured by checking avgs in source vs reference data, unless erroneous zeroes continue for more than a week
66-
* Also partially captured by outlier checking, depending on `size_cut` setting. If zeroes aren't outliers, then it's hard to say that they're erroneous at all.
67-
* Use known erroneous/anomalous days of source data to tune static thresholds and test behavior
68-
* If can't get data from API, do we want to use substitute data for the comparative checks instead?
69-
* Currently, any API fetch problems just doesn't do comparative checks at all.
70-
* E.g. most recent successful API pull -- might end up being a couple weeks older
71-
* Improve performance and reduce runtime (no particular goal, just avoid being painfully slow!)
59+
* Regions with small populations (e.g. small counties or MSAs) and rare signals (e.g. deaths, since it's << cases) likely to cause false positives
60+
* This test is captured by `check_avg_val_vs_reference`, as long as erroneous zeroes occur for less than the reference period (1-2 weeks)
61+
* Also partially captured by `check_positive_negative_spikes`, depending on `size_cut` setting. However, `check_positive_negative_spikes` has limited applicability and only applies to incident cases and deaths signals.
62+
* Instead of failing validation for a single check error, compare rate of check failures to historical rate? Requires caching and updating historical failure rates by signal, data source, and geo region. Unclear if worthwhile.
63+
* Improve performance and reduce runtime (no particular goal, just handle low-hanging fruit and avoid being painfully slow!)
7264
* Profiling (iterate)
7365
* Save intermediate files?
7466
* Currently a bottleneck at "individual file checks" section. Parallelize?
7567
* Make `all_frames` MultiIndex-ed by geo type and signal name? Make a dict of data indexed by geo type and signal name? May improve performance or may just make access more readable.
76-
* Ensure validator runs on signals that require AWS credentials (iterate)
68+
* Revisit tuning of thresholds for outlier-related checks (`check_positive_negative_spikes`, `check_avg_val_vs_reference`) or parameters set in params.json.template
69+
* Currently using manually tuned z-score thresholds using 1-2 months of data (June-July 2021), but signal behavior may change
70+
* Certain signals (e.g. locally monotonic signals, sparse signals) exhibit different behavior and may require signal-specific paramters for checks such as z-scores.
71+
* Use caching to store params and update these dynamically using recent data?
72+
* Create different error levels for checks beyond warning and critical: useful because certain checks clearly indicate some form of data corruption (e.g. `check_missing_date_files` identifying missing data), while other checks just report abnormal behavior that may be able to be explained.
73+
* Compare current validator model against known instances of data issues to evaluate performance (may be difficult if data corrections are issued)
7774

78-
### Longer-term issues
75+
### Longer-term features
7976

8077
* Data correctness and consistency over longer time periods (weeks to months). Compare data against long-ago (3 months?) API data for changes in trends.
8178
* Long-term trends and correlations between time series. Currently, checks only look at a data window of a few days

_delphi_utils_python/delphi_utils/validator/README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ The validator is run by executing the Python module contained in this
1515
directory from the main directory of the indicator of interest.
1616

1717
The safest way to do this is to create a virtual environment,
18-
install the common DELPHI tools, install the indicator module and its
19-
dependencies, and then install the validator module and its
20-
dependencies to the virtual environment.
18+
and install the common DELPHI tools, including the validator, and the
19+
validator module and its dependencies to the virtual environment.
2120

2221
To do this, navigate to the main directory of the indicator of interest and run the following code:
2322

@@ -26,15 +25,14 @@ python -m venv env
2625
source env/bin/activate
2726
pip install ../_delphi_utils_python/.
2827
pip install .
29-
pip install ../validator
3028
```
3129

3230
To execute the module and validate source data (by default, in `receiving`), run the indicator to generate data files, then run
3331
the validator, as follows:
3432

3533
```
3634
env/bin/python -m delphi_INDICATORNAME
37-
env/bin/python -m delphi_validator
35+
env/bin/python -m delphi_utils.validator
3836
```
3937

4038
Once you are finished with the code, you can deactivate the virtual environment
@@ -53,7 +51,10 @@ Please update the follow settings:
5351

5452
* `common`: global validation settings
5553
* `data_source`: should match the [formatting](https://cmu-delphi.github.io/delphi-epidata/api/covidcast_signals.html) as used in COVIDcast API calls
56-
* `end_date`: specifies the last date to be checked; this can be specified as `YYYY-MM-DD`, `today`, or `today-{num}`. The latter is interpretted as `num` days before the current date.
54+
* `dry_run`: boolean; `true` prevent system exit with error and ensures that the success() method of the ValidationReport always returns true.
55+
* `end_date`: specifies the last date to be checked; this can be specified as `YYYY-MM-DD`, `today`, `today-{num}`, or `sunday+{num}`. `today-num` is interpreted as `num` days before the current date. `sunday+{num}` sets the end date as the most recent day of the week prior to today (as specified by the user). The numeric input represents the day of the week (`sunday+1` denotes Monday, and so on, with 0 or 7 both indicating Sunday). Defaults to the global minimum of `min_expected_lag` days behind today.
56+
* `max_expected_lag` (default: 10 for all unspecified signals): dictionary of signal name-string pairs specifying the maximum number of days of expected lag (time between event occurrence and when data about that event was published) for that signal. Default values can also be set using 'all' as the key: individually setting keys on top of this will override the default. Values are either numeric or `sunday+{num},{num}`. `sunday+{num},{num}` is used for indicators that update data on a regular weekly basis. The first number denotes the day of the week (with Monday = 1 and so on, and Sunday taking either 0 or 7). The second number denotes the expected lag for the data upon upload date. In other words, if the signal updates weekly on Wednesday's for the past week's data up til Sunday, the correct parameter would be something like `sunday+3,3`.
57+
* `min_expected_lag` (default: 1 for all unspecified signals): dictionary of signal name-string pairs specifying the minimum number of days of expected lag (time between event occurrence and when data about that event was published) for that signal. Default values can be changed by using the 'all' key. See `max_expected_lag` for further details.
5758
* `span_length`: specifies the number of days before the `end_date` to check. `span_length` should be long enough to contain all recent source data that is still in the process of being updated (i.e. in the backfill period), for example, if the data source of interest has a 2-week lag before all reports are in for a given date, `span_length` should be 14 days
5859
* `suppressed_errors`: list of objects specifying errors that have been manually verified as false positives or acceptable deviations from expected. These errors can be specified with the following variables, where omitted values are interpreted as a wildcard, i.e., not specifying a date applies to all dates:
5960
* `check_name`: name of the check, as specified in the validation output
@@ -67,9 +68,8 @@ Please update the follow settings:
6768
* `missing_sample_size_allowed` (default: False): whether signals with missing sample sizes are valid
6869
* `additional_valid_geo_values` (default: `{}`): map of geo type names to lists of geo values that are not recorded in the GeoMapper but are nonetheless valid for this indicator
6970
* `dynamic`: settings for validations that require comparison with external COVIDcast API data
70-
* `ref_window_size` (default: 7): number of days over which to look back for comparison
71+
* `ref_window_size` (default: 14): number of days over which to look back for comparison
7172
* `smoothed_signals`: list of the names of the signals that are smoothed (e.g. 7-day average)
72-
* `expected_lag` (default: 1 for all unspecified signals): dictionary of signal name-int pairs specifying the number of days of expected lag (time between event occurrence and when data about that event was published) for that signal
7373

7474

7575
## Testing the code
@@ -80,14 +80,13 @@ To test the code, please create a new virtual environment in the main module dir
8080
python -m venv env
8181
source env/bin/activate
8282
pip install ../_delphi_utils_python/.
83-
pip install .
8483
```
8584

8685
To do a static test of the code style, it is recommended to run **pylint** on
8786
the module. To do this, run the following from the main module directory:
8887

8988
```
90-
env/bin/pylint delphi_validator
89+
env/bin/pylint delphi_utils.validator
9190
```
9291

9392
The most aggressive checks are turned off; only relatively important issues
@@ -96,7 +95,7 @@ should be raised and they should be manually checked (or better, fixed).
9695
Unit tests are also included in the module. To execute these, run the following command from this directory:
9796

9897
```
99-
(cd tests && ../env/bin/pytest --cov=delphi_validator --cov-report=term-missing)
98+
(cd tests && ../env/bin/pytest --cov=delphi_utils.validator --cov-report=term-missing)
10099
```
101100

102101
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 tests should fail and the code lines that are not covered by unit tests should be small and should not include critical sub-routines.

_delphi_utils_python/delphi_utils/validator/datafetcher.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from os import listdir
77
from os.path import isfile, join
88
import warnings
9+
import requests
910
import pandas as pd
1011
import numpy as np
1112

@@ -109,14 +110,47 @@ def get_geo_signal_combos(data_source):
109110
110111
Cross references based on combinations reported available by COVIDcast metadata.
111112
"""
113+
# Maps data_source name with what's in the API, lists used in case of multiple names
114+
source_signal_mappings = {
115+
'chng': ['chng-cli', 'chng-covid'],
116+
'indicator-combination': ['indicator-combination-cases-deaths'],
117+
'quidel': ['quidel-covid-ag'],
118+
'safegraph': ['safegraph-weekly']
119+
}
112120
meta = covidcast.metadata()
113121
source_meta = meta[meta['data_source'] == data_source]
114122
# Need to convert np.records to tuples so they are hashable and can be used in sets and dicts.
115123
geo_signal_combos = list(map(tuple,
116124
source_meta[["geo_type", "signal"]].to_records(index=False)))
117-
118-
return geo_signal_combos
119-
125+
# Only add new geo_sig combos if status is active
126+
new_geo_signal_combos = []
127+
# Use a seen dict to save on multiple calls:
128+
# True/False indicate if status is active, "unknown" means we should check
129+
sig_combo_seen = dict()
130+
for combo in geo_signal_combos:
131+
if source_signal_mappings.get(data_source):
132+
src_list = source_signal_mappings.get(data_source)
133+
else:
134+
src_list = [data_source]
135+
for src in src_list:
136+
sig = combo[1]
137+
geo_status = sig_combo_seen.get((sig, src), "unknown")
138+
if geo_status is True:
139+
new_geo_signal_combos.append(combo)
140+
elif geo_status == "unknown":
141+
epidata_signal = requests.get(
142+
"https://api.covidcast.cmu.edu/epidata/covidcast/meta",
143+
params={'signal': f"{src}:{sig}"})
144+
# Not an active signal
145+
active_status = [val['active'] for i in epidata_signal.json()
146+
for val in i['signals']]
147+
if active_status == []:
148+
sig_combo_seen[(sig, src)] = False
149+
continue
150+
sig_combo_seen[(sig, src)] = active_status[0]
151+
if active_status[0] is True:
152+
new_geo_signal_combos.append(combo)
153+
return new_geo_signal_combos
120154

121155
def fetch_api_reference(data_source, start_date, end_date, geo_type, signal_type):
122156
"""

_delphi_utils_python/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
setup(
2626
name="delphi_utils",
27-
version="0.1.10",
27+
version="0.1.11",
2828
description="Shared Utility Functions for Indicators",
2929
long_description=long_description,
3030
long_description_content_type="text/markdown",

_delphi_utils_python/tests/validator/test_datafetcher.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,29 @@ def test_make_date_filter(self):
2424
@mock.patch("covidcast.metadata")
2525
def test_get_geo_signal_combos(self, mock_metadata):
2626
"""Test that the geo signal combos are correctly pulled from the covidcast metadata."""
27-
mock_metadata.return_value = pd.DataFrame({"data_source": ["a", "a", "a",
28-
"b", "b", "b"],
29-
"signal": ["w", "x", "x",
30-
"y", "y", "z"],
27+
# Need to use actual data_source and signal names since we reference the API
28+
mock_metadata.return_value = pd.DataFrame({"data_source": ["chng", "chng", "chng",
29+
"covid-act-now",
30+
"covid-act-now",
31+
"covid-act-now"],
32+
"signal": ["smoothed_outpatient_cli",
33+
"smoothed_outpatient_covid",
34+
"smoothed_outpatient_covid",
35+
"pcr_specimen_positivity_rate",
36+
"pcr_specimen_positivity_rate",
37+
"pcr_specimen_total_tests"],
3138
"geo_type": ["state", "state", "county",
3239
"hrr", "msa", "msa"]
3340
})
3441

35-
assert set(get_geo_signal_combos("a")) == set([("state", "w"),
36-
("state", "x"),
37-
("county", "x")])
38-
assert set(get_geo_signal_combos("b")) == set([("hrr", "y"),
39-
("msa", "y"),
40-
("msa", "z")])
42+
assert set(get_geo_signal_combos("chng")) == set(
43+
[("state", "smoothed_outpatient_cli"),
44+
("state", "smoothed_outpatient_covid"),
45+
("county", "smoothed_outpatient_covid")])
46+
assert set(get_geo_signal_combos("covid-act-now")) == set(
47+
[("hrr", "pcr_specimen_positivity_rate"),
48+
("msa", "pcr_specimen_positivity_rate"),
49+
("msa", "pcr_specimen_total_tests")])
4150

4251
@mock.patch("covidcast.signal")
4352
def test_threaded_api_calls(self, mock_signal):

0 commit comments

Comments
 (0)