Skip to content

Commit c196353

Browse files
authored
Merge pull request #1342 from cmu-delphi/release/indicators_v0.2.3_utils_v0.2.3
Release covidcast-indicators 0.2.3
2 parents 82fff3d + 4437763 commit c196353

33 files changed

+1344
-363
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.2.2
2+
current_version = 0.2.3
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.2.2
2+
current_version = 0.2.3
33
commit = True
44
message = chore: bump delphi_utils to {new_version}
55
tag = False

_delphi_utils_python/.pylintrc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ disable=logging-format-interpolation,
1212
[BASIC]
1313

1414
# Allow arbitrarily short-named variables.
15-
variable-rgx=[a-z_][a-z0-9_]*
16-
argument-rgx=[a-z_][a-z0-9_]*
17-
attr-rgx=[a-z_][a-z0-9_]*
15+
variable-rgx=([a-z_][a-z0-9_]*|[a-zA-Z])
16+
argument-rgx=([a-z_][a-z0-9_]*|[a-zA-Z])
17+
attr-rgx=([a-z_][a-z0-9_]*|[a-zA-Z])
1818

1919
[DESIGN]
2020

_delphi_utils_python/delphi_utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313
from .smooth import Smoother
1414
from .signal import add_prefix
1515
from .nancodes import Nans
16+
from .weekday import Weekday
1617

17-
__version__ = "0.2.2"
18+
__version__ = "0.2.3"

doctor_visits/delphi_doctor_visits/weekday.py renamed to _delphi_utils_python/delphi_utils/weekday.py

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,48 @@
33
44
Created: 2020-05-06
55
"""
6-
7-
8-
9-
# third party
106
import cvxpy as cp
11-
from cvxpy.error import SolverError
127
import numpy as np
13-
14-
# first party
15-
from .config import Config
8+
from cvxpy.error import SolverError
169

1710

1811
class Weekday:
1912
"""Class to handle weekday effects."""
2013

2114
@staticmethod
22-
def get_params(data, logger):
15+
def get_params(data, denominator_col, numerator_cols, date_col, scales, logger):
16+
r"""Fit weekday correction for each col in numerator_cols.
17+
18+
Return a matrix of parameters: the entire vector of betas, for each time
19+
series column in the data.
20+
"""
21+
tmp = data.reset_index()
22+
denoms = tmp.groupby(date_col).sum()[denominator_col]
23+
nums = tmp.groupby(date_col).sum()[numerator_cols]
24+
25+
# Construct design matrix to have weekday indicator columns and then day
26+
# indicators.
27+
X = np.zeros((nums.shape[0], 6 + nums.shape[0]))
28+
not_sunday = np.where(nums.index.dayofweek != 6)[0]
29+
X[not_sunday, np.array(nums.index.dayofweek)[not_sunday]] = 1
30+
X[np.where(nums.index.dayofweek == 6)[0], :6] = -1
31+
X[:, 6:] = np.eye(X.shape[0])
32+
33+
npnums, npdenoms = np.array(nums), np.array(denoms)
34+
params = np.zeros((nums.shape[1], X.shape[1]))
35+
36+
# Loop over the available numerator columns and smooth each separately.
37+
for i in range(nums.shape[1]):
38+
result = Weekday._fit(X, scales, npnums[:, i], npdenoms)
39+
if result is None:
40+
logger.error("Unable to calculate weekday correction")
41+
else:
42+
params[i,:] = result
43+
44+
return params
45+
46+
@staticmethod
47+
def _fit(X, scales, npnums, npdenoms):
2348
r"""Correct a signal estimated as numerator/denominator for weekday effects.
2449
2550
The ordinary estimate would be numerator_t/denominator_t for each time point
@@ -53,57 +78,31 @@ def get_params(data, logger):
5378
5479
ll = (numerator * (X*b + log(denominator)) - sum(exp(X*b) + log(denominator)))
5580
/ num_days
56-
57-
Return a matrix of parameters: the entire vector of betas, for each time
58-
series column in the data.
5981
"""
60-
denoms = data.groupby(Config.DATE_COL).sum()["Denominator"]
61-
nums = data.groupby(Config.DATE_COL).sum()[Config.CLI_COLS + Config.FLU1_COL]
62-
63-
# Construct design matrix to have weekday indicator columns and then day
64-
# indicators.
65-
X = np.zeros((nums.shape[0], 6 + nums.shape[0]))
66-
not_sunday = np.where(nums.index.dayofweek != 6)[0]
67-
X[not_sunday, np.array(nums.index.dayofweek)[not_sunday]] = 1
68-
X[np.where(nums.index.dayofweek == 6)[0], :6] = -1
69-
X[:, 6:] = np.eye(X.shape[0])
70-
71-
npnums, npdenoms = np.array(nums), np.array(denoms)
72-
params = np.zeros((nums.shape[1], X.shape[1]))
73-
74-
# Loop over the available numerator columns and smooth each separately.
75-
for i in range(nums.shape[1]):
76-
b = cp.Variable((X.shape[1]))
77-
78-
lmbda = cp.Parameter(nonneg=True)
79-
lmbda.value = 10 # Hard-coded for now, seems robust to changes
80-
81-
ll = (
82-
cp.matmul(npnums[:, i], cp.matmul(X, b) + np.log(npdenoms))
83-
- cp.sum(cp.exp(cp.matmul(X, b) + np.log(npdenoms)))
84-
) / X.shape[0]
85-
penalty = (
86-
lmbda * cp.norm(cp.diff(b[6:], 3), 1) / (X.shape[0] - 2)
87-
) # L-1 Norm of third differences, rewards smoothness
88-
scales = [1, 1e5, 1e10, 1e15]
89-
for scale in scales:
90-
try:
91-
prob = cp.Problem(cp.Minimize((-ll + lmbda * penalty) / scale))
92-
_ = prob.solve()
93-
params[i,:] = b.value
94-
break
95-
except SolverError:
96-
# If the magnitude of the objective function is too large, an error is
97-
# thrown; Rescale the objective function by going through loop
98-
pass
99-
else:
100-
# Leaving params[i,:] = 0 is equivalent to not performing weekday correction
101-
logger.error("Unable to calculate weekday correction")
102-
103-
return params
82+
b = cp.Variable((X.shape[1]))
83+
84+
lmbda = cp.Parameter(nonneg=True)
85+
lmbda.value = 10 # Hard-coded for now, seems robust to changes
86+
87+
ll = (
88+
cp.matmul(npnums, cp.matmul(X, b) + np.log(npdenoms)) -
89+
cp.sum(cp.exp(cp.matmul(X, b) + np.log(npdenoms)))
90+
) / X.shape[0]
91+
# L-1 Norm of third differences, rewards smoothness
92+
penalty = lmbda * cp.norm(cp.diff(b[6:], 3), 1) / (X.shape[0] - 2)
93+
for scale in scales:
94+
try:
95+
prob = cp.Problem(cp.Minimize((-ll + lmbda * penalty) / scale))
96+
_ = prob.solve()
97+
return b.value
98+
except SolverError:
99+
# If the magnitude of the objective function is too large, an error is
100+
# thrown; Rescale the objective function by going through loop
101+
continue
102+
return None
104103

105104
@staticmethod
106-
def calc_adjustment(params, sub_data):
105+
def calc_adjustment(params, sub_data, cols, date_col):
107106
"""Apply the weekday adjustment to a specific time series.
108107
109108
Extracts the weekday fixed effects from the parameters and uses these to
@@ -122,14 +121,15 @@ def calc_adjustment(params, sub_data):
122121
-- this has the same effect.
123122
124123
"""
125-
for i, c in enumerate(Config.CLI_COLS + Config.FLU1_COL):
126-
wd_correction = np.zeros((len(sub_data[c])))
124+
tmp = sub_data.copy()
125+
126+
for i, c in enumerate(cols):
127+
wd_correction = np.zeros((len(tmp[c])))
127128

128129
for wd in range(7):
129-
mask = sub_data[Config.DATE_COL].dt.dayofweek == wd
130-
wd_correction[mask] = sub_data.loc[mask, c] / (
130+
mask = tmp[date_col].dt.dayofweek == wd
131+
wd_correction[mask] = tmp.loc[mask, c] / (
131132
np.exp(params[i, wd]) if wd < 6 else np.exp(-np.sum(params[i, :6]))
132133
)
133-
sub_data.loc[:, c] = wd_correction
134-
135-
return sub_data
134+
tmp.loc[:, c] = wd_correction
135+
return tmp

_delphi_utils_python/setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
required = [
88
"boto3",
99
"covidcast",
10+
"cvxpy",
1011
"epiweeks",
1112
"freezegun",
1213
"gitpython",
@@ -25,7 +26,7 @@
2526

2627
setup(
2728
name="delphi_utils",
28-
version="0.2.2",
29+
version="0.2.3",
2930
description="Shared Utility Functions for Indicators",
3031
long_description=long_description,
3132
long_description_content_type="text/markdown",
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import logging
2+
3+
import numpy as np
4+
import pandas as pd
5+
from delphi_utils.weekday import Weekday
6+
7+
8+
class TestWeekday:
9+
10+
TEST_DATA = pd.DataFrame({
11+
"num": np.arange(1, 11, 1),
12+
"den": np.arange(11, 21, 1),
13+
"date": pd.date_range("2020-01-01", "2020-01-10")
14+
})
15+
16+
def test_get_params(self):
17+
TEST_LOGGER = logging.getLogger()
18+
19+
result = Weekday.get_params(self.TEST_DATA, "den", ["num"], "date", [1], TEST_LOGGER)
20+
print(result)
21+
expected_result = [
22+
-0.05993665,
23+
-0.0727396,
24+
-0.05618517,
25+
0.0343405,
26+
0.12534997,
27+
0.04561813,
28+
-2.27669028,
29+
-1.89564374,
30+
-1.5695407,
31+
-1.29838116,
32+
-1.08216513,
33+
-0.92089259,
34+
-0.81456355,
35+
-0.76317802,
36+
-0.76673598,
37+
-0.82523745,
38+
]
39+
assert np.allclose(result, expected_result)
40+
41+
def test_calc_adjustment_with_zero_parameters(self):
42+
params = np.array([[0, 0, 0, 0, 0, 0, 0]])
43+
44+
result = Weekday.calc_adjustment(params, self.TEST_DATA, ["num"], "date")
45+
46+
# Data should be unchanged when params are 0's
47+
assert np.allclose(result["num"].values, self.TEST_DATA["num"].values)
48+
assert np.allclose(result["den"].values, self.TEST_DATA["den"].values)
49+
assert np.array_equal(result["date"].values, self.TEST_DATA["date"].values)
50+
51+
def test_calc_adjustment(self):
52+
params = np.array([[1, -1, 1, -1, 1, -1, 1]])
53+
54+
result = Weekday.calc_adjustment(params, self.TEST_DATA, ["num"], "date")
55+
56+
print(result["num"].values)
57+
print(result["den"].values)
58+
expected_nums = [
59+
0.36787944,
60+
5.43656366,
61+
1.10363832,
62+
10.87312731,
63+
5,
64+
2.20727665,
65+
19.0279728,
66+
2.94303553,
67+
24.46453646,
68+
3.67879441,
69+
]
70+
71+
# The date and "den" column are unchanged by this function
72+
assert np.allclose(result["num"].values, expected_nums)
73+
assert np.allclose(result["den"].values, self.TEST_DATA["den"].values)
74+
assert np.array_equal(result["date"].values, self.TEST_DATA["date"].values)

ansible/templates/covid_act_now-params-prod.json.j2

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"common": {
3-
"export_dir": "/common/covidcast/receiving/covid-act-now",
3+
"export_dir": "./receiving",
44
"log_filename": "/var/log/indicators/covid_act_now.log"
55
},
66
"indicator": {
@@ -44,5 +44,8 @@
4444
"smoothed_signals": [
4545
]
4646
}
47-
}
47+
},
48+
"delivery": {
49+
"delivery_dir": "/common/covidcast/receiving/covid-act-now"
50+
}
4851
}

ansible/templates/google_symptoms-params-prod.json.j2

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"span_length": 14,
2626
"min_expected_lag": {"all": "3"},
2727
"max_expected_lag": {"all": "4"},
28-
"dry_run": true,
2928
"suppressed_errors": [
3029
]
3130
},

ansible/templates/quidel_covidtest-params-prod.json.j2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"common": {
3-
"export_dir": "/common/covidcast/receiving/quidel",
3+
"export_dir": "./receiving",
44
"log_filename": "/var/log/indicators/quidel_covidtest.log"
55
},
66
"indicator": {
@@ -47,5 +47,8 @@
4747
"covid_ag_smoothed_pct_positive"
4848
]
4949
}
50+
},
51+
"delivery": {
52+
"delivery_dir": "/common/covidcast/receiving/quidel"
5053
}
5154
}

ansible/templates/usafacts-params-prod.json.j2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"common": {
3-
"export_dir": "/common/covidcast/receiving/usa-facts",
3+
"export_dir": "./receiving",
44
"input_dir": "./input-cache",
55
"log_filename": "/var/log/indicators/usafacts.log"
66
},
@@ -48,5 +48,8 @@
4848
"deaths_7dav_incidence_num",
4949
"deaths_7dav_incidence_prop"]
5050
}
51+
},
52+
"delivery": {
53+
"delivery_dir": "/common/covidcast/receiving/usa-facts"
5154
}
5255
}

changehc/delphi_changehc/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@
1313
from . import run
1414
from . import sensor
1515
from . import update_sensor
16-
from . import weekday
1716

1817
__version__ = "0.0.0"

changehc/delphi_changehc/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def run_module(params: Dict[str, Dict[str, Any]]):
184184
file_dict["flu_like"],file_dict["covid_like"],dropdate_dt,"fips")
185185
more_stats = su_inst.update_sensor(
186186
data,
187-
params["common"]["export_dir"]
187+
params["common"]["export_dir"],
188188
)
189189
stats.extend(more_stats)
190190

0 commit comments

Comments
 (0)